PageSourceSearch

https://www.wilburgstetten.de/fileadmin/templates/extensions/hw_solr/js/hw-autocomplete.js

js wilburgstetten.de collected 2026-10-02 21:55:37 UTC 20,624 bytes, 505 lines download raw bytes

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.