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.