PageSourceSearch

https://www.cinecitta.de/_assets/2fb5f727823f481bdd3ba68aa14c389e/…search-autocomplete-json-data.js?1789655928

js cinecitta.de collected 2026-10-02 10:03:39 UTC 14,558 bytes, 450 lines download raw bytes

1(function () {
2  const searchFilmsForm = document.getElementById('filmSearchForm');
3  const input = document.getElementById('searchFilms');
4
5  if (!searchFilmsForm || !input) return;
6
7  const minLength = 2;
8
9  // Parse JSON data
10  const jsonScript = document.getElementById('jsonSearch');
11  const filmData = jsonScript ? JSON.parse(jsonScript.textContent) : [];
12  const filmListHTML = (filmData[0] && filmData[0].films) || [];
13  const noFilmsFoundHTML = (filmData[0] && filmData[0].noFilmsFound) || '';
14
15  // Normalize search strings
16  function normalize(str) {
17    return str
18      .normalize('NFKD')
19      .replace(/\s+/g, ' ')
20      .trim()
21      .toLowerCase();
22  }
23
24  // Create autocomplete container
25  let container = document.getElementById('autocompleteContainer');
26  if (!container) {
27    container = document.createElement('div');
28    container.id = 'autocompleteContainer';
29    container.className = 'autocomplete-films-box custom-scrollbar';
30    container.setAttribute('role', 'presentation');
31    container.style.position = 'absolute';
32    container.style.zIndex = '9999';
33    container.style.display = 'none';
34    document.body.appendChild(container);
35  }
36
37  // Live region for screen readers
38  let live = document.getElementById('autocompleteLive');
39  if (!live) {
40    live = document.createElement('div');
41    live.id = 'autocompleteLive';
42    live.setAttribute('role', 'status');
43    live.setAttribute('aria-live', 'polite');
44    live.setAttribute('aria-atomic', 'true');
45    live.className = 'sr-only';
46    document.body.appendChild(live);
47  }
48
49  input.setAttribute('role', 'combobox');
50  input.setAttribute('aria-autocomplete', 'list');
51  input.setAttribute('aria-haspopup', 'listbox');
52  input.setAttribute('aria-expanded', 'false');
53
54  let currentFocus = -1;
55
56  function updateAutocompletePosition() {
57    const rect = input.getBoundingClientRect();
58    container.style.top = rect.bottom + window.scrollY + 'px';
59    container.style.left = rect.left + window.scrollX + 'px';
60    container.style.width = rect.width + 'px';
61  }
62  window.addEventListener('scroll', updateAutocompletePosition);
63  window.addEventListener('resize', updateAutocompletePosition);
64
65  function showAutocomplete() {
66    container.style.display = 'block';
67    input.setAttribute('aria-expanded', 'true');
68    updateAutocompletePosition();
69  }
70
71  function hideAutocomplete() {
72    container.style.display = 'none';
73    input.setAttribute('aria-expanded', 'false');
74    input.removeAttribute('aria-activedescendant');
75    currentFocus = -1;
76  }
77
78  function clearAutocomplete() {
79    container.innerHTML = '';
80    hideAutocomplete();
81    live.textContent = '';
82  }
83
84  function selectOption(opt) {
85    if (opt.id === 'film-option-no-results') return;
86    const link = opt.querySelector('a[href]');
87    if (link) {
88      window.location.href = link.href;
89    } else {
90      input.value = opt.textContent.trim();
91      searchFilmsForm.submit();
92    }
93    clearAutocomplete();
94  }
95
96  function renderResultsFromArray(filtered) {
97    if (filtered.length === 0) {
98      clearAutocomplete();
99      return;
100    }
101
102    container.innerHTML = `
103      <ul class="filmapi-container__list--inner p-2 pb-0" role="listbox" aria-labelledby="searchFilms" id="autocompleteFilmList">
104        ${filtered.join('')}
105      </ul>`;
106
107    const list = container.querySelector('ul');
108    input.setAttribute('aria-controls', list.id);
109
110    const options = Array.from(list.querySelectorAll('li'));
111    options.forEach((opt, i) => {
112      opt.setAttribute('role', 'option');
113      if (!opt.id) opt.id = `film-option-${i}`;
114      opt.setAttribute('tabindex', '-1');
115
116      // ✅ Use click instead of pointerdown for touch compatibility
117      opt.addEventListener('click', (ev) => {
118        ev.preventDefault();
119        selectOption(opt);
120      });
121    });
122
123    if (document.documentElement.lang === "de-DE") {
124        live.textContent = `${options.length} ${options.length !== 1 ? "Vorschläge" : "Vorschlag"} verfügbar.`;
125    } else {
126        live.textContent = `${options.length} suggestion${options.length !== 1 ? "s" : ""} available.`;
127    }
128
129    showAutocomplete();
130  }
131
132  // Main search
133  function autocompleteSearchFilmsRequest() {
134    const value = normalize(input.value);
135    if (!value || value.length < minLength) {
136      clearAutocomplete();
137      return;
138    }
139
140    const filtered = filmListHTML.filter((itemHTML) => {
141      const temp = document.createElement('div');
142      temp.innerHTML = itemHTML;
143      const title = temp.querySelector('[data-title]')?.getAttribute('data-title') || '';
144      return normalize(title).includes(value);
145    });
146
147    if (filtered.length === 0 && noFilmsFoundHTML) {
148      renderResultsFromArray([noFilmsFoundHTML]);
149    } else {
150      renderResultsFromArray(filtered);
151    }
152  }
153
154  // Dynamic search as user types
155  input.addEventListener('input', () => {
156    if (input.value.trim().length >= minLength) {
157      autocompleteSearchFilmsRequest();
158    } else {
159      clearAutocomplete();
160    }
161  });
162
163  // Enter key triggers search
164  input.addEventListener('keydown', (e) => {
165    if (e.key === 'Enter') {
166      e.preventDefault();
167      const value = input.value.trim();
168      if (value.length >= minLength) {
169        autocompleteSearchFilmsRequest();
170      } else {
171        clearAutocomplete();
172      }
173    }
174  });
175
176  // Keyboard navigation
177  input.addEventListener('keydown', (e) => {
178    const list = container.querySelector('[role="listbox"]');
179    if (!list) return;
180    const items = Array.from(list.querySelectorAll('[role="option"]'));
181    if (items.length === 0) return;
182
183    if (e.key === 'ArrowDown') {
184      e.preventDefault();
185      currentFocus = (currentFocus + 1) % items.length;
186      updateActiveOption(items);
187    } else if (e.key === 'ArrowUp') {
188      e.preventDefault();
189      currentFocus = (currentFocus - 1 + items.length) % items.length;
190      updateActiveOption(items);
191    } else if (e.key === 'Escape') {
192      clearAutocomplete();
193    } else if (e.key === 'Enter' && currentFocus > -1) {
194      e.preventDefault();
195      selectOption(items[currentFocus]);
196    }
197  });
198
199  function updateActiveOption(items) {
200    items.forEach((it, idx) => {
201      if (idx === currentFocus) {
202        it.classList.add('active');
203        input.setAttribute('aria-activedescendant', it.id);
204        it.scrollIntoView({ block: 'nearest' });
205      } else {
206        it.classList.remove('active');
207      }
208    });
209  }
210
211  // Blur event closes autocomplete
212  input.addEventListener('blur', () => {
213    setTimeout(() => {
214      if (!container.contains(document.activeElement)) {
215        clearAutocomplete();
216      }
217    }, 200);
218  });
219
220  // Click outside closes autocomplete
221  document.addEventListener('click', (ev) => {
222    if (!container.contains(ev.target) && ev.target !== input) {
223      clearAutocomplete();
224    }
225  });
226})();
227
228
229(function () {
230    const searchFilmsFormHeader = document.getElementById("filmSearchFormHeader");
231    const inputHeader = document.getElementById("searchFilmsHeader");
232
233    if (!searchFilmsFormHeader || !inputHeader) return;
234
235    const minLength = 2;
236
237    // Parse JSON data
238    const jsonScript = document.getElementById("jsonSearch");
239    const filmData = jsonScript ? JSON.parse(jsonScript.textContent) : [];
240    const filmListHTML = (filmData[0] && filmData[0].films) || [];
241    const noFilmsFoundHTML = (filmData[0] && filmData[0].noFilmsFound) || "";
242
243    // Normalize search strings
244    function normalize(str) {
245        return str.normalize("NFKD").replace(/\s+/g, " ").trim().toLowerCase();
246    }
247
248    // Create autocomplete container
249    let container = document.getElementById("autocompleteContainer");
250    if (!container) {
251        container = document.createElement("div");
252        container.id = "autocompleteContainer";
253        container.className = "autocomplete-films-box custom-scrollbar";
254        container.setAttribute("role", "presentation");
255        container.style.position = "absolute";
256        container.style.zIndex = "9999";
257        container.style.display = "none";
258        document.body.appendChild(container);
259    }
260
261    // Live region for screen readers
262    let live = document.getElementById("autocompleteLive");
263    if (!live) {
264        live = document.createElement("div");
265        live.id = "autocompleteLive";
266        live.setAttribute("role", "status");
267        live.setAttribute("aria-live", "polite");
268        live.setAttribute("aria-atomic", "true");
269        live.className = "sr-only";
270        document.body.appendChild(live);
271    }
272
273    inputHeader.setAttribute("role", "combobox");
274    inputHeader.setAttribute("aria-autocomplete", "list");
275    inputHeader.setAttribute("aria-haspopup", "listbox");
276    inputHeader.setAttribute("aria-expanded", "false");
277
278    let currentFocus = -1;
279
280    function updateAutocompletePosition() {
281        const rect = inputHeader.getBoundingClientRect();
282        container.style.top = rect.bottom + window.scrollY + "px";
283        container.style.left = rect.left + window.scrollX + "px";
284        container.style.width = rect.width + "px";
285    }
286    window.addEventListener("scroll", updateAutocompletePosition);
287    window.addEventListener("resize", updateAutocompletePosition);
288
289    function showAutocomplete() {
290        container.style.display = "block";
291        inputHeader.setAttribute("aria-expanded", "true");
292        updateAutocompletePosition();
293    }
294
295    function hideAutocomplete() {
296        container.style.display = "none";
297        inputHeader.setAttribute("aria-expanded", "false");
298        inputHeader.removeAttribute("aria-activedescendant");
299        currentFocus = -1;
300    }
301
302    function clearAutocomplete() {
303        container.innerHTML = "";
304        hideAutocomplete();
305        live.textContent = "";
306    }
307
308    function selectOption(opt) {
309        if (opt.id === "film-option-no-results") return;
310        const link = opt.querySelector("a[href]");
311        if (link) {
312            window.location.href = link.href;
313        } else {
314            inputHeader.value = opt.textContent.trim();
315            searchFilmsFormHeader.submit();
316        }
317        clearAutocomplete();
318    }
319
320    function renderResultsFromArray(filtered) {
321        if (filtered.length === 0) {
322            clearAutocomplete();
323            return;
324        }
325
326        container.innerHTML = `
327      <ul class="filmapi-container__list--inner p-2 pb-0" role="listbox" aria-labelledby="searchFilms" id="autocompleteFilmList">
328        ${filtered.join("")}
329      </ul>`;
330
331        const list = container.querySelector("ul");
332        inputHeader.setAttribute("aria-controls", list.id);
333
334        const options = Array.from(list.querySelectorAll("li"));
335        options.forEach((opt, i) => {
336            opt.setAttribute("role", "option");
337            if (!opt.id) opt.id = `film-option-${i}`;
338            opt.setAttribute("tabindex", "-1");
339
340            // ✅ Use click instead of pointerdown for touch compatibility
341            opt.addEventListener("click", (ev) => {
342                ev.preventDefault();
343                selectOption(opt);
344            });
345        });
346
347        if (document.documentElement.lang === "de-DE") {
348            live.textContent = `${options.length} ${options.length !== 1 ? "Vorschläge" : "Vorschlag"} verfügbar.`;
349        } else {
350            live.textContent = `${options.length} suggestion${options.length !== 1 ? "s" : ""} available.`;
351        }
352
353        showAutocomplete();
354    }
355
356    // Main search
357    function autocompleteSearchFilmsRequest() {
358        const value = normalize(inputHeader.value);
359        if (!value || value.length < minLength) {
360            clearAutocomplete();
361            return;
362        }
363
364        const filtered = filmListHTML.filter((itemHTML) => {
365            const temp = document.createElement("div");
366            temp.innerHTML = itemHTML;
367            const title = temp.querySelector("[data-title]")?.getAttribute("data-title") || "";
368            return normalize(title).includes(value);
369        });
370
371        if (filtered.length === 0 && noFilmsFoundHTML) {
372            renderResultsFromArray([noFilmsFoundHTML]);
373        } else {
374            renderResultsFromArray(filtered);
375        }
376    }
377
378    // Dynamic search as user types
379    inputHeader.addEventListener("input", () => {
380        if (inputHeader.value.trim().length >= minLength) {
381            autocompleteSearchFilmsRequest();
382        } else {
383            clearAutocomplete();
384        }
385    });
386
387    // Enter key triggers search
388    inputHeader.addEventListener("keydown", (e) => {
389        if (e.key === "Enter") {
390            e.preventDefault();
391            const value = inputHeader.value.trim();
392            if (value.length >= minLength) {
393                autocompleteSearchFilmsRequest();
394            } else {
395                clearAutocomplete();
396            }
397        }
398    });
399
400    // Keyboard navigation
401    inputHeader.addEventListener("keydown", (e) => {
402        const list = container.querySelector('[role="listbox"]');
403        if (!list) return;
404        const items = Array.from(list.querySelectorAll('[role="option"]'));
405        if (items.length === 0) return;
406
407        if (e.key === "ArrowDown") {
408            e.preventDefault();
409            currentFocus = (currentFocus + 1) % items.length;
410            updateActiveOption(items);
411        } else if (e.key === "ArrowUp") {
412            e.preventDefault();
413            currentFocus = (currentFocus - 1 + items.length) % items.length;
414            updateActiveOption(items);
415        } else if (e.key === "Escape") {
416            clearAutocomplete();
417        } else if (e.key === "Enter" && currentFocus > -1) {
418            e.preventDefault();
419            selectOption(items[currentFocus]);
420        }
421    });
422
423    function updateActiveOption(items) {
424        items.forEach((it, idx) => {
425            if (idx === currentFocus) {
426                it.classList.add("active");
427                inputHeader.setAttribute("aria-activedescendant", it.id);
428                it.scrollIntoView({ block: "nearest" });
429            } else {
430                it.classList.remove("active");
431            }
432        });
433    }
434
435    // Blur event closes autocomplete
436    inputHeader.addEventListener("blur", () => {
437        setTimeout(() => {
438            if (!container.contains(document.activeElement)) {
439                clearAutocomplete();
440            }
441        }, 200);
442    });
443
444    // Click outside closes autocomplete
445    document.addEventListener("click", (ev) => {
446        if (!container.contains(ev.target) && ev.target !== inputHeader) {
447            clearAutocomplete();
448        }
449    });
450})();

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.