PageSourceSearch

https://tourismus.bayern/wp-content/plugins/baytm-self-check/assets/frontend.js

js tourismus.bayern collected 2026-10-02 10:23:11 UTC 10,188 bytes, 309 lines download raw bytes

1(function () {
2	function clamp(value, min, max) {
3		return Math.max(min, Math.min(max, value));
4	}
5
6	function escapeHtml(value) {
7		return String(value)
8			.replace(/&/g, '&')
9			.replace(/</g, '&lt;')
10			.replace(/>/g, '&gt;')
11			.replace(/"/g, '&quot;')
12			.replace(/'/g, '&#39;');
13	}
14
15	function getResultText(config, percentage) {
16		const levels = Array.isArray(config.results) ? config.results : [];
17		for (let index = 0; index < levels.length; index += 1) {
18			const level = levels[index];
19			if (percentage >= Number(level.min) && percentage <= Number(level.max)) {
20				return level.text || '';
21			}
22		}
23		return '';
24	}
25
26	function setQuestionVisibility(questions, currentIndex) {
27		questions.forEach(function (question, index) {
28			question.hidden = index !== currentIndex;
29		});
30	}
31
32	function initSelfCheck(root) {
33		if (!root || root.__baytmSelfCheckInit) {
34			return;
35		}
36		root.__baytmSelfCheckInit = true;
37
38		const config = JSON.parse(root.getAttribute('data-config') || '{}');
39		const strings = config.strings || {};
40		const questionElements = Array.from(root.querySelectorAll('[data-question-index]'));
41		const progressLabel = root.querySelector('[data-self-check-progress-label]');
42		const progressFill = root.querySelector('[data-self-check-progress-fill]');
43		const result = root.querySelector('[data-self-check-result]');
44		const resultActions = root.querySelector('[data-self-check-result-actions]');
45		const restartButton = root.querySelector('[data-self-check-restart]');
46		const answers = new Map();
47		let currentIndex = 0;
48		let evaluated = false;
49
50		function setFormLocked(isLocked) {
51			questionElements.forEach(function (questionElement) {
52				Array.from(questionElement.querySelectorAll('[data-option-value], [data-self-check-prev], [data-self-check-next]')).forEach(function (control) {
53					control.disabled = isLocked;
54				});
55			});
56		}
57
58		function clearQuestionOptions(questionElement) {
59			Array.from(questionElement.querySelectorAll('[data-option-value]')).forEach(function (button) {
60				button.classList.remove('is-selected');
61				button.classList.remove('is-correct');
62				button.classList.remove('is-wrong');
63				button.setAttribute('aria-checked', 'false');
64			});
65		}
66
67		questionElements.forEach(clearQuestionOptions);
68
69		function updateProgress() {
70			const total = questionElements.length;
71			const current = Math.min(currentIndex + 1, total);
72			if (progressLabel) {
73				const template = strings.progress || 'Frage %1$d von %2$d';
74				progressLabel.textContent = template
75					.replace('%1$d', String(current))
76					.replace('%2$d', String(total));
77			}
78			if (progressFill) {
79				const percent = total > 0 ? (answers.size / total) * 100 : 0;
80				progressFill.style.width = percent + '%';
81			}
82		}
83
84		function updatePager() {
85			if (evaluated) {
86				setFormLocked(true);
87				return;
88			}
89
90			questionElements.forEach(function (questionElement, index) {
91				const prevButton = questionElement.querySelector('[data-self-check-prev]');
92				const nextButton = questionElement.querySelector('[data-self-check-next]');
93				const answered = answers.has(index);
94				if (prevButton) {
95					prevButton.disabled = index === 0;
96				}
97				if (nextButton) {
98					nextButton.disabled = !answered;
99				}
100			});
101		}
102
103		function renderResult() {
104			if (!result || evaluated) {
105				return;
106			}
107			evaluated = true;
108
109			let html = '';
110
111			if (config.mode === 'score') {
112				let achieved = 0;
113				let total = 0;
114				(config.questions || []).forEach(function (question, index) {
115					const currentAnswer = answers.get(index);
116					const currentValue = Number(currentAnswer && Number.isFinite(currentAnswer.value) ? currentAnswer.value : 0);
117					achieved += currentValue;
118					total += Number(question.maxPoints || 0);
119				});
120				const percentage = total > 0 ? Math.round((achieved / total) * 100) : 0;
121				const resultText = getResultText(config, percentage);
122				html =
123					'<div class="baytm-self-check__resultLabel">' + escapeHtml(strings.scoreLabel || 'Ihr Ergebnis') + '</div>' +
124					'<div class="baytm-self-check__resultValue">' + percentage + ' %</div>' +
125					(resultText ? '<p class="baytm-self-check__resultText">' + escapeHtml(resultText) + '</p>' : '');
126			} else if (config.mode === 'quiz') {
127				let correctCount = 0;
128				const totalQuestions = (config.questions || []).length;
129				(config.questions || []).forEach(function (question, index) {
130					const currentAnswer = answers.get(index) || {};
131					const selectedOptionIndex = Number(currentAnswer.optionIndex);
132					const correctOptionIndex = Number(question.correctOption);
133					if (Number.isFinite(selectedOptionIndex) && selectedOptionIndex === correctOptionIndex) {
134						correctCount += 1;
135					}
136				});
137				const quizResultText = getResultText(config, correctCount);
138				html =
139					'<div class="baytm-self-check__resultLabel">' + escapeHtml(strings.quizLabel || 'Quiz-Ergebnis') + '</div>' +
140					'<div class="baytm-self-check__resultValue">' + correctCount + ' / ' + totalQuestions + '</div>' +
141					(quizResultText ? '<p class="baytm-self-check__resultText">' + escapeHtml(quizResultText) + '</p>' : '');
142			} else {
143				const closingText = config.closingText || strings.reflectionDone || 'Alle Fragen sind beantwortet.';
144				html =
145					'<div class="baytm-self-check__resultLabel">' + escapeHtml(strings.reflectionLabel || 'Abgeschlossen') + '</div>' +
146					'<p class="baytm-self-check__resultText">' + closingText + '</p>';
147			}
148
149			result.innerHTML = html;
150			result.hidden = false;
151			if (resultActions) {
152				resultActions.hidden = false;
153			}
154			setFormLocked(true);
155			result.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
156		}
157
158		function resetAll() {
159			answers.clear();
160			currentIndex = 0;
161			evaluated = false;
162			questionElements.forEach(function (questionElement) {
163				clearQuestionOptions(questionElement);
164				const noteElement = questionElement.querySelector('[data-self-check-note]');
165				if (noteElement) {
166					noteElement.hidden = true;
167				}
168			});
169			if (result) {
170				result.innerHTML = '';
171				result.hidden = true;
172			}
173			if (resultActions) {
174				resultActions.hidden = true;
175			}
176			setFormLocked(false);
177			refreshView();
178		}
179
180		function refreshView() {
181			setQuestionVisibility(questionElements, currentIndex);
182			updateProgress();
183			updatePager();
184		}
185
186		questionElements.forEach(function (questionElement) {
187			const questionIndex = Number(questionElement.getAttribute('data-question-index'));
188			const options = Array.from(questionElement.querySelectorAll('[data-option-value]'));
189			const noteElement = questionElement.querySelector('[data-self-check-note]');
190
191			function applySelection(selectedButton) {
192				options.forEach(function (button) {
193					const isSelected = button === selectedButton;
194					button.setAttribute('aria-checked', isSelected ? 'true' : 'false');
195					button.classList.toggle('is-selected', isSelected);
196					button.classList.remove('is-correct');
197					button.classList.remove('is-wrong');
198				});
199			}
200
201			options.forEach(function (button, optionIndex) {
202				button.addEventListener('click', function () {
203					if (evaluated) {
204						return;
205					}
206
207					if (config.mode === 'quiz' && answers.has(questionIndex)) {
208						return;
209					}
210
211					const value = Number(button.getAttribute('data-option-value') || 0);
212					const selectedIndex = Number(button.getAttribute('data-option-index') || 0);
213					answers.set(questionIndex, {
214						value: value,
215						optionIndex: selectedIndex,
216					});
217					applySelection(button);
218
219					if (config.mode === 'quiz') {
220						const questionConfig = (config.questions || [])[questionIndex] || {};
221						const correctOption = Number(questionConfig.correctOption);
222						const correctButton = options.find(function (item) {
223							return Number(item.getAttribute('data-option-index')) === correctOption;
224						});
225						if (selectedIndex === correctOption) {
226							button.classList.add('is-correct');
227						} else {
228							button.classList.add('is-wrong');
229							if (correctButton) {
230								correctButton.classList.add('is-correct');
231							}
232						}
233						if (noteElement) {
234							noteElement.hidden = false;
235						}
236					}
237
238					updatePager();
239					updateProgress();
240				});
241
242				button.addEventListener('keydown', function (event) {
243					if (event.key !== 'ArrowRight' && event.key !== 'ArrowDown' && event.key !== 'ArrowLeft' && event.key !== 'ArrowUp') {
244						return;
245					}
246					event.preventDefault();
247					const direction = event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 1 : -1;
248					const nextIndex = clamp(optionIndex + direction, 0, options.length - 1);
249					options[nextIndex].focus();
250				});
251			});
252
253			const prevButton = questionElement.querySelector('[data-self-check-prev]');
254			const nextButton = questionElement.querySelector('[data-self-check-next]');
255
256			if (prevButton) {
257				prevButton.addEventListener('click', function () {
258					currentIndex = clamp(questionIndex - 1, 0, questionElements.length - 1);
259					refreshView();
260					const target = questionElements[currentIndex].querySelector('[data-option-value]');
261					if (target) {
262						target.focus();
263					}
264				});
265			}
266
267			if (nextButton) {
268				nextButton.addEventListener('click', function () {
269					if (!answers.has(questionIndex)) {
270						return;
271					}
272					const isLast = questionIndex === questionElements.length - 1;
273					if (isLast) {
274						renderResult();
275						return;
276					}
277					currentIndex = clamp(questionIndex + 1, 0, questionElements.length - 1);
278					refreshView();
279					const target = questionElements[currentIndex].querySelector('[data-option-value]');
280					if (target) {
281						target.focus();
282					}
283				});
284			}
285		});
286
287		if (restartButton) {
288			restartButton.addEventListener('click', function () {
289				resetAll();
290				const target = questionElements[0] && questionElements[0].querySelector('[data-option-value]');
291				if (target) {
292					target.focus();
293				}
294			});
295		}
296
297		refreshView();
298	}
299
300	function initAllSelfChecks() {
301		document.querySelectorAll('[data-baytm-self-check]').forEach(initSelfCheck);
302	}
303
304	if (document.readyState === 'loading') {
305		document.addEventListener('DOMContentLoaded', initAllSelfChecks);
306	} else {
307		initAllSelfChecks();
308	}
309})();

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.