PageSourceSearch

https://www.flanderijn.nl/assets/js/accessibility.js

js flanderijn.nl collected 2026-10-05 07:30:36 UTC 12,673 bytes, 455 lines download raw bytes

1function initializeWidgetAria() {
2  const translations = {
3    default: "Widget taalvertaling",
4    ar: "أداة ترجمة اللغة",
5    "zh-TW": "視窗語言翻譯",
6    "zh-CN": "窗口语言翻译",
7    de: "Widget Sprachübersetzung",
8    en: "Widget Language Translate",
9    fr: "Widget de traduction de langue",
10    pl: "Widget tłumaczenia języka",
11    es: "Widget de traducción de idiomas",
12    tr: "Dil çeviri widget'ı",
13  };
14
15  const applyLabel = (element) => {
16    const currentLang = document.documentElement.lang;
17    const newLabel = translations[currentLang] || translations["default"];
18    element.setAttribute("aria-label", newLabel);
19  };
20
21  // 1. Check if it already exists
22  const existingSelect = document.querySelector(".goog-te-combo");
23  if (existingSelect) {
24    applyLabel(existingSelect);
25  }
26
27  // 2. Watch the DOM for when the widget is injected
28  const observer = new MutationObserver((mutations, obs) => {
29    const select = document.querySelector(".goog-te-combo");
30    if (select) {
31      applyLabel(select);
32      // Optional: Stop watching once we've found it and fixed it
33      // obs.disconnect();
34    }
35  });
36
37  observer.observe(document.body, {
38    childList: true,
39    subtree: true,
40  });
41}
42
43initializeWidgetAria();
44
45function initializeLocationToggles() {
46  var $locationsContainer = $("ul.locations");
47  console.log("init");
48  if ($locationsContainer.length > 0) {
49    $(".location > h4 button").on("click", function () {
50      console.log("click");
51      if ($(window).width() <= 767) {
52        var $currentBtn = $(this);
53        var $parentLi = $currentBtn.parent().parent(".location");
54
55        $(".location").not($parentLi).removeClass("active");
56        $(".location > button").not($currentBtn).removeClass("active");
57
58        $parentLi.toggleClass("active");
59        $currentBtn.toggleClass("active");
60      }
61    });
62  }
63}
64
65initializeLocationToggles();
66
67function initializeTranslateErrorMessages() {
68  if (
69    document.documentElement.lang !== "nl" ||
70    document.querySelector(".status-open")?.getAttribute("lang") === "en"
71  )
72    return;
73
74  function updateErrorMessages() {
75    const errorMessages = document.querySelectorAll("p.error-message");
76
77    errorMessages.forEach((msg) => {
78      const originalText = msg.textContent.trim();
79
80      // Find the wrapper element to extract the field label name
81      const fieldWrapper = msg.closest(".freeform-column");
82      if (!fieldWrapper) return;
83      const label = fieldWrapper.querySelector("label.freeform-label");
84      if (!label) return;
85
86      let fieldName = label.textContent.trim();
87      fieldName = fieldName.replace("*", "").trim();
88
89      // Condition A: Required fields
90      if (originalText === "This field is required") {
91        msg.textContent = `Fout: het veld '${fieldName}' is verplicht`;
92      }
93
94      // Condition B: Date formatting fields
95      else if (originalText.includes("does not conform to")) {
96        msg.textContent = `Fout: het veld '${fieldName}' voldoet niet aan het formaat 'DD-MM-YYYY'`;
97      }
98    });
99  }
100
101  const observer = new MutationObserver(() => {
102    updateErrorMessages();
103  });
104
105  observer.observe(document.body, {
106    childList: true,
107    subtree: true,
108  });
109
110  updateErrorMessages();
111}
112
113initializeTranslateErrorMessages();
114
115function initializeTranslateErrorMessagesEN() {
116  if (document.querySelector(".status-open")?.getAttribute("lang") !== "en")
117    return;
118
119  function updateErrorMessages() {
120    const errorMessages = document.querySelectorAll("p.error-message");
121
122    errorMessages.forEach((msg) => {
123      if (msg.textContent.trim() === "This field is required") {
124        const fieldWrapper = msg.closest(".freeform-column");
125        if (!fieldWrapper) return;
126        const label = fieldWrapper.querySelector("label.freeform-label");
127        if (!label) return;
128        let fieldName = label.textContent.trim();
129        fieldName = fieldName.replace("*", "").trim();
130        msg.textContent = `Error: the field '${fieldName}' is required`;
131      }
132    });
133  }
134
135  const observer = new MutationObserver(() => {
136    updateErrorMessages();
137  });
138
139  observer.observe(document.body, {
140    childList: true,
141    subtree: true,
142  });
143
144  updateErrorMessages();
145}
146
147initializeTranslateErrorMessagesEN();
148
149function initializeErrorAlertRoles() {
150  const addRoles = () => {
151    const errorMessages = document.querySelectorAll("p.error-message");
152
153    errorMessages.forEach((msg) => {
154      if (!msg.hasAttribute("role")) {
155        msg.setAttribute("role", "alert");
156      }
157    });
158  };
159
160  addRoles();
161
162  const observer = new MutationObserver(addRoles);
163
164  observer.observe(document.body, {
165    childList: true,
166    subtree: true,
167  });
168}
169
170initializeErrorAlertRoles();
171
172function initializeFreeformRequiredFields() {
173  const requiredLabels = document.querySelectorAll("label.freeform-required");
174
175  requiredLabels.forEach((label) => {
176    const column = label.closest(".freeform-column");
177
178    if (column) {
179      const input = column.querySelector("input, textarea, select");
180
181      if (input) {
182        input.required = true;
183        input.setAttribute("aria-required", "true");
184      }
185    }
186  });
187}
188
189function initializeApplyAutocompletetoForm() {
190  var targetForms = document.querySelectorAll("form");
191
192  if (targetForms.length === 0) return;
193
194  var mapping = {
195    voornaam: "given-name",
196    voorletters: "given-name",
197    tussenvoegsel: "additional-name",
198    achternaam: "family-name",
199    last_name: "family-name",
200    geboortedatum: "bday",
201    "geboortedatum-day": "bday-day",
202    "geboortedatum-month": "bday-month",
203    "geboortedatum-year": "bday-year",
204    postcode: "postal-code",
205    huisnummer: "address-line2",
206    straatnaam: "address-line1",
207    adres: "address-line1",
208    stad: "address-level2",
209    emailadres: "email",
210    email: "email",
211    telefoonnummer: "tel",
212  };
213
214  targetForms.forEach(function (targetForm) {
215    var inputs = targetForm.querySelectorAll("input[name], select[name]");
216
217    inputs.forEach(function (input) {
218      var nameValue = input.getAttribute("name").toLowerCase();
219      if (mapping[nameValue]) {
220        input.setAttribute("autocomplete", mapping[nameValue]);
221      }
222    });
223  });
224}
225
226initializeApplyAutocompletetoForm();
227
228function initializeWhatsAppFocus() {
229  const container = document.querySelector(".c-messenger");
230  const messengerBtn = document.querySelector("[js-messenger-toggle]");
231  const messengerLink = document.querySelector(".messenger__link");
232
233  if (!container || !messengerBtn || !messengerLink) return;
234
235  container.addEventListener("keydown", (e) => {
236    if (e.key !== "Tab") return;
237
238    const isOpen = container.classList.contains("is--visible");
239
240    if (isOpen) {
241      if (document.activeElement === messengerBtn && !e.shiftKey) {
242        e.preventDefault();
243        messengerLink.focus();
244      } else if (document.activeElement === messengerLink && e.shiftKey) {
245        e.preventDefault();
246        messengerBtn.focus();
247      }
248    }
249  });
250}
251
252initializeWhatsAppFocus();
253
254function initializeNavigationFocus() {
255  const header = document.querySelector(".header");
256  const toggle = header && header.querySelector(".navbar-toggle");
257
258  if (!header || !toggle) return;
259
260  function isVisible(el) {
261    if (!el) return false;
262    return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
263  }
264
265  function getLastTopLevelTab() {
266    const tabs = Array.from(
267      header.querySelectorAll(".navbar__tabs .navbar__tab")
268    ).filter(isVisible);
269
270    return tabs.length ? tabs[tabs.length - 1] : null;
271  }
272
273  function getActivePanel() {
274    const activePanel =
275      header.querySelector(".navbar__panel.is--active") ||
276      header.querySelector('.navbar__panel[js-navbar-panel="active"]');
277
278    return isVisible(activePanel) ? activePanel : null;
279  }
280
281  function getFocusableInPanel(panel) {
282    if (!panel) return [];
283
284    return Array.from(
285      panel.querySelectorAll(
286        'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])'
287      )
288    ).filter(isVisible);
289  }
290
291  function getActiveParentTab() {
292    // Prefer the active desktop tab in the left column
293    const activeDesktopTab = header.querySelector(
294      ".navbar__tabs .navbar__tab.is--active"
295    );
296    if (isVisible(activeDesktopTab)) return activeDesktopTab;
297
298    // Fallback: active mobile tab in the panels column
299    const activeMobileTab = header.querySelector(
300      ".navbar__panels .navbar__tab--mobile.is--active"
301    );
302    if (isVisible(activeMobileTab)) return activeMobileTab;
303
304    return null;
305  }
306
307  header.addEventListener(
308    "keydown",
309    function (e) {
310      if (e.key !== "Tab" || e.shiftKey) return;
311
312      const active = document.activeElement;
313      if (!active) return;
314
315      // CASE 1:
316      // From the final top-level .navbar__tab -> back to the toggle
317      const lastTopLevelTab = getLastTopLevelTab();
318      if (lastTopLevelTab && active === lastTopLevelTab) {
319        e.preventDefault();
320        toggle.focus();
321        return;
322      }
323
324      // CASE 2:
325      // From the last focusable item in the active panel -> back to the parent tab
326      const activePanel = getActivePanel();
327      if (!activePanel) return;
328
329      const focusables = getFocusableInPanel(activePanel);
330      if (!focusables.length) return;
331
332      const lastFocusable = focusables[focusables.length - 1];
333
334      if (active === lastFocusable) {
335        const parentTab = getActiveParentTab();
336        if (parentTab) {
337          e.preventDefault();
338          parentTab.focus();
339        }
340      }
341    },
342    true
343  );
344}
345
346initializeNavigationFocus();
347
348function initializeNavigationFocusMobile() {
349  const navToggle = document.querySelector(".navbar-toggle");
350  const navWrapper = document.querySelector(".nav-wrapper");
351
352  if (!navToggle || !navWrapper) return;
353
354  navToggle.addEventListener("keydown", function (e) {
355    if (e.key === "Tab" && !e.shiftKey) {
356      const isExpanded = navToggle.getAttribute("aria-expanded") === "true";
357
358      if (isExpanded) {
359        const firstMobileTab = navWrapper.querySelector(".navbar__tab--mobile");
360
361        if (firstMobileTab) {
362          e.preventDefault();
363          firstMobileTab.focus();
364        }
365      }
366    }
367  });
368
369  navWrapper.addEventListener("keydown", function (e) {
370    if (e.key !== "Tab") return;
371
372    const target = e.target;
373
374    const allMobileTabs = Array.from(
375      navWrapper.querySelectorAll(".navbar__tab--mobile, .navbar__tab--contact")
376    );
377
378    // 1. Tab behavior on mobile tabs
379    if (target.classList.contains("navbar__tab--mobile")) {
380      const isPanelActive = target.classList.contains("is--active");
381
382      const currentIndex = allMobileTabs.indexOf(target);
383      const isLastTab = currentIndex === allMobileTabs.length - 1;
384
385      if (!e.shiftKey) {
386        // If panel is closed → skip children
387        if (!isPanelActive) {
388          if (isLastTab) {
389            // 🔁 LOOP BACK TO TOGGLE
390            e.preventDefault();
391            navToggle.focus();
392          } else {
393            const nextTab = allMobileTabs[currentIndex + 1];
394            if (nextTab) {
395              e.preventDefault();
396              nextTab.focus();
397            }
398          }
399        }
400      }
401    }
402
403    // 2. Last link inside panel → back to its tab
404    if (!e.shiftKey && target.tagName === "A") {
405      const currentPanel = target.closest(".navbar__panel");
406      if (!currentPanel) return;
407
408      const currentTab = navWrapper.querySelector(
409        `.navbar__tab--mobile[aria-controls="${currentPanel.id}"]`
410      );
411
412      if (!currentTab) return;
413
414      const focusableLinks = Array.from(
415        currentPanel.querySelectorAll(
416          ".navbar__item-link, .navbar__child-link, .navbar__grandchild-link"
417        )
418      );
419
420      const lastLink = focusableLinks[focusableLinks.length - 1];
421
422      if (target === lastLink) {
423        e.preventDefault();
424        currentTab.focus();
425      }
426    }
427  });
428}
429
430initializeNavigationFocusMobile();
431
432function initLocationButton() {
433  const locationButtons = document.querySelectorAll(".location h4 button");
434
435  if (locationButtons.length === 0) {
436    return;
437  }
438
439  locationButtons.forEach(function (button) {
440    button.addEventListener("click", function () {
441      // 1. Check current state and flip it to the opposite
442      const isExpanded = this.getAttribute("aria-expanded") === "true";
443      this.setAttribute("aria-expanded", !isExpanded);
444
445      // 2. Reset all OTHER location buttons to false
446      locationButtons.forEach(function (otherButton) {
447        if (otherButton !== button) {
448          otherButton.setAttribute("aria-expanded", "false");
449        }
450      });
451    });
452  });
453}
454
455initLocationButton();

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.