PageSourceSearch

https://forarch.cz/wp-content/plugins/abf-custom-blocks/src/js/program-popup.js?ver=1790236805

js forarch.cz collected 2026-09-25 05:27:24 UTC 6,237 bytes, 167 lines download raw bytes

1/**
2 * Program — filtrace přednášek.
3 *
4 * Obsluha popupu řečníka se přesunula do src/js/speaker-popup.js (společná
5 * s blokem Řečníci). Tady zůstávají jen filtry.
6 */
7(function () {
8  const qs = (sel, root = document) => root.querySelector(sel);
9  const qsa = (sel, root = document) => Array.from(root.querySelectorAll(sel));
10
11  const TEXTS = (typeof window !== 'undefined' && window.abfProgramTexts) || {};
12
13  function text(key, fallback) {
14    const value = TEXTS[key];
15    return typeof value === 'string' && value !== '' ? value : fallback;
16  }
17
18  // Skloňování drží stejné pravidlo jako abf_program_count_text() v PHP,
19  // aby text po prvním filtrování neposkočil do jiného tvaru.
20  function form(n) {
21    if (n === 1) return 'One';
22    if (n >= 2 && n <= 4) return 'Few';
23    return 'Many';
24  }
25
26  function countText(found, total) {
27    if (found === total) {
28      const fallback = { One: 'Celkem %d přednáška', Few: 'Celkem %d přednášky', Many: 'Celkem %d přednášek' };
29      return text('countTotal' + form(total), fallback[form(total)]).replace('%d', String(total));
30    }
31
32    const fallback = {
33      One: 'Nalezena %d přednáška z %t',
34      Few: 'Nalezeny %d přednášky z %t',
35      Many: 'Nalezeno %d přednášek z %t',
36    };
37    return text('countFound' + form(found), fallback[form(found)])
38      .replace('%d', String(found))
39      .replace('%t', String(total));
40  }
41
42  function applyFilters(tabPane) {
43    const filtersRoot = qs('[data-program-filters]', tabPane);
44    if (!filtersRoot) return;
45
46    const placeVal = qs('select[data-filter="place"]', filtersRoot)?.value || '';
47    const timewindowVal = qs('select[data-filter="timewindow"]', filtersRoot)?.value || '';
48    const speakerVal = qs('select[data-filter="speaker"]', filtersRoot)?.value || '';
49    const filtering = !!(placeVal || timewindowVal || speakerVal);
50
51    // 1) přednášky
52    const events = qsa('.event-list-item-wrapper', tabPane);
53    let found = 0;
54
55    events.forEach((el) => {
56      const elPlace = el.getAttribute('data-place') || '';
57      const elTimewindow = el.getAttribute('data-timewindow') || '';
58
59      // Přednáška může mít víc řečníků — atribut je seznam ID oddělený čárkou,
60      // takže se musí hledat v seznamu, ne porovnávat celý řetězec.
61      const elSpeakerIds = (el.getAttribute('data-speaker-id') || '')
62        .split(',')
63        .map((id) => id.trim())
64        .filter(Boolean);
65
66      const okPlace = !placeVal || elPlace === placeVal;
67      const okTime = !timewindowVal || elTimewindow === timewindowVal;
68      const okSpeaker = !speakerVal || elSpeakerIds.includes(speakerVal);
69
70      const show = okPlace && okTime && okSpeaker;
71      el.style.display = show ? '' : 'none';
72      if (show) found++;
73    });
74
75    // 2) sekce (hala / blok programu) — bez filtru se nikdy neskrývá nic,
76    //    aby se nezměnil výchozí vzhled stránky. Viditelnost počítáme z výsledku
77    //    filtru, ne z vykreslené viditelnosti: neaktivní záložka dne je display:none.
78    qsa('.program-section-wrapper', tabPane).forEach((section) => {
79      const inside = qsa('.event-list-item-wrapper', section);
80      const hasVisible = inside.some((el) => el.style.display !== 'none');
81      section.style.display = filtering && !hasVisible ? 'none' : '';
82    });
83
84    // 3) počet výsledků, tlačítko pro zrušení filtrů a prázdný stav
85    const countEl = qs('[data-program-count]', tabPane);
86    const total = countEl ? parseInt(countEl.getAttribute('data-total') || '', 10) : NaN;
87    const totalCount = Number.isFinite(total) ? total : events.length;
88
89    if (countEl) countEl.textContent = countText(found, totalCount);
90
91    qsa('[data-program-reset]', tabPane).forEach((btn) => {
92      const inEmptyState = !!btn.closest('[data-program-empty]');
93      if (!inEmptyState) btn.hidden = !filtering;
94    });
95
96    const emptyEl = qs('[data-program-empty]', tabPane);
97    if (emptyEl) emptyEl.hidden = !(filtering && found === 0);
98  }
99
100  function resetFilters(tabPane) {
101    const filtersRoot = qs('[data-program-filters]', tabPane);
102    if (!filtersRoot) return;
103
104    qsa('select', filtersRoot).forEach((sel) => {
105      sel.value = '';
106    });
107
108    applyFilters(tabPane);
109
110    const firstSelect = qs('select', filtersRoot);
111    if (firstSelect) firstSelect.focus();
112  }
113
114  function bindProgramFilters(scope) {
115    const panes = qsa('.w-tab-pane', scope);
116
117    panes.forEach((pane) => {
118      const filtersRoot = qs('[data-program-filters]', pane);
119      if (!filtersRoot) return;
120
121      // Kořeny `.wp-block-custom-block` se můžou překrývat (blok v bloku) a stejný
122      // panel by se pak prošel dvakrát — bez pojistky by se posluchače navázaly opakovaně.
123      if (pane.dataset.abfFiltersBound === '1') {
124        applyFilters(pane);
125        return;
126      }
127      pane.dataset.abfFiltersBound = '1';
128
129      qsa('select', filtersRoot).forEach((sel) => {
130        sel.addEventListener('change', () => applyFilters(pane));
131      });
132
133      qsa('[data-program-reset]', pane).forEach((btn) => {
134        btn.addEventListener('click', (e) => {
135          e.preventDefault();
136          resetFilters(pane);
137        });
138      });
139
140      // init
141      applyFilters(pane);
142    });
143
144    // když přepneš tab, Webflow jen přepíná classes
145    // takže je dobré při kliknutí na tab link znovu aplikovat filtry na aktivní pane
146    qsa('.w-tab-link', scope).forEach((link) => {
147      link.addEventListener('click', () => {
148        // micro delay – po přepnutí tabů
149        setTimeout(() => {
150          const activePane = qs('.w-tab-pane.w--tab-active', scope);
151          if (activePane) applyFilters(activePane);
152        }, 0);
153      });
154    });
155  }
156
157  // pokud může být program blok na stránce víckrát, radši scope:
158  const boot = () => qsa('.wp-block-custom-block').forEach((block) => bindProgramFilters(block));
159
160  // Optimalizační pluginy občas vloží skript až po DOMContentLoaded — pak by se
161  // posluchač nikdy nespustil a filtry by zůstaly mrtvé. Stejně jako archiv-akci.js.
162  if (document.readyState === 'loading') {
163    document.addEventListener('DOMContentLoaded', boot);
164  } else {
165    boot();
166  }
167})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.