1(function () { 2 'use strict'; 3 4 document.addEventListener('DOMContentLoaded', function () { 5 var form = document.getElementById('ztr-locator-form'); 6 var resultsEl = document.getElementById('ztr-locator-results'); 7 var messageEl = document.getElementById('ztr-locator-message'); 8 var srStatusEl = document.getElementById('ztr-locator-sr-status'); 9 var queryLabelEl = document.getElementById('ztr-locator-query-label'); 10 var geoBtn = document.getElementById('ztr-locator-geo'); 11 var resetBtn = document.getElementById('ztr-locator-reset'); 12 var addressInput = document.getElementById('ztr-locator-address'); 13 var suggestionsEl = document.getElementById('ztr-locator-address-suggestions'); 14 var latInput = document.getElementById('ztr-locator-lat'); 15 var lngInput = document.getElementById('ztr-locator-lng'); 16 var categorySelect = document.getElementById('ztr-locator-category'); 17 18 if (!form || !resultsEl || typeof ztrLocator === 'undefined') { 19 return; 20 } 21 22 // Zwischenspeicher des zuletzt aufgelösten Suchortes (lat/lng/Stadt), 23 // damit Pagination-Klicks nicht jedes Mal erneut geocodieren müssen. 24 // null = aktuell keine aktive Suche (alphabetische Liste). 25 var lastOrigin = null; 26 27 function setMessage(text) { 28 messageEl.textContent = text || ''; 29 } 30 31 function setQueryLabel(text) { 32 if (queryLabelEl) { 33 queryLabelEl.textContent = text || ''; 34 } 35 } 36 37 function setSrStatus(text) { 38 if (srStatusEl) { 39 srStatusEl.textContent = text || ''; 40 } 41 } 42 43 function search(params) { 44 setMessage(ztrLocator.i18n.searching); 45 46 var body = new FormData(); 47 body.append('action', 'ztr_locator_search'); 48 body.append('nonce', ztrLocator.nonce); 49 body.append('page', params.page || 1); 50 51 if (typeof params.category === 'string') { 52 body.append('category', params.category); 53 } 54 55 if (params.lat && params.lng) { 56 body.append('lat', params.lat); 57 body.append('lng', params.lng); 58 if (typeof params.city === 'string') { 59 body.append('city', params.city); 60 } 61 } else if (params.address) { 62 body.append('address', params.address); 63 } 64 65 fetch(ztrLocator.ajaxUrl, { 66 method: 'POST', 67 credentials: 'same-origin', 68 body: body 69 }) 70 .then(function (response) { 71 return response.json(); 72 }) 73 .then(function (data) { 74 if (data.success) { 75 resultsEl.innerHTML = data.data.html; 76 lastOrigin = data.data.origin || null; 77 // Ladehinweis wieder leeren - die Trefferzahl wird nicht mehr sichtbar 78 // angezeigt (redundant, da immer alle Institute der Kategorie ausgegeben 79 // werden), sondern nur noch für Screenreader in der versteckten 80 // Live-Region angesagt (WCAG 4.1.3). 81 setMessage(''); 82 setSrStatus(data.data.resultsMessage || ''); 83 // Nur bei einer neuen Suche gesetzt (siehe Aufrufer) - bei reiner 84 // Pagination bleibt das zuletzt gesetzte Label unverändert stehen. 85 if (typeof params.queryLabel !== 'undefined') { 86 setQueryLabel(params.queryLabel); 87 } 88 } else { 89 setMessage((data.data && data.data.message) || ztrLocator.i18n.genericError); 90 } 91 }) 92 .catch(function () { 93 setMessage(ztrLocator.i18n.genericError); 94 }); 95 } 96 97 form.addEventListener('submit', function (e) { 98 e.preventDefault(); 99 latInput.value = ''; 100 lngInput.value = ''; 101 102 var address = addressInput.value.trim(); 103 var label = address 104 ? ztrLocator.i18n.queryLabelAddress.replace('%s', function () { return address; }) 105 : ''; 106 107 search({ address: address, page: 1, queryLabel: label, category: categorySelect ? categorySelect.value : '' }); 108 }); 109 110 if (geoBtn) { 111 geoBtn.addEventListener('click', function () { 112 if (!navigator.geolocation) { 113 setMessage(ztrLocator.i18n.geoUnsupported); 114 return; 115 } 116 117 setMessage(ztrLocator.i18n.locating); 118 119 navigator.geolocation.getCurrentPosition( 120 function (position) { 121 latInput.value = position.coords.latitude; 122 lngInput.value = position.coords.longitude; 123 addressInput.value = ''; 124 search({ 125 lat: position.coords.latitude, 126 lng: position.coords.longitude, 127 page: 1, 128 queryLabel: ztrLocator.i18n.queryLabelLocation, 129 category: categorySelect ? categorySelect.value : '' 130 }); 131 }, 132 function () { 133 setMessage(ztrLocator.i18n.geoError); 134 } 135 ); 136 }); 137 } 138 139 if (resetBtn) { 140 resetBtn.addEventListener('click', function () { 141 addressInput.value = ''; 142 latInput.value = ''; 143 lngInput.value = ''; 144 if (categorySelect) { 145 categorySelect.value = ''; 146 } 147 lastOrigin = null; 148 setMessage(''); 149 search({ page: 1, queryLabel: '', category: '' }); 150 }); 151 } 152 153 // Autocomplete: Adress-Vorschläge während der Eingabe, entkoppelt (debounced) 154 // und mit Schutz gegen aus der Reihenfolge geratene Antworten. 155 if (suggestionsEl) { 156 var suggestTimer = null; 157 var suggestRequestId = 0; 158 159 addressInput.addEventListener('input', function () { 160 var query = addressInput.value.trim(); 161 162 window.clearTimeout(suggestTimer); 163 164 if (query.length < 3) { 165 suggestionsEl.innerHTML = ''; 166 return; 167 } 168 169 suggestTimer = window.setTimeout(function () { 170 var requestId = ++suggestRequestId; 171 var url = ztrLocator.ajaxUrl 172 + '?action=ztr_locator_autocomplete' 173 + '&nonce=' + encodeURIComponent(ztrLocator.nonce) 174 + '&q=' + encodeURIComponent(query); 175 176 fetch(url, { credentials: 'same-origin' }) 177 .then(function (response) { 178 return response.json(); 179 }) 180 .then(function (data) { 181 // Ãltere, inzwischen überholte Antwort ignorieren. 182 if (requestId !== suggestRequestId || !data.success) { 183 return; 184 } 185 186 suggestionsEl.innerHTML = '';
187 (data.data.suggestions || []).forEach(function (label) { 188 var option = document.createElement('option'); 189 option.value = label; 190 suggestionsEl.appendChild(option); 191 }); 192 }) 193 .catch(function () { 194 // Vorschläge sind eine Zusatzfunktion - Fehler hier bewusst still ignorieren. 195 }); 196 }, 300); 197 }); 198 } 199 200 // Kategorie-Filter wirkt bewusst erst nach Klick auf "Suchen" (Formular- 201 // Submit) bzw. beim nächsten Standort-/Pagination-Request - kein 202 // automatisches Nachladen allein durch die Auswahl im Dropdown. 203 204 // Pagination-Buttons werden bei jeder Suche neu ins DOM gerendert, 205 // daher Event-Delegation auf dem stabilen Ergebnis-Container. 206 resultsEl.addEventListener('click', function (e) { 207 var btn = e.target.closest('[data-page]'); 208 if (!btn || btn.disabled) { 209 return; 210 } 211 212 var page = parseInt(btn.getAttribute('data-page'), 10); 213 if (!page || page < 1) { 214 return; 215 } 216 217 var params = { page: page, category: categorySelect ? categorySelect.value : '' }; 218 if (lastOrigin) { 219 params.lat = lastOrigin.lat; 220 params.lng = lastOrigin.lng; 221 params.city = lastOrigin.city; 222 } 223 224 search(params); 225 226 var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 227 resultsEl.scrollIntoView({ behavior: reduceMotion ? 'auto' : 'smooth', block: 'start' }); 228 }); 229 230 // Ankunft über das Mini-Suchformular im Header (?address=...): Feld ist 231 // serverseitig bereits vorbefüllt (siehe search-form.php) - Suche direkt 232 // auslösen, statt einen zusätzlichen Klick auf "Suchen" zu verlangen. 233 var initialAddress = addressInput.value.trim(); 234 if (initialAddress) { 235 var initialLabel = ztrLocator.i18n.queryLabelAddress.replace('%s', function () { return initialAddress; }); 236 search({ address: initialAddress, page: 1, queryLabel: initialLabel, category: categorySelect ? categorySelect.value : '' }); 237 } 238 }); 239})();
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.