1 2;/* eslint-disable no-undef */ 3(function($) { 4 5 /** 6 * Class for Menu Module 7 * 8 * @since 1.6.1 9 */ 10 FLBuilderMenu = function( settings ){ 11 12 // set params 13 this.nodeId = settings.id; 14 this.nodeClass = '.fl-node-' + settings.id; 15 this.wrapperClass = this.nodeClass + ' .fl-menu'; 16 this.type = settings.type; 17 this.mobileToggle = settings.mobile; 18 this.mobileBelowRow = settings.mobileBelowRow; 19 this.mobileFlyout = settings.mobileFlyout; 20 this.breakPoints = settings.breakPoints; 21 this.mobileBreakpoint = settings.mobileBreakpoint; 22 this.currentBrowserWidth = $( window ).width(); 23 this.postId = settings.postId; 24 this.mobileStacked = settings.mobileStacked; 25 this.submenuIcon = settings.submenuIcon; 26 this.flyoutWidth = settings.flyoutWidth; 27 28 // initialize the menu 29 this._initMenu(); 30 31 // check if viewport is resizing 32 $( window ).on( 'resize', $.proxy( function() { 33 34 var width = $( window ).width(); 35 36 // if screen width is resized, reload the menu 37 if( width != this.currentBrowserWidth ){ 38 39 this.currentBrowserWidth = width; 40 this._initMenu( true ); 41 this._clickOrHover( true ); 42 } 43 44 this._resizeFlyoutMenuPanel(); 45 }, this ) ); 46 47 $( window ).on( 'scroll', $.proxy( function() { 48 this._resizeFlyoutMenuPanel(); 49 }, this ) ); 50 51 // Move focus to the first menu item if the layout is set to BelowRow 52 $( this.wrapperClass ).on( 'keydown', '.fl-menu-mobile-toggle, .menu-item:first a:first', $.proxy( function ( event ) { 53 // Make sure it is a tab key with both the menu toggle enabled & below row layout set 54 if ( event.key !== 'Tab' || ! this._isMenuToggle() || ! this._isMobileBelowRowEnabled() ) { 55 return; 56 } 57 // Trigger focus on the mobile toggle button if the user is shift tabbing from the first menu item 58 if ( event.shiftKey && $( event.currentTarget ).is( 'a' ) ) { 59 event.preventDefault(); 60 $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ).trigger( 'focus' ); 61 } 62 // Trigger focus on the first menu item if the user is tabbing from the mobile toggle button 63 else if ( ! event.shiftKey && $( event.currentTarget ).hasClass( 'fl-menu-mobile-toggle' ) && $( event.currentTarget ).hasClass( 'fl-active' ) ) { 64 event.preventDefault(); 65 $( this.wrapperClass ).find( '.menu-item' ).first().find( 'a' ).first().trigger( 'focus' ); 66 } 67 }, this ) ); 68 69 // Close mobile menu or accordion when tabbing out from the first or last menu item 70 $( this.wrapperClass ).on( 'focusout', $.proxy( function ( event ) { 71 if ( $( this.wrapperClass + ' nav' ).has( $( event.relatedTarget ) ).length === 0 ) { 72 if ( this.type === 'accordion' ) { 73 this._toggleSubmenu( $( event.target ).parents( '.fl-has-submenu' ).last(), false ); 74 } 75 else { 76 this._clickOrHover( true ); 77 } 78 const mobileToggle = $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ); 79 if ( this._isMenuToggle() && mobileToggle.hasClass( 'fl-active' ) && ! $( event.relatedTarget ).is( mobileToggle ) && 'expanded' !== this.mobileToggle ) { 80 mobileToggle.trigger( 'click' ); 81 } 82 } 83 }, this ) ); 84 85 }; 86 87 FLBuilderMenu.prototype = { 88 nodeClass : '', 89 wrapperClass: '', 90 type : '', 91 breakPoints : {}, 92 $submenus : null, 93 94 /** 95 * Check if the screen size fits a mobile viewport. 96 * 97 * @since 1.6.1 98 * @return bool 99 */ 100 _isMobile: function(){ 101 return this.currentBrowserWidth <= this.breakPoints.small ? true : false; 102 }, 103 104 /** 105 * Check if the device is connected to a mouse. 106 * 107 * @since 2.9.0 108 * @return bool 109 */ 110 _isMouseAvailable: function(){ 111 return window.matchMedia("(pointer: fine) and (hover: hover)").matches; 112 }, 113 114 /** 115 * Check if the screen size fits a medium viewport. 116 * 117 * @since 1.10.5 118 * @return bool 119 */ 120 _isMedium: function(){ 121 return this.currentBrowserWidth <= this.breakPoints.medium ? true : false; 122 }, 123 124 /** 125 * Check if the screen size fits a large viewport. 126 * 127 * @since 1.10.5 128 * @return bool 129 */ 130 _isLarge: function(){ 131 return this.currentBrowserWidth <= this.breakPoints.large ? true : false;
132 }, 133 134 /** 135 * Check if the menu should toggle for the current viewport base on the selected breakpoint. 136 * 137 * @see this._isMobile() 138 * @see this._isMedium() 139 * @since 1.10.5 140 * @return bool 141 */ 142 _isMenuToggle: function(){ 143 if ( ( 'always' == this.mobileBreakpoint 144 || ( this._isMobile() && 'mobile' == this.mobileBreakpoint ) 145 || ( this._isMedium() && 'medium-mobile' == this.mobileBreakpoint ) 146 || ( this._isLarge() && 'large-mobile' == this.mobileBreakpoint ) 147 ) && ( $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ).is(':visible') || 'expanded' == this.mobileToggle ) ) { 148 return true; 149 } 150 151 return false; 152 }, 153 154 /** 155 * Initialize the toggle logic for the menu. 156 * 157 * @see this._isMenuToggle() 158 * @see this._menuOnCLick() 159 * @see this._clickOrHover() 160 * @see this._submenuOnRight() 161 * @see this._submenuRowZindexFix() 162 * @see this._toggleForMobile() 163 * @since 1.6.1 164 * @return void 165 */ 166 _initMenu: function( resized = false ){ 167 this._setupSubmenu(); 168 if ( ! resized ) { 169 this._menuOnFocus(); 170 this._menuOnClick(); 171 this._menuOnEscape(); 172 } 173 if ( $( this.nodeClass ).length && this.type == 'horizontal' ) { 174 this._initMegaMenus(); 175 } 176 177 if( this._isMenuToggle() || this.type == 'accordion' ){ 178 179 $( this.wrapperClass ).off( 'mouseenter mouseleave' ); 180 this._clickOrHover(); 181 182 } else { 183 this._submenuOnRight(); 184 this._submenuRowZindexFix(); 185 } 186 187 if( this.mobileToggle != 'expanded' ){ 188 this._toggleForMobile(); 189 } 190 191 if( $( this.wrapperClass ).find( '.fl-menu-search-item' ).length ){ 192 this._toggleMenuSearch(); 193 } 194 195 if( $( this.wrapperClass ).find( '.fl-menu-cart-item').length ){ 196 this._wooUpdateParams(); 197 } 198 }, 199 200 /** 201 * Whether the toggle behavior for submenus is through hovering or not. 202 * 203 * @since 2.5 204 * @return void 205 */ 206 _setupSubmenu: function() { 207 if ( ! this._isMouseAvailable() || this._isMenuToggle() || this.type === 'accordion' ) { 208 $( this.wrapperClass ).addClass( 'no-hover' ); 209 } 210 else { 211 $( this.wrapperClass ).removeClass( 'no-hover' ); 212 } 213 }, 214 215 /** 216 * Logic for menu items when focused. 217 * 218 * @since 1.9.0 219 * @return void 220 */ 221 _menuOnFocus: function(){ 222 $( this.wrapperClass ).on( 'focus', 'a:not(.fl-button), .fl-menu-toggle', $.proxy( function( event ) { 223 const focusedMenuItem = $( event.currentTarget ).closest( '.menu-item' ); 224 const blurredMenuItem = $( event.relatedTarget ).closest( '.menu-item' ); 225 // In case the blurred & focused items are siblings 226 if ( focusedMenuItem.closest( 'ul' ).is( blurredMenuItem.closest( 'ul' ) ) ) { 227 // Check if the focus event is not between the link & submenu icon of the same menu item 228 if ( ! focusedMenuItem.is( blurredMenuItem ) ) { 229 this._toggleSubmenu( blurredMenuItem, false ); 230 } 231 } 232 // In case neither items are child & parent nor vice versa 233 else if ( focusedMenuItem.has( blurredMenuItem ).length === blurredMenuItem.has( focusedMenuItem ).length ) { 234 // Get the blurred item parent & is the sibling of the focused item 235 blurredMenuItem.parents( '.fl-has-submenu' ).each( ( _, parent ) => { 236 if ( focusedMenuItem.closest( 'ul' ).is( $( parent ).closest( 'ul' ) ) ) { 237 this._toggleSubmenu( $( parent ), false ); 238 return false; 239 } 240 } ); 241 } 242 // In case the blurred item is a child of the focused item 243 else if ( focusedMenuItem.has( blurredMenuItem ).length ) { 244 if ( this.type !== 'accordion' && this.submenuIcon === 'none' ) { 245 this._toggleSubmenu( blurredMenuItem, false ); 246 } 247 } 248 }, this ) ); 249 }, 250 251 /** 252 * Logic for menu items when Escape key is pressed. 253 * 254 * @since 2.7.1 255 * @return void 256 */ 257 _menuOnEscape: function(){ 258 $( this.wrapperClass ).on( 'keydown', $.proxy( function( event ){ 259 if ( event.key !== 'Escape' ) return; 260 const menuItem = $( event.target ).closest( '.menu-item' ); 261 const mobileToggle = $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ); 262 // In case there is a focused menu item 263 if ( menuItem.length || $( event.target ).hasClass( 'fl-menu-mobile-close' ) ) { 264 // Close the search form if it is open 265 if ( menuItem.hasClass( 'fl-menu-search-item' ) && $( this.wrapperClass ).find('.fl-search-form-input-wrap').is( ':visible' ) ) { 266 $( this.wrapperClass ).find('.fl-menu-search-item .fl-button:is(a, button:not([type="submit"]))').trigger( 'focus' ); 267 } 268 // Close all the submenus of the focused menu item 269 else if ( menuItem.hasClass( 'fl-has-submenu' ) && menuItem.find( '.sub-menu' ).first().is( ':visible' ) ) { 270 this._toggleSubmenu( menuItem, false ); 271 } 272 else { 273 // Shift focus to the parent while closing all its submenus 274 const parentMenuItem = menuItem.parents( '.fl-has-submenu' ).not( '.mega-menu.hide-heading, .mega-menu-disabled.hide-heading' ).first(); 275 // Close the mobile menu if there is no parent menu item and the mobile toggle is active 276 if ( parentMenuItem.length === 0 && this._isMenuToggle() && mobileToggle.hasClass( 'fl-active' ) ) { 277 // Blur to trigger the focus-out event for closing the menu 278 mobileToggle.trigger( 'blur' ).trigger( 'focus' ); 279 } 280 // Close the parent submenu and shift focus to its link 281 else if ( parentMenuItem.length !== 0 ) { 282 this._toggleSubmenu( parentMenuItem, false ); 283 parentMenuItem.find( 'a' ).first().trigger( 'focus' ); 284 } 285 } 286 } 287 // Close the mobile menu if the mobile toggle is active 288 else if ( $( event.target ).hasClass( 'fl-menu-mobile-toggle' ) && mobileToggle.hasClass( 'fl-active' ) ) { 289 // Blur to trigger the focus-out event for closing the menu 290 mobileToggle.trigger( 'blur' ).trigger( 'focus' ); 291 } 292 }, this ) ); 293 }, 294 295 /** 296 * Logic for menu items when clicked. 297 * 298 * @since 1.6.1 299 * @return void 300 */ 301 _menuOnClick: function(){ 302 // Fallback for span elements and fake links to interact with the keyboard 303 $( this.wrapperClass ).on( 'keydown', 'a:not(.fl-button, [href]), span.fl-menu-toggle', $.proxy( function( event ) { 304 if ( event.key === 'Enter' || event.key === ' ' ) { 305 event.preventDefault(); 306 $( event.currentTarget ).trigger( 'click' ); 307 } 308 }, this ) ); 309 $( this.wrapperClass ).on( 'click', 'a, .fl-menu-toggle', $.proxy( function( event ) { 310 // Only allow mouse clicks with accordion & mobile menus 311 if ( this._isMouseAvailable() && ! this._isMenuToggle() && event.detail && this.type !== 'accordion' ) return; 312 // Links only open & do not toggle submenus if there is either a submenu icon or an accordion layout 313 if ( $( event.currentTarget ).is( 'a' ) && ( this.submenuIcon !== 'none' || this.type === 'accordion' ) ) return; 314 const menuItem = $( event.currentTarget ).closest( '.menu-item, .fl-menu-logo' ); 315 const menuLink = menuItem.find( 'a' ).first().attr( 'href' ); 316 const submenuHidden = menuItem.find( '.sub-menu' ).first().is( ':hidden' ); 317 if ( typeof menuLink === 'undefined' || menuLink === '#' || submenuHidden ) { 318 event.preventDefault(); 319 } 320 if ( $( event.currentTarget ).hasClass( 'fl-menu-toggle' ) || submenuHidden ) { 321 this._toggleSubmenu( menuItem, submenuHidden ); 322 } 323 }, this ) ); 324 }, 325 326 /** 327 * Controls toggling the visibility of all submenu items for desktop & mobile. 328 * 329 * @since 2.10 330 * @return void 331 */ 332 _toggleSubmenu: function ( menuItem, opened ) { 333 const togglingClass = this._isMenuToggle() || this.type === 'accordion' ? 'fl-active' : 'focus'; 334 const toggleElement = this.submenuIcon === 'none' ? 'a' : '.fl-menu-toggle'; 335 const hiddenMenu = '.mega-menu.hide-heading, .mega-menu-disabled.hide-heading'; 336 if ( opened && menuItem.hasClass( 'fl-has-submenu' ) && ! menuItem.is( hiddenMenu ) ) { 337 menuItem.addClass( togglingClass ); 338 menuItem.find( toggleElement ).first().attr( 'aria-expanded', true ); 339 if ( this._isMenuToggle() || this.type === 'accordion' ) { 340 menuItem.find( '.sub-menu' ).first().filter( ':hidden' ).slideDown(); 341 } 342 } 343 else { 344 menuItem.parent().find( '.menu-item' ).removeClass( togglingClass ); 345 menuItem.parent().find( '.fl-has-submenu' ).not( hiddenMenu ).find( toggleElement ).attr( 'aria-expanded', false ); 346 if ( this._isMenuToggle() || this.type === 'accordion' ) {
347 menuItem.find( '.sub-menu' ).filter( ':visible' ).slideUp(); 348 } 349 } 350 }, 351 352 /** 353 * Changes general styling and behavior of menus based on mobile / desktop viewport. 354 * 355 * @see this._isMenuToggle() 356 * @since 1.6.1 357 * @return void 358 */ 359 _clickOrHover: function( clear = false ){ 360 const selector = this._isMobileBelowRowEnabled() ? this.nodeClass + '-clone' : this.nodeClass; 361 this.$submenus = this.$submenus || $( selector ).find( '.sub-menu' ); 362 363 const className = this._isMenuToggle() || this.type === 'accordion' ? 'fl-active' : 'focus'; 364 const toggleElement = this.submenuIcon === 'none' ? 'a' : '.fl-menu-toggle'; 365 const hiddenMenu = '.mega-menu.hide-heading, .mega-menu-disabled.hide-heading'; 366 $( selector ).find( '.fl-has-submenu' ).not( hiddenMenu ).each( function() { 367 if( clear || ! $ ( this ).hasClass( className ) ){ 368 if ( clear ) $( this ).removeClass( className ); 369 $( this ).find( toggleElement ).first().attr( 'aria-expanded', false ); 370 if ( className === 'fl-active' ) { 371 $( this ).find( '.sub-menu' ).fadeOut(); 372 } else if ( className === 'focus' ) { 373 $( this ).find( '.sub-menu' ).css( { 374 'display' : '', 375 'opacity' : '' 376 } ); 377 } 378 } 379 } ); 380 }, 381 382 /** 383 * Logic to prevent submenus to go outside viewport boundaries. 384 * 385 * @since 1.6.1 386 * @return void 387 */ 388 _submenuOnRight: function(){ 389 390 $( this.wrapperClass ) 391 .on( 'mouseenter focus', '.fl-has-submenu', $.proxy( function( e ){ 392 393 if( $ ( e.currentTarget ).find('.sub-menu').length === 0 ) { 394 return; 395 } 396 397 var $link = $( e.currentTarget ), 398 $parent = $link.parent(), 399 $subMenu = $link.find( '.sub-menu' ), 400 subMenuWidth = $subMenu.width(), 401 subMenuPos = 0, 402 bodyWidth = $( 'body' ).width(); 403 404 if( $link.closest( '.fl-menu-submenu-right' ).length !== 0) { 405 406 $link.addClass( 'fl-menu-submenu-right' ); 407 408 } else if( $( 'body' ).hasClass( 'rtl' ) ) { 409 410 subMenuPos = $parent.is( '.sub-menu' ) ? 411 $parent.offset().left - subMenuWidth: 412 $link.offset().left - $link.width() - subMenuWidth; 413 414 if( subMenuPos <= 0 ) { 415 $link.addClass( 'fl-menu-submenu-right' ); 416 } 417 418 } else { 419 420 subMenuPos = $parent.is( '.sub-menu' ) ? 421 $parent.offset().left + $parent.width() + subMenuWidth : 422 $link.offset().left + $link.width() + subMenuWidth; 423 424 if( subMenuPos > bodyWidth ) { 425 $link.addClass('fl-menu-submenu-right'); 426 } 427 } 428 }, this ) ) 429 .on( 'mouseleave', '.fl-has-submenu', $.proxy( function( e ){ 430 $( e.currentTarget ).removeClass( 'fl-menu-submenu-right' ); 431 }, this ) ); 432 433 }, 434 435 /** 436 * Logic to prevent submenus to go behind the next overlay row. 437 * 438 * @since 1.10.9 439 * @return void 440 */ 441 _submenuRowZindexFix: function(){ 442 443 $( this.wrapperClass ) 444 .on( 'mouseenter', 'ul.menu > .fl-has-submenu', $.proxy( function( e ){ 445 446 if( $ ( e.currentTarget ).find('.sub-menu').length === 0 ) { 447 return; 448 } 449 450 $( this.nodeClass ) 451 .closest( '.fl-row' ) 452 .find( '.fl-row-content' ) 453 .css( 'z-index', '10' ); 454 455 }, this ) ) 456 .on( 'mouseleave', 'ul.menu > .fl-has-submenu', $.proxy( function(){ 457 458 $( this.nodeClass ) 459 .closest( '.fl-row' ) 460 .find( '.fl-row-content' ) 461 .css( 'z-index', '' ); 462 463 }, this ) ); 464 }, 465 466 /** 467 * Logic for the mobile menu button. 468 * 469 * @since 1.6.1 470 * @return void 471 */ 472 _toggleForMobile: function(){ 473 474 var $wrapper = null, 475 $menu = null, 476 self = this; 477 478 $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ).attr('aria-controls', $( this.wrapperClass ).find( 'ul.menu' ).attr('id')); 479 480 if( this._isMenuToggle() ){ 481 482 if ( this._isMobileBelowRowEnabled() ) { 483 this._placeMobileMenuBelowRow(); 484 $wrapper = $( this.wrapperClass ); 485 $menu = $( this.nodeClass + '-clone' ); 486 $menu.find( 'ul.menu' ).show(); 487 } 488 else { 489 $wrapper = $( this.wrapperClass ); 490 $menu = $wrapper.find( '.menu' ); 491 } 492 493 if( !$wrapper.find( '.fl-menu-mobile-toggle' ).hasClass( 'fl-active' ) && ! self.mobileFlyout ){ 494 $menu.css({ display: 'none' }); 495 } 496 497 // Flayout Menu 498 if ( self.mobileFlyout ) { 499 this._initFlyoutMenu(); 500 } 501 502 $wrapper.on( 'click', '.fl-menu-mobile-toggle', function( e ){ 503 e.stopImmediatePropagation(); 504 505 $( this ).toggleClass( 'fl-active' ); 506 507 if ( self.mobileFlyout ) { 508 self._toggleFlyoutMenu(); 509 const flyoutWrapper = $( '.fl-menu-mobile-flyout' ); 510 if ( $( this ).hasClass( 'fl-active' ) ) { 511 flyoutWrapper.attr( 'aria-hidden', false ); 512 flyoutWrapper.find( 'a[href], button, input, select, textarea, span.fl-menu-toggle, [tabindex="-1"]' ).attr( 'tabindex', 0 ); 513 } 514 else { 515 flyoutWrapper.attr( 'aria-hidden', true ); 516 flyoutWrapper.find( 'a[href], button, input, select, textarea, span.fl-menu-toggle, [tabindex]:not
516([tabindex="-1"])' ).attr( 'tabindex', -1 ); 517 } 518 } 519 else { 520 var targetMenu = null; 521 522 if ( self.mobileBelowRow ) { 523 var $closestCol = $( this ).parents( '.fl-col, .fl-module-box' ), 524 $closestColGroup = $closestCol.length ? $closestCol.parent( '.fl-col-group' ) : null; 525 targetMenu = $closestCol.length ? $closestCol.last().next( '.fl-menu-mobile-clone' ) : null; 526 527 if ( $closestColGroup.length ) { 528 if ( $closestColGroup.hasClass( 'fl-col-group-responsive-reversed' ) ) { 529 $closestColGroup.find( '.fl-menu-mobile-clone' ).css( 'order', -1 ); 530 } else if ( $closestColGroup ) { 531 $closestColGroup.find( '.fl-menu-mobile-clone' ).css( 'order', 2 ); 532 } 533 } 534 } else { 535 targetMenu = $( this ).closest( '.fl-menu' ).find( 'ul.menu' ); 536 } 537 538 if ( targetMenu.length ) { 539 $menu = $( targetMenu ); 540 } 541 542 $menu.slideToggle(); 543 } 544 545 e.stopPropagation(); 546 } ); 547 548 // Hide active menu when click on anchor link ID that exists on a page 549 $menu.off().on( 'click', '.menu-item > a[href*="#"]:not([href="#"])', function(){ 550 var $href = $(this).attr('href'), 551 $targetID = $href.split('#')[1], 552 element = $('#' + $targetID); 553 if ( $('body').find(element).length > 0 ) { 554 $( this ).toggleClass( 'fl-active' ); 555 FLBuilderLayout._scrollToElement( element ); 556 if ( ! self._isMenuToggle() ) { 557 $menu.slideToggle(); 558 } 559 } 560 }); 561 } 562 else { 563 564 if ( this._isMobileBelowRowEnabled() ) { 565 this._removeMenuFromBelowRow(); 566 } 567 568 $wrapper = $( this.wrapperClass ), 569 $menu = $wrapper.find( 'ul.menu' ); 570 $wrapper.find( '.fl-menu-mobile-toggle' ).removeClass( 'fl-active' ); 571 $menu.css({ display: '' }); 572 573 if ( ! this._isMobileBelowRowEnabled() ) { 574 $menu.off( 'click', '.menu-item > a[href*="#"]:not([href="#"])' ); 575 } 576 577 if ( this.mobileFlyout && $wrapper.find( '.fl-menu-mobile-flyout' ).length > 0 ) { 578 $( 'body' ).css( 'margin', '' ); 579 $( '.fl-builder-ui-pinned-content-transform' ).css( 'transform', '' ); 580 $menu.unwrap(); 581 $wrapper.find( '.fl-menu-mobile-close' ).remove(); 582 $wrapper.find( '.fl-menu-mobile-opacity' ).remove(); 583 } 584 } 585 }, 586 587 /** 588 * Init any mega menus that exist. 589 * 590 * @see this._isMenuToggle() 591 * @since 1.10.4 592 * @return void 593 */ 594 _initMegaMenus: function(){ 595 596 var module = $( this.nodeClass ), 597 rowContent = module.closest( '.fl-row-content' ), 598 rowWidth = rowContent.width(), 599 megas = module.find( '.mega-menu' ), 600 disabled = module.find( '.mega-menu-disabled' ), 601 isToggle = this._isMenuToggle(); 602 603 if ( isToggle ) { 604 megas.removeClass( 'mega-menu' ).addClass( 'mega-menu-disabled' ); 605 module.find( 'li.mega-menu-disabled > ul.sub-menu' ).css( 'width', '' ); 606 rowContent.css( 'position', '' ); 607 } else { 608 disabled.removeClass( 'mega-menu-disabled' ).addClass( 'mega-menu' ); 609 module.find( 'li.mega-menu > ul.sub-menu' ).css( 'width', rowWidth + 'px' ); 610 rowContent.css( 'position', 'relative' ); 611 } 612 }, 613 614 /** 615 * Check to see if Below Row should be enabled. 616 * 617 * @since 1.11 618 * @return boolean 619 */ 620 _isMobileBelowRowEnabled: function() { 621 return this.mobileBelowRow && ( $( this.nodeClass ).parents( '.fl-col, .fl-module-box' ).length ); 622 }, 623 624 /** 625 * Logic for putting the mobile menu below the menu's 626 * column so it spans the full width of the page. 627 * 628 * @since 1.10 629 * @return void 630 */ 631 _placeMobileMenuBelowRow: function(){ 632 633 if ( $( this.nodeClass + '-clone' ).length ) { 634 return; 635 } 636 637 var module = $( this.nodeClass ), 638 clone = null, 639 col = module.parents( '.fl-col, .fl-module-box' ).last(); 640 641 if ( module.length < 1 ) { 642 return; 643 } 644 645 clone = ( module.length > 1 ) ? $( module[0] ).clone() : module.clone(); 646 module.find( 'ul.menu' ).remove(); 647 clone.addClass( ( this.nodeClass + '-clone' ).replace( '.', '' ) ); 648 clone.addClass( 'fl-menu-mobile-clone' ); 649 clone.find( '.fl-menu-mobile-toggle' ).remove(); 650 col.after( clone ); 651 652 // Removes animation when enabled 653 if ( module.hasClass( 'fl-animation' ) ) { 654 clone.removeClass( 'fl-animation' ); 655 } 656 657 this._menuOnFocus(); 658 this._menuOnClick(); 659 this._menuOnEscape(); 660 }, 661 662 /** 663 * Logic for removing the mobile menu from below the menu's 664 * column and putting it back in the main wrapper. 665 * 666 * @since 1.10 667 * @return void 668 */ 669 _removeMenuFromBelowRow: function(){ 670 671 if ( ! $( this.nodeClass + '-clone' ).length ) { 672 return; 673 } 674 675 var module = $( this.nodeClass ), 676 clone = $( this.nodeClass + '-clone' ), 677 menu = clone.find( 'ul.menu' ), 678 nav = module.find( 'nav' ); 679
680 if ( nav.length ) { 681 nav.append( menu ); 682 } else { 683 module.find( '.fl-menu-mobile-toggle' ).after( menu ); 684 } 685 clone.remove(); 686 menu.find( 'a' ).each( FLBuilderLayout._initAnchorLink ); 687 }, 688 689 /** 690 * Logic for Flyout responsive menu. 691 * 692 * @since 2.2 693 * @return void 694 */ 695 _initFlyoutMenu: function(){ 696 var wrapper = $( this.wrapperClass ), 697 menu = wrapper.find( 'ul.menu' ), 698 button = wrapper.find( '.fl-menu-mobile-toggle' ); 699 700 if ( 0 === wrapper.find( '.fl-menu-mobile-flyout' ).length ) { 701 menu.wrap( '<div class="fl-menu-mobile-flyout" aria-hidden="true"></div>' ); 702 } 703 704 if ( 0 === wrapper.find( '.fl-menu-mobile-close' ).length ) { 705 var close = window.fl_responsive_close || 'Close' 706 wrapper.find( '.fl-menu-mobile-flyout' ).prepend( '<button class="fl-menu-mobile-close fl-content-ui-button" aria-label="' + close + '"><i class="fas fa-times"></i></button>' ); 707 } 708 709 // Push with opacity 710 if ( wrapper.hasClass( 'fl-menu-responsive-flyout-push-opacity' ) && 0 === wrapper.find( '.fl-menu-mobile-opacity' ).length ) { 711 wrapper.append( '<div class="fl-menu-mobile-opacity"></div>' ); 712 } 713 714 wrapper.off( 'click', '.fl-menu-mobile-opacity, .fl-menu-mobile-close' ).on( 'click', '.fl-menu-mobile-opacity, .fl-menu-mobile-close', function(e){ 715 button.trigger('focus').trigger( 'click' ); 716 e.stopPropagation(); 717 }); 718 719 if ( 'undefined' !== typeof FLBuilder ) { 720 FLBuilder.addHook('restartEditingSession', function(){ 721 $( '.fl-builder-ui-pinned-content-transform' ).css( 'transform', '' ); 722 723 // Toggle active menu 724 if ( button.hasClass( 'fl-active' ) ) { 725 button.trigger( 'click' ); 726 } 727 }); 728 } 729 $( '.fl-menu-mobile-flyout' ).find( 'a[href], button, input, select, textarea, span.fl-menu-toggle, [tabindex]:not([tabindex="-1"])' ).attr( 'tabindex', -1 ); 730 }, 731 732 /** 733 * Logic to enable/disable the Flyout menu on button click. 734 * 735 * @since 2.2 736 * @return void 737 */ 738 _toggleFlyoutMenu: function(){ 739 var wrapper = $( this.wrapperClass ), 740 button = wrapper.find( '.fl-menu-mobile-toggle' ), 741 position = wrapper.hasClass( 'fl-flyout-right' ) ? 'right' : 'left', 742 pushMenu = wrapper.hasClass( 'fl-menu-responsive-flyout-push' ) || wrapper.hasClass( 'fl-menu-responsive-flyout-push-opacity' ), 743 opacity = wrapper.find( '.fl-menu-mobile-opacity' ), 744 marginPos = {}, 745 fixedPos = {}, 746 fixedHeader = $('header, header > div'); 747 748 this._resizeFlyoutMenuPanel(); 749 750 // Fix the push menu when builder ui panel is pinned 751 if ( $( '.fl-builder-ui-pinned-content-transform' ).length > 0 && ! $( 'body' ).hasClass( 'fl-builder-edit' ) ) { 752 $( '.fl-builder-ui-pinned-content-transform' ).css( 'transform', 'none' ); 753 } 754 755 if ( pushMenu ) { 756 marginPos[ 'margin-' + position ] = button.hasClass( 'fl-active' ) ? this.flyoutWidth + 'px' : '0px'; 757 $( 'body' ).animate( marginPos, 200); 758 759 // Fixed header 760 if ( fixedHeader.length > 0 ) { 761 fixedPos[ position] = button.hasClass( 'fl-active' ) ? this.flyoutWidth + 'px' : '0px'; 762 fixedHeader.each(function(){ 763 if ( 'fixed' == $( this ).css( 'position' ) ) { 764 $( this ).css({ 'transition' : 'none' }); 765 $( this ).animate( fixedPos, 200 ); 766 } 767 }); 768 } 769 } 770 771 if ( opacity.length > 0 && button.hasClass( 'fl-active' ) ) { 772 opacity.show(); 773 } 774 else { 775 opacity.hide(); 776 } 777 }, 778 779 /** 780 * Resize or reposition the Flyout Menu Panel. 781 * 782 * @since 2.8.1 783 * @return void 784 */ 785 _resizeFlyoutMenuPanel: function(){ 786 const wrapper = $( this.wrapperClass ); 787 const wrapFlyout = wrapper.find( '.fl-menu-mobile-flyout' ); 788 789 if ( wrapFlyout.length > 0 ) { 790 wrapFlyout.css( this._getFlyoutMenuPanelPosition() ); 791 } 792 }, 793 794 /** 795 * Compute the Flyout Menu Panel's position on the screen. 796 * 797 * @since 2.8.1 798 * @return object 799 */ 800 _getFlyoutMenuPanelPosition: function() { 801 var wrapper = $( this.wrapperClass ), 802 button = wrapper.find( '.fl-menu-mobile-toggle' ), 803 side = wrapper.hasClass( 'fl-flyout-right' ) ? 'right' : 'left', 804 winHeight = $(window).outerHeight(), 805 winTop = $(window).scrollTop(), 806 adminBarHeight = $( '#wpadminbar' ).length ? $( '#wpadminbar' ).height() : 0, 807 flyoutPosition = {}; 808 809 flyoutPosition[ side ] = '-' + ( parseInt( this.flyoutWidth ) + 15 ) + 'px'; 810 if ( ! button.hasClass( 'fl-active' ) ) { 811 return flyoutPosition; 812 } 813 814 flyoutPosition[ side ] = '0px'; 815 flyoutPosition[ 'height' ] = winHeight + 'px'; 816 flyoutPosition[ 'top' ] = '0px'; 817 818 if ( adminBarHeight > 0 ) { 819 const diff = adminBarHeight - winTop; 820 flyoutPosition[ 'top' ] = diff <= 0 ? '0px' : (diff) + 'px'; 821 } 822 823 return flyoutPosition; 824 }, 825 826 /** 827 * Shows or hides the nav search form. 828 * 829 * @since 2.5 830 * @method _toggleMenuSearch 831 */ 832 _toggleMenuSearch: function(){ 833 const form = $( this.wrapperClass ).find('.fl-menu-search-item .fl-search-form-input-wrap');
834 const button = $( this.wrapperClass ).find('.fl-menu-search-item .fl-button:is(a, button:not([type="submit"]))'); 835 form.on('submit', () => form.stop().fadeToggle('fast')); 836 // Close the search form when focus is lost 837 form.on('focusout', (event) => { 838 if (form.is(':visible') && form.has($(event.relatedTarget)).length === 0) { 839 button.trigger('click'); 840 } 841 }); 842 button.attr('aria-expanded', false); 843 // Close other open submenus when focusing on the search button 844 button.on('focus', (event) => { 845 const parentMenus = $(event.relatedTarget).parents('.menu-item.fl-has-submenu'); 846 if (event.relatedTarget && parentMenus.length) { 847 this._toggleSubmenu(parentMenus, false); 848 } 849 }); 850 button.on('click', (event) => { 851 // Prevent toggling when clicking with mouse while the form is already open 852 if (event.detail && event.type === 'click' && form.is(':visible')) return; 853 form.stop().fadeToggle('fast', () => { 854 button.attr('aria-expanded', form.is(':visible')); 855 if(form.is(':visible')) { 856 form.find('.fl-search-text').focus(); 857 } 858 }); 859 }); 860 }, 861 862 /** 863 * Adds menu node and post ID to WooCommerce ajax URL requests. 864 * 865 * @since 2.5 866 * @return void 867 */ 868 _wooUpdateParams: function() { 869 if ( 'undefined' !== typeof wc_cart_fragments_params ) { 870 wc_cart_fragments_params.wc_ajax_url += '&fl-menu-node='+ this.nodeId +'&post-id='+ this.postId; 871 } 872 if ( 'undefined' !== typeof wc_add_to_cart_params ) { 873 wc_add_to_cart_params.wc_ajax_url += '&fl-menu-node='+ this.nodeId +'&post-id='+ this.postId; 874 } 875 }, 876 }; 877 878})(jQuery); 879 880(function($) { 881 882 $(function() { 883 884 new FLBuilderMenu({ 885 id: 'bvszxpm672ta', 886 type: 'vertical', 887 mobile: 'expanded', 888 mobileBelowRow: false, 889 mobileFlyout: false, 890 breakPoints: { 891 large: 1200, 892 medium: 992, 893 small: 768 }, 894 mobileBreakpoint: 'mobile', 895 postId : '8269', 896 mobileStacked: true, 897 submenuIcon: 'none', 898 flyoutWidth: '250', 899 }); 900 901 }); 902 903})(jQuery); 904 905;/* eslint-disable no-undef */ 906(function($) { 907 908 FLBuilderSearchForm = function(settings) { 909 this.settings = settings; 910 this.nodeClass = '.fl-node-' + settings.id; 911 this.searchForm = $(this.nodeClass + ' .fl-search-form'); 912 this.form = this.searchForm.find('form'); 913 this.input = this.form.find('input[type=search]'); 914 this.button = this.searchForm.find('.fl-button:is(a, button:not([type="submit"]), .fl-button:is(a, button:not([type="submit"]) *'); 915 this.resultsEl = $(this.nodeClass + ' .fl-search-results-content'); 916 917 this._init(); 918 }; 919 920 FLBuilderSearchForm.prototype = { 921 922 settings: {}, 923 nodeClass: '', 924 searchForm: '', 925 form: null, 926 input: null, 927 button: null, 928 resultsEl: '', 929 searching: false, 930 prevSearchData: {}, 931 request: null, 932 933 _init: function() { 934 this._bindEvents(); 935 this._popupSearch(); 936 }, 937 938 _bindEvents: function(){ 939 var $this = this, 940 keyCode = null, 941 keyType = null, 942 enterPressed = false, 943 t, et; 944 945 this.button.on('click', $.proxy(this._buttonClick, this)); 946 this.input.on('input', (event) => { 947 const input = event.target; 948 if ( input.checkValidity() ) { 949 input.removeAttribute('aria-invalid'); 950 } else { 951 input.setAttribute('aria-invalid', true); 952 } 953 }); 954 955 if ( this.searchForm.hasClass('fl-search-button-expand') ) { 956 this.input.attr({'id': 'input-' + this.settings.id, 'tabindex': -1}); 957 this.button.closest('.fl-button').attr({'aria-controls': 'input-' + this.settings.id, 'aria-expanded': false}); 958 } 959 else if ( this.searchForm.hasClass('fl-search-button-reveal') ) { 960 const form = this.searchForm.find('.fl-search-form-input-wrap'); 961 form.attr('id', 'form-' + this.settings.id); 962 this.button.closest('.fl-button').attr({'aria-controls': 'form-' + this.settings.id, 'aria-expanded': false, 'aria-haspopup': true}); 963 form.on('focusout', (event) => { 964 if (form.is(':visible') && form.has($(event.relatedTarget)).length === 0) { 965 this._buttonClick(event); 966 } 967 }); 968 this.searchForm.on('keydown', (event) => { 969 if (event.key === 'Escape' && form.is(':visible')) { 970 this.button.focus(); 971 } 972 }); 973 } 974 975 if ( 'ajax' == this.settings.result ) { 976 $(document).on('click touchend', function(e){ 977 if( $(e.target).is('input') ) return; 978 979 $this._hideResults(); 980 } ); 981 982 $this.resultsEl.bind("click touchend", function (e) { 983 e.stopImmediatePropagation(); 984 }); 985 986 // Disable form submit. 987 $this.form.on( 'submit', function (e) { 988 e.preventDefault(); 989 }); 990 991 // Prevent auto-clearing errors in the FLBuilderLayout helper class. 992 this.input.off('focus'); 993 994 this.input.on('keyup', function(e) { 995 if (window.event) { 996 keyCode = window.event.keyCode; 997 keyType = window.event.type; 998 } else if (e) { 999 keyCode = e.which; 1000 keyType = e.type; 1001 } 1002 1003 // Prevent rapid enter 1004 if ( 13 == keyCode ) { 1005 clearTimeout(et); 1006 et = setTimeout(function(){ 1007 enterPressed = false;
1008 }, 300); 1009 if ( enterPressed ) { 1010 return false; 1011 } else { 1012 enterPressed = true; 1013 } 1014 } 1015 1016 if ( $this.input.val().length >= 3 && 'keyup' == keyType && 13 == keyCode ) { 1017 $this._search(e); 1018 return false; 1019 } 1020 }); 1021 1022 this.input.on('click input', function(e) { 1023 if (window.event) { 1024 keyCode = window.event.keyCode; 1025 keyType = window.event.type; 1026 } else if (e) { 1027 keyCode = e.which; 1028 keyType = e.type; 1029 } 1030 1031 // F1 to F12 1032 if ( (keyCode >= 37 && keyCode <= 40) || (keyCode >= 112 && keyCode <= 123) ) { 1033 return; 1034 } 1035 1036 if ($this.input.val().length < 3) { 1037 $this._hideLoader(); 1038 $this._hideResults(); 1039 $this.input.attr('aria-invalid', true); 1040 if ($this.post != null) $this.post.abort(); 1041 clearTimeout(t); 1042 return; 1043 } 1044 1045 if ( 'click' == keyType || keyCode == 32 ) { 1046 if ( $this.resultsEl.html().length != 0 ) { 1047 clearTimeout(t); 1048 if( $this.resultsEl.hasClass('fl-search-open') ) return; 1049 $this._showResults(); 1050 } 1051 else { 1052 $this._hideResults(); 1053 } 1054 return; 1055 } 1056 1057 if ($this.request != null) $this.request.abort(); 1058 $this._hideLoader(); 1059 1060 clearTimeout(t); 1061 t = setTimeout(function() { 1062 $this._search(e); 1063 }, 100); 1064 }); 1065 } 1066 }, 1067 1068 _search: function(e) { 1069 if ($.trim(this.input.val()).length < 1 || ('ajax' == this.settings.result && this.input.val().length < 3)) { 1070 this.input.attr('aria-invalid', true); 1071 return; 1072 } 1073 e.preventDefault(); 1074 1075 if ( 'ajax' == this.settings.result ) { 1076 this._doAjaxSearch(); 1077 } 1078 else { 1079 this.form.submit(); 1080 // TODO: _doRedirectResults() 1081 } 1082 1083 return false; 1084 }, 1085 1086 _doAjaxSearch: function() { 1087 var searchText = this.input.val(), 1088 postId = this.searchForm.closest( '.fl-builder-content' ).data( 'post-id' ), 1089 templateId = this.searchForm.data( 'template-id' ), 1090 templateNodeId = this.searchForm.data( 'template-node-id' ), 1091 ajaxData = {}, 1092 self = this; 1093 1094 if ( searchText.length < 1 ) return; 1095 1096 this.searching = true; 1097 1098 // Show loader 1099 this._showLoader(); 1100 1101 ajaxData = { 1102 action : 'fl_search_query', 1103 keyword : searchText, 1104 post_id : postId, 1105 template_id : templateId, 1106 template_node_id : templateNodeId, 1107 node_id : this.settings.id, 1108 nonce : this.settings.nonce, 1109 } 1110 1111 // Check to see if searching the same keywords. 1112 if ( JSON.stringify(ajaxData) === JSON.stringify(this.prevSearchData) ) { 1113 if ( ! this.resultsEl.hasClass('fl-search-open') ) { 1114 this._showResults(); 1115 } 1116 this._hideLoader(); 1117 return false; 1118 } 1119 1120 // Send server request. 1121 this.request = $.post( FLBuilderLayoutConfig.paths.wpAjaxUrl, ajaxData, function(response){ 1122 self._hideLoader(); 1123 1124 self.resultsEl.html(""); 1125 self.resultsEl.html(response); 1126 self._showResults(); 1127 1128 self.prevSearchData = ajaxData; 1129 }); 1130 }, 1131 1132 _popupSearch: function() { 1133 var inputWrap = this.searchForm.find('.fl-search-form-input-wrap'), 1134 $this = this; 1135 1136 if ('button' !== this.settings.layout || ( 'fullscreen' !== this.settings.btnAction && 'reveal' !== this.settings.btnAction )) { 1137 return; 1138 } 1139 1140 this.button.off('click'); 1141 this.button.magnificPopup({ 1142 type: 'inline', 1143 mainClass: 'fl-node-' + this.settings.id, 1144 items: { 1145 src: inputWrap[0], 1146 }, 1147 alignTop: true, 1148 showCloseBtn: $this.settings.showCloseBtn, 1149 closeBtnInside: false, 1150 enableEscapeKey: true, 1151 closeOnBgClick: false, 1152 focus: 'input[type=search]', 1153 tLoading: '<i class="fas fa-spinner fa-spin fa-3x fa-fw"></i>', 1154 callbacks: { 1155 open: function(){ 1156 $this.input.trigger('click'); 1157 } 1158 } 1159 }); 1160 this.resultsEl.appendTo( inputWrap ); 1161 }, 1162 1163 _buttonClick: function(e) { 1164 e.stopImmediatePropagation(); 1165 if (this.searchForm.hasClass('fl-search-button-expand')) { 1166 this.searchForm.find('.fl-search-form-wrap').toggleClass('fl-search-expanded'); 1167 1168 if (this.searchForm.find('.fl-search-form-wrap').hasClass('fl-search-expanded')) { 1169 this.button.closest('.fl-button').attr('aria-expanded', true); 1170 this.input.attr('tabindex', 0); 1171 this.input.focus(); 1172 } 1173 else { 1174 this._hideResults(); 1175 this.input.attr('tabindex', -1); 1176 this.button.closest('.fl-button').attr('aria-expanded', false); 1177 } 1178 1179 return false;
1180 } else if (this.searchForm.hasClass('fl-search-button-reveal') && $(e.target).closest('.fl-button:not([type="submit"])')) { 1181 const form = this.searchForm.find('.fl-search-form-input-wrap'); 1182 if (e.detail && e.type === 'click' && form.is(':visible')) return false; 1183 form.fadeToggle('fast', () => { 1184 this.button.attr('aria-expanded', form.is(':visible')); 1185 if (form.is(':visible')) { 1186 this.input.focus(); 1187 } 1188 }); 1189 1190 return false; 1191 } else { 1192 this._search(e); 1193 } 1194 }, 1195 1196 _showResults: function(){ 1197 // Close any search results in a page. 1198 this._hideResults(); 1199 this.resultsEl.addClass('fl-search-open'); 1200 1201 if ('button' == this.settings.layout && 'expand' == this.settings.btnAction) { 1202 this.searchForm.find('.fl-search-form-input-wrap').css('overflow', 'visible'); 1203 } 1204 }, 1205 1206 _hideResults: function(){ 1207 $('.fl-search-results-content').removeClass('fl-search-open'); 1208 1209 if ('button' == this.settings.layout && 'expand' == this.settings.btnAction) { 1210 this.searchForm.find('.fl-search-form-input-wrap').removeAttr('style'); 1211 } 1212 }, 1213 1214 _doRedirectResults: function(){ 1215 // TODO 1216 }, 1217 1218 _showLoader: function(){ 1219 $(this.nodeClass + ' .fl-search-loader-wrap').show(); 1220 }, 1221 1222 _hideLoader: function(){ 1223 this.searching = false; 1224 $(this.nodeClass + ' .fl-search-loader-wrap').hide(); 1225 }, 1226 1227 _cleanInput: function(s) { 1228 return encodeURIComponent(s).replace(/%20/g, '+'); 1229 } 1230 1231 } 1232 1233})(jQuery); 1234(function($) { 1235 1236 $(function() { 1237 new FLBuilderSearchForm({ 1238 id: 'lc5j6vxib0km', 1239 layout: 'inline', 1240 btnAction: 'expand', 1241 result: 'redirect', 1242 showCloseBtn: true, 1243 nonce: 'da3a64aa90', 1244 }); 1245 }); 1246 1247})(jQuery); 1248 1249/* Start Layout Custom JS */ 1250 1251/* End Layout Custom JS */ 1252 1253 1254 1255(function($) { 1256 1257 $(function() { 1258 1259 new FLBuilderMenu({ 1260 id: 'wbfcv3skh950', 1261 type: 'horizontal', 1262 mobile: 'hamburger', 1263 mobileBelowRow: true, 1264 mobileFlyout: false, 1265 breakPoints: { 1266 large: 1200, 1267 medium: 992, 1268 small: 768 }, 1269 mobileBreakpoint: 'mobile', 1270 postId : '8282', 1271 mobileStacked: true, 1272 submenuIcon: 'arrows', 1273 flyoutWidth: '250', 1274 }); 1275 1276 }); 1277 1278})(jQuery); 1279jQuery(function($) { 1280 1281 $(function() { 1282 $( '.fl-node-ey27qnasmvk3 .fl-photo-img' ) 1283 .on( 'mouseenter', function( e ) { 1284 $( this ).data( 'title', $( this ).attr( 'title' ) ).removeAttr( 'title' ); 1285 } ) 1286 .on( 'mouseleave', function( e ){ 1287 $( this ).attr( 'title', $( this ).data( 'title' ) ).data( 'title', null ); 1288 } ); 1289 }); 1290 window._fl_string_to_slug_regex = 'a-zA-Z0-9'; 1291}); 1292 1293/* Start Layout Custom JS */ 1294 1295/* End Layout Custom JS */ 1296 1297(function($){ 1298 1299 /** 1300 * Helper class for header layout logic. 1301 * 1302 * @since 1.0 1303 * @class FLThemeBuilderHeaderLayout 1304 */ 1305 FLThemeBuilderHeaderLayout = { 1306 1307 /** 1308 * A reference to the window object for this page. 1309 * 1310 * @since 1.0 1311 * @property {Object} win 1312 */ 1313 win : null, 1314 1315 /** 1316 * A reference to the body object for this page. 1317 * 1318 * @since 1.0 1319 * @property {Object} body 1320 */ 1321 body : null, 1322 1323 /** 1324 * A reference to the header object for this page. 1325 * 1326 * @since 1.0 1327 * @property {Object} header 1328 */ 1329 header : null, 1330 1331 /** 1332 * Whether this header overlays the content or not. 1333 * 1334 * @since 1.0 1335 * @property {Boolean} overlay 1336 */ 1337 overlay : false, 1338 1339 /** 1340 * Whether the page has the WP admin bar or not. 1341 * 1342 * @since 1.0 1343 * @property {Boolean} hasAdminBar 1344 */ 1345 hasAdminBar : false, 1346 1347 /** 1348 * Breakpoint for when the sticky header should apply. 1349 * 1350 * @since 1.4 1351 * @property {String} stickyOn 1352 */ 1353 stickyOn: '', 1354 1355 /** 1356 * A reference of the sticky and shrink header breakpoint. 1357 * 1358 * @since 1.2.5 1359 * @property {Number} breakpointWidth 1360 */ 1361 breakpointWidth: 0, 1362 1363 /** 1364 * Initializes header layout logic. 1365 * 1366 * @since 1.0 1367 * @method init 1368 */ 1369 init: function() 1370 { 1371 var editing = $( 'html.fl-builder-edit' ).length, 1372 header = $( '.fl-builder-content[data-type=header]' ), 1373 menuModule = header.find( '.fl-module-menu' ), 1374 breakpoint = null; 1375 1376 if ( ! editing && header.length ) { 1377 1378 header.imagesLoaded( $.proxy( function() { 1379 1380 this.win = $( window ); 1381 this.body = $( 'body' ); 1382 this.header = header.eq( 0 ); 1383 this.overlay = !! Number( header.attr( 'data-overlay' ) ); 1384 this.hasAdminBar = !! $( 'body.admin-bar' ).length; 1385 this.stickyOn = this.header.data( 'sticky-on' ); 1386 breakpoint = this.header.data( 'sticky-breakpoint' ); 1387 1388 if ( '' == this.stickyOn ) { 1389 if ( typeof FLBuilderLayoutConfig.breakpoints[ breakpoint ] !== undefined ) { 1390 this.breakpointWidth = FLBuilderLayoutConfig.breakpoints[ breakpoint ]; 1391 } 1392 else { 1393 this.breakpointWidth = FLBuilderLayoutConfig.breakpoints.medium; 1394 } 1395 } 1396 1397 if ( Number( header.attr( 'data-sticky' ) ) ) { 1398 1399 this.header.data( 'original-top', this.header.offset().top ); 1400 this.win.on( 'resize', $.throttle( 500, $.proxy( this._initSticky, this ) ) ); 1401 this._initSticky(); 1402 1403 } 1404 1405 }, this ) ); 1406 } 1407 }, 1408 1409 /** 1410 * Initializes sticky logic for a header. 1411 * 1412 * @since 1.0 1413 * @access private 1414 * @method _initSticky 1415 */ 1416 _initSticky: function( e ) 1417 {
1418 var header = $('.fl-builder-content[data-type=header]'), 1419 windowSize = this.win.width(), 1420 makeSticky = false; 1421 1422 makeSticky = this._makeWindowSticky( windowSize ); 1423 if ( makeSticky || ( this.breakpointWidth > 0 && windowSize >= this.breakpointWidth ) ) { 1424 this.win.on( 'scroll.fl-theme-builder-header-sticky', $.proxy( this._doSticky, this ) ); 1425 // 1426 // Check if Event Type is 'resize' then invoke this._doSticky() 1427 // only if the 'fl-theme-builder-header-sticky' class is already present. 1428 // 1429 if ( e && 'resize' === e.type ) { 1430 if ( this.header.hasClass( 'fl-theme-builder-header-sticky' ) ) { 1431 this._doSticky( e ); 1432 } 1433 this._adjustStickyHeaderWidth(); 1434 } 1435 1436 if ( Number( header.attr( 'data-shrink' ) ) ) { 1437 this.header.data( 'original-height', this.header.outerHeight() ); 1438 this.win.on( 'resize', $.throttle( 500, $.proxy( this._initShrink, this ) ) ); 1439 this._initShrink(); 1440 } 1441 1442 this._initFlyoutMenuFix( e ); 1443 } else { 1444 this.win.off( 'scroll.fl-theme-builder-header-sticky' ); 1445 this.win.off( 'resize.fl-theme-builder-header-sticky' ); 1446 1447 this.header.removeClass( 'fl-theme-builder-header-sticky' ); 1448 this.header.removeAttr( 'style' ); 1449 this.header.parent().css( 'padding-top', '0' ); 1450 } 1451 }, 1452 1453 /** 1454 * Check if Header should be sticky at a particular Window size. 1455 * 1456 * @since 1.4 1457 * @access private 1458 * @param widowSize 1459 * @method _makeWindowSticky 1460 */ 1461 _makeWindowSticky: function ( windowSize ) 1462 { 1463 var makeSticky = false; 1464 1465 switch (this.stickyOn) { 1466 case 'xl': 1467 makeSticky = windowSize > FLBuilderLayoutConfig.breakpoints['large']; 1468 break; 1469 case '': // Default 1470 case 'desktop': 1471 makeSticky = windowSize >= FLBuilderLayoutConfig.breakpoints['medium']; 1472 break; 1473 case 'desktop-medium': 1474 makeSticky = windowSize > FLBuilderLayoutConfig.breakpoints['small']; 1475 break; 1476 case 'large': 1477 makeSticky = windowSize > FLBuilderLayoutConfig.breakpoints['medium'] && windowSize <= FLBuilderLayoutConfig.breakpoints['large']; 1478 break; 1479 case 'large-medium': 1480 makeSticky = windowSize > FLBuilderLayoutConfig.breakpoints['small'] && windowSize <= FLBuilderLayoutConfig.breakpoints['large']; 1481 break; 1482 case 'medium': 1483 makeSticky = ( windowSize <= FLBuilderLayoutConfig.breakpoints['medium'] && windowSize > FLBuilderLayoutConfig.breakpoints['small'] ); 1484 break; 1485 case 'medium-mobile': 1486 makeSticky = (windowSize <= FLBuilderLayoutConfig.breakpoints['medium']); 1487 break; 1488 case 'mobile': 1489 makeSticky = (windowSize <= FLBuilderLayoutConfig.breakpoints['small']); 1490 break; 1491 case 'all': 1492 makeSticky = true; 1493 break; 1494 } 1495 1496 return makeSticky; 1497 }, 1498 1499 /** 1500 * Sticks the header when the page is scrolled. 1501 * 1502 * @since 1.0 1503 * @access private 1504 * @method _doSticky 1505 */ 1506 _doSticky: function( e ) 1507 { 1508 var winTop = Math.floor( this.win.scrollTop() ), 1509 headerTop = Math.floor( this.header.data( 'original-top' ) ), 1510 hasStickyClass = this.header.hasClass( 'fl-theme-builder-header-sticky' ), 1511 hasScrolledClass = this.header.hasClass( 'fl-theme-builder-header-scrolled' ), 1512 beforeHeader = this.header.prevAll( '.fl-builder-content' ), 1513 bodyTopPadding = parseInt( jQuery('body').css('padding-top') ), 1514 winBarHeight = $('#wpadminbar').length ? $('#wpadminbar').outerHeight() : 0, 1515 headerHeight = 0; 1516 1517 if ( isNaN( bodyTopPadding ) ) { 1518 bodyTopPadding = 0; 1519 } 1520 1521 if ( this.hasAdminBar && this.win.width() > 600 ) { 1522 winTop += Math.floor( winBarHeight ); 1523 } 1524 1525 if ( winTop > headerTop ) { 1526 if ( ! hasStickyClass ) { 1527 if ( e && ( 'scroll' === e.type || 'smartscroll' === e.type ) ) { 1528 this.header.addClass( 'fl-theme-builder-header-sticky' ); 1529 if ( this.overlay && beforeHeader.length ) { 1530 this.header.css( 'top', winBarHeight); 1531 } 1532 } 1533 1534 if ( ! this.overlay ) { 1535 this._adjustHeaderHeight(); 1536 } 1537 } 1538 } 1539 else if ( hasStickyClass ) { 1540 this.header.removeClass( 'fl-theme-builder-header-sticky' ); 1541 this.header.removeAttr( 'style' ); 1542 this.header.parent().css( 'padding-top', '0' ); 1543 } 1544 1545 this._adjustStickyHeaderWidth(); 1546 1547 if ( winTop > headerTop ) { 1548 if ( ! hasScrolledClass ) {
1549 this.header.addClass( 'fl-theme-builder-header-scrolled' ); 1550 } 1551 } else if ( hasScrolledClass ) { 1552 this.header.removeClass( 'fl-theme-builder-header-scrolled' ); 1553 } 1554 1555 this._flyoutMenuFix( e ); 1556 }, 1557 1558 /** 1559 * Initializes flyout menu fixes on sticky header. 1560 * 1561 * @since 1.4.1 1562 * @method _initFlyoutMenuFix 1563 */ 1564 _initFlyoutMenuFix: function( e ) { 1565 var header = this.header, 1566 menuModule = header.closest( '.fl-menu' ), 1567 flyoutMenu = menuModule.find( '.fl-menu-mobile-flyout' ), 1568 isPushMenu = menuModule.hasClass( 'fl-menu-responsive-flyout-push' ) || menuModule.hasClass( 'fl-menu-responsive-flyout-push-opacity' ), 1569 isOverlay = menuModule.hasClass( 'fl-menu-responsive-flyout-overlay' ), 1570 flyoutPos = menuModule.hasClass( 'fl-flyout-right' ) ? 'right' : 'left', 1571 flyoutParent = header.parent().is( 'header' ) ? header.parent().parent() : header.parent(); 1572 isFullWidth = this.win.width() === header.width(), 1573 flyoutLayout = '', 1574 activePos = 250, 1575 headerPos = 0; 1576 1577 if ( ! flyoutMenu.length ) { 1578 return; 1579 } 1580 1581 if ( this.win.width() > header.parent().width() ) { 1582 headerPos = ( this.win.width() - header.width() ) / 2; 1583 } 1584 1585 if ( isOverlay ) { 1586 activePos = headerPos; 1587 } 1588 else if ( isPushMenu ) { 1589 activePos = activePos + headerPos; 1590 } 1591 flyoutMenu.data( 'activePos', activePos ); 1592 1593 if ( isPushMenu ) { 1594 flyoutLayout = 'push-' + flyoutPos; 1595 } 1596 else if ( isOverlay ) { 1597 flyoutLayout = 'overlay-' + flyoutPos; 1598 } 1599 1600 if ( isPushMenu && ! $( 'html' ).hasClass( 'fl-theme-builder-has-flyout-menu' ) ) { 1601 $( 'html' ).addClass( 'fl-theme-builder-has-flyout-menu' ); 1602 } 1603 1604 if ( ! flyoutParent.hasClass( 'fl-theme-builder-flyout-menu-' + flyoutLayout ) ) { 1605 flyoutParent.addClass( 'fl-theme-builder-flyout-menu-' + flyoutLayout ); 1606 } 1607 1608 if ( ! header.hasClass( 'fl-theme-builder-flyout-menu-overlay' ) && isOverlay ) { 1609 header.addClass( 'fl-theme-builder-flyout-menu-overlay' ); 1610 } 1611 1612 if ( ! header.hasClass( 'fl-theme-builder-header-full-width' ) && isFullWidth ) { 1613 header.addClass( 'fl-theme-builder-header-full-width' ); 1614 } 1615 else if ( ! isFullWidth ) { 1616 header.removeClass( 'fl-theme-builder-header-full-width' ); 1617 } 1618 1619 menuModule.on( 'click', '.fl-menu-mobile-toggle', $.proxy( function( event ){ 1620 if ( menuModule.find( '.fl-menu-mobile-toggle.fl-active' ).length ) { 1621 $( 'html' ).addClass( 'fl-theme-builder-flyout-menu-active' ); 1622 event.stopImmediatePropagation(); 1623 } 1624 else { 1625 $( 'html' ).removeClass( 'fl-theme-builder-flyout-menu-active' ); 1626 } 1627 1628 this._flyoutMenuFix( event ); 1629 }, this ) ); 1630 }, 1631 1632 /** 1633 * Fix flyout menu inside the sticky header. 1634 * 1635 * @since 1.4.1 1636 * @method _flyoutMenuFix 1637 */ 1638 _flyoutMenuFix: function( e ){ 1639 var header = this.header, 1640 menuModule = $( e.target ).closest( '.fl-menu' ), 1641 flyoutMenu = menuModule.find( '.fl-menu-mobile-flyout' ), 1642 flyoutPos = menuModule.hasClass( 'fl-flyout-right' ) ? 'right' : 'left', 1643 menuOpacity = menuModule.find( '.fl-menu-mobile-opacity' ), 1644 isScroll = 'undefined' !== typeof e && 'scroll' === e.handleObj.type, 1645 activePos = 'undefined' !== typeof flyoutMenu.data( 'activePos' ) ? flyoutMenu.data( 'activePos' ) : 0, 1646 headerPos = ( this.win.width() - header.width() ) / 2, 1647 inactivePos = headerPos > 0 ? activePos + 4 : 254; 1648 1649 if ( ! flyoutMenu.length ) { 1650 return; 1651 } 1652 1653 if ( this.overlay ) { 1654 return; 1655 } 1656 1657 if( $( '.fl-theme-builder-flyout-menu-active' ).length ) { 1658 1659 if ( isScroll && ! flyoutMenu.hasClass( 'fl-menu-disable-transition' ) ) { 1660 flyoutMenu.addClass( 'fl-menu-disable-transition' ); 1661 } 1662 1663 if ( header.hasClass( 'fl-theme-builder-header-sticky' ) ) { 1664 if ( ! isScroll ) { 1665 setTimeout( $.proxy( function(){ 1666 flyoutMenu.css( flyoutPos, '-' + activePos + 'px' ); 1667 }, this ), 1 ); 1668 } 1669 else { 1670 flyoutMenu.css( flyoutPos, '-' + activePos + 'px' ); 1671 } 1672 } 1673 else { 1674 flyoutMenu.css( flyoutPos, '0px' ); 1675 } 1676 } 1677 else { 1678 if ( flyoutMenu.hasClass( 'fl-menu-disable-transition' ) ) { 1679 flyoutMenu.removeClass( 'fl-menu-disable-transition' ); 1680 } 1681
1682 if ( header.hasClass( 'fl-theme-builder-flyout-menu-overlay' ) && headerPos > 0 && headerPos < 250 ) { 1683 if ( header.hasClass( 'fl-theme-builder-header-sticky' ) ) { 1684 inactivePos = headerPos + 254; 1685 } 1686 else { 1687 inactivePos = 254; 1688 } 1689 } 1690 1691 if ( e && e.type === 'resize' ) { 1692 inactivePos = headerPos + 254; 1693 } 1694 1695 flyoutMenu.css( flyoutPos, '-' + inactivePos + 'px' ); 1696 } 1697 1698 if ( e && menuModule.is('.fl-menu-responsive-flyout-overlay') && $.infinitescroll ) { 1699 e.stopImmediatePropagation(); 1700 } 1701 1702 if( menuOpacity.length ) { 1703 if ( header.hasClass( 'fl-theme-builder-header-sticky' ) ) { 1704 if ( '0px' === menuOpacity.css( 'left' ) ) { 1705 menuOpacity.css( 'left', '-' + headerPos + 'px' ); 1706 } 1707 } 1708 else { 1709 menuOpacity.css( 'left', '' ); 1710 } 1711 } 1712 }, 1713 1714 /** 1715 * Adjust sticky header width if BB Theme Boxed Layout is used. 1716 * 1717 * @since 1.4 1718 * @access private 1719 * @method _adjustStickyHeaderWidth 1720 */ 1721 _adjustStickyHeaderWidth: function () { 1722 if ( $('body').hasClass( 'fl-fixed-width' ) ) { 1723 var parentWidth = this.header.parent().width(); 1724 1725 // Better if this is set in the stylesheet file. 1726 if ( this.win.width() >= 992 ) { 1727 this.header.css({ 1728 'margin': '0 auto', 1729 'max-width': parentWidth, 1730 }); 1731 } 1732 else { 1733 this.header.css({ 1734 'margin': '', 1735 'max-width': '', 1736 }); 1737 } 1738 } 1739 }, 1740 1741 /** 1742 * Adjust Sticky Header Height 1743 * 1744 * @since 1.4 1745 * @access private 1746 * @method _adjustHeaderHeight 1747 */ 1748 _adjustHeaderHeight: function () { 1749 var beforeHeader = this.header.prevAll('.fl-builder-content'), 1750 beforeHeaderHeight = 0, 1751 beforeHeaderFix = 0, 1752 headerHeight = Math.floor( this.header.outerHeight() ), 1753 bodyTopPadding = parseInt( $( 'body' ).css( 'padding-top' ) ), 1754 wpAdminBarHeight = 0, 1755 totalHeaderHeight = 0; 1756 1757 if ( isNaN( bodyTopPadding ) ) { 1758 bodyTopPadding = 0; 1759 } 1760 1761 if ( beforeHeader.length ) { 1762 $.each( beforeHeader, function() { 1763 beforeHeaderHeight += Math.floor( $(this).outerHeight() ); 1764 }); 1765 // Subtract this value from the header parent's top padding. 1766 beforeHeaderFix = 2; 1767 } 1768 1769 if ( this.hasAdminBar && this.win.width() <= 600 ) { 1770 wpAdminBarHeight = Math.floor( $('#wpadminbar').outerHeight() ); 1771 } 1772 1773 totalHeaderHeight = Math.floor( beforeHeaderHeight + headerHeight); 1774 1775 if ( headerHeight > 0 ) { 1776 var headerParent = this.header.parent(), 1777 headerParentTopPadding = 0; 1778 1779 // If the header's parent container is the BODY tag ignore its top padding. 1780 if ( $( headerParent ).is('body') ) { 1781 headerParentTopPadding = Math.floor( headerHeight - wpAdminBarHeight ); 1782 } else { 1783 headerParentTopPadding = Math.floor( headerHeight - bodyTopPadding - wpAdminBarHeight ); 1784 } 1785 1786 $( headerParent ).css( 'padding-top', ( headerParentTopPadding - beforeHeaderFix ) + 'px' ); 1787 1788 this.header.css({ 1789 '-webkit-transform': 'translate(0px, -' + totalHeaderHeight + 'px)', 1790 '-ms-transform': 'translate(0px, -' + totalHeaderHeight + 'px)', 1791 'transform': 'translate(0px, -' + totalHeaderHeight + 'px)' 1792 }); 1793 1794 } 1795 1796 }, 1797 1798 /** 1799 * Initializes shrink logic for a header. 1800 * 1801 * @since 1.0 1802 * @access private 1803 * @method _initShrink 1804 */ 1805 _initShrink: function( e ) 1806 { 1807 if ( this.win.width() >= this.breakpointWidth ) { 1808 this.win.on( 'scroll.fl-theme-builder-header-shrink', $.proxy( this._doShrink, this ) ); 1809 this._setImageMaxHeight(); 1810 1811 if ( this.win.scrollTop() > 0 ){ 1812 this._doShrink(); 1813 } 1814 1815 } else { 1816 this.header.parent().css( 'padding-top', '0' ); 1817 this.win.off( 'scroll.fl-theme-builder-header-shrink' ); 1818 this._removeShrink(); 1819 this._removeImageMaxHeight(); 1820 } 1821 }, 1822 1823 /** 1824 * Shrinks the header when the page is scrolled. 1825 * 1826 * @since 1.0 1827 * @access private 1828 * @method _doShrink 1829 */ 1830 _doShrink: function( e ) 1831 { 1832 var winTop = this.win.scrollTop(), 1833 headerTop = this.header.data('original-top'), 1834 headerHeight = this.header.data('original-height'), 1835 shrinkImageHeight = this.header.data('shrink-image-height'), 1836 windowSize = this.win.width(), 1837 makeSticky = this._makeWindowSticky( windowSize ),
1838 hasClass = this.header.hasClass( 'fl-theme-builder-header-shrink' ); 1839 1840 1841 if ( this.hasAdminBar ) { 1842 winTop += 32; 1843 } 1844 1845 if ( makeSticky && ( winTop > headerTop + headerHeight ) ) { 1846 if ( ! hasClass ) { 1847 1848 this.header.addClass( 'fl-theme-builder-header-shrink' ); 1849 1850 // Shrink images but don't include lightbox and menu images. 1851 this.header.find('img').each( function( i ) { 1852 var image = $( this ), 1853 maxMegaMenu = image.closest( '.max-mega-menu' ).length, 1854 imageInLightbox = image.closest( '.fl-button-lightbox-content' ).length, 1855 imageInNavMenu = image.closest( 'li.menu-item' ).length; 1856 1857 if ( ! ( imageInLightbox || imageInNavMenu || maxMegaMenu ) ) { 1858 image.css( 'max-height', shrinkImageHeight ); 1859 } 1860 1861 }); 1862 1863 this.header.find( '.fl-row-content-wrap' ).each( function() { 1864 1865 var row = $( this ); 1866 1867 if ( parseInt( row.css( 'padding-bottom' ) ) > 5 ) { 1868 row.addClass( 'fl-theme-builder-header-shrink-row-bottom' ); 1869 } 1870 1871 if ( parseInt( row.css( 'padding-top' ) ) > 5 ) { 1872 row.addClass( 'fl-theme-builder-header-shrink-row-top' ); 1873 } 1874 } ); 1875 1876 this.header.find( '.fl-module' ).each( function() { 1877 1878 var module = $( this ).find( '.fl-module-content' ).length ? $( this ).find( '.fl-module-content' ) : $( this ); 1879 1880 if ( parseInt( module.css( 'margin-bottom' ) ) > 5 ) { 1881 module.addClass( 'fl-theme-builder-header-shrink-module-bottom' ); 1882 } 1883 1884 if ( parseInt( module.css( 'margin-top' ) ) > 5 ) { 1885 module.addClass( 'fl-theme-builder-header-shrink-module-top' ); 1886 } 1887 } ); 1888 } 1889 } else if (hasClass) { 1890 this.header.find( 'img' ).css( 'max-height', '' ); 1891 this._removeShrink(); 1892 } 1893 1894 // Fixes Shrink header issue with BB Theme when window is scrolled then resized and back. 1895 if ( 'undefined' === typeof( e ) && $('body').hasClass( 'fl-fixed-width' ) ) { 1896 if ( ! this.overlay ) { 1897 this._adjustHeaderHeight(); 1898 } 1899 } 1900 1901 }, 1902 1903 /** 1904 * Removes the header shrink effect. 1905 * 1906 * @since 1.0 1907 * @access private 1908 * @method _removeShrink 1909 */ 1910 _removeShrink: function() 1911 { 1912 var rows = this.header.find( '.fl-row-content-wrap' ), 1913 modules = this.header.find('.fl-module, .fl-module-content'); 1914 1915 rows.removeClass( 'fl-theme-builder-header-shrink-row-bottom' ); 1916 rows.removeClass( 'fl-theme-builder-header-shrink-row-top' ); 1917 modules.removeClass( 'fl-theme-builder-header-shrink-module-bottom' ); 1918 modules.removeClass( 'fl-theme-builder-header-shrink-module-top' ); 1919 this.header.removeClass( 'fl-theme-builder-header-shrink' ); 1920 }, 1921 1922 /** 1923 * Adds max height to images in modules for smooth scrolling. 1924 * 1925 * @since 1.1.1 1926 * @access private 1927 * @method _setImageMaxHeight 1928 */ 1929 _setImageMaxHeight: function() 1930 { 1931 var head = $( 'head' ), 1932 stylesId = 'fl-header-styles-' + this.header.data( 'post-id' ), 1933 styles = '', 1934 images = this.header.find( '.fl-module img' ); 1935 1936 if ( $( '#' + stylesId ).length ) { 1937 return; 1938 } 1939 1940 images.each( function( i ) { 1941 var image = $( this ), 1942 height = image.height(), 1943 node = image.closest( '.fl-module' ).data( 'node' ), 1944 className = 'fl-node-' + node + '-img-' + i, 1945 maxMegaMenu = image.closest( '.max-mega-menu' ).length, 1946 imageInLightbox = image.closest( '.fl-button-lightbox-content' ).length, 1947 imageInNavMenu = image.closest( 'li.menu-item' ).length; 1948 1949 if ( ! ( imageInLightbox || imageInNavMenu || maxMegaMenu ) ) { 1950 image.addClass( className ); 1951 styles += '.' + className + ' { max-height: ' + ( height ? height : image[0].height ) + 'px }'; 1952 } 1953 1954 } ); 1955 1956 if ( '' !== styles ) { 1957 head.append( '<style id="' + stylesId + '">' + styles + '</style>' ); 1958 } 1959 }, 1960 1961 /** 1962 * Removes max height on images in modules for smooth scrolling. 1963 * 1964 * @since 1.1.1 1965 * @access private 1966 * @method _removeImageMaxHeight 1967 */ 1968 _removeImageMaxHeight: function() 1969 { 1970 $( '#fl-header-styles-' + this.header.data( 'post-id' ) ).remove(); 1971 }, 1972 }; 1973 1974 $( function() { FLThemeBuilderHeaderLayout.init(); } ); 1975 1976})(jQuery); 1977
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.