// global sekFrontLocalized, nimbleListenTo /* ------------------------------------------------------------------------- * * MENU /* ------------------------------------------------------------------------- */ (function(w, d){ var callbackFunc = function() { jQuery( function($){ // Set the attribute data-sek-is-mobile-vertical-menu on page load and dynamically set on resize var _setVerticalMobileBooleanAttribute = function() { // Set vertical mobile boolean attribute var breakpoint = 768, deviceWidth; $('nav.sek-nav-wrap').each( function() { breakpoint = $(this).data('sek-mobile-menu-breakpoint') || breakpoint; // cast to integer breakpoint = parseInt( breakpoint, 10 ); deviceWidth = window.innerWidth > 0 ? window.innerWidth : screen.width; // console.log('window.innerWidth ??', window.innerWidth, window.innerWidth > 0 ); // console.log('SOO ? breakpoint | device width', breakpoint + ' | ' + deviceWidth ); // add a data attribute so we can target the mobile menu with dynamic css rules // @needed when coding : https://github.com/presscustomizr/nimble-builder/issues/491 $(this).attr('data-sek-is-mobile-vertical-menu', deviceWidth < breakpoint ? 'yes' : 'no'); }); }; _setVerticalMobileBooleanAttribute(); nb_.cachedElements.$window.on('resize', nb_.debounce( _setVerticalMobileBooleanAttribute, 100) ); // HELPER TO DETERMINE IF A NODE BELONGS TO A MOBILE MENU // this is the element var nodeBelongsToAMobileMenu = function() { if ( this.length && this.length > 0 ) { // Note that [data-sek-is-mobile-vertical-menu] value is set on page load and dynamically on window resize return "yes" === this.closest('[data-sek-is-mobile-vertical-menu]').attr('data-sek-is-mobile-vertical-menu'); } return false; }; //DESKTOP DROPDOWN var desktopDropdownOnHover = function() { //dropdown var DATA_KEY = 'sek.sekDropdown', EVENT_KEY = '.' + DATA_KEY, Event = { PLACE_ME : 'placeme'+ EVENT_KEY, PLACE_ALL : 'placeall' + EVENT_KEY, SHOWN : 'shown' + EVENT_KEY, SHOW : 'show' + EVENT_KEY, HIDDEN : 'hidden' + EVENT_KEY, HIDE : 'hide' + EVENT_KEY, CLICK : 'click' + EVENT_KEY, TAP : 'tap' + EVENT_KEY, }, ClassName = { DROPDOWN : 'sek-dropdown-menu', DROPDOWN_SUBMENU : 'sek-dropdown-submenu', SHOW : 'show', PARENTS : 'menu-item-has-children', ALLOW_POINTER_ON_SCROLL : 'allow-pointer-events-on-scroll' }, Selector = { DATA_SHOWN_TOGGLE_LINK : '.' +ClassName.SHOW+ '> a', HOVER_MENU : '.sek-nav-wrap', HOVER_PARENT : '.sek-nav-wrap .menu-item-has-children', PARENTS : '.sek-nav-wrap .menu-item-has-children', SNAKE_PARENTS : '.sek-nav-wrap .menu-item-has-children', CHILD_DROPDOWN : 'ul.sek-dropdown-menu' }; // unify all the dropdowns classes whether the menu is a proper menu or the all pages fall-back $( '.sek-nav .children, .sek-nav .sub-menu' ).addClass( ClassName.DROPDOWN ); $( '.sek-nav-wrap .page_item_has_children' ).addClass( ClassName.PARENTS ); $( '.sek-nav' + ' .' + ClassName.DROPDOWN + ' .' + ClassName.PARENTS ).addClass( ClassName.DROPDOWN_SUBMENU ); //Handle dropdown on hover via js var dropdownMenuOnHover = function() { var _dropdown_selector = Selector.HOVER_PARENT; bindEvents(); function _addOpenClass( evt ) { var $_el = $(this), $_child_dropdown = $_el.find( Selector.CHILD_DROPDOWN ).first(); // Jan 2021 : start of a fix for https://github.com/presscustomizr/nimble-builder/issues/772 if ( nb_.cachedElements.$body.hasClass('is-touch-device') ) { // When navigating the regular menu ( horizontal ) on a mobile touch device, typically a tablet in landscape orientation // we want to prevent opening the link of a parent menu if the children are not displayed yet if ( "true" != $_child_dropdown.attr('aria-expanded') && !nodeBelongsToAMobileMenu.call($_child_dropdown) ) { evt.preventDefault(); } } //a little delay to balance the one added in removing the open class var _debounced_addOpenClass = nb_.debounce( function() { //do nothing if menu is mobile if( 'static' == $_el.find( '.'+ClassName.DROPDOWN ).css( 'position' ) ) { return false; } var $_child_dropdown = $_el.find( Selector.CHILD_DROPDOWN ).first(); if ( !$_el.hasClass(ClassName.SHOW) ) { nb_.cachedElements.$body.addClass( ClassName.ALLOW_POINTER_ON_SCROLL ); $_el.trigger( Event.SHOW ) .addClass(ClassName.SHOW) .trigger( Event.SHOWN); if ( $_child_dropdown.length > 0 ) { $_child_dropdown[0].setAttribute('aria-expanded', 'true'); } } }, 30); _debounced_addOpenClass(); } function _removeOpenClass() { var $_el = $(this), $_child_dropdown = $_el.find( Selector.CHILD_DROPDOWN ).first(); //a little delay before closing to avoid closing a parent before accessing the child var _debounced_removeOpenClass = nb_.debounce( function() { if ( $_el.find("ul li:hover").length < 1 && ! $_el.closest('ul').find('li:hover').is( $_el ) ) { // april 2020 => some actions should be only done when not on a "touch" device // otherwise we have a bug on submenu expansion // see : https://github.com/presscustomizr/customizr/issues/1824 //if ( !nb_.cachedElements.$body.hasClass('is-touch-device') ) { // $_el.trigger( Event.HIDE ) // .removeClass( ClassName.SHOW) // .trigger( Event.HIDDEN ); //} $_el.trigger( Event.HIDE ) .removeClass( ClassName.SHOW) .trigger( Event.HIDDEN ); //make sure pointer events on scroll are still allowed if there's at least one submenu opened if ( $_el.closest( Selector.HOVER_MENU ).find( '.' + ClassName.SHOW ).length < 1 ) { nb_.cachedElements.$body.removeClass( ClassName.ALLOW_POINTER_ON_SCROLL ); } if ( $_child_dropdown.length > 0 ) { $_child_dropdown[0].setAttribute('aria-expanded', 'false'); } } }, 30 ); _debounced_removeOpenClass(); } function bindEvents() { // april 2020 : is-touch-device class is added on body on the first touch // This way, we can prevent the problem reported on https://github.com/presscustomizr/customizr/issues/1824 // ( two touches needed to reveal submenus on touch devices ) nb_.cachedElements.$body.on('touchstart', function() { if ( !$(this).hasClass('is-touch-device') ) { $(this).addClass('is-touch-device'); } }); //BIND nb_.cachedElements.$body .on( 'mouseenter', _dropdown_selector, function(evt) { if ( !nodeBelongsToAMobileMenu.call($(this)) ) { _addOpenClass.call($(this), evt ); } }) .on( 'mouseleave', _dropdown_selector , function(evt) { if ( !nodeBelongsToAMobileMenu.call($(this)) ) { _removeOpenClass.call($(this), evt ); } }) .on( 'click', _dropdown_selector, function(evt) { if ( !nodeBelongsToAMobileMenu.call($(this)) ) { _addOpenClass.call($(this), evt ); } }); } }, // DESKTOP SNAKE dropdownPlacement = function() { var isRTL = 'rtl' === $('html').attr('dir'), doingAnimation = false; nb_.cachedElements.$window //on resize trigger Event.PLACE on active dropdowns .on( 'resize', function() { if ( ! doingAnimation ) { doingAnimation = true; window.requestAnimationFrame(function() { //trigger a placement on the open dropdowns $( Selector.SNAKE_PARENTS+'.'+ClassName.SHOW) .trigger(Event.PLACE_ME); doingAnimation = false; }); } }); $( document ) .on( Event.PLACE_ALL, function() { //trigger a placement on all $( Selector.SNAKE_PARENTS ) .trigger(Event.PLACE_ME); }) //snake bound on menu-item shown and place .on( Event.SHOWN+' '+Event.PLACE_ME, Selector.SNAKE_PARENTS, function(evt) { evt.stopPropagation(); _do_snake( $(this), evt ); }); //snake //$_el is the menu item with children whose submenu will be 'snaked' function _do_snake( $_el, evt ) { if ( !( evt && evt.namespace && DATA_KEY === evt.namespace ) ) { return; } var $_this = $_el, $_dropdown = $_this.children( '.'+ClassName.DROPDOWN ); if ( !$_dropdown.length ) { return; } //stage /* * we display the dropdown so that jQuery is able to retrieve exact size and positioning * we also hide whatever overflows the menu item with children whose submenu will be 'snaked' * this to avoid some glitches that would made it lose the focus: * During RTL testing when a menu item with children reached the left edge of the window * it happened that while the submenu was showing (because of the show class added, so not depending on the snake) * this submenu (ul) stole the focus and then released it in a very short time making the mouseleave callback * defined in dropdownMenuOnHover react, hence closing the whole submenu tree. * This might be a false positive, as we don't really test RTL with RTL browsers (only the html direction changes), * but since the 'cure' has no side effects, let's be pedantic! */ $_el.css( 'overflow', 'hidden' ); $_dropdown.css( { 'zIndex' : '-100', 'display' : 'block' }); _maybe_move( $_dropdown, $_el ); //unstage $_dropdown.css({ 'zIndex' : '', 'display' : '' }); $_el.css( 'overflow', '' ); }//_so_snake function _maybe_move( $_dropdown, $_el ) { var Direction = isRTL ? { //when in RTL we open the submenu by default on the left side _DEFAULT : 'left', _OPPOSITE : 'right' } : { //when in LTR we open the submenu by default on the right side _DEFAULT : 'right', _OPPOSITE : 'left' }, ClassName = { OPEN_PREFIX : 'open-', DD_SUBMENU : 'sek-dropdown-submenu', CARET_TITLE_FLIP : 'sek-menu-link__row-reverse', //CARET : 'caret__dropdown-toggler', DROPDOWN : 'sek-dropdown-menu' }, _caret_title_maybe_flip = function( $_el, _direction, _old_direction ) { $.each( $_el, function() { var $_el = $(this), $_a = $_el.find( 'a' ).first(); if ( 1 == $_a.length ) { $_a.toggleClass( ClassName.CARET_TITLE_FLIP, _direction == Direction._OPPOSITE ); } }); }, _setOpenDirection = function( _direction ) { //retrieve the old direction => used to remove the old direction class var _old_direction = _direction == Direction._OPPOSITE ? Direction._DEFAULT : Direction._OPPOSITE; //tell the dropdown to open on the direction _direction (hence remove the old direction class) $_dropdown.removeClass( ClassName.OPEN_PREFIX + _old_direction ).addClass( ClassName.OPEN_PREFIX + _direction ); if ( $_el.hasClass( ClassName.DD_SUBMENU ) ) { _caret_title_maybe_flip( $_el, _direction, _old_direction ); //make the first level submenus caret inherit this _caret_title_maybe_flip( $_dropdown.children( '.' + ClassName.DD_SUBMENU ), _direction, _old_direction ); } }; //snake inheritance if ( $_dropdown.parent().closest( '.'+ClassName.DROPDOWN ).hasClass( ClassName.OPEN_PREFIX + Direction._OPPOSITE ) ) { //open on the opposite direction _setOpenDirection( Direction._OPPOSITE ); } else { //open on the default direction _setOpenDirection( Direction._DEFAULT ); } //let's compute on which side open the dropdown if ( $_dropdown.offset().left + $_dropdown.width() > nb_.cachedElements.$window.width() ) { //open on the left _setOpenDirection( 'left' ); } else if ( $_dropdown.offset().left < 0 ) { //open on the right _setOpenDirection( 'right' ); } }//_maybe_move };//dropdownPlacement //FireAll dropdownMenuOnHover(); dropdownPlacement(); };//desktopDropdownOnHover // FIRE DESKTOP MENU METHODS desktopDropdownOnHover(); // MOBILE MENU HAMBURGER BUTTON // handle the mobile hamburger hover effect $( document ) .on( 'mouseenter', '.sek-nav-toggler', function(){ $(this).addClass( 'hovering' ); } ) .on( 'mouseleave', '.sek-nav-toggler', function(){ $(this).removeClass( 'hovering' ); } ) .on( 'show.sek.sekCollapse hide.sek.sekCollapse', '.sek-nav-collapse', function() { $('[data-target="#'+$(this).attr('id')+'"]').removeClass( 'hovering' ); nb_.cachedElements.$window.trigger('scroll'); }); // MOBILE MENU VISIBILITY toggleMobileMenuVisibility = function() { var EVENT_KEY = ".nbMobMenuBtn", TRANSITION_DURATION = 400, Event = { SHOW: "show" + EVENT_KEY, SHOWN: "shown" + EVENT_KEY, HIDE: "hide" + EVENT_KEY, HIDDEN: "hidden" + EVENT_KEY, CLICK_EVENT: "click" + EVENT_KEY }, ClassName = { COLLAPSING: 'sek-collapsing', COLLAPSED: 'sek-collapsed' }, Selector = { MM_TOGGLER: '.sek-nav-toggler' }; // attach click event nb_.cachedElements.$body.on( Event.CLICK_EVENT, Selector.MM_TOGGLER, function (event, params) { // preventDefault only for elements (which change the URL) not inside the collapsible element if (event.currentTarget.tagName === 'A') { event.preventDefault(); } var $toggler = $(this), //get the data toggle _mob_menu_selector = $toggler.data('target'); $(_mob_menu_selector).each( function () { var $mobMenuWrapper = $(this), mobMenuIsExpanded = "expanded" === $mobMenuWrapper.attr('data-sek-mm-state'), $maybeHeaderParentEl = $mobMenuWrapper.closest('#nimble-header'); // console.log('"$mobMenuWrapper ?', $mobMenuWrapper ); // console.log('mobMenuIsExpanded ?', mobMenuIsExpanded ); $mobMenuWrapper.stop()[ mobMenuIsExpanded ? 'slideUp' : 'slideDown' ]({ duration: (params && params.close_fast) ? 0 : TRANSITION_DURATION, start : function() { $mobMenuWrapper.addClass(ClassName.COLLAPSING).trigger( mobMenuIsExpanded ? Event.HIDE : Event.SHOW ); if ( mobMenuIsExpanded ) { $toggler.addClass( ClassName.COLLAPSED ).attr( 'aria-expanded', 'false' ); if ( $maybeHeaderParentEl.length > 0 ) { $maybeHeaderParentEl.removeClass('sek-header-mobile-menu-expanded'); } } else { $toggler.removeClass( ClassName.COLLAPSED ).attr( 'aria-expanded', 'true' ); $mobMenuWrapper.attr('data-sek-mm-state', 'expanded'); if ( $maybeHeaderParentEl.length > 0 ) { $maybeHeaderParentEl.addClass('sek-header-mobile-menu-expanded'); } } }, complete: function() { // console.log('SOO DATA ?', mobMenuIsExpanded, $mobMenuWrapper.attr('data-sek-mm-state') ); if ( mobMenuIsExpanded ) { $mobMenuWrapper.removeClass(ClassName.COLLAPSING).trigger(Event.HIDDEN); $mobMenuWrapper.attr('data-sek-mm-state', 'collapsed'); } else { $mobMenuWrapper.removeClass(ClassName.COLLAPSING).trigger(Event.SHOWN); } //remove all the inline style added by the slideUp/Down methods $mobMenuWrapper.css({ 'display' : '', 'paddingTop' : '', 'marginTop' : '', 'paddingBottom' : '', 'marginBottom' : '', 'height' : '' }); } });//end slideUp/slideDown });//end each });//end attach click event // close mobile menu on resize event nb_.cachedElements.$window.on('resize', nb_.debounce( function() { $(Selector.MM_TOGGLER).each(function() { var associated_mob_menu_selector = $(this).data('target'); if ( 'true' == $(this).attr( 'aria-expanded' ) ) { if ( $(associated_mob_menu_selector).length && !nodeBelongsToAMobileMenu.call( $(associated_mob_menu_selector) ) ) { $(this).trigger(Event.CLICK_EVENT, {close_fast:true}); } } }); }, 100) ); };//toggleMobileMenuVisibility() toggleMobileMenuVisibility(); //////////////////////////////////////////////////////////////////////// //////////// COLLAPSIBLE MENU ( janv 2021 ) //hueman theme inspired var maybeApplyCollapsibleMenu = function() { var $mobMenuWrapper = this; if ( 'true' == $mobMenuWrapper.data('nb-mm-menu-is-instantiated') ) return; // Flag so we don't instantiate twice ( typically when previewing) $mobMenuWrapper.data('nb-mm-menu-is-instantiated', 'true'); //specific class added to this mobile menu which tells its submenus have to be expanded on click (purpose: style) $mobMenuWrapper.addClass( 'nb-collapsible-mobile-menu' ); var EVENT_KEY = '.nb.submenu', Event = { SHOW : 'show' + EVENT_KEY, HIDE : 'hide' + EVENT_KEY, CLICK : 'mousedown' + EVENT_KEY, FOCUSIN : 'focusin' + EVENT_KEY, FOCUSOUT : 'focusout' + EVENT_KEY }, Classname = { DD_TOGGLE_ON_CLICK : 'nb-collapsible-mobile-menu', SHOWN : 'expanded', DD_TOGGLE : 'nb-dd-mm-toggle', DD_TOGGLE_WRAPPER : 'nb-dd-mm-toggle-wrapper', SCREEN_READER : 'screen-reader-text', }, Selector = { DD_TOGGLE_PARENT : '.menu-item-has-children, .page_item_has_children', CURRENT_ITEM_ANCESTOR : '.current-menu-ancestor', SUBMENU : '.sub-menu' }, // Add dropdown toggle that displays child menu items. dropdownToggle = $( '