PageSourceSearch

https://www.ovb.de/_assets/591954c268d6a7d5bf39e8f45d857458/js/desktop-navigation-new.js?1790084361

js ovb.de collected 2026-10-04 21:48:39 UTC 17,168 bytes, 500 lines download raw bytes

1class NavigationManager {
2  constructor() {
3    this.navbar = document.querySelector("#navbarNavDropdown");
4    this.navList = document.querySelector("#nav-main-ul");
5    this.burgerButton = document.querySelector(".navbar-toggler");
6    this.isMobileNav = false;
7    this.isTransitioning = false;
8    this.openDropdowns = [];
9    this.mobileBreakpoint = 768;
10    this.init();
11  }
12  init() {
13    if (!this.navList) return;
14    this.createBackButtons();
15    this.checkNavFit();
16    this.setupEventListeners();
17    this.setupResizeObserver();
18    this.setupKeyboardNavigation();
19  }
20  /**
21   * Erstellt Zurück-Buttons für mobile Dropdowns
22   */
23  createBackButtons() {
24    const dropdowns = this.navList.querySelectorAll(".dropdown-menu");
25    dropdowns.forEach((dropdown) => {
26      let backButton;
27      if (!dropdown.querySelector(".mobile-back-button")) {
28        backButton = document.createElement("li");
29        backButton.className = "mobile-back-button col-12";
30        backButton.innerHTML = `
31                    <button type="button" class="btn-back" aria-label="Zurück">
32                        <span class="arrow-left"></span> Zurück
33                    </button>
34                `;
35        dropdown.insertBefore(backButton, dropdown.firstChild);
36      } else {
37        backButton = dropdown.querySelector(".mobile-back-button");
38      }
39      backButton.querySelector(".btn-back").addEventListener("click", (e) => {
40        e.preventDefault();
41        e.stopPropagation();
42        this.closeDropdown(dropdown);
43      });
44    });
45  }
46  /**
47   * Setup aller Event Listeners
48   */
49  setupEventListeners() {
50    const navItems = this.navList.querySelectorAll(".nav-item.dropdown");
51    navItems.forEach((item) => {
52      const link = item.querySelector(".nav-link");
53      const button = item.querySelector(".dropdown-toggle");
54      const dropdown = item.querySelector(".dropdown-menu");
55      if (!button || !dropdown) return;
56      item.addEventListener("mouseenter", () => {
57        if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) {
58          this.openDropdown(dropdown, item);
59        }
60      });
61      item.addEventListener("mouseleave", () => {
62        if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) {
63          this.closeDropdown(dropdown, item);
64        }
65      });
66      button.addEventListener("click", (e) => {
67        if (this.isMobileNav || window.innerWidth < this.mobileBreakpoint) {
68          e.preventDefault();
69          e.stopPropagation();
70          this.toggleDropdown(dropdown, item);
71        }
72      });
73      button.addEventListener("click", (e) => {
74        if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) {
75          e.preventDefault();
76          e.stopPropagation();
77        }
78      });
79      link.addEventListener("click", (e) => {
80        if (e.target === link) {
81          return true;
82        }
83      });
84    });
85    this.setupSubDropdowns();
86    if (this.burgerButton) {
87      this.burgerButton.addEventListener("click", () => {
88        this.toggleMobileNav();
89      });
90    }
91    document.addEventListener("click", (e) => {
92      if (!e.target.closest(".navbar-nav")) {
93        this.closeAllDropdowns();
94      }
95    });
96  }
97  /**
98   * Setup für Keyboard Navigation
99   */
100  setupKeyboardNavigation() {
101    const navItems = this.navList.querySelectorAll(".nav-item.dropdown");
102    navItems.forEach((item) => {
103      const link = item.querySelector(".nav-link");
104      const button = item.querySelector(".dropdown-toggle");
105      const dropdown = item.querySelector(".dropdown-menu");
106      if (!link || !button || !dropdown) return;
107      link.addEventListener("keydown", (e) => {
108        switch (e.key) {
109          case " ":
110            e.preventDefault();
111            e.stopPropagation();
112            this.toggleDropdown(dropdown, item);
113            if (dropdown.classList.contains("show")) {
114              setTimeout(() => {
115                const firstLink = dropdown.querySelector("a, button");
116                if (firstLink) firstLink.focus();
117              }, 50);
118            }
119            break;
120          case "Enter":
121            if (dropdown.classList.contains("show")) {
122              this.closeDropdown(dropdown, item);
123            }
124            break;
125          case "Escape":
126            e.preventDefault();
127            this.closeDropdown(dropdown, item);
128            link.focus();
129            break;
130          case "ArrowDown":
131            e.preventDefault();
132            if (!dropdown.classList.contains("show")) {
133              this.openDropdown(dropdown, item);
134            }
135            setTimeout(() => {
136              const firstLink = dropdown.querySelector("a, button");
137              if (firstLink) firstLink.focus();
138            }, 50);
139            break;
140          case "ArrowUp":
141            e.preventDefault();
142            if (!dropdown.classList.contains("show")) {
143              this.openDropdown(dropdown, item);
144            }
145            setTimeout(() => {
146              const links = dropdown.querySelectorAll("a, button:not(.btn-back)");
147              if (links.length > 0) {
148                links[links.length - 1].focus();
149              }
150            }, 50);
151            break;
152        }
153      });
154      dropdown.addEventListener("keydown", (e) => {
155        if (e.key === "Tab") {
156          const focusableElements = dropdown.querySelectorAll("a, button");
157          const firstElement = focusableElements[0];
158          const lastElement = focusableElements[focusableElements.length - 1];
159          if (e.shiftKey && document.activeElement === firstElement) {
160            e.preventDefault();
161            this.closeDropdown(dropdown, item);
162            link.focus();
163          } else if (!e.shiftKey && document.activeElement === lastElement) {
164            this.closeDropdown(dropdown, item);
165          }
166        }
167      });
168    });
169    document.addEventListener("keydown", (e) => {
170      if (e.key === "Escape" && this.openDropdowns.length > 0) {
171        e.preventDefault();
172        const lastOpenDropdown = this.openDropdowns[this.openDropdowns.length - 1];
173        const parentItem = lastOpenDropdown.closest(".nav-item");
174        const link = parentItem?.querySelector(".nav-link");
175        this.closeDropdown(lastOpenDropdown, parentItem);
176        if (link) {
177          link.focus();
178        }
179      }
180    });
181  }
182  /**
183   * Setup für verschachtelte Sub-Dropdowns
184   */
185  setupSubDropdowns() {
186    const subItems = this.navList.querySelectorAll(".dropdown-menu .dropdown-toggle");
187    subItems.forEach((button) => {
188      const parentLi = button.closest("li");
189      const subDropdown = parentLi.querySelector(".dropdown-menu");
190      const subLink = parentLi.querySelector("a.nav-link, a.dropdown-item");
191      if (!subDropdown) return;
192      button.addEventListener("click", (e) => {
193        e.preventDefault();
194        e.stopPropagation();
195        if (this.isMobileNav || window.innerWidth < this.mobileBreakpoint) {
196          this.openDropdown(subDropdown, parentLi);
197        } else {
198          this.toggleDropdown(subDropdown, parentLi);
199        }
200      });
201      if (subLink) {
202        subLink.addEventListener("keydown", (e) => {
203          switch (e.key) {
204            case " ":
205              e.preventDefault();
206              e.stopPropagation();
207              this.toggleDropdown(subDropdown, parentLi);
208              if (subDropdown.classList.contains("show")) {
209                setTimeout(() => {
210                  const firstLink = subDropdown.querySelector("a, button");
211                  if (firstLink) firstLink.focus();
212                }, 50);
213              }
214              break;
215            case "Enter":
216              if (subDropdown.classList.contains("show")) {
217                this.closeDropdown(subDropdown, parentLi);
218              }
219              break;
220            case "Escape":
221              e.preventDefault();
222              this.closeDropdown(subDropdown, parentLi);
223              subLink.focus();
224              break;
225            case "ArrowRight":
226              e.preventDefault();
227              if (!subDropdown.classList.contains("show")) {
228                this.openDropdown(subDropdown, parentLi);
229                setTimeout(() => {
230                  const firstLink = subDropdown.querySelector("a, button");
231                  if (firstLink) firstLink.focus();
232                }, 50);
233              }
234              break;
235            case "ArrowLeft":
236              e.preventDefault();
237              if (subDropdown.classList.contains("show")) {
238                this.closeDropdown(subDropdown, parentLi);
239                subLink.focus();
240              }
241              break;
242            case "ArrowDown":
243              e.preventDefault();
244              if (!subDropdown.classList.contains("show")) {
245                this.openDropdown(subDropdown, parentLi);
246              }
247              setTimeout(() => {
248                const firstLink = subDropdown.querySelector("a, button");
249                if (firstLink) firstLink.focus();
250              }, 50);
251              break;
252            case "ArrowUp":
253              e.preventDefault();
254              if (!subDropdown.classList.contains("show")) {
255                this.openDropdown(subDropdown, parentLi);
256              }
257              setTimeout(() => {
258                const links = subDropdown.querySelectorAll("a, button:not(.btn-back)");
259                if (links.length > 0) {
260                  links[links.length - 1].focus();
261                }
262              }, 50);
263              break;
264          }
265        });
266      }
267      if (window.innerWidth >= this.mobileBreakpoint) {
268        parentLi.addEventListener("mouseenter", () => {
269          if (!this.isMobileNav) {
270            this.openDropdown(subDropdown, parentLi);
271          }
272        });
273        parentLi.addEventListener("mouseleave", () => {
274          if (!this.isMobileNav) {
275            this.closeDropdown(subDropdown, parentLi);
276          }
277        });
278      }
279    });
280  }
281  /**
282   * Öffnet ein Dropdown
283   */
284  openDropdown(dropdown, parentItem) {
285    if (this.isTransitioning) return;
286    if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) {
287      const siblings = Array.from(parentItem.parentElement.children);
288      siblings.forEach((sibling) => {
289        if (sibling !== parentItem) {
290          const siblingDropdown = sibling.querySelector(".dropdown-menu");
291          if (siblingDropdown) {
292            this.closeDropdown(siblingDropdown, sibling);
293          }
294        }
295      });
296    }
297    dropdown.classList.add("show");
298    if (!this.isMobileNav && window.innerWidth >= this.mobileBreakpoint) {
299      dropdown.style.display = "flex";
300    } else {
301      dropdown.style.display = "block";
302    }
303    dropdown.removeAttribute("inert");
304    parentItem.classList.add("open");
305    const button = parentItem.querySelector(".dropdown-toggle");
306    if (button) {
307      button.setAttribute("aria-expanded", "true");
308    }
309    if (!this.openDropdowns.includes(dropdown)) {
310      this.openDropdowns.push(dropdown);
311    }
312    if (this.isMobileNav || window.innerWidth < this.mobileBreakpoint) {
313      requestAnimationFrame(() => {
314      });
315    }
316  }
317  /**
318   * Schließt ein Dropdown
319   */
320  closeDropdown(dropdown, parentItem = null) {
321    if (!dropdown) return;
322    if (!parentItem) {
323      parentItem = dropdown.closest("li");
324    }
325    if (this.isMobileNav || window.innerWidth < this.mobileBreakpoint) {
326      setTimeout(() => {
327        dropdown.classList.remove("show");
328        dropdown.style.display = "none";
329        if (parentItem) {
330          parentItem.classList.remove("open");
331        }
332      }, 300);
333    } else {
334      dropdown.classList.remove("show");
335      dropdown.style.display = "none";
336      if (dropdown.classList.contains("dropdown-menu--lvl-2")) {
337        dropdown.setAttribute("inert", "");
338      }
339      if (parentItem) {
340        parentItem.classList.remove("open");
341      }
342    }
343    const button = parentItem?.querySelector(".dropdown-toggle");
344    if (button) {
345      button.setAttribute("aria-expanded", "false");
346    }
347    const index = this.openDropdowns.indexOf(dropdown);
348    if (index > -1) {
349      this.openDropdowns.splice(index, 1);
350    }
351    const subDropdowns = dropdown.querySelectorAll(".dropdown-menu.show");
352    subDropdowns.forEach((sub) => this.closeDropdown(sub));
353  }
354  /**
355   * Toggle Dropdown
356   */
357  toggleDropdown(dropdown, parentItem) {
358    if (dropdown.classList.contains("show")) {
359      this.closeDropdown(dropdown, parentItem);
360    } else {
361      this.openDropdown(dropdown, parentItem);
362    }
363  }
364  /**
365   * Schließt alle offenen Dropdowns
366   */
367  closeAllDropdowns() {
368    [...this.openDropdowns].forEach((dropdown) => {
369      this.closeDropdown(dropdown);
370    });
371  }
372  /**
373   * Toggle Mobile Navigation
374   */
375  toggleMobileNav() {
376    this.navbar.classList.toggle("show");
377    const isOpen = this.navbar.classList.contains("show");
378    if (this.burgerButton) {
379      this.burgerButton.setAttribute("aria-expanded", isOpen);
380      this.burgerButton.classList.toggle("collapsed");
381    }
382    if (!isOpen) {
383      this.closeAllDropdowns();
384    }
385  }
386  /**
387   * Prüft ob Navigation in Container passt
388   */
389  checkNavFit() {
390    if (!this.navList) return;
391    const windowWidth = window.innerWidth;
392    if (windowWidth < this.mobileBreakpoint) {
393      this.switchToMobile();
394      return;
395    }
396    const clone = this.navList.cloneNode(true);
397    clone.style.position = "absolute";
398    clone.style.visibility = "hidden";
399    clone.style.display = "flex";
400    clone.style.flexDirection = "row";
401    clone.style.width = "auto";
402    clone.style.whiteSpace = "nowrap";
403    const cloneInnerDropdowns = clone.querySelectorAll(".dropdown-menu");
404    cloneInnerDropdowns.forEach((cloneInnerDropdown) => {
405      cloneInnerDropdown.style.display = "none";
406    });
407    clone.classList.add("navbar-nav-cloned");
408    document.body.appendChild(clone);
409    let totalWidth = 0;
410    const items = clone.querySelectorAll(".nav-item");
411    items.forEach((item) => {
412      item.style.display = "block";
413      const rect = item.getBoundingClientRect();
414      totalWidth += rect.width;
415    });
416    document.body.removeChild(clone);
417    const containerWidth = windowWidth - 160;
418    if (totalWidth > containerWidth) {
419      this.switchToMobile();
420    } else {
421      this.switchToDesktop();
422    }
423  }
424  /**
425   * Wechselt zur Mobile-Darstellung
426   */
427  switchToMobile() {
428    if (this.isMobileNav) return;
429    this.navbar.querySelectorAll(".navbar-nav").forEach((navElement) => {
430      navElement.classList.remove("loaded");
431    });
432    this.isMobileNav = true;
433    document.body.classList.add("body-mobile-nav");
434    document.body.classList.remove("body-desktop-nav");
435    this.navbar.classList.add("mobile-nav");
436    this.navbar.classList.remove("desktop-nav", "show");
437    if (this.burgerButton) {
438      this.burgerButton.style.display = "flex";
439      this.burgerButton.classList.add("collapsed");
440    }
441    this.closeAllDropdowns();
442    this.navbar.querySelectorAll(".dropdown-menu");
443    const dropdownToggles = this.navbar.querySelectorAll(".dropdown-toggle");
444    dropdownToggles.forEach((dropdownToggle) => dropdownToggle.setAttribute("tabindex", "0"));
445    const backButtons = this.navList.querySelectorAll(".mobile-back-button");
446    backButtons.forEach((btn) => btn.style.display = "block");
447    setTimeout(() => {
448      this.navbar.querySelectorAll(".navbar-nav").forEach((navElement) => {
449        navElement.classList.add("loaded");
450      });
451    }, 1e3);
452  }
453  /**
454   * Wechselt zur Desktop-Darstellung
455   */
456  switchToDesktop() {
457    this.isMobileNav = false;
458    document.body.classList.add("body-desktop-nav");
459    document.body.classList.remove("body-mobile-nav");
460    this.navbar.classList.remove("mobile-nav");
461    this.navbar.classList.add("desktop-nav", "show");
462    if (this.burgerButton) {
463      this.burgerButton.style.display = "none";
464    }
465    this.closeAllDropdowns();
466    const backButtons = this.navList.querySelectorAll(".mobile-back-button");
467    backButtons.forEach((btn) => btn.style.display = "none");
468    const dropdownToggles = this.navbar.querySelectorAll(".dropdown-toggle");
469    dropdownToggles.forEach((dropdownToggle) => dropdownToggle.setAttribute("tabindex", "-1"));
470    const dropdowns = this.navList.querySelectorAll(".dropdown-menu");
471    dropdowns.forEach((dropdown) => {
472      if (dropdown.classList.contains("dropdown-menu--lvl-2")) {
473        dropdown.setAttribute("inert", "");
474      }
475    });
476    this.navbar.querySelectorAll(".navbar-nav").forEach((navElement) => {
477      navElement.classList.add("loaded");
478    });
479  }
480  /**
481   * Setup Resize Observer für bessere Performance
482   */
483  setupResizeObserver() {
484    let resizeTimer;
485    window.addEventListener("resize", () => {
486      clearTimeout(resizeTimer);
487      resizeTimer = setTimeout(() => {
488        this.closeAllDropdowns();
489        this.checkNavFit();
490      }, 250);
491    });
492    setTimeout(() => {
493      this.checkNavFit();
494    }, 100);
495  }
496}
497document.addEventListener("DOMContentLoaded", () => {
498  window.navManager = new NavigationManager();
499});
500//# sourceMappingURL=desktop-navigation-new.js.map

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.