1document.addEventListener('DOMContentLoaded', function () { 2 maxTopNavWidth(); 3 realignSubMenu1(); 4 menuMaxHeight(); 5 pullContent(); 6 initializeMenuKeyboardNavigation(); 7}); 8window.addEventListener('resize', function () { 9 maxTopNavWidth(); 10 realignSubMenu1(); 11 menuMaxHeight(); 12 pullContent(); 13}); 14window.addEventListener('scroll', function () { 15 menuMaxHeight(); 16}); 17 18/** 19 * Initialize keyboard navigation for menu buttons 20 * Prevents .open class from being applied during keyboard navigation 21 */ 22function initializeMenuKeyboardNavigation() { 23 // Find all menu buttons with onclick handlers 24 const menuButtons = document.querySelectorAll('.menu button[onclick*="openSubMenu"]'); 25 26 menuButtons.forEach(button => { 27 // Add keyboard event listener to prevent .open class during keyboard navigation 28 button.addEventListener('keydown', function(event) { 29 if (event.key === 'Enter' || event.key === ' ') { 30 // Mark this button as activated by keyboard 31 this.setAttribute('data-keyboard-activated', 'true'); 32 // Remove the attribute after a short delay to reset for next interaction 33 setTimeout(() => { 34 this.removeAttribute('data-keyboard-activated'); 35 }, 100); 36 } 37 }); 38 }); 39} 40 41/** 42 * maxTopNavWidth 43 * 44 * Calculates the maximum width of the top navigation and sets it 45 * 46 * @return void 47 */ 48function maxTopNavWidth() { 49 let header = $('header'); 50 if (header.hasClass('full')) { 51 let headerContentInner = header.find('.header-content .header-content-inner'); 52 let nav = headerContentInner.find('.menucolumn .menu-wrapper.desktop .menu'); 53 let topNav = headerContentInner.children('.topnav'); 54 let menuStartPosX = nav.offset().left; 55 let screenWidth = $(window).width(); 56 let topNavMaxWidth = screenWidth - menuStartPosX; 57 topNav.css('width', topNavMaxWidth); 58 } 59} 60 61/** 62 * 63 * toggleSearchDialog 64 * 65 * Toggles the search dialog in the header 66 * 67 * @param {*} element The element that was clicked 68 * @returns void 69 */ 70function toggleSearchDialog(element) { 71 // If the url contains ?s= and the passed element contains the class="addsearchvariant" and the body contains the class="search-results" do not open the search dialog 72 if (window.location.search.includes('?s=') && element.classList.contains('addsearchvariant') && document.body.classList.contains('search-results')) { 73 // Try to find the search input on the site .addsearch-searchfield input type="search" and focus it 74 let searchInput = document.querySelector('.addsearch-searchfield input[type="search"]'); 75 if (searchInput) { 76 searchInput.focus(); 77 } 78 // Return to prevent opening the dialog 79 return; 80 } 81 82 let searchWrapper = element.closest('.top-search-wrapper');
83 if (!searchWrapper) { 84 return; 85 } 86 87 let searchDialog = searchWrapper.querySelector('#searchdialog'); 88 89 if (searchDialog.open) { 90 searchDialog.close(); 91 searchWrapper.classList.remove('open'); 92 } else { 93 searchDialog.show(); 94 searchWrapper.classList.add('open'); 95 } 96 97 // Close by clicking outside the dialog and with the Escape key 98 document.addEventListener('click', function (event) { 99 if (!searchWrapper.contains(event.target) && searchDialog.open) { 100 searchDialog.close(); 101 searchWrapper.classList.remove('open'); 102 } 103 }); 104 document.addEventListener('keydown', function (event) { 105 if (event.key === 'Escape' && searchDialog.open) { 106 searchDialog.close(); 107 searchWrapper.classList.remove('open'); 108 } 109 }); 110} 111 112/** 113 * 114 * togglePortalsDialog 115 * 116 * Toggles the portals dialog in the header 117 * 118 * @param {*} element The element that was clicked 119 * @returns void 120 */ 121function togglePortalsDialog(element) { 122 let searchWrapper = element.closest('.top-portals-wrapper');
123 if (!searchWrapper) { 124 return; 125 } 126 127 let searchDialog = searchWrapper.querySelector('#portalsdialog'); 128 129 if (searchDialog.open) { 130 searchDialog.close(); 131 searchWrapper.classList.remove('open'); 132 } else { 133 searchDialog.show(); 134 searchWrapper.classList.add('open'); 135 } 136 137 // Close by clicking outside the dialog and with the Escape key 138 document.addEventListener('click', function (event) { 139 if (!searchWrapper.contains(event.target) && searchDialog.open) { 140 searchDialog.close(); 141 searchWrapper.classList.remove('open'); 142 } 143 }); 144 document.addEventListener('keydown', function (event) { 145 if (event.key === 'Escape' && searchDialog.open) { 146 searchDialog.close(); 147 searchWrapper.classList.remove('open'); 148 } 149 }); 150} 151 152 153/** 154 * 155 * openSubMenu1 156 * 157 * Opens the first submenu level 158 * 159 * @param {*} button The button that was clicked 160 */ 161function openSubMenu1(button) { 162 let pageItemButton = $(button); 163 let pageItem = pageItemButton.parent('.page_item'); 164 165 // Check if this was activated by keyboard 166 let isKeyboardActivation = button.hasAttribute('data-keyboard-activated'); 167 168 pageItem.siblings().removeClass('open'); 169 170 // Only apply open class for mouse interactions, not keyboard navigation 171 if (!isKeyboardActivation) { 172 pageItem.toggleClass('open'); 173 } 174} 175 176/** 177 * 178 * openMobileSubMenu1 179 * 180 * Opens the first submenu level on mobile devices 181 * 182 * @param {*} button The button that was clicked 183 */ 184function openMobileSubMenu1(button) { 185 let pageItemButton = $(button); 186 let pageItem = pageItemButton.parent('.page_item'); 187 pageItem.siblings().removeClass('open'); 188 pageItem.siblings().children('.submenu').slideUp(150); 189 pageItem.toggleClass('open'); 190 if (pageItem.hasClass('open')) { 191 pageItem.children('.submenu').slideDown(150); 192 } else { 193 pageItem.children('.submenu').slideUp(150); 194 } 195} 196 197/** 198 * 199 * openSubMenu2 200 * 201 * Opens the second submenu level on desktop devices 202 * 203 * @param {*} button The button that was clicked 204 */ 205function openSubMenu2(button) { 206 let pageItemButton = $(button); 207 let pageItem = pageItemButton.parent('.page_item'); 208 let subMenu = pageItemButton.siblings('.submenu'); 209 210 // Check if this was activated by keyboard 211 let isKeyboardActivation = button.hasAttribute('data-keyboard-activated'); 212 213 // Only apply open class for mouse interactions, not keyboard navigation 214 if (!isKeyboardActivation) { 215 pageItem.toggleClass('open'); 216 if (pageItem.hasClass('open')) { 217 subMenu.slideDown(150); 218 } else { 219 subMenu.slideUp(150); 220 } 221 } 222} 223 224/** 225 * 226 * realignSubMenu1 227 * 228 * Realigns the first submenu level to the right if it runs off the right edge of the menu 229 * 230 * @returns void 231 */ 232function realignSubMenu1() { 233 // find the menu container 234 const menu = document.querySelector('.header-content-inner nav.menu'); 235 if (!menu) return; 236 237 // how far right the menu itself goes 238 const menuRect = menu.getBoundingClientRect(); 239 const menuRight = menuRect.left + menuRect.width; 240 241 // for each topâlevel page_item 242 menu.querySelectorAll(':scope > .page_item').forEach(pageItem => { 243 const subMenu = pageItem.querySelector(':scope > .submenu'); 244 if (!subMenu) return; 245 246 // reset any old overflow-logic 247 subMenu.classList.remove('submenu_alignright', 'centered'); 248 249 // if the submenu would run off the right edge, flip it left 250 const rect = subMenu.getBoundingClientRect(); 251 if (rect.left + rect.width > menuRight) { 252 subMenu.classList.add('submenu_alignright'); 253 } 254 255 // **always** drop it exactly 27px below the parent 256 subMenu.style.top = ''; 257 subMenu.style.bottom = '-27px'; 258 }); 259} 260 261/** 262 * 263 * responsiveMenu 264 * 265 * Checks if the menu fits into the header and switches between full and burger menu 266 * 267 * @return void 268 */ 269function responsiveMenu() { 270 let menuType = ''; 271 let windowWidth = $(window).width(); 272 $(window).on('load', function () {
273 menuType = checkMenuSizes(menuType); 274 }); 275 $(window).resize(function () { 276 if (windowWidth != $(window).width()) { 277 windowWidth = $(window).width(); 278 menuType = checkMenuSizes(menuType); 279 } 280 }); 281} 282/* Responsive Menu */ 283responsiveMenu(); 284 285/** 286 * 287 * checkMenuSizes 288 * 289 * Checks if the menu fits into the header and switches between full and burger menu 290 * Trigger: Inline in the header-content.php when the font is loaded, when the page is fully loaded, window.resize() 291 * 292 * @param {*} menuType 293 * @returns string menuType 294 */ 295function checkMenuSizes(menuType) { 296 const header = document.querySelector('header'); 297 header.classList.remove('burger'); 298 299 const headerContentInner = document.querySelector('.header-content-inner'); 300 const headerContentInnerWidth = headerContentInner.offsetWidth - 60; 301 302 const menuCalcMaxWidth = (headerContentInnerWidth * 0.82) - 90 + 5; 303 304 const menu = headerContentInner.querySelector('.menu-wrapper.desktop .menu'); 305 let menuInnerWidth = 0; 306 307 const pageItems = Array.from(menu.children).filter(el => el.classList.contains('page_item')); 308 309 pageItems.forEach((item) => { 310 const itemWidth = Math.ceil(item.offsetWidth); 311 menuInnerWidth += itemWidth; 312 }); 313 314 if (menuInnerWidth < 800) { 315 menuInnerWidth = 800; 316 } 317 318 // Force mobile menu at 1200px or below 319 if (window.innerWidth <= 1200 || parseInt(menuInnerWidth) > parseInt(menuCalcMaxWidth)) { 320 menuType = switchMenu(menuType, 'burger'); 321 header.classList.add('burger'); 322 document.querySelectorAll('.search-menu').forEach(el => el.style.display = 'flex'); 323 324 // Update tabindex when switching to burger mode 325 updateNavigationTabindex(); 326 } else { 327 menuType = switchMenu(menuType, 'full'); 328 document.querySelectorAll('.search-menu').forEach(el => el.style.display = 'none'); 329 330 // Update tabindex when switching to full mode 331 updateNavigationTabindex(); 332 } 333 334 return menuType; 335} 336 337/** 338 * 339 * switchMenu 340 * 341 * Switches between the full and burger menu 342 * 343 * @param {*} menuType The current menu type 344 * @param {*} targetType The target menu type 345 * @returns string menuType 346 */ 347function switchMenu(menuType, targetType) { 348 if (menuType !== targetType) { 349 let header = $('header'); 350 let desktopMenu = header.find('.menu-wrapper.desktop .menu'); 351 if (targetType == 'burger') { 352 desktopMenu.css('max-width', desktopMenu.outerWidth()); 353 } else { 354 desktopMenu.css('max-width', 'inherit'); 355 } 356 header.removeClass('burger'); 357 header.removeClass('full'); 358 header.addClass(targetType); 359 menuType = targetType; 360 pullContent(); 361 } 362 return menuType; 363} 364 365/** 366 * pullContent 367 * 368 * Function to pull the content down to the header if the header is full 369 * 370 * @return void 371 */ 372function pullContent() { 373 // If body does not contain the class "is-template1" exit function early 374 if (!document.body.classList.contains('is-template1')) { 375 return; 376 } 377 378 setTimeout(function () { 379 let header = $('header'); 380 let headerPlaceholder = document.getElementsByClassName('header-placeholder')[0]; 381 if (header.hasClass('full')) { 382 let headerHeight = header.outerHeight(); 383 let firstPageElement = $('.page-element-1'); 384 if (firstPageElement.length > 0) { 385 let firstPageElementClasses = firstPageElement.attr('class').split(" "); 386 let pageElementsOfEffect = [ 387 'slider', 388 'video_slider', 389 'header_image', 390 'slider_2' 391 ]; 392 393 $(pageElementsOfEffect).each(function (key, value) { 394 if (firstPageElementClasses.indexOf(value) >= 0) { 395 header.css('margin-bottom', -headerHeight); 396 if (headerPlaceholder) { 397 headerPlaceholder.style.marginBottom = -headerHeight + "px"; 398 } 399 } 400 }); 401 } 402 } else { 403 header.css('margin-bottom', 0); 404 } 405 }, 150); 406} 407 408/** 409 * 410 * openMobileMenu 411 * 412 * Opens the mobile menu and toggles the menu wrapper 413 * 414 * @param {*} button The button that was clicked 415 * 416 * @return void 417 */ 418function openMobileMenu(button) { 419 let burgerButton = document.querySelector('.logo-search-menu .burger'); 420 421 // toggle open class on button 422 burgerButton.classList.toggle('open'); 423 424 const menuWrapper = document.querySelector('.menu-wrapper.mobile'); 425 const menu = menuWrapper.querySelector('.menu'); 426 427 menuWrapper.classList.toggle('open'); 428 429 if (menuWrapper.classList.contains('open')) { 430 menu.style.display = 'flex'; 431 menu.style.transition = 'max-height 0.15s ease-in-out'; 432 menu.style.maxHeight = menu.scrollHeight + 'px'; 433 } else { 434 menu.style.transition = 'max-height 0.15s ease-in-out'; 435 menu.style.maxHeight = '0'; 436 setTimeout(() => { 437 menu.style.display = 'none'; 438 }, 150); 439 } 440 441 menuMaxHeight(); 442} 443 444/** 445 * 446 * updateNavigationTabindex 447 * 448 * Updates tabindex for mobile and desktop navigation based on menu mode 449 * When in burger mode: mobile items get tabindex="0", desktop items get tabindex="-1" 450 * When in full mode: mobile items get tabindex="-1", desktop items get tabindex="0" 451 * 452 * @return void 453 */ 454function updateNavigationTabindex() { 455 const header = document.querySelector('header'); 456 const isBurgerMode = header && header.classList.contains('burger'); 457 458 // Find mobile menu items 459 const mobileMenuItems = document.querySelectorAll('.menu-wrapper.mobile .menu a, .menu-wrapper.mobile .menu button'); 460 461 // Find desktop menu items 462 const desktopMenuItems = document.querySelectorAll('.menu-wrapper.desktop .menu a, .menu-wrapper.desktop .menu button'); 463 464 // Update mobile menu items
465 mobileMenuItems.forEach(item => { 466 item.setAttribute('tabindex', isBurgerMode ? '0' : '-1'); 467 }); 468 469 // Update desktop menu items (opposite of mobile) 470 desktopMenuItems.forEach(item => { 471 item.setAttribute('tabindex', isBurgerMode ? '-1' : '0'); 472 }); 473} 474 475/** 476 * 477 * openMobileSearch 478 * 479 * Opens the mobile search and toggles the search wrapper 480 * 481 * @param {*} button The button that was clicked 482 */ 483function openMobileSearch(button) { 484 // If the url contains ?s= and the passed button contains the class="addsearchvariant" and the body contains the class="search-results" do not open the search dialog 485 if (window.location.search.includes('?s=') && button.classList.contains('addsearchvariant') && document.body.classList.contains('search-results')) { 486 // Try to find the search input on the site .addsearch-searchfield input type="search" and focus it 487 let searchInput = document.querySelector('.addsearch-searchfield input[type="search"]'); 488 if (searchInput) { 489 searchInput.focus(); 490 } 491 // Return to prevent opening the dialog 492 return; 493 } 494 495 const mobilesearch = button.closest('.mobilesearch'); 496 // Find **either** the #searchform **or** the AddSearch wrapper: 497 const searchContainer = mobilesearch.querySelector('#searchform, .addsearch-wrapper'); 498 const searchInput = searchContainer.querySelector('input[type="search"], input[type="text"]'); 499 500 if (!searchContainer) { 501 return; 502 } 503 504 // Toggle open/closed 505 if (mobilesearch.classList.contains('open')) { 506 mobilesearch.classList.remove('open'); 507 searchContainer.style.zIndex = ''; 508 document.body.removeEventListener('mousedown', handleOutsideClick); 509 } else { 510 mobilesearch.classList.add('open'); 511 searchContainer.style.zIndex = '2'; 512 513 // Focus the search input 514 if (searchInput) { 515 searchInput.focus(); 516 } 517 518 // Give the browser a tick so the opening click doesnât immediately trigger our outside-click handler 519 setTimeout(() => { 520 document.body.addEventListener('mousedown', handleOutsideClick); 521 }, 0); 522 } 523 524 /** 525 * 526 * handleOutsideClick 527 * 528 * Handles clicks outside the mobile search dialog 529 * 530 * @param {*} e The click event 531 */ 532 function handleOutsideClick(e) { 533 // If click is _not_ inside the .mobilesearch **and** not inside the AddSearch dropdown 534 const isInMobilesearch = mobilesearch.contains(e.target); 535 const acDropdown = document.querySelector('[id^="addsearch-ac"]'); 536 const isInAddsearchAC = acDropdown && acDropdown.contains(e.target); 537 538 if (!isInMobilesearch && !isInAddsearchAC) { 539 mobilesearch.classList.remove('open'); 540 searchContainer.style.zIndex = ''; 541 document.body.removeEventListener('mousedown', handleOutsideClick); 542 } 543 } 544} 545 546/** 547 * 548 * useMobileSearch 549 * 550 * Uses the mobile search and redirects to the search page with the search input 551 * 552 * @returns void 553 */ 554function useSearch(event, element) { 555 // prevent default action of form submission 556 event.preventDefault(); 557 558 // get the search input value from the parent element of the clicked element 559 const searchInputElement = element.parentElement.querySelector('.search-field'); 560 const searchInput = searchInputElement ? searchInputElement.value : ''; 561 562 // If the search input is empty, do not redirect 563 if (searchInput === '') { 564 return false; 565 } 566 567 // Get the current URL origin 568 const url = window.location.origin; 569 570 // Redirect to the search page with the search input as a query parameter 571 window.location.href = `${url}?s=${encodeURIComponent(searchInput)}&search=${encodeURIComponent(searchInput)}`; 572} 573 574// Helper function to handle clicks on the logo container 575function handleLogoContainerClick() { 576 const logoContainers = document.querySelectorAll('.logo-container'); 577 logoContainers.forEach(container => { 578 container.addEventListener('click', function (e) { 579 if (!e.target.closest('.logo')) { 580 e.preventDefault(); 581 } 582 }); 583 }); 584} 585document.addEventListener('DOMContentLoaded', function () { 586 handleLogoContainerClick(); 587}); 588 589/** 590 * 591 * toggleSmallHeader 592 * 593 * Adds a class to the header to make it display smaller 594 * 595 * @param {*} distanceScrolled The distance scrolled from the top of the page 596 * @returns void 597 */ 598function toggleSmallHeader(distanceScrolled) { 599 600 // Vars 601 let header = document.getElementsByTagName('header')[0]; 602 let headerPlaceholder = document.getElementsByClassName('header-placeholder')[0]; 603
604 // Cancel if not all conditions for a scrolling header are met 605 if (!isHeaderScrollable(header, headerPlaceholder)) { 606 return false; 607 } 608 609 // If the scroll distance is higher than the header placeholder, add the class "scrolled", otherwise leave it as unscrolled 610 if (distanceScrolled > header.offsetHeight) { 611 header.classList.add('scrolled'); 612 header.classList.remove('unscrolled'); 613 } else { 614 header.classList.remove('scrolled'); 615 header.classList.add('unscrolled'); 616 } 617} 618window.onscroll = function(e) { 619 620 // Vars 621 let distanceScrolled = document.documentElement.scrollTop; 622 623 // Adds a class to the header to make it display smaller 624 toggleSmallHeader(distanceScrolled); 625 626} 627 628/** 629 * 630 * resizeHeaderPlaceholder 631 * 632 * Adjusts the size of the header placeholder to match the header 633 * 634 * @returns void 635 */ 636function resizeHeaderPlaceholder() { 637 // Vars 638 let header = document.getElementsByTagName('header')[0]; 639 let headerPlaceholder = document.getElementsByClassName('header-placeholder')[0]; 640 641 // Cancel if not all conditions for a scrolling header are met 642 if (!isHeaderScrollable(header, headerPlaceholder)) { 643 return false; 644 } 645 646 let headerHeight = header.offsetHeight; 647 648 // On mobile, ensure a minimum height for the placeholder 649 if (window.innerWidth <= 1200 && headerHeight < 90) { 650 headerHeight = 90; 651 } 652 653 // Set the size of the placeholder 654 headerPlaceholder.style.height = headerHeight + "px"; 655 656} 657document.addEventListener('DOMContentLoaded', function () { 658 659 // Vars 660 let header = document.getElementsByTagName('header')[0]; 661 let logo = document.querySelector('.logo-column img'); 662 663 // Add unscrolled class initially if header exists 664 if (header) { 665 header.classList.add('unscrolled'); 666 } 667 668 // Adjusts the size of the header placeholder to match the header 669 resizeHeaderPlaceholder(); 670 671 // Adjusts the size of the header placeholder to match the header once the logo has loaded 672 if(logo !== null) { 673 logo.onload = function(e) { 674 resizeHeaderPlaceholder(); 675 } 676 } 677 678}); 679window.onresize = function(e) { 680 // Adjusts the size of the header placeholder to match the header 681 resizeHeaderPlaceholder(); 682} 683 684/** 685 * 686 * isHeaderScrollable 687 * 688 * Checks if the header is scrollable 689 * 690 * @param {*} header The header element 691 * @param {*} headerPlaceholder The header placeholder element 692 * @returns boolean True if the header is scrollable, false if not 693 */ 694function isHeaderScrollable(header, headerPlaceholder) { 695 696 // Cancel if there is no header 697 if (!header) { 698 return false; 699 } 700 701 // Cancel if the header should not scroll along 702 if (!header.classList.contains('scrollable')) { 703 return false; 704 } 705 706 // Cancel if there is no placeholder 707 if (!headerPlaceholder) { 708 return false; 709 } 710 711 return true; 712} 713 714/** 715* 716* menuMaxHeight 717* 718* Sets the maximum height of the mobile menu to the window height minus the menu's top offset 719* 720* @returns void 721*/ 722function menuMaxHeight() { 723 // Vars 724 let header = document.getElementsByTagName('header')[0]; 725 let headerPlaceholder = document.getElementsByClassName('header-placeholder')[0]; 726 let menu = document.querySelector('.menu-wrapper.mobile .menu'); 727 let menuYOffset = menu.getBoundingClientRect().y; 728 let windowHeight = window.innerHeight; 729 let maxMenuHeight = 0; 730 731 // Cancel if there is no header or menu 732 if (!header || !menu) { 733 return false; 734 } 735 736 // For scrollable headers, use the original logic 737 if (isHeaderScrollable(header, headerPlaceholder)) { 738 // Subtract the top distance of the menu from the browser height 739 if (windowHeight && windowHeight > 0 && menuYOffset && menuYOffset > 0) { 740 maxMenuHeight = windowHeight - menuYOffset; 741 } 742 } else { 743 // For non-scrollable headers, calculate max height to prevent content overflow 744 if (windowHeight && windowHeight > 0 && menuYOffset && menuYOffset > 0) { 745 // Subtract a small buffer to prevent viewport overflow 746 maxMenuHeight = windowHeight - menuYOffset - 20; 747 } 748 } 749 750 if (maxMenuHeight && maxMenuHeight > 0) { 751 menu.style.maxHeight = maxMenuHeight+"px"; 752 } 753}
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.