(function($) { /** * Class for Menu Module * * @since 1.6.1 */ FLBuilderMenu = function( settings ){ // set params this.nodeId = settings.id; this.nodeClass = '.fl-node-' + settings.id; this.wrapperClass = this.nodeClass + ' .fl-menu'; this.type = settings.type; this.mobileToggle = settings.mobile; this.mobileBelowRow = settings.mobileBelowRow; this.mobileFlyout = settings.mobileFlyout; this.breakPoints = settings.breakPoints; this.mobileBreakpoint = settings.mobileBreakpoint; this.currentBrowserWidth = $( window ).width(); this.postId = settings.postId; this.mobileStacked = settings.mobileStacked; // initialize the menu this._initMenu(); // check if viewport is resizing $( window ).on( 'resize', $.proxy( function( e ) { var width = $( window ).width(); // if screen width is resized, reload the menu if( width != this.currentBrowserWidth ){ this.currentBrowserWidth = width; this._initMenu(); this._clickOrHover(); } }, this ) ); $( 'body' ).on( 'click', $.proxy( function( e ) { if ( 'undefined' !== typeof FLBuilderConfig ){ return; } var activeMobileMenu = $(this.wrapperClass + ' .fl-menu-mobile-toggle.fl-active' ); if ( activeMobileMenu.length && ( 'expanded' !== this.mobileToggle ) ){ $( activeMobileMenu ).trigger('click'); } $( this.wrapperClass ).find( '.fl-has-submenu' ).removeClass( 'focus' ); $( this.wrapperClass ).find( '.fl-has-submenu .sub-menu' ).removeClass( 'focus' ); }, this ) ); // Close Mobile menu when tabbing out from the last menu item. $( this.wrapperClass + ' ul.menu > li:last-child' ).on( 'focusout', $.proxy(function (e) { if ( $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ).hasClass( 'fl-active' ) && ( 'expanded' !== this.mobileToggle ) ) { if ( ! $( e.relatedTarget ).parent().hasClass( 'menu-item' ) ) { $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ).trigger( 'click' ); } } }, this ) ); }; FLBuilderMenu.prototype = { nodeClass : '', wrapperClass : '', type : '', breakPoints : {}, $submenus : null, /** * Check if the screen size fits a mobile viewport. * * @since 1.6.1 * @return bool */ _isMobile: function(){ return this.currentBrowserWidth <= this.breakPoints.small ? true : false; }, /** * Check if the screen size fits a medium viewport. * * @since 1.10.5 * @return bool */ _isMedium: function(){ return this.currentBrowserWidth <= this.breakPoints.medium ? true : false; }, /** * Check if the screen size fits a large viewport. * * @since 1.10.5 * @return bool */ _isLarge: function(){ return this.currentBrowserWidth <= this.breakPoints.large ? true : false; }, /** * Check if the menu should toggle for the current viewport base on the selected breakpoint * * @see this._isMobile() * @see this._isMedium() * @since 1.10.5 * @return bool */ _isMenuToggle: function(){ if ( ( 'always' == this.mobileBreakpoint || ( this._isMobile() && 'mobile' == this.mobileBreakpoint ) || ( this._isMedium() && 'medium-mobile' == this.mobileBreakpoint ) || ( this._isLarge() && 'large-mobile' == this.mobileBreakpoint ) ) && ( $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ).is(':visible') || 'expanded' == this.mobileToggle ) ) { return true; } return false; }, /** * Initialize the toggle logic for the menu. * * @see this._isMenuToggle() * @see this._menuOnCLick() * @see this._clickOrHover() * @see this._submenuOnRight() * @see this._submenuRowZindexFix() * @see this._toggleForMobile() * @since 1.6.1 * @return void */ _initMenu: function(){ this._setupSubmenu(); this._menuOnFocus(); this._menuOnEscape(); this._submenuOnClick(); if ( $( this.nodeClass ).length && this.type == 'horizontal' ) { this._initMegaMenus(); } if( this._isMenuToggle() || this.type == 'accordion' ){ $( this.wrapperClass ).off( 'mouseenter mouseleave' ); this._menuOnClick(); this._clickOrHover(); } else { $( this.wrapperClass ).off( 'click' ); this._submenuOnRight(); this._submenuRowZindexFix(); } if( this.mobileToggle != 'expanded' ){ this._toggleForMobile(); } if( $( this.wrapperClass ).find( '.fl-menu-search-item' ).length ){ this._toggleMenuSearch(); } if( $( this.wrapperClass ).find( '.fl-menu-cart-item').length ){ this._wooUpdateParams(); } }, /** * Initializes submenu dropdowns. * * @since 3.0 * @return void */ _setupSubmenu: function() { $( this.wrapperClass + ' ul.sub-menu' ).each( function(){ $( this ).closest( 'li' ).attr( 'aria-haspopup', 'true' ); }); }, /** * Adds a focus class to menu elements similar to be used similar to CSS :hover psuedo event * * @since 1.9.0 * @return void */ _menuOnFocus: function(){ var cKey = 0, isShifted = false; $( this.nodeClass ).off('keydown').on( 'keydown', 'a', function( e ){ cKey = e.which; isShifted = e.shiftKey; }); $( this.nodeClass ).off('focus').on( 'focus', 'a', $.proxy( function( e ){ var $menuItem = $( e.target ).parents( '.menu-item' ).first(), $parents = $( e.target ).parentsUntil( this.wrapperClass ); $('.fl-menu .focus').removeClass('focus'); $parents.addClass('focus') if ( ! $menuItem.closest('.fl-has-submenu').hasClass('escaped') ) { $menuItem.addClass('focus') } else { $menuItem.closest('.fl-has-submenu').removeClass('focus escaped') } }, this ) ).on( 'focusout', 'a', $.proxy( function( e ){ var el = $(e.target).parent(), $megaMenu = el.closest( '.mega-menu' ), $megaLastItem = $megaMenu.find('> .sub-menu > .menu-item:last-child'), $lastItem = el.parents('.fl-has-submenu:last').find('.menu-item:last'), isLastChild = ! $megaMenu.length && el.is( $lastItem ); if( $megaMenu.length ) { isLastChild = el.is( $megaLastItem ) || el.is( $megaLastItem.find( '.menu-item:last-child' ) ); } if ( isLastChild && cKey === 9 && isShifted ) { isLastChild = false; cKey = 0; isShifted = false; } else if ( cKey === 27 ) { isLastChild = false; } if ( isLastChild ) { $( e.target ).parentsUntil( this.wrapperClass ).removeClass( 'focus' ); } }, this ) ); }, /** * Logic for submenu items when Escape key is pressed. * * @since 2.7.1 * @return void */ _menuOnEscape: function(){ $( 'body' ).off('keydown').on( 'keydown', $.proxy( function( e ){ if ( e.which !== 27 ) { return; } if ( $( e.target ).closest('.menu-item').length ) { var activeSubmenu = null, menuItem = $( e.target ).closest('.menu-item'), type = menuItem.closest('.fl-menu-accordion').length ? 'accordion' : 'horizontal'; if ( 'horizontal' === type ) { if ( menuItem.hasClass( 'fl-has-submenu' ) && menuItem.hasClass( 'focus' ) ) { activeSubmenu = menuItem.find('> ul.sub-menu'); } else { activeSubmenu = menuItem.closest('ul.sub-menu'); } activeSubmenu.parent().addClass('escaped'); activeSubmenu.parent().find('a:first').focus(); } else { if ( menuItem.hasClass( 'fl-has-submenu' ) && 'accordion' === type && menuItem.hasClass( 'fl-active' ) ) { activeSubmenu = menuItem.find('> ul.sub-menu'); } else { activeSubmenu = menuItem.closest('ul.sub-menu'); } activeSubmenu.slideUp(400, function(){ if ( menuItem.hasClass( 'fl-has-submenu' ) && menuItem.hasClass( 'fl-active' ) ) { activeSubmenu.parent().last().find('a:first').focus(); } else { menuItem.removeClass('focus'); menuItem.parents('.menu-item').first().find('a:first').focus(); } activeSubmenu.parent().last().removeClass( 'fl-active' ); }); } } else { $('.fl-menu').find( 'li.menu-item.focus' ).last().removeClass('focus'); } }, this ) ); }, /** * Logic for submenu toggling on accordions or mobile menus (vertical, horizontal) * * @since 1.6.1 * @return void */ _menuOnClick: function(){ $( this.wrapperClass ).off().on( 'click', '.fl-has-submenu-container', $.proxy( function( e ){ var $link = $( e.target ).parents( '.fl-has-submenu' ).first(), $subMenu = $link.children( '.sub-menu' ).first(), $href = $link.children('.fl-has-submenu-container').first().find('> a').attr('href'), $subMenuParents = $( e.target ).parents( '.sub-menu' ), $activeParents = $( e.target ).parents( '.fl-has-submenu.fl-active' ); if( !$subMenu.is(':visible') || $(e.target).hasClass('fl-menu-toggle') || ($subMenu.is(':visible') && (typeof $href === 'undefined' || $href == '#')) ){ e.preventDefault(); } else { e.stopPropagation(); window.location.href = $href; return; } if ($(this.wrapperClass).hasClass('fl-menu-accordion-collapse')) { if ( !$link.parents('.menu-item').hasClass('fl-active') ) { $('.menu .fl-active', this.wrapperClass).not($link).removeClass('fl-active'); } else if ($link.parents('.menu-item').hasClass('fl-active') && $link.parent('.sub-menu').length) { $('.menu .fl-active', this.wrapperClass).not($link).not($activeParents).removeClass('fl-active'); } $('.sub-menu', this.wrapperClass).not($subMenu).not($subMenuParents).slideUp('normal'); } if ( ! this.mobileStacked && 'horizontal' == this.type && 'expanded' == this.mobileToggle ) { $( this.wrapperClass ).find( '.fl-active' ).not($link).not($activeParents).removeClass( 'fl-active' ); } else { $subMenu.slideToggle(); } $link.toggleClass( 'fl-active' ); e.stopPropagation(); }, this ) ); }, /** * Logic for submenu items click event * * @since 1.10.6 * @return void */ _submenuOnClick: function(){ $( this.wrapperClass + ' .sub-menu' ).off().on( 'click', 'a', $.proxy( function( e ){ if ( $( e.target ).parent().hasClass('focus') ) { $( e.target ).parentsUntil( this.wrapperClass ).removeClass('focus'); } }, this ) ); }, /** * Changes general styling and behavior of menus based on mobile / desktop viewport. * * @see this._isMenuToggle() * @since 1.6.1 * @return void */ _clickOrHover: function(){ this.$submenus = this.$submenus || $( this.wrapperClass ).find( '.sub-menu' ); var $wrapper = $( this.wrapperClass ), $menu = $wrapper.find( '.menu' ); $li = $wrapper.find( '.fl-has-submenu' ); if( this._isMenuToggle() ){ $li.each( function( el ){ if( !$(this).hasClass('fl-active') ){ $(this).find( '.sub-menu' ).fadeOut(); } } ); } else { $li.each( function( el ){ if( !$(this).hasClass('fl-active') ){ $(this).find( '.sub-menu' ).css( { 'display' : '', 'opacity' : '' } ); } } ); } }, /** * Logic to prevent submenus to go outside viewport boundaries. * * @since 1.6.1 * @return void */ _submenuOnRight: function(){ $( this.wrapperClass ) .on( 'mouseenter focus', '.fl-has-submenu', $.proxy( function( e ){ if( $ ( e.currentTarget ).find('.sub-menu').length === 0 ) { return; } var $link = $( e.currentTarget ), $parent = $link.parent(), $subMenu = $link.find( '.sub-menu' ), subMenuWidth = $subMenu.width(), subMenuPos = 0, bodyWidth = $( 'body' ).width(); if( $link.closest( '.fl-menu-submenu-right' ).length !== 0) { $link.addClass( 'fl-menu-submenu-right' ); } else if( $( 'body' ).hasClass( 'rtl' ) ) { subMenuPos = $parent.is( '.sub-menu' ) ? $parent.offset().left - subMenuWidth: $link.offset().left - $link.width() - subMenuWidth; if( subMenuPos <= 0 ) { $link.addClass( 'fl-menu-submenu-right' ); } } else { subMenuPos = $parent.is( '.sub-menu' ) ? $parent.offset().left + $parent.width() + subMenuWidth : $link.offset().left + $link.width() + subMenuWidth; if( subMenuPos > bodyWidth ) { $link.addClass('fl-menu-submenu-right'); } } }, this ) ) .on( 'mouseleave', '.fl-has-submenu', $.proxy( function( e ){ $( e.currentTarget ).removeClass( 'fl-menu-submenu-right' ); }, this ) ); }, /** * Logic to prevent submenus to go behind the next overlay row. * * @since 1.10.9 * @return void */ _submenuRowZindexFix: function( e ){ $( this.wrapperClass ) .on( 'mouseenter', 'ul.menu > .fl-has-submenu', $.proxy( function( e ){ if( $ ( e.currentTarget ).find('.sub-menu').length === 0 ) { return; } $( this.nodeClass ) .closest( '.fl-row' ) .find( '.fl-row-content' ) .css( 'z-index', '10' ); }, this ) ) .on( 'mouseleave', 'ul.menu > .fl-has-submenu', $.proxy( function( e ){ $( this.nodeClass ) .closest( '.fl-row' ) .find( '.fl-row-content' ) .css( 'z-index', '' ); }, this ) ); }, /** * Logic for the mobile menu button. * * @since 1.6.1 * @return void */ _toggleForMobile: function(){ var $wrapper = null, $menu = null, self = this; if( this._isMenuToggle() ){ if ( this._isMobileBelowRowEnabled() ) { this._placeMobileMenuBelowRow(); $wrapper = $( this.wrapperClass ); $menu = $( this.nodeClass + '-clone' ); $menu.find( 'ul.menu' ).show(); } else { $wrapper = $( this.wrapperClass ); $menu = $wrapper.find( '.menu' ); } if( !$wrapper.find( '.fl-menu-mobile-toggle' ).hasClass( 'fl-active' ) && ! self.mobileFlyout ){ $menu.css({ display: 'none' }); } // Flayout Menu if ( self.mobileFlyout ) { this._initFlyoutMenu(); } $wrapper.on( 'click', '.fl-menu-mobile-toggle', function( e ){ $( this ).toggleClass( 'fl-active' ); if ( self.mobileFlyout ) { self._toggleFlyoutMenu(); } else { var targetMenu = null; if ( self.mobileBelowRow ) { targetMenu = $( this ).closest( '.fl-col' ).next( '.fl-menu-mobile-clone' ); } else { targetMenu = $( this ).closest( '.fl-menu' ).find( 'ul.menu' ); } if ( targetMenu.length ) { $menu = $( targetMenu ); } $menu.slideToggle(); } e.stopPropagation(); } ); // Hide active menu when click on anchor link ID that exists on a page. $menu.off().on( 'click', '.menu-item > a[href*="#"]:not([href="#"])', function(e){ var $href = $(this).attr('href'), $targetID = $href.split('#')[1], element = $('#' + $targetID); if ( $('body').find(element).length > 0 ) { $( this ).toggleClass( 'fl-active' ); FLBuilderLayout._scrollToElement( element ); if ( ! self._isMenuToggle() ) { $menu.slideToggle(); } } }); } else { if ( this._isMobileBelowRowEnabled() ) { this._removeMenuFromBelowRow(); } $wrapper = $( this.wrapperClass ), $menu = $wrapper.find( 'ul.menu' ); $wrapper.find( '.fl-menu-mobile-toggle' ).removeClass( 'fl-active' ); $menu.css({ display: '' }); if ( ! this._isMobileBelowRowEnabled() ) { $menu.off( 'click', '.menu-item > a[href*="#"]:not([href="#"])' ); } if ( this.mobileFlyout && $wrapper.find( '.fl-menu-mobile-flyout' ).length > 0 ) { $( 'body' ).css( 'margin', '' ); $( '.fl-builder-ui-pinned-content-transform' ).css( 'transform', '' ); $menu.unwrap(); $wrapper.find( '.fl-menu-mobile-close' ).remove(); $wrapper.find( '.fl-menu-mobile-opacity' ).remove(); } } }, /** * Init any mega menus that exist. * * @see this._isMenuToggle() * @since 1.10.4 * @return void */ _initMegaMenus: function(){ var module = $( this.nodeClass ), rowContent = module.closest( '.fl-row-content' ), rowWidth = rowContent.width(), megas = module.find( '.mega-menu' ), disabled = module.find( '.mega-menu-disabled' ), isToggle = this._isMenuToggle(); if ( isToggle ) { megas.removeClass( 'mega-menu' ).addClass( 'mega-menu-disabled' ); module.find( 'li.mega-menu-disabled > ul.sub-menu' ).css( 'width', '' ); rowContent.css( 'position', '' ); } else { disabled.removeClass( 'mega-menu-disabled' ).addClass( 'mega-menu' ); module.find( 'li.mega-menu > ul.sub-menu' ).css( 'width', rowWidth + 'px' ); rowContent.css( 'position', 'relative' ); } }, /** * Check to see if Below Row should be enabled. * * @since 1.11 * @return boolean */ _isMobileBelowRowEnabled: function() { return this.mobileBelowRow && $( this.nodeClass ).closest( '.fl-col' ).length; }, /** * Logic for putting the mobile menu below the menu's * column so it spans the full width of the page. * * @since 1.10 * @return void */ _placeMobileMenuBelowRow: function(){ if ( $( this.nodeClass + '-clone' ).length ) { return; } var module = $( this.nodeClass ), clone = null, col = module.closest( '.fl-col' ); if ( module.length < 1 ) { return; } clone = ( module.length > 1 ) ? $( module[0] ).clone() : module.clone(); module.find( 'ul.menu' ).remove(); clone.addClass( ( this.nodeClass + '-clone' ).replace( '.', '' ) ); clone.addClass( 'fl-menu-mobile-clone' ); clone.find( '.fl-menu-mobile-toggle' ).remove(); col.after( clone ); // Removes animation when enabled. if ( module.hasClass( 'fl-animation' ) ) { clone.removeClass( 'fl-animation' ); } this._menuOnClick(); }, /** * Logic for removing the mobile menu from below the menu's * column and putting it back in the main wrapper. * * @since 1.10 * @return void */ _removeMenuFromBelowRow: function(){ if ( ! $( this.nodeClass + '-clone' ).length ) { return; } var module = $( this.nodeClass ), clone = $( this.nodeClass + '-clone' ), menu = clone.find( 'ul.menu' ); module.find( '.fl-menu-mobile-toggle' ).after( menu ); clone.remove(); menu.find( 'a' ).each( FLBuilderLayout._initAnchorLink ); }, /** * Logic for Flyout responsive menu. * * @since 2.2 * @return void */ _initFlyoutMenu: function(){ var win = $( window ), wrapper = $( this.wrapperClass ), menu = wrapper.find( 'ul.menu' ), button = wrapper.find( '.fl-menu-mobile-toggle' ); if ( 0 === wrapper.find( '.fl-menu-mobile-flyout' ).length ) { menu.wrap( '
' ); } if ( 0 === wrapper.find( '.fl-menu-mobile-close' ).length ) { close = window.fl_responsive_close || 'Close' wrapper.find( '.fl-menu-mobile-flyout' ) .prepend( '' ); } // Push with opacity if ( wrapper.hasClass( 'fl-menu-responsive-flyout-push-opacity' ) && 0 === wrapper.find( '.fl-menu-mobile-opacity' ).length ) { wrapper.append( '' ); } wrapper.on( 'click', '.fl-menu-mobile-opacity, .fl-menu-mobile-close', function(e){ button.trigger( 'click' ); e.stopPropagation(); }); if ( 'undefined' !== typeof FLBuilder ) { FLBuilder.addHook('restartEditingSession', function(){ $( '.fl-builder-ui-pinned-content-transform' ).css( 'transform', '' ); // Toggle active menu. if ( button.hasClass( 'fl-active' ) ) { button.trigger( 'click' ); } }); } }, /** * Logic to enable/disable the Flyout menu on button click. * * @since 2.2 * @return void */ _toggleFlyoutMenu: function(){ var wrapper = $( this.wrapperClass ), button = wrapper.find( '.fl-menu-mobile-toggle' ), wrapFlyout = wrapper.find( '.fl-menu-mobile-flyout' ), position = wrapper.hasClass( 'fl-flyout-right' ) ? 'right' : 'left', pushMenu = wrapper.hasClass( 'fl-menu-responsive-flyout-push' ) || wrapper.hasClass( 'fl-menu-responsive-flyout-push-opacity' ), opacity = wrapper.find( '.fl-menu-mobile-opacity' ), marginPos = {}, posAttr = {}, fixedPos = {}, winHeight = $(window).height(), fixedHeader = $('header, header > div'); if ( button.hasClass( 'fl-active' ) ) { posAttr[ position ] = '0px'; posAttr[ 'height' ] = winHeight + 'px'; } else { posAttr[ position ] = '-267px'; } wrapFlyout.css( posAttr ); // Fix the push menu when builder ui panel is pinned. if ( $( '.fl-builder-ui-pinned-content-transform' ).length > 0 && ! $( 'body' ).hasClass( 'fl-builder-edit' ) ) { $( '.fl-builder-ui-pinned-content-transform' ).css( 'transform', 'none' ); } if ( pushMenu ) { marginPos[ 'margin-' + position ] = button.hasClass( 'fl-active' ) ? '250px' : '0px'; $( 'body' ).animate( marginPos, 200); // Fixed header if ( fixedHeader.length > 0 ) { fixedPos[ position] = button.hasClass( 'fl-active' ) ? '250px' : '0px'; fixedHeader.each(function(){ if ( 'fixed' == $( this ).css( 'position' ) ) { $( this ).css({ '-webkit-transition': 'none', '-o-transition' : 'none', 'transition' : 'none' }); $( this ).animate( fixedPos, 200 ); } }); } } if ( opacity.length > 0 && button.hasClass( 'fl-active' ) ) { opacity.show(); } else { opacity.hide(); } }, /** * Shows or hides the nav search form. * * @since 2.5 * @method _toggleMenuSearch */ _toggleMenuSearch: function(){ var wrapper = $( this.wrapperClass ).find('.fl-menu-search-item'), button = wrapper.find('a.fl-button'), form = wrapper.find('.fl-search-form-input-wrap'), self = this; button.on('click', function(e){ e.preventDefault(); if(form.is(':visible')) { form.stop().fadeOut(200); } else { form.stop().fadeIn(200); $('body').on('click.fl-menu-search', $.proxy(self._hideMenuSearch, self)); form.find('.fl-search-text').focus(); } }); }, /** * Hides the nav search form. * * @since 2.5 * @method _hideMenuSearch */ _hideMenuSearch: function(e){ var form = $( this.wrapperClass ).find('.fl-search-form-input-wrap'); if(e !== undefined) { if($(e.target).closest('.fl-menu-search-item').length > 0) { return; } } form.stop().fadeOut(200); $('body').off('click.fl-menu-search'); }, /** * Adds menu node and post ID to WooCommerce ajax URL requests. * * @since 3.0 * @return void */ _wooUpdateParams: function() { if ( 'undefined' !== typeof wc_cart_fragments_params ) { wc_cart_fragments_params.wc_ajax_url += '&fl-menu-node='+ this.nodeId +'&post-id='+ this.postId; } if ( 'undefined' !== typeof wc_add_to_cart_params ) { wc_add_to_cart_params.wc_ajax_url += '&fl-menu-node='+ this.nodeId +'&post-id='+ this.postId; } }, }; })(jQuery);