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.