PageSourceSearch

https://www.ztr-rechenschwaeche.de/wp-content/plugins/ztr-institute-locator/assets/js/locator.js?ver=1.3.0

js ztr-rechenschwaeche.de collected 2026-10-02 18:37:00 UTC 7,710 bytes, 239 lines download raw bytes

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.