/** * Main Navigation JavaScript. * * @author Matthias Kittsteiner * @copyright 2018 RegioHelden GmbH * @version 1.0.6 */ // variable mobileWidth will be declared via customizer var mobileWidthSmallScreens = 840; var isMobile = ! window.matchMedia( '(min-width: ' + mobileWidth + 'px)' ).matches; // document loaded document.addEventListener( 'DOMContentLoaded', function( event ) { var content = document.getElementById( 'page' ) || document.getElementById( 'primary' ); // #primary as fallback if there is no #page var dropDownToggles = document.querySelectorAll( '#site-navigation .dropdown-toggle' ); var header = document.getElementById( 'masthead' ); var headerPos = header ? header.offsetTop : 0; // don’t resort var isAdminBarFixed = ! window.matchMedia( '(max-width: 600px)' ).matches; var nav = document.getElementById( 'site-navigation' ); var navBlocker = createNewElement( 'div', 'nav-blocker', document.body, 'id' ); var navItems = document.querySelectorAll('.main-navigation a'); var navToggleBtn = document.getElementById( 'mobile-menu-toggle' ); var navToggleBtnAlt = document.getElementById( 'mobile-menu-toggle-inside' ); var navWrapper = document.getElementById( 'nav-wrapper' ); var page = document.body; var siteHeader = document.querySelector( '.site-header' ); if ( isMobile ) { // mobile initEvents(); } setTimeout( function() { gapForAnchorLinks(); }, 50 ); if ( nav && nav.classList.contains( 'nav-fixed' ) ) { // if navigation is fixed fixedNav(); if ( navWrapper && ! isMobile ) { var inner_content = document.querySelector( '.no-keyvisual #content' ); if ( inner_content ) { inner_content.style.marginTop = header.offsetHeight + 'px'; } } } // scroll event window.addEventListener( 'scroll', function() { isMobile = ! window.matchMedia( '(min-width: ' + mobileWidth + 'px)' ).matches; if ( nav && nav.classList.contains( 'nav-fixed' ) ) { // if navigation is fixed fixedNav(); } } ); // resize event window.addEventListener( 'resize', function() { // recalculate variables headerPos = header ? header.offsetTop : 0; isMobile = ! window.matchMedia( '(min-width: ' + mobileWidth + 'px)' ).matches; if ( isMobile ) { // mobile initEvents(); if ( navWrapper ) { var inner_content = document.querySelector( '.no-keyvisual #content' ); if ( inner_content ) { inner_content.style.removeProperty( 'margin-top' ); } } } if ( nav && nav.classList.contains( 'nav-fixed' ) ) { // if navigation is fixed fixedNav(); } if ( ! isMobile ) { // force closing on non-mobile devices _closeMenu(); if ( navWrapper ) { var inner_content = document.querySelector( '.no-keyvisual #content' ); if ( inner_content ) { inner_content.style.marginTop = header.offsetHeight + 'px'; } } } }, true ); // click event on menu items Array.from( navItems ).forEach( function( item ) { item.addEventListener( 'click', function( event ) { var actualTarget = event.target; var isEmpty = actualTarget.getAttribute( 'href' ) === '' || actualTarget.getAttribute( 'href' ) === '#'; if ( ! actualTarget.classList.contains( 'dropdown-toggle' ) && ! isEmpty ) { _closeMenu(); } if ( ( isEmpty ) && actualTarget.querySelector( '.dropdown-toggle' ) ) { toggleMobileSubNavigation( event, actualTarget.nextElementSibling ); } } ); } ); /** * Fixing the navigation via CSS classes. */ function fixedNav() { var adminBar = document.getElementById( 'wpadminbar' ); var adminBarOffsetHeight = adminBar ? adminBar.offsetHeight : 0; var isAlongside = !!navWrapper; var isBefore = siteHeader.classList.contains( 'nav-before' ); var isMobileWidthSmallScreens = ! window.matchMedia( '(min-width: ' + mobileWidthSmallScreens + 'px)' ).matches; var scrollTop = window.pageYOffset; // if mobile and navigation is open if ( isMobile && document.body.classList.contains( 'nav-open' ) ) return; // reset values if ( ! isMobileWidthSmallScreens && navWrapper ) navWrapper.removeAttribute( 'style' ); // while admin bar is not fixed, scroll the fixed menu with it if ( ! isAdminBarFixed ) { var topValue = adminBarOffsetHeight - scrollTop; if ( navWrapper && ! isMobileWidthSmallScreens ) { if ( topValue > 0 ) { navWrapper.style.top = topValue + 'px'; } if ( scrollTop > adminBarOffsetHeight ) { navWrapper.style.top = '0'; } } else { if ( topValue > 0 ) { navToggleBtn.style.top = topValue + 'px'; navToggleBtn.style.transition = 'none'; } if ( scrollTop > adminBarOffsetHeight ) { navToggleBtn.style.top = 0; navToggleBtn.style.transition = 'none'; } } } if ( isBefore ) { // nav before logo if ( scrollTop > navToggleBtn.offsetTop - adminBarOffsetHeight ) { content.classList.add( 'fixed' ); header.classList.add( 'fixed' ); if ( isMobile ) { header.style.paddingTop = navToggleBtn ? navToggleBtn.offsetHeight + 'px' : 0; } else { header.style.paddingTop = nav ? nav.offsetHeight + 'px' : 0; } } } else if ( isAlongside ) { // nav alongside logo if ( ( scrollTop > header.offsetTop - adminBarOffsetHeight && scrollTop > 0 ) || scrollTop < 0 ) { content.classList.add( 'fixed' ); header.classList.add( 'fixed' ); if ( window.matchMedia( '(min-width: ' + mobileWidthSmallScreens + 'px)' ).matches ) { content.classList.add( 'fixed' ); header.classList.add( 'fixed' ); header.classList.add( 'fixed-wrapper' ); } } else { content.classList.remove( 'fixed' ); header.classList.remove( 'fixed' ); header.classList.remove( 'fixed-wrapper' ); header.removeAttribute( 'style' ); } } else { // nav after logo if ( scrollTop >= _navPosition() ) { content.classList.add( 'fixed' ); header.classList.add( 'fixed' ); if ( isMobile ) { header.style.paddingTop = navToggleBtn ? navToggleBtn.offsetHeight + 'px' : 0; } else { header.style.paddingTop = nav ? nav.offsetHeight + 'px' : 0; } } else { content.classList.remove( 'fixed' ); header.classList.remove( 'fixed' ); header.classList.remove( 'fixed-wrapper' ); header.removeAttribute( 'style' ); } } } /** * Add a gap by scrolling the window so that an anchor is still visible * with fixed navigation. */ function gapForAnchorLinks() { // check if a hash is available and valid var hashRegex = new RegExp( '^#[A-Za-z0-9_\-]+$' ); if ( ! window.location.hash || ! hashRegex.test( window.location.hash ) ) return; if ( ! document.querySelector( window.location.hash ) ) return; var adminBar = document.getElementById( 'wpadminbar' ); var adminBarOffset = adminBar ? adminBar.offsetHeight : 0; var hashElement = document.querySelector( window.location.hash ); var hashElementPos = getCoords( hashElement ); var headerHeight = 0; var isAlongside = !!navWrapper; var scrollPos = window.pageYOffset || document.documentElement.scrollTop; if ( isAlongside && window.matchMedia( '(min-width: ' + mobileWidthSmallScreens + 'px)' ).matches ) { headerHeight = header.offsetHeight; } else if ( isMobile ) { headerHeight = navToggleBtn.offsetHeight; } else { headerHeight = nav ? nav.offsetHeight : 0; } headerHeight += adminBarOffset; if ( scrollPos > hashElementPos.top - headerHeight ) { window.scrollTo( 0, scrollPos - headerHeight ); } } /** * Initialize events. */ function initEvents() { // remove any previous event listener if ( navToggleBtn ) navToggleBtn.removeEventListener( 'click', toggleMobileNavigationState ); if ( navToggleBtnAlt ) navToggleBtnAlt.removeEventListener( 'click', toggleMobileNavigationState ); if ( navBlocker ) navBlocker.removeEventListener( 'click', toggleMobileNavigationState ); // add new event listener if ( navToggleBtn ) navToggleBtn.addEventListener( 'click', toggleMobileNavigationState ); if ( navToggleBtnAlt ) navToggleBtnAlt.addEventListener( 'click', toggleMobileNavigationState ); if ( navBlocker ) navBlocker.addEventListener( 'click', toggleMobileNavigationState ); Array.from( dropDownToggles ).forEach( function( dropDownToggle ) { dropDownToggle.removeEventListener( 'click', toggleMobileSubNavigation ); // mobile only if ( isMobile ) { dropDownToggle.addEventListener('click', toggleMobileSubNavigation); } } ); } /** * Opens or closes the main navigation depending on * its current state. * @param event */ function toggleMobileNavigationState( event ) { if ( nav && nav.classList.contains( 'nav-open' ) ) { _closeMenu(); } else { _openMenu(); } } /** * Opens or closes a sub navigation. * @param {Event} event * @param {Element} subMenu */ function toggleMobileSubNavigation ( event, subMenu ) { event.preventDefault(); var currentTarget = subMenu ? event.currentTarget.querySelector( '.dropdown-toggle' ) : event.currentTarget; var thisSubMenu = subMenu || currentTarget.parentElement.nextElementSibling; if ( ! isHidden( thisSubMenu ) ) { currentTarget.classList.add( 'bottom' ); currentTarget.classList.add( 'closed' ); currentTarget.classList.remove( 'open' ); currentTarget.classList.remove( 'top' ); thisSubMenu.classList.add( 'sub-menu-closed' ); thisSubMenu.classList.remove( 'sub-menu-open' ); thisSubMenu.setAttribute( 'aria-expanded', 'false' ); } else { currentTarget.classList.add( 'open' ); currentTarget.classList.add( 'top' ); currentTarget.classList.remove( 'bottom' ); currentTarget.classList.remove( 'closed' ); thisSubMenu.classList.add( 'sub-menu-open' ); thisSubMenu.classList.remove( 'sub-menu-closed' ); thisSubMenu.setAttribute( 'aria-expanded', 'true' ); } } /** * Close the menu. * @private */ function _closeMenu() { content.classList.remove( 'nav-open' ); if ( nav ) nav.classList.remove( 'nav-open' ); if ( navToggleBtn ) navToggleBtn.classList.remove( 'nav-open' ); page.classList.remove( 'nav-open' ); } /** * Calculate the position of the navigation. * @return {number} * @private */ function _navPosition() { var adminBar = document.getElementById( 'wpadminbar' ); var adminBarHeight = isAdminBarFixed ? 0 : ( adminBar ? adminBar.offsetHeight : 0 ); var navHeight = isMobile && navToggleBtn ? navToggleBtn.offsetHeight : ( nav ? nav.offsetHeight : 0 ); return header.offsetHeight + adminBarHeight - navHeight; } /** * Open the menu. * @private */ function _openMenu() { content.classList.add( 'nav-open' ); if ( nav ) nav.classList.add( 'nav-open' ); if ( navToggleBtn ) navToggleBtn.classList.add( 'nav-open' ); page.classList.add( 'nav-open' ); content.classList.remove('fixed'); if ( ! window.matchMedia( '(min-width: ' + mobileWidthSmallScreens + 'px)' ).matches ) { header.classList.remove('fixed'); header.classList.remove('fixed-wrapper'); header.removeAttribute('style'); } navToggleBtn.removeAttribute( 'style' ); } } ); /** * Create a new HTML element. * * @param {string} name * @param {string} newSelector * @param {Element} parent * @param {string} idOrClass * @returns {Element} */ function createNewElement( name, newSelector, parent, idOrClass ) { var element = document.createElement( name ); if ( newSelector.length ) { if ( idOrClass === 'class' ) { element.classList.add( newSelector ); } else { element.setAttribute( 'id', newSelector ); } } parent.appendChild( element ); return element; } /** * Crossbrowser version to get element’s coordinates. * * @see https://stackoverflow.com/a/26230989/3461955 * @param {Element} elem * @return {object} */ function getCoords( elem ) { if ( ! elem ) { return { top: 0, left: 0 }; } var box = elem.getBoundingClientRect(); var body = document.body; var docEl = document.documentElement; var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; var clientTop = docEl.clientTop || body.clientTop || 0; var clientLeft = docEl.clientLeft || body.clientLeft || 0; var top = box.top + scrollTop - clientTop; var left = box.left + scrollLeft - clientLeft; return { top: Math.round(top), left: Math.round(left) }; } /** * Check if element is hidden. * @param {Element} element * @returns {boolean} */ function isHidden( element ) { return ( element.offsetParent === null ); } // Fix `Array.from` in IE 11 if (!Array.from) { Array.from = (function () { var toStr = Object.prototype.toString; var isCallable = function (fn) { return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; }; var toInteger = function (value) { var number = Number(value); if (isNaN(number)) { return 0; } if (number === 0 || !isFinite(number)) { return number; } return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); }; var maxSafeInteger = Math.pow(2, 53) - 1; var toLength = function (value) { var len = toInteger(value); return Math.min(Math.max(len, 0), maxSafeInteger); }; // The length property of the from method is 1. return function from(arrayLike/*, mapFn, thisArg */) { // 1. Let C be the this value. var C = this; // 2. Let items be ToObject(arrayLike). var items = Object(arrayLike); // 3. ReturnIfAbrupt(items). if (arrayLike == null) { throw new TypeError("Array.from requires an array-like object - not null or undefined"); } // 4. If mapfn is undefined, then let mapping be false. var mapFn = arguments.length > 1 ? arguments[1] : void undefined; var T; if (typeof mapFn !== 'undefined') { // 5. else // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. if (!isCallable(mapFn)) { throw new TypeError('Array.from: when provided, the second argument must be a function'); } // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. if (arguments.length > 2) { T = arguments[2]; } } // 10. Let lenValue be Get(items, "length"). // 11. Let len be ToLength(lenValue). var len = toLength(items.length); // 13. If IsConstructor(C) is true, then // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len. // 14. a. Else, Let A be ArrayCreate(len). var A = isCallable(C) ? Object(new C(len)) : new Array(len); // 16. Let k be 0. var k = 0; // 17. Repeat, while k < len… (also steps a - h) var kValue; while (k < len) { kValue = items[k]; if (mapFn) { A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); } else { A[k] = kValue; } k += 1; } // 18. Let putStatus be Put(A, "length", len, true). A.length = len; // 20. Return A. return A; }; }()); }