1/* 2Wrapper f+r die Slicknav bzgl. BITV 3 4- Setzt die entsprechenden Aria-Attribute (aria-label, aria-expanded usw.), entfernt falsche Attribute 5- Schlie\u00dfen des Men\u00fcs per Escape-Taste und Klick ausserhalb des Men\u00fcs 6- \u00d6ffnen des Men\u00fcs per Hash 7 8Beispieleinbindung 9 10<script src="/layout/premium/assets/js/slicknav/jquery.slicknav.min.js?__ts=1593169378"></script> 11<script src="/output/js/ikiss/ikissSlicknav.js" charset="UTF-8"></script> 12 13<script> 14 15$(function() { 16 17 $('.modern-nav #rid_0').ikissSlicknav({ 18 19 allowParentLinks: true, 20 label: 'Menü', 21 prependTo: '#nav-mob', 22 23 //Pfeile \u00f6ffnen/schlie\u00dfen voher closedSymbol/openedSymbol 24 symbolTag: 'span', 25 symbolOpenedCls: 'fa-regular fa-angle-up', 26 symbolClosedCls: 'fa-regular fa-angle-down', 27 28 //Event Callbacks 29 beforeOpen : function() {}, 30 afterOpen : function() {}, 31 beforeClose : function() {}, 32 afterClose : function() {}, 33 34 //Hash zum \u00d6ffnen des Men\u00fcs 35 hash: '#jump-menu', 36 37 // Aria-Label Statustext auf den Pfeilen zum \u00d6ffnen von Submen\u00fcs verwenden? 38 ariaLabelStatus: true, 39 40 //Optional: Wenn sich die Slicknav in einem Element befindet, welches das Ein-/Ausblenden steuert 41 wrapSel: '.modern-nav', 42 wrapActiveCls: 'active', 43 44 //Optional: Falls eigene Buttons verweddnet werden 45 openButtonSel: '.header-right .modern-nav-btn', 46 closeButtonSel: '.modern-nav .modern-nav-btn', 47 48 //Klick Aktionen f\u00fcr eigene Buttons 49 onOpenButtonClick: function() { 50 console.log('Klick auf Button "\u00d6ffnen"'); 51 }, 52 53 onCloseButtonClick: function() { 54 console.log('Klick auf Button "Schlie\u00dfen"'); 55 } 56 57 }); 58}); 59 60<script> 61 62 63Aktuelles Markup der Slicknav: 64<ul class="slicknav_nav" style="display: block !important;" aria-hidden="true" id="slicknav-main-container" aria-expanded="true"> 65 <li class="npt_off npt_first childs slicknav_parent slicknav_collapsed"> 66 <span class="slicknav_parent-link slicknav_row"> 67 <a href="/Basisumfang/" ikissedit="nav|3731.2.1" class="nlk_off npt_first childs hastabindex" role="menuitem"> 68 Basisumfang 69 </a> 70 <button role="menuitem" aria-haspopup="true" aria-expanded="false" class="slicknav_item" tabindex="-1" style="outline: none;"> 71 <span class="slicknav_arrow"> 72 <span class="fa-regular fa-angle-down"></span> 73 </span> 74 </button> 75 </span> 76 <ul class="nlv_2 slicknav_hidden" style="display: none;" aria-hidden="true" id="slicknav-menu-container-0"> 77 <li class="npt_off npt_first childs npt_nolink slicknav_parent slicknav_collapsed"> 78 <span class="slicknav_parent-link slicknav_row"> 79 <a href="/Basisumfang/Texte/" ikissedit="nav|3731.17.1" class="nlk_off npt_first childs npt_nolink hastabindex" role="menuitem" tabindex="-1"> 80 Texte 81 </a> 82 <button role="menuitem" aria-haspopup="true" aria-expanded="false" class="slicknav_item" tabindex="-1" style="outline: none;"> 83 <span class="slicknav_arrow"> 84 <span class="fa-regular fa-angle-down"></span> 85 </span> 86 </button> 87 </span> 88 <ul class="nlv_3 slicknav_hidden" aria-hidden="true" style="display: none;" id="slicknav-menu-container-1"> 89 <li class="npt_off npt_first npt_nolink"> 90 <a href="/Basisumfang/Texte/Text-mit-Randspalte/" ikissedit="nav|3731.19.1" class="nlk_off npt_first npt_nolink hastabindex" role="menuitem" tabindex="-1"> 91 Text mit Randspalte</a> 92 </li> 93 <li class="npt_off npt_nolink"> 94 <a href="/Basisumfang/Texte/Text-ohne-Randspalte/" ikissedit="nav|3731.20.1" class="nlk_off npt_nolink hastabindex" role="menuitem" tabindex="-1"> 95 Text ohne Randspalte 96 </a> 97 </li> 98 </ul> 99 </li> 100 <li class="npt_off"> 101 <a href="/Basisumfang/Medien/" ikissedit="nav|3731.18.1" class="nlk_off hastabindex" role="menuitem" tabindex="-1"> 102 Medien 103 </a> 104 </li> 105 </ul> 106 </li> 107 <li class="npt_off npt_nolink"><a href="/Hauptpunkt-6/" ikissedit="nav|3731.30.1" class="nlk_off npt_nolink hastabindex" role="menuitem">Hauptpunkt 6</a></li> 108</ul> 109 110 111 112*/ 113 114;(function ($) { 115 116 function Plugin(rootNode, options) { 117 var me = this; 118 119 $.extend(me, { 120 rootNode : rootNode, 121 options : options || {} 122 }); 123
124 me.init(); 125 return me; 126 } 127 128 Plugin.prototype = { 129 130 init: function() { 131 var me = this, 132 op = me.options; 133 134 if (!$.fn.slicknav) { 135 console.log('Slicknav ist nicht vorhanden, bitte Einbindung der slicknav.js \u00fcberpr\u00fcfen.'); 136 return; 137 } 138 139 me.menuCt = $(op.prependTo); 140 141 me.settings = $.extend({}, op, { 142 init : $.proxy(me.onInit, me), 143 beforeOpen : $.proxy(me.onBeforeOpen, me), 144 afterOpen : $.proxy(me.onAfterOpen, me), 145 beforeClose : $.proxy(me.onBeforeClose, me), 146 afterClose : $.proxy(me.onAfterClose, me), 147 closedSymbol : '<'+op.symbolTag+' class="'+op.symbolClosedCls+'"></'+op.symbolTag+'>', 148 openedSymbol : '<'+op.symbolTag+' class="'+op.symbolOpenedCls+'"></'+op.symbolTag+'>', 149 nestedParentLinks : false 150 }); 151 152 $(me.rootNode).slicknav(me.settings); 153 }, 154 155 onInit: function() { 156 var me = this, 157 op = me.options; 158 159 $.extend(me, { 160 slicknavRoot : $('.slicknav_nav', me.menuCt), 161 slicknavButton : $('.slicknav_btn'), 162 childs : $('a.childs', me.slicknavRoot), 163 hash : op.hash || '', 164 openButton : op.openButtonSel ? $(op.openButtonSel) : false, 165 closeButton : op.closeButtonSel ? $(op.closeButtonSel) : false, 166 wrapEl : op.wrapSel ? $(op.wrapSel) : false, 167 ariaLabelStatus : op.ariaLabelStatus || false 168 }); 169 170 me.openCurrentPath(); 171 me.setIds(); 172 me.syncExpandedAttr(); 173 174 // Keydown-Handler entfernen, da wir die Tastatursteuerung selbst \u00fcbernehmen 175 me.slicknavRoot.off('keydown'); 176 177 $(document).on('keydown', $.proxy(me.onKeyDown, me)); 178 $(document.body).on('click', $.proxy(me.onBodyClick, me)); 179 $(window).on('load hashchange', $.proxy(me.checkHash, me)); 180 181 if (me.openButton) 182 me.openButton.on('click', $.proxy(me.onOpenButtonClick, me)); 183 184 if (me.closeButton) 185 me.closeButton.on('click', $.proxy(me.onCloseButtonClick, me)); 186 187 if (me.wrapEl) { 188 me.slicknavRoot 189 .show() 190 .removeClass('slicknav_hidden') 191 .attr({ 192 'style': 'display: block !important;', 193 'aria-hidden': 'false', 194 'aria-expanded': 'true' 195 }); 196 } 197 198 $('ul', me.slicknavRoot).attr('role', 'menu'); 199 $('a', me.slicknavRoot).attr('role', 'menuitem'); 200 201 202 if (typeof op.init === 'function') 203 op.init.apply(me, arguments); 204 }, 205 206 isOpen: function() { 207 var me = this, 208 op = me.options; 209 210 return me.wrapEl ? me.wrapEl.hasClass(op.wrapActiveCls) : me.slicknavButton.hasClass('slicknav_open'); 211 }, 212 213 onKeyDown: function(ev) { 214 var me = this, 215 keyCode = ev.keyCode || ev.which, 216 $target = $(ev.target), 217 inNav = $target.closest('.slicknav_nav').length > 0, 218 isMainBtn = $target.hasClass('slicknav_btn'), 219 isCloseButton = me.closeButton && $target.closest(me.closeButton).length > 0, 220 handled = false; 221 222 // Aktuelle Nodes auslesen 223 var $currentLi = $target.closest('li'), 224 $currentUl = $currentLi.parent('ul'), 225 $siblings = $currentUl.children('li'), 226 currentIdx = $siblings.index($currentLi); 227 228 // Hier setzen wir f\u00fcr den Enter-Key me.keyboardTriggered true 229 if (me.isOpen() && keyCode === 13 && isCloseButton) { 230 me.closeButtonKeyboardEvent = true; 231 return; 232 } 233 234 // Escape: Auch au\u00dferhalb der Nav soll das Men\u00fc geschlossen werden 235 if (keyCode === 27 && me.isOpen()) { 236 237 if (inNav) { 238 var $parentLi = $currentUl.closest('li.slicknav_parent'); 239 if ($parentLi.length) { 240 // Untermen\u00fc schlie\u00dfen, Fokus auf Eltern-Link 241 me._closeSubmenu($parentLi); 242 me._focusLink($parentLi); 243 } else { 244 // Auf oberster Ebene \u2192 Hauptmen\u00fc schlie\u00dfen 245 me.closeButtonKeyboardEvent = true; 246 me.keyboardTriggered = true; 247 me._closeMenu(true); 248 } 249 } else { 250 me.closeButtonKeyboardEvent = true; 251 me.keyboardTriggered = true; 252 me._closeMenu(true); 253 } 254 255 ev.preventDefault(); 256 ev.stopPropagation(); 257 return; 258 } 259 260 // Alle anderen Tasten nur innerhalb der Slicknav oder auf dem Hauptmen\u00fc-Button 261 if (!inNav && !isMainBtn) 262 return; 263 264 // Hauptbutton - Pfeil runter/hoch \u2192 Men\u00fc \u00f6ffnen, Fokus auf erstes/letztes Element setzen 265 if (isMainBtn) { 266 if ((keyCode === 40 || keyCode === 38) && !me.isOpen()) { 267 me.keyboardTriggered = true; 268 me.slicknavButton.trigger('click'); 269 setTimeout(function() { 270 var listChildren = me.slicknavRoot.children('li'); 271 var focusTarget = keyCode === 40 ? listChildren.first() : listChildren.last(
271); 272 me._focusLink(focusTarget); 273 }, 50); 274 } 275 return; 276 } 277 278 switch (keyCode) { 279 case 40: // Pfeil runter \u2192 N\u00e4chster Men\u00fcpunkt gleicher Ebene 280 me._focusLink($siblings.eq((currentIdx + 1) % $siblings.length)); 281 handled = true; 282 break; 283 284 case 38: // Pfeil hoch \u2192 Vorheriger Men\u00fcpunkt gleicher Ebene 285 me._focusLink($siblings.eq((currentIdx - 1 + $siblings.length) % $siblings.length)); 286 handled = true; 287 break; 288 289 case 39: // Pfeil rechts \u2192 Untermen\u00fc \u00f6ffnen, ersten Eintrag fokussieren 290 if ($currentLi.hasClass('slicknav_parent')) { 291 if (!$currentLi.hasClass('slicknav_open')) 292 me._openSubmenu($currentLi); 293 294 setTimeout(function() { 295 me._focusLink($currentLi.children('ul').children('li').first()); 296 }, 50); 297 handled = true; 298 } 299 break; 300 301 case 37: // Pfeil links \u2192 Untermen\u00fc schlie\u00dfen, Fokus auf Eltern-Link 302 var $parent = $currentUl.closest('li.slicknav_parent'); 303 if ($parent.length) { 304 me._closeSubmenu($parent); 305 me._focusLink($parent); 306 handled = true; 307 } 308 break; 309 310 case 36: // Home \u2192 Erster Men\u00fcpunkt der aktuellen Ebene 311 me._focusLink($siblings.first()); 312 handled = true; 313 break; 314 315 case 35: // End \u2192 Letzter Men\u00fcpunkt der aktuellen Ebene 316 me._focusLink($siblings.last()); 317 handled = true; 318 break; 319 } 320 321 if (handled) { 322 ev.preventDefault(); 323 ev.stopPropagation(); 324 } 325 }, 326 327 // Fokussiert den Link (<a>) eines Men\u00fcpunkts (<li>) 328 _focusLink: function($li) { 329 var $link = $li.children('a').length ? 330 $li.children('a').first() : 331 $li.children('.slicknav_parent-link').children('a').first(); 332 333 $('.tab-focus', this.slicknavRoot).removeClass('tab-focus'); 334 $link.addClass('tab-focus').focus(); 335 }, 336 337 // Gibt den Toggle-Button (oder Toggle-Link) eines Men\u00fcpunkts mit Untermen\u00fc zur\u00fcck 338 _getToggleBtn: function($li) { 339 var $row = $li.children('.slicknav_parent-link'); 340 return $row.children('button').length ? 341 $row.children('button').first() : 342 $row.children('a.slicknav_item').first(); 343 }, 344 345 // \u00d6ffnet das Untermen\u00fc eines Men\u00fcpunkts per Klick auf den Toggle-Button 346 _openSubmenu: function($li) { 347 var $btn = this._getToggleBtn($li); 348 if ($btn.length && !$li.hasClass('slicknav_open')) 349 $btn.trigger('click'); 350 }, 351 352 // Schlie\u00dft das Untermen\u00fc eines Men\u00fcpunkts per Klick auf den Toggle-Button 353 _closeSubmenu: function($li) { 354 var me = this, 355 $btn = me._getToggleBtn($li); 356 357 if ($btn.length && $li.hasClass('slicknav_open')) { 358 $btn.trigger('click'); 359 360 // Wenn die Sichtbarkeit \u00fcber ein eigenes Wrapper-Element gesteuert wird, 361 // darf die Slicknav-Library nicht 'slicknav_hidden' auf das Root-Element setzen, 362 // da sonst die Links auf tabindex="-1" gesetzt werden. 363 if (me.wrapEl) { 364 me.slicknavRoot.removeClass('slicknav_hidden'); 365 } 366 } 367 }, 368 369 // Schlie\u00dft das Hauptmen\u00fc und setzt den Fokus auf den Men\u00fc-Button 370 _closeMenu: function(isKeyboardEvent) { 371 var me = this; 372 373 if (me.closeButton) me.closeButton.trigger('click'); 374 else me.slicknavButton.trigger('click'); 375 376 if (isKeyboardEvent || me.keyboardTriggered) { 377 me.slicknavButton.addClass('tab-focus'); 378 } 379 380 me.slicknavButton.focus(); 381 382 me.keyboardTriggered = false;
383 }, 384 385 onBodyClick: function(ev) { 386 var me = this, 387 op = me.options; 388 389 if (!me.isOpen()) 390 return; 391 392 var $trg = $(ev.target), 393 hit = 0; 394 395 $.each([me.wrapEl, me.menuCt, me.slicknavButton, me.openButton, me.closeButton], function(idx, el) { 396 if (el) 397 hit += $trg.closest(el).length; 398 }); 399 400 if (hit === 0 && !$trg.hasClass(op.symbolClosedCls) && !$trg.hasClass(op.symbolOpenedCls)) { 401 me.closeButton ? me.closeButton.trigger('click') : me.slicknavButton.trigger('click'); 402 } 403 }, 404 405 onOpenButtonClick: function(ev) { 406 var me = this, 407 st = me.settings; 408 409 if (typeof st.onOpenButtonClick === 'function') { 410 st.onOpenButtonClick.apply(me, arguments); 411 } 412 413 var setTabFocus = me.keyboardTriggered || false; 414 me.keyboardTriggered = false; 415 me.focusCurrentNav(setTabFocus); 416 }, 417 418 onCloseButtonClick: function(ev) { 419 var me = this, 420 st = me.settings; 421 422 var setTabFocus = me.closeButtonKeyboardEvent || false; 423 me.closeButtonKeyboardEvent = false; 424 if (typeof st.onCloseButtonClick === 'function') { 425 st.onCloseButtonClick.call(me, ev, setTabFocus); 426 } 427 }, 428 429 checkHash: function() { 430 var me = this, 431 st = me.settings; 432 433 if (me.hash && window.location.hash === me.hash) { 434 if (typeof st.onOpenButtonClick === 'function') { 435 st.onOpenButtonClick.apply(me, arguments); 436 } else { 437 me.slicknavButton.trigger('click'); 438 } 439 } 440 }, 441 442 focusFirstLink: function(bt) { 443 var me = this, 444 isRoot = bt.hasClass('slicknav_btn'), 445 $firstLink = $('a.nlk_on', me.slicknavRoot).length ? $('a.nlk_on', me.slicknavRoot).last() : $('a', me.slicknavRoot).first(); 446 447 if (!isRoot) 448 return; 449 450 $firstLink.focus(); 451 if (bt.hasClass('tab-focus') || $(document.activeElement).hasClass('tab-focus')) { 452 $firstLink.addClass('tab-focus'); 453 } 454 }, 455 456 onBeforeOpen: function(bt) { 457 var me = this, 458 op = me.options; 459 460 me.focusFirstLink(bt); 461 462 if (typeof op.beforeOpen === 'function') { 463 op.beforeOpen.apply(me, arguments); 464 } 465 }, 466 467 onAfterOpen: function(bt) { 468 var me = this, 469 op = me.options; 470 471 me.focusFirstLink(bt); 472 473 me.syncExpandedAttr(); 474 475 if (typeof op.afterOpen === 'function') { 476 op.afterOpen.apply(me, arguments); 477 } 478 }, 479 480 onBeforeClose: function() { 481 var me = this, 482 op = me.options; 483 484 if (typeof op.beforeClose === 'function') 485 op.beforeClose.apply(me, arguments); 486 }, 487 488 onAfterClose: function(bt) { 489 var me = this, 490 op = me.options, 491 isRoot = bt.hasClass('slicknav_btn'); 492 493 if (isRoot) { 494 if ($(document.activeElement).hasClass('tab-focus')) { 495 me.slicknavButton.addClass('tab-focus'); 496 } 497 me.slicknavButton.focus(); 498 } 499 500 me.syncExpandedAttr(); 501 502 if (typeof op.afterClose === 'function') 503 op.afterClose.apply(me, arguments); 504 }, 505 506 openCurrentPath: function() { 507 var set = this.settings; 508 $('li.npt_on', this.slicknavRoot).each(function() { 509 $(this).toggleClass('slicknav_collapsed slicknav_open'); 510 $('> ul', this).removeClass('slicknav_hidden').attr('aria-hidden', 'false').show(); 511 $('.slicknav_arrow', this).first().html(set.openedSymbol); 512 }); 513 }, 514 515 getAriaLabelStatusText: function(isOpen) { 516 return ''; 517 }, 518 519 setIds: function() { 520 var me = this; 521 522 this.childs.each(function(idx, node) { 523 var idAttr = 'slicknav-menu-container-'+idx, 524 isOpen = $(node).closest('li.slicknav_parent').hasClass('slicknav_open'), 525 ariaLabel = 'Untermen\u00fc ' + $(node).html() + me.getAriaLabelStatusText(isOpen); 526 527 $(node) 528 .next('button') 529 .attr({'aria-controls': idAttr, 'aria-expanded': isOpen ? 'true' : 'false', 'aria-label': ariaLabel}) 530 .removeAttr('aria-haspopup') 531 .removeAttr('tabindex'); 532 533 var $li = $(node).closest('li'); 534 var $toggle = me._getToggleBtn($li); 535 if ($toggle.length) { 536 $toggle.attr('aria-label', ariaLabel); 537 } 538 539 $(node) 540 .parent('span') 541 .next('ul') 542 .attr('id', idAttr); 543 }); 544 545 // Aria-Label der Untermen\u00fc-Links um den Namen des Elternmen\u00fcs erweitern 546 $('ul', me.slicknavRoot).each(function() { 547 var $ul = $(this); 548 var $parentLi = $ul.closest('li.slicknav_parent'); 549 if ($parentLi.length) { 550 var $parentLink = $parentLi.find('> .slicknav_parent-link > a').first(); 551 var parentText = $parentLink.html(); 552 $ul.find('a').each(function() { 553 var $a = $(this); 554 var linkText = $a.html(); 555 var ariaLabel = linkText + ' im Untermen\u00fc ' + parentText; 556 $a.attr('aria-label', ariaLabel); 557 }); 558 } 559 }); 560 561 me.slicknavButton.attr({'aria-expanded': 'false', 'aria-label': 'Hauptmen\u00fc \u00f6ffnen', 'aria-controls': 'slicknav-main-container'}).removeAttr('aria-haspopup'); 562 me.slicknavRoot.attr({'id': 'slicknav-main-container', 'role': 'menu'}); 563 me.slicknavRoot.find('ul').attr('role', 'menu'); 564 }, 565 566 syncExpandedAttr: function() { 567 var me = this; 568 569 me.childs.each(function(idx, node) { 570 var slicknavParent = $(node).closest('li.slicknav_parent'); 571 var isOpen = slicknavParent.hasClass('slicknav_open'); 572 var ariaLabel = 'Untermen\u00fc ' + $(node).html() + me.getAriaLabelStatusText(isOpen); 573 var $li = $(node).closest('li'); 574 var $toggle = me._getToggleBtn($li); 575 if ($toggle.length) { 576 $toggle.attr({'aria-expanded': isOpen ? 'true' : 'false', 'aria-label': ariaLabel}); 577 } 578 }); 579 580 // Pauschal alle tabindex auf der ersten Ebene entfernen 581 $('> li > .slicknav_row *[tabindex="-1"]', me.slicknavRoot).removeAttr('tabindex'); 582 $('> li > a[tabindex="-1"]', me.slicknavRoot).removeAttr('tabindex'); 583 584 $('ul', me.slicknavRoot).each(function(idx, node) { 585 if ($(node).closest('li.slicknav_parent').hasClass('slicknav_open')) { 586 $('> li > .slicknav_row *[tabindex="-1"]', node).removeAttr('tabindex'); 587 $('> li > a[tabindex="-1"]', node).removeAttr('tabindex'); 588 } 589 }); 590 591 592 me.slicknavButton.attr({'aria-expanded': me.isOpen() ? 'true' : 'false', 'aria-label': me.isOpen() ? 'Hauptmen\u00fc schlie\u00dfen' : 'Hauptmen\u00fc \u00f6ffnen'}); 593 }, 594 595 focusCurrentNav: function(setTabFocus) { 596 var me = this, 597 isOpen = me.isOpen(), 598 arExp = isOpen ? 'true' : 'false'; 599 600 if (isOpen) { 601 $('a[tabindex="-1"]', me.slicknavRoot).addClass('hastabindex').removeAttr('tabindex'); 602 var $firstLink = $('a', me.slicknavRoot).first(), 603 target = $firstLink[0]; 604 605 if (setTabFocus) { 606 $('.tab-focus', me.slicknavRoot).removeClass('tab-focus'); 607 $firstLink.addClass('tab-focus'); 608 } 609 610 if (target) {
611 target.focus(); 612 if (document.activeElement !== target) { 613 requestAnimationFrame(function() { target.focus(); }); 614 } 615 } 616 617 } else { 618 $('a.hastabindex', me.slicknavRoot).attr('tabindex', '-1'); 619 } 620 621 me.slicknavRoot.attr('aria-expanded', arExp); 622 if (me.openButton) me.openButton.attr('aria-expanded', arExp); 623 else me.slicknavButton.attr('aria-expanded', arExp); 624 } 625 626 }; 627 628 $.fn.ikissSlicknav = function (options) { 629 var pKey = 'ikissSlicknav'; 630 return this.each(function() { 631 if (!$.data(this, pKey)) { 632 $.data(this, pKey, new Plugin(this, options)); 633 } 634 }); 635 }; 636 637})(jQuery);
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.