1export class HWAutocomplete { 2 3 /** 4 * Instantiates a Basic Autocomplete for HW Projects 5 * @param {string[]} autocompleteClasses - classes for autocomplete 6 * @param {array[]} autocompleteContent - ??? 7 * @param {int} maxAutocompleteCount - maximum count of shown suggestions 8 */ 9 constructor(autocompleteClasses, autocompleteContent, maxAutocompleteCount) { 10 11 if (!autocompleteClasses.length) { 12 console.error("The classes array for the autocomplete is empty!"); 13 return; 14 } 15 16 if (typeof autocompleteClasses === 'undefined' || autocompleteClasses.length <= 0) { 17 autocompleteClasses = ['hw-autocomplete']; 18 } 19 20 if ( typeof autocompleteContent === 'undefined' || autocompleteContent.length <= 0) { 21 autocompleteContent = []; 22 } 23 24 if ( typeof maxAutocompleteCount === 'undefined' ) { 25 maxAutocompleteCount = 10; 26 } 27 28 this.autocompleteClasses = autocompleteClasses; 29 this.autocompleteContent = autocompleteContent; 30 31 this.autocompleteCharacterCountMin = 3; 32 33 this.ajaxStringPlaceholder = '$1'; 34 this.autocompleteResponseObjectProperty = 'suggestions'; 35 this.autocompleteMaxCount = maxAutocompleteCount; 36 37 this.xhttpSuggestionContent = null; 38 39 /** @type {HTMLElement[]} */ 40 this._autocompleteInstances = []; 41 42 this.initAutocomplete(); 43 } 44 45 /** 46 * @private 47 */ 48 initAutocomplete() { 49 50 // Alle angegebenen Inputfelder für Autocomplete initialiseren 51 for ( let i = 0; i < this.autocompleteClasses.length; i++ ) { 52 53 // Alle Inputs für den Selektor sammeln 54 let inputs = document.querySelectorAll('.' + this.autocompleteClasses[i]); 55 56 // Alle Inputs durchgehen 57 for (let j = 0; j < inputs.length; j++) { 58 59 let input = inputs[j]; 60 61 if ( typeof this.autocompleteContent[i] === 'undefined' || this.autocompleteContent[i].length <= 0 ) { 62 this.autocompleteContent[i] = []; 63 } 64 65 // Wenn der Input existiert, prüfen, ob für das Autocomplete bereits eine Liste vorhanden ist 66 // Ansonsten AJAX/Element-Data Modus verwenden 67 if (typeof input !== 'undefined' && input) { 68 this.autocomplete(input, this.autocompleteContent[i]); 69 } 70 } 71 } 72 } 73 74 /** 75 * autocomplete 76 * 77 * Fügt einem Inputfeld die autocomplete Funktion hinzu 78 * 79 * @param input 80 * @param autocompleteContent 81 */ 82 autocomplete(input, autocompleteContent) { 83 84 let ajaxMode = false; 85 86 // Prüfen, ob es ein vorgefertigtes Set gibt -> wenn nicht, am Element schauen, 87 // ob es per data ein Set gibt. 88 // Ansonsten per AJAX versuchen 89 if (autocompleteContent === 'undefined' || autocompleteContent.length <= 0) { 90 91 autocompleteContent = []; 92 93 if ( typeof input.dataset.autocompletecontent !== 'undefined' && input.dataset.autocompletecontent ) { 94 95 // Den String anhand des Komma separieren 96 if ( input.dataset.autocompletecontent.length > 0 ) { 97 autocompleteContent = input.dataset.autocompletecontent.split(','); 98 99 // Sicherstellen, dass die Werte alle getrimmed werden 100 for ( let k = 0; k < autocompleteContent.length; k++ ) { 101 autocompleteContent[k] = autocompleteContent[k].trim(); 102 } 103 } 104 } 105 } 106 107 // Wenn es noch keinen Content gibt -> AJAX Modus 108 if ( autocompleteContent.length <= 0 ) { 109 ajaxMode = true; 110 } 111 112 // Fokus initaliseren 113 let currentFocus; 114 115 // Das Browser autocomplete verhindern 116 input.setAttribute('autocomplete', 'off'); 117 118 // this cachen 119 let _this = this; 120 121 // Eventlistener auf das Input Feld setzen (für input) 122 input.addEventListener("input", function(e) { 123 124 let val = this.value; 125 126 // Alle bereits geöffneten Fenster sicherheitshalber schlieÃen! 127 closeAllLists(); 128 129 // Wenn es keine Werte gibt -> return false 130 if (!val) { 131 return false;
132 } 133 134 // Prüfen, ob die Mindestanzahl der Buchstaben erreicht ist 135 if ( val.length < _this.autocompleteCharacterCountMin ) { 136 return false; 137 } 138 139 // Fokus auf -1 140 currentFocus = -1; 141 142 // DIV erstellen, dass die Werte beinhaltet 143 let div = document.createElement("DIV"); 144 div.setAttribute("id", this.id + "autocomplete-list"); 145 div.setAttribute("class", "autocomplete-items"); 146 147 // Dem Parent Element das DIV hinzufügen 148 // this.parentNode.appendChild(div); 149 150 // Position und Breite des DIVs ermitteln und setzen 151 let positionInfo = input.getBoundingClientRect(); 152 153 let listTop = ( positionInfo.top + positionInfo.height ), 154 listLeft = positionInfo.left, 155 listWidth = positionInfo.width; 156 157 div.setAttribute("style", "top:" + listTop + "px; left: " + listLeft + "px; width: " + listWidth + "px;"); 158 159 // Das DIV dem body hinzufügen 160 document.body.appendChild(div); 161 162 let autocompleteCount = 0; 163 164 // Aufbau und Befüllung des Containers in Callback Funktion zwischenspeichern 165 // Die Funktion wird als Anonyme Funktion in eine Variable gespeichert, so dass sie den 166 // aktuellen Context behält. Somit müssen die ganzen Variablen nicht als Parameter übergeben werden 167 // Evtl. sauberer lösen?! -> Wenn mehr Zeit oder jemand Lust hat 168 let autocompleteCallbackFunc = function() { 169 170 // Alle Items im Array durchgehen 171 for (let i = 0; i < autocompleteContent.length; i++) { 172 173 // Prüfen, ob das Item mit dem gleichen Buchstaben beginnt, wie im Inputfeld 174 if (autocompleteContent[i].substr(0, val.length).toUpperCase() === val.toUpperCase()) { 175 176 // Prüfen, ob die maximal Anzahl an Vorschlägen erreicht wurde 177 if ( autocompleteCount >= _this.autocompleteMaxCount ) { 178 break; 179 } 180 181 // Für jeden Treffer ein DIV erstellen 182 let divInner = document.createElement("DIV"); 183 184 // Passende Buchstaben fett markieren 185 divInner.innerHTML = "<strong>" + autocompleteContent[i].substr(0, val.length) + "</strong>"; 186 divInner.innerHTML += autocompleteContent[i].substr(val.length); 187 188 // Hidden field erzeugen, das den aktuellen Suchstring beinhaltet 189 // Dies wird dafür verwendet, dass die Pfeiltasten navigation korrekt funktioniert 190 divInner.innerHTML += "<input type='hidden' value='" + autocompleteContent[i] + "'>"; 191 192 // Klick auf das DIV (aktuelles Item) binden 193 divInner.addEventListener("click", function(e) { 194 195 // Den aktuellen Wert in das Input-Feld eintragen 196 input.value = this.getElementsByTagName("input")[0].value; 197 198 // Alle Autocomplete-Listen schlieÃen! WICHTIG 199 closeAllLists(); 200 201 // Prüfen, ob das Formular bei einem Klick auf den entsprechenden Listeneintrag direkt abgeschickt werden soll 202 if ( input.dataset.autocompletesubmitonclick && input.dataset.autocompletesubmitonclick === '1' ) { 203 204 if ( typeof input.form !== 'undefined' && input.form ) { 205 input.form.submit(); 206 } 207 } 208 }); 209 210 // Inneres DIV an das äuÃere anhängen 211 div.appendChild(divInner); 212 213 autocompleteCount++; 214 } 215 } 216 }; 217 218 // Kein Ajax mode -> Aufbaufunktion aufrufen 219 // Ajax mode -> Daten abrufen und dann Aufbaufunktion als Callback aufrufen 220 if ( !ajaxMode ) { 221 autocompleteCallbackFunc(); 222 } else { 223 224 let ajaxUrl = ''; 225 226 // AJAX Url ermitteln 227 if ( typeof input.dataset.autocompleteurl !== 'undefined' && input.dataset.autocompleteurl ) { 228 ajaxUrl = input.dataset.autocompleteurl; 229 230 } else { 231 232 // ACHTUNG SONDERFALL SOLR CONTENT: 233 // Hier ist die URL an dem <form>-Element! 234 if ( typeof input.form !== 'undefined' && typeof input.form.dataset.suggest !== 'undefined' && input.form.dataset.suggest ) { 235 let formSuggest = input.form; 236 237 if ( formSuggest ) { 238 239 // Ajax URL zusammenbauen 240 ajaxUrl = formSuggest.dataset.suggest + '&tx_solr[callback]=&tx_solr[queryString]=' + _this.ajaxStringPlaceholder; 241 } 242 } 243 } 244 245 // Prüfen, ob der String den Platzhalter enthält 246 if ( ajaxUrl !== '' && ajaxUrl.indexOf(_this.ajaxStringPlaceholder) !== -1 ) { 247 248 // Timestamp an die URL anhängen 249 ajaxUrl += '&_t=' + Date.now(); 250
251 // Den Suchstring im Platzhalter(_this.ajaxStringPlaceholder) der AJAX-URL eintragen 252 ajaxUrl = ajaxUrl.replace(_this.ajaxStringPlaceholder, val); 253 254 // WICHTIG! Content-Array leeren 255 autocompleteContent = []; 256 257 // Zuerst prüfen, ob bereits ein Request läuft, diesen ggf. abbrechen 258 if ( _this.xhttpSuggestionContent !== null && typeof _this.xhttpSuggestionContent.abort === 'function' ) { 259 _this.xhttpSuggestionContent.abort(); 260 } 261 262 // AJAX-Request 263 _this.xhttpSuggestionContent = new window.XMLHttpRequest(); 264 265 // Response Funktion 266 _this.xhttpSuggestionContent.onreadystatechange = function () { 267 268 // Sobald die Verbindung geöffnet ist... 269 if (this.readyState === 1) { 270 //... 271 } 272 273 // Sobald die Antwort da ist, JSON in das richtige Format bringen 274 // Und der Content Variable hinzufügen. Danach das Autocomplete-Fenster anzeigen 275 if (this.readyState === 4 && this.status === 200) { 276 let response = this.responseText; 277 278 // ACHTUNG: Die Antwort von SOLR z.B. ist teilweise text/html und nicht application/json 279 // Dadurch kann es vorkommen, dass an erster und letzer Stelle noch Klammern sind 280 // Diese müssen entfernt werden, da sonst aus der Antwort 281 // kein JSON Objekt erstellt werden kann 282 if ( response.charAt(0) === '(' ) { 283 response = response.substr(1); 284 } 285 286 if ( response.charAt(response.length - 1) === ')' ) { 287 response = response.substring(0, response.length - 1); 288 } 289 290 // Versuchen die Antwort in ein JSON Format zu bekommen 291 try { 292 let responseJSON = JSON.parse(response); 293 294 // Prüfen, ob die Antwort die korrekte Property hat 295 if ( responseJSON.hasOwnProperty(_this.autocompleteResponseObjectProperty) ) { 296 let suggestions = responseJSON[_this.autocompleteResponseObjectProperty]; 297 298 let suggestionCount = 0; 299 300 // Objekt erstellen, das prüft, ob es ein Eintrag bereits vorhanden ist 301 let usedSuggestions = {}; 302 303 // Alle Vorschläge durchgehen 304 for (let suggestion in suggestions) { 305 306 // Prüfen, ob der Vorschlag schon vorhanden ist 307 if ( usedSuggestions.hasOwnProperty(suggestion) ) { 308 continue; 309 } 310 311 // Prüfen, ob die maximal Anzahl an Vorschlägen erreicht ist 312 if ( suggestionCount >= _this.autocompleteMaxCount ) { 313 break; 314 } 315 316 // Sicherstellen, dass die Eigenschaft auch existiert (Absicherung) 317 if (suggestions.hasOwnProperty(suggestion)) { 318 319 // Vorschlag in das Content Array schreiben und Zähler hochzählen 320 autocompleteContent.push(suggestion); 321 suggestionCount++; 322 323 // Vorschlag in den Cache speichern 324 usedSuggestions[suggestion] = true; 325 } 326 } 327 328 // Prüfen, ob Vorschläge enthalten sind und ggf. die 329 // Callback-Funktion aufrufen 330 if ( autocompleteContent.length > 0 ) { 331 autocompleteCallbackFunc(); 332 } 333 } 334 335 } catch (e) { 336 // Detaillierte Angabe des Fehlers 337 console.warn('Server Antwort des Autocompletes war kein gültiges JSON!'); 338 console.warn('URL:'); 339 console.warn(ajaxUrl); 340 console.warn('Input Feld:');
341 console.warn(input); 342 console.warn('Error:'); 343 console.warn(e); 344 } 345 } 346 }; 347 348 // AJAX-Request abschicken 349 _this.xhttpSuggestionContent.open("GET", ajaxUrl); 350 _this.xhttpSuggestionContent.send(); 351 } 352 } 353 }); 354 355 // Beim Fokusverlust des Inputs alle Listen ausblenden 356 // ACHTUNG: Muss mit einem kleinen Timeout gemacht werden, da sonst bei einem Klick auf 357 // ein Listeneintrag blur zu früh ausgeführt wird und das Klick-Event untergeht 358 input.addEventListener("blur", function(e) { 359 window.setTimeout( 360 function() { 361 closeAllLists(); 362 }, 250); 363 }); 364 365 // keydown speziell behandeln (für Navigation mit den Pfeiltasten + Enter + Escape) 366 input.addEventListener("keydown", function(e) { 367 368 let autocompleteList = document.getElementById(this.id + "autocomplete-list"); 369 370 // Alle inneren Divs ermitteln 371 if (autocompleteList) { 372 autocompleteList = autocompleteList.getElementsByTagName("div"); 373 } 374 375 // Pfeiltaste nach unten 376 if (e.keyCode === 40) { 377 378 e.preventDefault(); 379 380 // Aktueller Fokus um 1 erhöhen 381 currentFocus++; 382 383 // Aktives Element Sichtbarkeit erhöhen 384 addActive(autocompleteList); 385 386 // Pfeiltaste nach oben 387 } else if (e.keyCode === 38) { 388 389 e.preventDefault(); 390 391 // Aktueller Fokus um 1 reduzieren 392 currentFocus--; 393 394 // Aktives Element Sichtbarkeit erhöhen 395 addActive(autocompleteList); 396 397 // Enter Taste 398 } else if (e.keyCode === 13) { 399 400 // ESC Taste 401 } else if (e.keyCode === 27) { 402 e.preventDefault(); 403 currentFocus = -1; 404 closeAllLists(); 405 } 406 }); 407 408 /* 409 ACHTUNG: DASS DAS AUTOCOMPLETE KORREKT FUNKTIONIERT MÃSSEN DIE FUNKTIONEN HIER FÃR JEDES AUTOCOMPLETE 410 DEFINIERT SEIN, DA SONST DER KONTEXT NICHT MEHR KORREKT IST! 411 NUR NACH AUÃEN SCHIEBEN, WENN EINE BESSERE LÃSUNG GEFUNDEN WURDE! 412 BITTE VORHER MIT MIR (JAN) ABSPRECHEN, SOFERN ICH NOCH HIER BIN. ANSONSTEN GL&HF 413 */ 414 415 /** 416 * addActive 417 * 418 * Erhöht die Sichtbarkeit des fokussierten Elements, in dem die Klasse 419 * autocomplete-active hinzugefügt wird 420 * 421 * @param autocompleteList 422 * @returns {boolean} 423 */ 424 function addActive(autocompleteList) { 425 426 if (!autocompleteList) { 427 return false; 428 } 429 430 // Allen Items die autocomplete-active Klasse entfernen 431 removeActive(autocompleteList); 432 433 // Sollte der aktuelle Fokus höher sein, als es Einträge gibt -> auf ersten 434 // Dies passiert, wenn man den letzten fokussiert und nach unten drückt 435 if (currentFocus >= autocompleteList.length) { 436 currentFocus = 0; 437 } 438 439 // Kleinerer Fokus als 0 -> Fokus auf letzten Eintrag 440 // Dies passiert, wenn man den ersten fokussiert und nach oben drückt 441 if (currentFocus < 0) { 442 currentFocus = (autocompleteList.length - 1); 443 } 444 445 // Dem aktuellen Fokus die autocomplete-active Klasse hinzufügen 446 autocompleteList[currentFocus].classList.add("autocomplete-active"); 447 448 // Den ausgewählten Wert in das Input feld schreiben 449 input.value = autocompleteList[currentFocus].getElementsByTagName("input")[0].value; 450 } 451 452 /** 453 * removeActive 454 * 455 * Entfernt die autocomplete-active Klasse von allen Elementen der übergebenen Liste 456 * 457 * @param autocompleteList 458 */ 459 function removeActive(autocompleteList) { 460 461 // Entfernt die autocomplete-active Klasse von allen Elementen der übergebenen Liste 462 for (let i = 0; i < autocompleteList.length; i++) { 463 autocompleteList[i].classList.remove("autocomplete-active"); 464 } 465 } 466 467 /** 468 * closeAllLists 469 * 470 * SchlieÃt alle Autocomplete Listen (löscht diese) 471 * 472 * @param elmnt 473 */ 474 function closeAllLists(elmnt) { 475 476 // SchlieÃt alle Listen (auÃer die evtl. übergebene Liste) 477 var autocompleteList = document.getElementsByClassName("autocomplete-items"); 478 for (let i = 0; i < autocompleteList.length; i++) { 479 if (elmnt !== autocompleteList[i] && elmnt !== input) { 480 autocompleteList[i].parentNode.removeChild(autocompleteList[i]); 481 } 482 } 483 } 484 485 // Bei einem Klick ins Dokument prüfen, ob der Klick auÃerhalb einer Liste ist -> schlieÃen 486 document.addEventListener("click", function (e) { 487 closeAllLists(e.target); 488 }); 489 490 // Beim Window Resize -> alle Listen schlieÃen 491 window.addEventListener("resize", function (e) { 492 closeAllLists(); 493 }); 494 495 // Beim Window Scroll -> alle Listen schlieÃen 496 window.addEventListener("scroll", function(e) { 497 closeAllLists(); 498 }); 499 if (document.getElementById('menue_wrap_scroll')) { 500 document.getElementById('menue_wrap_scroll').addEventListener("scroll", function (e) { 501 closeAllLists(); 502 }); 503 } 504 } 505}
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.