PageSourceSearch

https://www.alpenverein-muenchen-oberland.de/arsv.1753970513/styles/js/js_nd/Navigation/Events.js

js alpenverein-muenchen-oberland.de collected 2026-10-01 12:33:16 UTC 10,463 bytes, 294 lines download raw bytes

1/**
2	25.9.2020 ker only add unbind and make it as function.
3	this is also important for class ModernMenu for reinitial the mobileview after resize
4*/
5
6class MobileEvents {
7    constructor() {
8		this.handleButtonClick = this.handleButtonClick.bind(this);
9		this.handleButtonKeydown = this.handleButtonKeydown.bind(this);
10        this.jsMobileNavigation = document.querySelector(".js-mobileNavigation");
11        this.jsNavOverlay = document.querySelector(".nd-o-header__overlay");
12        this.hasKeyDownListener = false;
13        this.init();
14    }
15
16    addDropdownButton(list, levelClass) {
17        for (const child of list.children) {
18            if (child.tagName === "UL") {
19                const levelElement = list.querySelector(`.${levelClass}`);
20                
21                if (levelElement && !levelElement.querySelector(".nd-o-header__navigation-dropdown-icon_sub_mobile")) {
22                    const btn = document.createElement("button");
23                    btn.className = "nd-o-header__navigation-dropdown-icon_sub_mobile";
24                    btn.setAttribute("data-target", `#${child.id || this.generateUniqueId()}`);
25					this.buttonNavigationAriaLabel(child, btn, true);
26					
27                    if (!child.id) {
28                        child.id = btn.getAttribute("data-target").substring(1);
29                    }
30
31                    levelElement.appendChild(btn);
32                    this.bindButtonEvents(btn);
33                }
34            }
35        }
36    }
37	buttonNavigationAriaLabel(child, btn, isUp){
38		var text = "";
39		var link = child.querySelector("a");
40		if(link){
41			if(isUp){
42				text = "Menu " + link.innerText.trim() + " öffnen";
43			}else{
44				text = "Menu " + link.innerText.trim() + " schließen";
45			}
46		}
47		btn.setAttribute("aria-label", text);
48	}
49
50	bindButtonEvents(btn) {
51		btn.removeEventListener("click", this.handleButtonClick);
52		btn.removeEventListener("keydown", this.handleButtonKeydown);
53		
54		btn.addEventListener("click", this.handleButtonClick);
55		btn.addEventListener("keydown", this.handleButtonKeydown);
56	}
57
58    handleButtonClick(event) {
59        const target = event.currentTarget;
60        const isUp = target.classList.contains("up");
61        const childId = target.getAttribute("data-target");
62        const childUl = document.querySelector(childId);
63        
64        if (childUl) {
65            target.classList.toggle("up", !isUp);
66            childUl.style.display = isUp ? "none" : "block";
67			this.buttonNavigationAriaLabel(childUl, target, isUp);
68        }
69    }
70
71    handleButtonKeydown(event) {
72         this.jsMobileNavigation = document.querySelector(".js-mobileNavigation");
73         this.jsNavOverlay = document.querySelector(".nd-o-header__overlay");
74        if (event.key === "Escape") {
75            if (this.jsMobileNavigation.classList.contains("closeButton")) {
76                this.jsMobileNavigation.click();
77                this.jsMobileNavigation.focus();
78            }
79        }
80    }
81
82
83    rebind() {
84        document.querySelectorAll(".nd-o-header__navigation-dropdown-icon_sub_mobile").forEach(btn => {
85            this.bindButtonEvents(btn);
86        });
87         // Rest of your initialization code...
88        document.querySelectorAll(".nd-o-header__navigation__box a").forEach(hyperlink => {
89            if(!hyperlink.hasKeyDownListener){
90                hyperlink.hasKeyDownListener = true;
91                hyperlink.addEventListener("keydown", (event) => {
92                    if (event.key === "Escape") {
93                        if (this.jsMobileNavigation.classList.contains("closeButton")) {
94                            this.jsMobileNavigation.click();
95                            this.jsMobileNavigation.focus();
96                        }
97                    }
98                });
99            }
100        });
101    }
102
103    generateUniqueId() {
104        return 'dropdown-' + Math.random().toString(36).substr(2, 9);
105    }
106
107    init() {
108        document.querySelectorAll(".list_level1").forEach(list => {
109            this.addDropdownButton(list, "level1");
110        });
111
112        document.querySelectorAll(".list_level2").forEach(list => {
113            this.addDropdownButton(list, "level2");
114        });
115
116       
117
118        this.jsMobileNavigation.addEventListener("click", (e) => {
119            e.preventDefault();
120			var navBox = document.querySelector(".nd-o-header__navigation__box")
121            navBox.classList.toggle("show-on-mobile");
122            document.documentElement.classList.toggle("no-scroll");
123            this.jsNavOverlay.classList.toggle("show");
124            this.jsMobileNavigation.classList.toggle("closeButton");
125            
126            const firstLink = document.querySelector(".show-on-mobile a");
127            if (firstLink) firstLink.focus();
128
129			 // aria-label aktualisieren
130			const isOpen = navBox.classList.contains("show-on-mobile");
131			e.target.setAttribute("aria-label", isOpen ? "Menü schließen" : "Menü öffnen");
132
133        });
134
135        this.jsNavOverlay.addEventListener("click", () => {
136            this.jsNavOverlay.classList.toggle("show");
137            this.jsMobileNavigation.classList.toggle("closeButton");
138            document.querySelector(".nd-o-header__navigation__box").classList.remove("show-on-mobile");
139        });
140    }
141}
142
143
144
145class DesktopEvents {
146    constructor() {
147        this.level1 = document.querySelectorAll(".nd-o-header__navigation__box .list_level1");
148        this.jsMobileNavigation = document.querySelector(".js-mobileNavigation");
149        this.menuContainer = document.querySelector(".nd-o-header__navigation__box"); // The menu container
150
151        this.timeoutsHover = [];
152        this.timeoutsMauseOut = [];
153
154        // Store bound versions of handlers for clean unbinding
155        this.boundHandleFocusOut = this.handleFocusOut.bind(this);
156    } 
157    // Detect if focus moved outside the menu, and close if so
158    handleFocusOut(event) {
159    // Check if the focused element is inside the menu or navigation button
160        if (!this.menuContainer.contains(event.target) && !this.jsMobileNavigation.contains(event.target)) {
161            this.closeAll();
162        }
163    }
164
165    
166    bind() {
167        document.addEventListener("focusin", this.boundHandleFocusOut); // Handles keyboard navigation
168        document.addEventListener("mousedown", this.boundHandleMouseDown);
169
170        this.level1.forEach((lvl) => {
171            let link = lvl.querySelector("a");
172              // Show submenu on mouse hover
173            link.onmouseover = (e) => {
174                this.clearTimeouts();
175                this.timeoutsHover.push(setTimeout(() => {
176                    this.showSub(e.target);
177                }, 300));
178            };
179
180           // Handle keyboard focus
181            link.onfocus = (e) => {
182                this.closeAll();
183                // Avoid re-binding key listener multiple times
184                if (!e.target.hasKeyListener) {
185                    e.target.addEventListener("keydown", (event) => {
186                        if (event.key === "Enter") {
187                            event.preventDefault();
188                            this.showSub(event.target);
189                        }
190                        if (event.key === " ") {
191                            event.preventDefault();
192                            e.target.click();
193                           // window.location.href = event.target.href; <-- this jumping google analytic tracker
194                        }
195                        if (event.key === "Escape") {
196                            this.closeAll();
197                        }
198                    });
199
200                    e.target.hasKeyListener = true;
201                }
202            };
203
204            // Reset timeout on mouse out
205            lvl.onmouseout = () => {
206                this.clearTimeouts();
207            };
208
209            // Hide submenu after delay when mouse leaves
210            link.onmouseleave = () => {
211                this.timeoutsMauseOut.push(setTimeout(() => {
212                    this.closeAll();
213                }, 400));
214            };
215        });
216    }
217
218    // Remove all event listeners for cleanup
219    unbind() {
220        document.removeEventListener("focusin", this.boundHandleFocusOut);
221    
222        this.level1.forEach((lvl) => {
223            let link = lvl.querySelector("a");
224
225            link.onmouseover = null; 
226            link.onfocus = null;
227            link.onmouseleave = null;
228            lvl.onmouseout = null;
229        });
230    }
231
232    // Utility: clear all active timeouts
233    clearTimeouts() {
234        this.timeoutsHover.forEach(clearTimeout); 
235        this.timeoutsMauseOut.forEach(clearTimeout);
236        this.timeoutsHover = [];
237        this.timeoutsMauseOut = [];
238    }
239
240
241    // Close all dropdowns
242    closeAll() {
243        this.level1.forEach((lvl) => {
244            let lvl1 = lvl.querySelector(".level1");
245            let lvl2 = lvl.querySelector(".level1 ~ ul");
246            document.documentElement.classList.remove("no-scroll");
247            lvl1.classList.remove("activeCat");
248            if (lvl2) {
249                lvl2.style.display = "";
250            }
251        });
252    }
253
254    // Delayed close when leaving level 2 menu
255    level2Out(ev) {
256        this.timeoutsMauseOut.push(setTimeout(() => {
257            document.dispatchEvent(new CustomEvent("navigation-close"));
258            this.closeAll();
259        }, 500));
260    }
261
262
263    // Show submenu for a top-level item
264    showSub(parent) {
265        this.closeAll();
266        parent = parent.parentElement.parentElement;
267        const lvl1 = parent.querySelector(".level1");
268        const lvl2 = parent.querySelector(".level1 ~ ul");
269        document.documentElement.classList.add("no-scroll");
270        lvl1.classList.add("activeCat");
271
272        if (lvl2) {
273            lvl2.style.display = "block";
274            lvl2.onmouseleave = this.level2Out.bind(this);
275
276            lvl2.addEventListener("keydown", (event) => {
277                if (event.key === "Escape") {
278                    parent.querySelector("a").focus();
279                    this.closeAll();
280                }
281            });
282
283            // Cancel close timer on re-entry
284            lvl2.onmouseenter = () => this.clearTimeouts();
285            lvl2.querySelectorAll("a, ul, li, div").forEach((el) => {
286                el.onmouseenter = () => this.clearTimeouts();
287            });
288
289            document.dispatchEvent(new CustomEvent("navigation-open", {
290                detail: { open: lvl2 }
291            }));
292        }
293    }
294}

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.