1var wpAjaxUrl = 'https://www.obdlink.com/wp-admin/admin-ajax.php';var flBuilderUrl = 'https://www.obdlink.com/wp-content/plugins/bb-plugin/';var FLBuilderLayoutConfig = { 2 anchorLinkAnimations : { 3 duration : 1000, 4 easing : 'swing', 5 offset : 100 6 }, 7 paths : { 8 pluginUrl : 'https://www.obdlink.com/wp-content/plugins/bb-plugin/', 9 wpAjaxUrl : 'https://www.obdlink.com/wp-admin/admin-ajax.php' 10 }, 11 breakpoints : { 12 small : 768, 13 medium : 992, 14 large : 1200 }, 15 waypoint: { 16 offset: 80 17 }, 18 emptyColWidth: '0%', 19 i18n : { 20 opensInNewTab : '(opens in new tab)' 21 } 22}; 23(function($){ 24 25 if(typeof FLBuilderLayout != 'undefined') { 26 return; 27 } 28 29 /** 30 * Helper class with generic logic for a builder layout. 31 * 32 * @class FLBuilderLayout 33 * @since 1.0 34 */ 35 FLBuilderLayout = { 36 37 /** 38 * Initializes a builder layout. 39 * 40 * @since 1.0 41 * @method init 42 */ 43 init: function() 44 { 45 // Destroy existing layout events. 46 FLBuilderLayout._destroy(); 47 48 // Init CSS classes. 49 FLBuilderLayout._initClasses(); 50 51 // Init backgrounds.
52 FLBuilderLayout._initBackgrounds(); 53 54 // Init buttons. 55 FLBuilderLayout._initButtons(); 56 57 // Init row shape layer height. 58 FLBuilderLayout._initRowShapeLayerHeight(); 59 60 // Init the new tab notice on links opening in a new tab. 61 FLBuilderLayout._initLinkNotices(); 62 63 // Only init if the builder isn't active. 64 if ( 0 === $('.fl-builder-edit').length ) { 65 66 // Init anchor links. 67 FLBuilderLayout._initAnchorLinks(); 68 69 // Init the browser hash. 70 FLBuilderLayout._initHash(); 71 72 // Init forms. 73 FLBuilderLayout._initForms(); 74 75 FLBuilderLayout._reorderMenu(); 76 } 77 else { 78 FLBuilderLayout._initNestedColsWidth(); 79 } 80 $('body').removeClass( 'fl-no-js' ); 81 }, 82 83 /** 84 * Public method for refreshing Wookmark or MosaicFlow galleries 85 * within an element. 86 * 87 * @since 1.7.4 88 * @method refreshGalleries 89 */ 90 refreshGalleries: function( element ) 91 { 92 var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ), 93 mfContent = $element.find( '.fl-mosaicflow-content' ), 94 wmContent = $element.find( '.fl-gallery' ), 95 mfObject = null; 96 97 if ( mfContent ) { 98 99 mfObject = mfContent.data( 'mosaicflow' ); 100 101 if ( mfObject ) { 102 mfObject.columns = $( [] ); 103 mfObject.columnsHeights = []; 104 mfContent.data( 'mosaicflow', mfObject ); 105 mfContent.mosaicflow( 'refill' ); 106 } 107 } 108 if ( wmContent ) { 109 wmContent.trigger( 'refreshWookmark' ); 110 } 111 }, 112 113 /** 114 * Public method for refreshing Masonry within an element 115 * 116 * @since 1.8.1 117 * @method refreshGridLayout 118 */ 119 refreshGridLayout: function( element ) 120 { 121 var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ), 122 msnryContent = $element.find('.masonry'); 123 124 if ( msnryContent.length ) { 125 msnryContent.masonry('layout'); 126 } 127 }, 128 129 /** 130 * Public method for reloading BxSlider within an element 131 * 132 * @since 1.8.1 133 * @method reloadSlider 134 */ 135 reloadSlider: function( content ) 136 { 137 var $content = 'undefined' == typeof content ? $('body') : $(content); 138 139 // reload sliders. 140 if ($content.find('.bx-viewport > div').length > 0) { 141 $.each($content.find('.bx-viewport > div'), function (key, slider) { 142 setTimeout(function () { 143 $(slider).data('bxSlider').reloadSlider(); 144 }, 100); 145 }); 146 } 147 }, 148 149 /** 150 * Public method for resizing WP audio player 151 * 152 * @since 1.8.2 153 * @method resizeAudio 154 */ 155 resizeAudio: function( element ) 156 { 157 var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ), 158 audioPlayers = $element.find('.wp-audio-shortcode.mejs-audio'), 159 player = null, 160 mejsPlayer = null, 161 rail = null, 162 railWidth = 400; 163 164 if ( audioPlayers.length && typeof mejs !== 'undefined' ) { 165 audioPlayers.each(function(){ 166 player = $(this); 167 mejsPlayer = mejs.players[player.attr('id')]; 168 rail = player.find('.mejs-controls .mejs-time-rail'); 169 var innerMejs = player.find('.mejs-inner'), 170 total = player.find('.mejs-controls .mejs-time-total'); 171 172 if ( typeof mejsPlayer !== 'undefined' ) { 173 railWidth = Math.ceil(player.width() * 0.8); 174 175 if ( innerMejs.length ) { 176 177 rail.css('width', railWidth +'px!important'); 178 //total.width(rail.width() - 10); 179 180 mejsPlayer.options.autosizeProgress = true; 181 182 // webkit has trouble doing this without a delay 183 setTimeout(function () { 184 mejsPlayer.setControlsSize(); 185 }, 50); 186 187 player.find('.mejs-inner').css({ 188 visibility: 'visible', 189 height: 'inherit' 190 }); 191 } 192 } 193 }); 194 } 195 }, 196 197 /** 198 * Public method for preloading WP audio player when it's inside the hidden element 199 * 200 * @since 1.8.2 201 * @method preloadAudio 202 */ 203 preloadAudio: function(element) 204 { 205 var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ), 206 contentWrap = $element.closest('.fl-accordion-item'), 207 audioPlayers = $element.find('.wp-audio-shortcode.mejs-audio'); 208 209 if ( ! contentWrap.hasClass('fl-accordion-item-active') && audioPlayers.find('.mejs-inner').length ) { 210 audioPlayers.find('.mejs-inner').css({ 211 visibility : 'hidden', 212 height: 0 213 }); 214 } 215 }, 216 217 /** 218 * Public method for resizing slideshow momdule within the tab 219 * 220 * @since 1.10.5 221 * @method resizeSlideshow 222 */ 223 resizeSlideshow: function(){ 224 if(typeof YUI !== 'undefined') { 225 YUI().use('node-event-simulate', function(Y) { 226 Y.one(window).simulate("resize"); 227 }); 228 } 229 }, 230 231 /** 232 * Public method for reloading an embedded Google Map within the tabs or hidden element. 233 * 234 * @since 2.2 235 * @method reloadGoogleMap 236 */ 237 reloadGoogleMap: function(element){ 238 var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
239 googleMap = $element.find( 'iframe[src*="google.com/maps"]' ); 240 241 if ( googleMap.length ) { 242 googleMap.attr( 'src', function(i, val) { 243 return val; 244 }); 245 } 246 }, 247 248 /** 249 * Unbinds builder layout events. 250 * 251 * @since 1.0 252 * @access private 253 * @method _destroy 254 */ 255 _destroy: function() 256 { 257 var win = $(window); 258 259 win.off('scroll.fl-bg-parallax'); 260 win.off('resize.fl-bg-video'); 261 }, 262 263 /** 264 * Checks to see if the current device has touch enabled. 265 * 266 * @since 1.0 267 * @access private 268 * @method _isTouch 269 * @return {Boolean} 270 */ 271 _isTouch: function() 272 { 273 if(('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch)) { 274 return true; 275 } 276 277 return false; 278 }, 279 280 /** 281 * Checks to see if the current device is mobile. 282 * 283 * @since 1.7 284 * @access private 285 * @method _isMobile 286 * @return {Boolean} 287 */ 288 _isMobile: function() 289 { 290 return /Mobile|Android|Silk\/|Kindle|BlackBerry|Opera Mini|Opera Mobi|webOS/i.test( navigator.userAgent ); 291 }, 292 293 /** 294 * Initializes builder body classes. 295 * 296 * @since 1.0 297 * @access private 298 * @method _initClasses 299 */ 300 _initClasses: function() 301 { 302 var body = $( 'body' ), 303 ua = navigator.userAgent; 304 305 // Add the builder body class (skip on block-only frontend to avoid layout break). 306 if ( ! body.hasClass( 'fl-builder-blocks-only' ) && ! body.hasClass( 'archive' ) && $( '.fl-builder-content-primary' ).length > 0 ) { 307 body.addClass('fl-builder'); 308 } 309 310 // Add the builder touch body class. 311 if(FLBuilderLayout._isTouch()) { 312 body.addClass('fl-builder-touch'); 313 } 314 315 // Add the builder mobile body class. 316 if(FLBuilderLayout._isMobile()) { 317 body.addClass('fl-builder-mobile'); 318 } 319 320 if ( $(window).width() < FLBuilderLayoutConfig.breakpoints.small ) { 321 body.addClass( 'fl-builder-breakpoint-small' ); 322 } 323 324 if ( $(window).width() > FLBuilderLayoutConfig.breakpoints.small && $(window).width() < FLBuilderLayoutConfig.breakpoints.medium ) { 325 body.addClass( 'fl-builder-breakpoint-medium' ); 326 } 327 328 if ( $(window).width() > FLBuilderLayoutConfig.breakpoints.medium && $(window).width() < FLBuilderLayoutConfig.breakpoints.large ) { 329 body.addClass( 'fl-builder-breakpoint-large' ); 330 } 331 332 if ( $(window).width() > FLBuilderLayoutConfig.breakpoints.large ) { 333 body.addClass( 'fl-builder-breakpoint-default' ); 334 } 335 336 // IE11 body class. 337 if ( ua.indexOf( 'Trident/7.0' ) > -1 && ua.indexOf( 'rv:11.0' ) > -1 ) { 338 body.addClass( 'fl-builder-ie-11' ); 339 } 340 }, 341 342 /** 343 * Initializes builder node backgrounds that require 344 * additional JavaScript logic such as parallax. 345 * 346 * @since 1.1.4 347 * @access private
348 * @method _initBackgrounds 349 */ 350 _initBackgrounds: function() 351 { 352 var win = $(window); 353 354 // Init parallax backgrounds. 355 if($('.fl-row-bg-parallax').length > 0 && !FLBuilderLayout._isMobile()) { 356 FLBuilderLayout._scrollParallaxBackgrounds(); 357 FLBuilderLayout._initParallaxBackgrounds(); 358 win.on('resize.fl-bg-parallax', FLBuilderLayout._initParallaxBackgrounds); 359 win.on('scroll.fl-bg-parallax', FLBuilderLayout._scrollParallaxBackgrounds); 360 } 361 362 // Init video backgrounds. 363 if($('.fl-bg-video').length > 0) { 364 FLBuilderLayout._initBgVideos(); 365 FLBuilderLayout._resizeBgVideos(); 366 367 // Ensure FLBuilderLayout._resizeBgVideos() is only called once on window resize. 368 var resizeBGTimer = null; 369 win.on('resize.fl-bg-video', function(e){ 370 clearTimeout( resizeBGTimer ); 371 resizeBGTimer = setTimeout(function() { 372 FLBuilderLayout._resizeBgVideos(e); 373 }, 100 ); 374 }); 375 } 376 }, 377 378 /** 379 * Initializes all buttons in a layout. 380 * 381 * @since 2.10 382 * @access private 383 * @method _initButtons 384 */ 385 _initButtons: function() 386 { 387 // Trigger click event on Enter or Space key press for deprecated button markup. 388 $('a.fl-button[role="button"]').on('keydown', function(event) { 389 if (event.key === 'Enter' || event.key === ' ') { 390 // Necessary to prevent the default behavior of the space key from scrolling the page 391 event.preventDefault(); 392 $(this).trigger('click'); 393 } 394 }); 395 }, 396 397 /** 398 * Initializes all parallax backgrounds in a layout. 399 * 400 * @since 1.1.4 401 * @access private 402 * @method _initParallaxBackgrounds 403 */ 404 _initParallaxBackgrounds: function() 405 { 406 $('.fl-row-bg-parallax').each(FLBuilderLayout._initParallaxBackground); 407 }, 408 409 /** 410 * Initializes a single parallax background. 411 * 412 * @since 1.1.4 413 * @access private 414 * @method _initParallaxBackgrounds 415 */ 416 _initParallaxBackground: function() 417 { 418 var row = $(this), 419 content = row.find('> .fl-row-content-wrap'), 420 winWidth = $(window).width(), 421 screenSize = '', 422 imageSrc = { 423 default: '', 424 medium: '', 425 responsive: '', 426 }; 427 428 imageSrc.default = row.data('parallax-image') || ''; 429 imageSrc.medium = row.data('parallax-image-medium') || imageSrc.default; 430 imageSrc.responsive = row.data('parallax-image-responsive') || imageSrc.medium; 431 432 if (winWidth > FLBuilderLayoutConfig.breakpoints.medium) { 433 screenSize = 'default'; 434 } else if (winWidth > FLBuilderLayoutConfig.breakpoints.small && winWidth <= FLBuilderLayoutConfig.breakpoints.medium ) { 435 screenSize = 'medium'; 436 } else if (winWidth <= FLBuilderLayoutConfig.breakpoints.small) { 437 screenSize = 'responsive'; 438 } 439 440 content.css('background-image', 'url(' + imageSrc[screenSize] + ')'); 441 row.data('current-image-loaded', screenSize ); 442 443 }, 444 445 /** 446 * Fires when the window is scrolled to adjust 447 * parallax backgrounds. 448 * 449 * @since 1.1.4 450 * @access private 451 * @method _scrollParallaxBackgrounds 452 */ 453 _scrollParallaxBackgrounds: function() 454 { 455 $('.fl-row-bg-parallax').each(FLBuilderLayout._scrollParallaxBackground); 456 }, 457 458 /** 459 * Fires when the window is scrolled to adjust 460 * a single parallax background. 461 * 462 * @since 1.1.4 463 * @access private 464 * @method _scrollParallaxBackground 465 */ 466 _scrollParallaxBackground: function() 467 { 468 var win = $(window), 469 row = $(this), 470 content = row.find('> .fl-row-content-wrap'), 471 speed = row.data('parallax-speed'), 472 offset = content.offset(), 473 yPos = -((win.scrollTop() - offset.top) / speed), 474 initialOffset = ( row.data('parallax-offset') != null ) ? row.data('parallax-offset') : 0, 475 totalOffset = yPos - initialOffset; 476 477 content.css('background-position', 'center ' + totalOffset + 'px'); 478 }, 479 480 /** 481 * Initializes all video backgrounds. 482 * 483 * @since 1.6.3.3 484 * @access private 485 * @method _initBgVideos 486 */ 487 _initBgVideos: function() 488 { 489 $('.fl-bg-video').each(FLBuilderLayout._initBgVideo); 490 }, 491 492 /** 493 * Initializes a video background. 494 * 495 * @since 1.6.3.3 496 * @access private 497 * @method _initBgVideo 498 */ 499 _initBgVideo: function() 500 { 501 var wrap = $( this ), 502 width = wrap.data( 'width' ), 503 height = wrap.data( 'height' ), 504 mp4 = wrap.data( 'mp4' ), 505 youtube = wrap.data( 'youtube'), 506 vimeo = wrap.data( 'vimeo'), 507 mp4Type = wrap.data( 'mp4-type' ), 508 webm = wrap.data( 'webm' ), 509 webmType = wrap.data( 'webm-type' ), 510 fallback = wrap.data( 'fallback' ), 511 loaded = wrap.data( 'loaded' ), 512 videoMobile = wrap.data( 'video-mobile' ), 513 playPauseButton = wrap.find('.fl-bg-video-play-pause-control'), 514 fallbackTag = '', 515 videoTag = null, 516 mp4Tag = null, 517 webmTag = null; 518 519 // Return if the video has been loaded for this row. 520 if ( loaded ) { 521 return; 522 } 523
524 videoTag = $( '<video autoplay loop muted playsinline></video>' ); 525 526 /** 527 * Add poster image (fallback image) 528 */ 529 if( 'undefined' != typeof fallback && '' != fallback ) { 530 videoTag.attr( 'poster', 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' ) 531 videoTag.css({ 532 backgroundImage: 'url("' + fallback + '")', 533 backgroundColor: 'transparent', 534 backgroundRepeat: 'no-repeat', 535 backgroundSize: 'cover', 536 backgroundPosition: 'center center', 537 }) 538 } 539 540 // MP4 Source Tag 541 if ( 'undefined' != typeof mp4 && '' != mp4 ) { 542 543 mp4Tag = $( '<source />' ); 544 mp4Tag.attr( 'src', mp4 ); 545 mp4Tag.attr( 'type', mp4Type ); 546 547 videoTag.append( mp4Tag ); 548 } 549 // WebM Source Tag 550 if ( 'undefined' != typeof webm && '' != webm ) { 551 552 webmTag = $( '<source />' ); 553 webmTag.attr( 'src', webm ); 554 webmTag.attr( 'type', webmType ); 555 556 videoTag.append( webmTag ); 557 } 558 559 // This is either desktop, or mobile is enabled. 560 if ( ! FLBuilderLayout._isMobile() || ( FLBuilderLayout._isMobile() && "yes" == videoMobile ) ) { 561 if ( 'undefined' != typeof youtube ) { 562 FLBuilderLayout._initYoutubeBgVideo.apply( this ); 563 } 564 else if ( 'undefined' != typeof vimeo ) { 565 FLBuilderLayout._initVimeoBgVideo.apply( this ); 566 } 567 else { 568 wrap.append( videoTag ); 569 570 if ( playPauseButton.length > 0 ) { 571 var video = videoTag[0]; 572 573 playPauseButton.on( 'click', { video: video }, function(e) { 574 var video = e.data.video; 575 576 if ( video.paused ) { 577 video.play(); 578 } else { 579 video.pause(); 580 } 581 } ); 582 583 $( video ).on( 'play playing', function () { 584 playPauseButton.removeClass( 'fa-play' ).addClass( 'fa-pause' ); 585 } ); 586 587 $( video ).on( 'pause ended waiting', function () { 588 playPauseButton.removeClass( 'fa-pause' ).addClass( 'fa-play' ); 589 } ); 590 } 591 } 592 } 593 else { 594 // if we are here, it means we are on mobile and NO is set so remove video src and use fallback 595 videoTag.attr('src', '') 596 wrap.append( videoTag ); 597 } 598 599 // Mark this video as loaded. 600 wrap.data('loaded', true); 601 }, 602 603 /** 604 * Initializes Youtube video background 605 * 606 * @since 1.9 607 * @access private 608 * @method _initYoutubeBgVideo 609 */ 610 _initYoutubeBgVideo: function() 611 { 612 var playerWrap = $(this), 613 videoId = playerWrap.data('video-id'), 614 videoPlayer = playerWrap.find('.fl-bg-video-player'), 615 enableAudio = playerWrap.data('enable-audio'), 616 audioButton = playerWrap.find('.fl-bg-video-audio'), 617 playPauseButton = playerWrap.find('.fl-bg-video-play-pause-control'), 618 startTime = 'undefined' !== typeof playerWrap.data('start') ? playerWrap.data('start') : 0, 619 startTime = 'undefined' !== typeof playerWrap.data('t') && startTime === 0 ? playerWrap.data('t') : startTime, 620 endTime = 'undefined' !== typeof playerWrap.data('end') ? playerWrap.data('end') : 0, 621 loop = 'undefined' !== typeof playerWrap.data('loop') ? playerWrap.data('loop') : 1, 622 stateCount = 0, 623 player,fallback_showing; 624 625 if ( videoId ) { 626 fallback = playerWrap.data('fallback') || false 627 if( fallback ) { 628 playerWrap.find('iframe').remove() 629 fallbackTag = $( '<div></div>' ); 630 fallbackTag.addClass( 'fl-bg-video-fallback' ); 631 fallbackTag.css( 'background-image', 'url(' + playerWrap.data('fallback') + ')' ); 632 fallbackTag.css( 'background-size', 'cover' ); 633 fallbackTag.css( 'transition', 'background-image 1s') 634 playerWrap.append( fallbackTag ); 635 fallback_showing = true; 636 } 637 FLBuilderLayout._onYoutubeApiReady( function( YT ) { 638 setTimeout( function() { 639 640 player = new YT.Player( videoPlayer[0], { 641 videoId: videoId, 642 events: { 643 onReady: function(event) { 644 if ( "no" === enableAudio || FLBuilderLayout._isMobile() ) { 645 event.target.mute(); 646 } 647 else if ( "yes" === enableAudio && event.target.isMuted ) { 648 event.target.unMute(); 649 } 650 651 // Store an instance to a parent 652 playerWrap.data('YTPlayer', player); 653 FLBuilderLayout._resizeYoutubeBgVideo.apply(playerWrap); 654 655 // Queue the video. 656 event.target.playVideo(); 657 658 if ( audioButton.length > 0 && ! FLBuilderLayout._isMobile() ) {
659 audioButton.on( 'click', {button: audioButton, player: player}, FLBuilderLayout._toggleBgVideoAudio ); 660 } 661 662 if ( playPauseButton.length > 0 ) { 663 playPauseButton.on( 'click', {player: player}, function(e) { 664 var player = e.data.player; 665 666 if ( 1 === player.getPlayerState() ) { 667 player.pauseVideo(); 668 } else { 669 player.playVideo(); 670 } 671 }); 672 } 673 }, 674 onStateChange: function( event ) { 675 676 if ( event.data === 1 ) { 677 if ( fallback_showing ) { 678 $( '.fl-bg-video-fallback' ).css( 'background-image', 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)' ) 679 } 680 } 681 // Manual check if video is not playable in some browsers. 682 // StateChange order: [-1, 3, -1] 683 if ( stateCount < 4 ) { 684 stateCount++; 685 } 686 687 // Comply with the audio policy in some browsers like Chrome and Safari. 688 if ( stateCount > 1 && -1 === event.data && "yes" === enableAudio ) { 689 player.mute(); 690 player.playVideo(); 691 audioButton.show(); 692 } 693 694 if ( event.data === YT.PlayerState.ENDED && 1 === loop ) { 695 if ( startTime > 0 ) { 696 player.seekTo( startTime ); 697 } 698 else { 699 player.playVideo(); 700 } 701 } 702 703 if ( event.data === YT.PlayerState.PLAYING ) { 704 playPauseButton.removeClass( 'fa-play' ).addClass( 'fa-pause' ); 705 } else if ( event.data === YT.PlayerState.PAUSED ) { 706 playPauseButton.removeClass( 'fa-pause' ).addClass( 'fa-play' ); 707 } else if ( event.data === YT.PlayerState.BUFFERING ) { 708 playPauseButton.removeClass( 'fa-play' ).addClass( 'fa-pause' ); 709 } else if ( event.data === YT.PlayerState.CUED ) { 710 playPauseButton.removeClass( 'fa-pause' ).addClass( 'fa-play' ); 711 } else if ( event.data === YT.PlayerState.ENDED ) { 712 playPauseButton.removeClass( 'fa-pause' ).addClass( 'fa-play' ); 713 } 714 }, 715 onError: function(event) { 716 console.info('YT Error: ' + event.data) 717 FLBuilderLayout._onErrorYoutubeVimeo(playerWrap) 718 } 719 }, 720 playerVars: { 721 playsinline: FLBuilderLayout._isMobile() ? 1 : 0, 722 controls: 0, 723 showinfo: 0, 724 rel : 0, 725 start: startTime, 726 end: endTime, 727 } 728 } ); 729 }, 1 ); 730 } ); 731 } 732 }, 733 734 /** 735 * On youtube or vimeo error show the fallback image if available. 736 * @since 2.0.7 737 */ 738 _onErrorYoutubeVimeo: function(playerWrap) { 739 740 fallback = playerWrap.data('fallback') || false 741 if( ! fallback ) { 742 return false; 743 } 744 playerWrap.find('iframe').remove() 745 fallbackTag = $( '<div></div>' ); 746 fallbackTag.addClass( 'fl-bg-video-fallback' ); 747 fallbackTag.css( 'background-image', 'url(' + playerWrap.data('fallback') + ')' ); 748 playerWrap.append( fallbackTag ); 749 }, 750 751 /** 752 * Check if Youtube API has been downloaded 753 * 754 * @since 1.9 755 * @access private 756 * @method _onYoutubeApiReady 757 * @param {Function} callback Method to call when YT API has been loaded 758 */ 759 _onYoutubeApiReady: function( callback ) { 760 if ( window.YT && YT.loaded ) { 761 callback( YT ); 762 } else { 763 // If not ready check again by timeout.. 764 setTimeout( function() { 765 FLBuilderLayout._onYoutubeApiReady( callback ); 766 }, 350 ); 767 } 768 }, 769 770 /** 771 * Initializes Vimeo video background 772 * 773 * @since 1.9 774 * @access private 775 * @method _initVimeoBgVideo 776 */ 777 _initVimeoBgVideo: function() 778 { 779 var playerWrap = $(this), 780 videoId = playerWrap.data('video-id'), 781 videoHash = playerWrap.data('video-hash'), 782 videoPlayer = playerWrap.find('.fl-bg-video-player'), 783 enableAudio = playerWrap.data('enable-audio'), 784 audioButton = playerWrap.find('.fl-bg-video-audio'), 785 playPauseButton = playerWrap.find('.fl-bg-video-play-pause-control'), 786 playerState = '', 787 player, 788 width = playerWrap.outerWidth(), 789 ua = navigator.userAgent; 790 791 if ( typeof Vimeo !== 'undefined' && videoId ) { 792 793 const vimOptions = { 794 loop : true, 795 title : false, 796 portrait : false, 797 background : true, 798 autopause : false, 799 muted : true, 800 }; 801 802 if ( videoHash.length ) { 803 vimOptions.url = `https://player.vimeo.com/video/${ videoId }?h=${ videoHash }`; 804 } else { 805 vimOptions.id = videoId; 806 } 807 808 player = new Vimeo.Player(videoPlayer[0], vimOptions ); 809 playerWrap.data('VMPlayer', player); 810 if ( "no" === enableAudio ) { 811 player.setVolume(0); 812 } 813 else if ("yes" === enableAudio ) { 814 // Chrome, Safari, Firefox have audio policy restrictions for autoplay videos. 815 if ( ua.indexOf("Safari") > -1 || ua.indexOf("Chrome") > -1 || ua.indexOf("Firefox") > -1 ) { 816 player.setVolume(0); 817 audioButton.show(); 818 } 819 else { 820 player.setVolume(1); 821 } 822 } 823 824 player.play().catch(function(error) { 825 FLBuilderLayout._onErrorYoutubeVimeo(playerWrap) 826 }); 827 828 if ( audioButton.length > 0 ) {
829 audioButton.on( 'click', {button: audioButton, player: player}, FLBuilderLayout._toggleBgVideoAudio ); 830 } 831 832 player.on( 'play', function() { 833 playerState = 'play'; 834 playPauseButton.removeClass( 'fa-play' ).addClass( 'fa-pause' ); 835 } ); 836 player.on( 'pause', function() { 837 playerState = 'pause'; 838 playPauseButton.removeClass( 'fa-pause' ).addClass( 'fa-play' ); 839 } ); 840 player.on( 'ended', function() { 841 playerState = 'ended'; 842 playPauseButton.removeClass( 'fa-pause' ).addClass( 'fa-play' ); 843 } ); 844 player.on( 'bufferstart', function() { 845 playerState = 'bufferstart'; 846 playPauseButton.removeClass( 'fa-play' ).addClass( 'fa-pause' ); 847 } ); 848 849 if ( playPauseButton.length > 0 ) { 850 playPauseButton.on( 'click', { player: player }, function( e ) { 851 var player = e.data.player; 852 853 if ( playerState === 'play' ) { 854 player.pause(); 855 } else { 856 player.play(); 857 } 858 } ); 859 } 860 } 861 }, 862 863 /** 864 * Mute / unmute audio on row's video background. 865 * It works for both Youtube and Vimeo. 866 * 867 * @since 2.1.3 868 * @access private 869 * @method _toggleBgVideoAudio 870 * @param {Object} e Method arguments 871 */ 872 _toggleBgVideoAudio: function( e ) { 873 var player = e.data.player, 874 control = e.data.button.find('.fl-audio-control'); 875 876 if ( control.hasClass( 'fa-volume-off' ) ) { 877 // Unmute 878 control 879 .removeClass( 'fa-volume-off' ) 880 .addClass( 'fa-volume-up' ); 881 e.data.button.find( '.fa-times' ).hide(); 882 883 if ( 'function' === typeof player.unMute ) { 884 player.unMute(); 885 } 886 else { 887 player.setVolume( 1 ); 888 } 889 } 890 else { 891 // Mute 892 control 893 .removeClass( 'fa-volume-up' ) 894 .addClass( 'fa-volume-off' ); 895 e.data.button.find( '.fa-times' ).show(); 896 897 if ( 'function' === typeof player.unMute ) { 898 player.mute(); 899 } 900 else { 901 player.setVolume( 0 ); 902 } 903 } 904 }, 905 906 /** 907 * Fires when there is an error loading a video 908 * background source and shows the fallback. 909 * 910 * @since 1.6.3.3 911 * @access private 912 * @method _videoBgSourceError 913 * @param {Object} e An event object 914 * @deprecated 2.0.3 915 */ 916 _videoBgSourceError: function( e ) 917 { 918 var source = $( e.target ), 919 wrap = source.closest( '.fl-bg-video' ), 920 vid = wrap.find( 'video' ), 921 fallback = wrap.data( 'fallback' ), 922 fallbackTag = ''; 923 source.remove(); 924 925 if ( vid.find( 'source' ).length ) { 926 // Don't show the fallback if we still have other sources to check. 927 return; 928 } else if ( '' !== fallback ) { 929 fallbackTag = $( '<div></div>' ); 930 fallbackTag.addClass( 'fl-bg-video-fallback' ); 931 fallbackTag.css( 'background-image', 'url(' + fallback + ')' ); 932 wrap.append( fallbackTag ); 933 vid.remove(); 934 } 935 }, 936 937 /** 938 * Fires when the window is resized to resize 939 * all video backgrounds. 940 * 941 * @since 1.1.4 942 * @access private 943 * @method _resizeBgVideos 944 */ 945 _resizeBgVideos: function() 946 { 947 $('.fl-bg-video').each( function() { 948 949 FLBuilderLayout._resizeBgVideo.apply( this ); 950 951 if ( $( this ).parent().find( 'img' ).length > 0 ) { 952 $( this ).parent().imagesLoaded( $.proxy( FLBuilderLayout._resizeBgVideo, this ) ); 953 } 954 } ); 955 }, 956 957 /** 958 * Fires when the window is resized to resize 959 * a single video background. 960 * 961 * @since 1.1.4 962 * @access private 963 * @method _resizeBgVideo 964 */ 965 _resizeBgVideo: function() 966 { 967 if ( 0 === $( this ).find( 'video' ).length && 0 === $( this ).find( 'iframe' ).length ) { 968 return; 969 } 970 971 var wrap = $(this), 972 wrapHeight = wrap.outerHeight(), 973 wrapWidth = wrap.outerWidth(), 974 vid = wrap.find('video'), 975 vidHeight = wrap.data('height'), 976 vidWidth = wrap.data('width'), 977 newWidth = wrapWidth, 978 newHeight = Math.round(vidHeight * wrapWidth/vidWidth), 979 newLeft = 0, 980 newTop = 0, 981 iframe = wrap.find('iframe'), 982 isRowFullHeight = $(this).closest('.fl-row-bg-video').hasClass('fl-row-full-height'), 983 vidCSS = { 984 top: '50%', 985 left: '50%', 986 transform: 'translate(-50%,-50%)', 987 }; 988 989 if ( vid.length ) { 990 if(vidHeight === '' || typeof vidHeight === 'undefined' || vidWidth === '' || typeof vidWidth === 'undefined') { 991 vid.css({ 992 'left' : '0px', 993 'top' : '0px', 994 'width' : newWidth + 'px' 995 }); 996 997 // Try to set the actual video dimension on 'loadedmetadata' when using URL as video source 998 vid.on('loadedmetadata', FLBuilderLayout._resizeOnLoadedMeta); 999 1000 return; 1001 } 1002 1003 if ( ! isRowFullHeight ) { 1004 if ( newHeight < wrapHeight ) { 1005 newHeight = wrapHeight; 1006 newWidth = vidHeight ? Math.round(vidWidth * wrapHeight/vidHeight) : newWidth; 1007 newLeft = -((newWidth - wrapWidth) / 2); 1008 } 1009 else { 1010 newTop = -((newHeight - wrapHeight)/2); 1011 } 1012 vidCSS = { 1013 left : newLeft + 'px', 1014 top : newTop + 'px', 1015 height : newHeight + 'px', 1016 width : newWidth + 'px', 1017 } 1018 } 1019 1020 vid.css( vidCSS ); 1021 1022 } 1023 else if ( iframe.length ) { 1024 1025 // Resize Youtube video player within iframe tag 1026 if ( typeof wrap.data('youtube') !== 'undefined' ) { 1027 FLBuilderLayout._resizeYoutubeBgVideo.apply(this); 1028 } 1029 } 1030 }, 1031 1032 /** 1033 * Fires when video meta has been loaded. 1034 * This will be Triggered when width/height attributes were not specified during video background resizing. 1035 * 1036 * @since 1.8.5 1037 * @access private 1038 * @method _resizeOnLoadedMeta 1039 */ 1040 _resizeOnLoadedMeta: function(){ 1041 var video = $(this), 1042 wrapHeight = video.parent().outerHeight(), 1043 wrapWidth = video.parent().outerWidth(), 1044 vidWidth = video[0].videoWidth, 1045 vidHeight = video[0].videoHeight, 1046 newHeight = Math.round(vidHeight * wrapWidth/vidWidth), 1047 newWidth = wrapWidth, 1048 newLeft = 0, 1049 newTop = 0; 1050 1051 if(newHeight < wrapHeight) { 1052 newHeight = wrapHeight; 1053 newWidth = Math.round(vidWidth * wrapHeight/vidHeight); 1054 newLeft = -((newWidth - wrapWidth)/2); 1055 } 1056 else { 1057 newTop = -((newHeight - wrapHeight)/2); 1058 } 1059 1060 video.parent().data('width', vidWidth); 1061 video.parent().data('height', vidHeight); 1062 1063 video.css({ 1064 'left' : newLeft + 'px', 1065 'top' : newTop + 'px', 1066 'width' : newWidth + 'px', 1067 'height' : newHeight + 'px' 1068 }); 1069 }, 1070 1071 /** 1072 * Fires when the window is resized to resize 1073 * a single Youtube video background. 1074 * 1075 * @since 1.9 1076 * @access private 1077 * @method _resizeYoutubeBgVideo 1078 */ 1079 _resizeYoutubeBgVideo: function() 1080 { 1081 var wrap = $(this), 1082 wrapWidth = wrap.outerWidth(), 1083 wrapHeight = wrap.outerHeight(), 1084 player = wrap.data('YTPlayer'), 1085 video = player ? player.getIframe() : null, 1086 aspectRatioSetting = '16:9', // Medium 1087 aspectRatioArray = aspectRatioSetting.split( ':' ),
1088 aspectRatio = aspectRatioArray[0] / aspectRatioArray[1], 1089 ratioWidth = wrapWidth / aspectRatio, 1090 ratioHeight = wrapHeight * aspectRatio, 1091 isWidthFixed = wrapWidth / wrapHeight > aspectRatio, 1092 width = isWidthFixed ? wrapWidth : ratioHeight, 1093 height = isWidthFixed ? ratioWidth : wrapHeight; 1094 1095 if ( video ) { 1096 $(video).width( width ).height( height ); 1097 } 1098 }, 1099 1100 /** 1101 * Opens a tab or accordion item if the browser hash is set 1102 * to the ID of one on the page. 1103 * 1104 * @since 1.6.0 1105 * @access private 1106 * @method _initHash 1107 */ 1108 _initHash: function() 1109 { 1110 var hash = window.location.hash.replace( '#', '' ).split( '/' ).shift(), 1111 element = null, 1112 tabs = null, 1113 responsiveLabel = null, 1114 tabIndex = null, 1115 label = null; 1116 1117 if ( '' !== hash ) { 1118 1119 try { 1120 1121 element = $( '#' + hash ); 1122 1123 if ( element.length > 0 ) { 1124 1125 if ( element.hasClass( 'fl-accordion-item' ) ) { 1126 setTimeout( function() { 1127 element.find( '.fl-accordion-button' ).trigger( 'click' ); 1128 }, 100 ); 1129 } 1130 if ( element.hasClass( 'fl-tabs-panel' ) ) { 1131 setTimeout( function() { 1132 tabs = element.closest( '.fl-tabs' ); 1133 responsiveLabel = element.find( '.fl-tabs-panel-label' ); 1134 tabIndex = responsiveLabel.data( 'index' ); 1135 label = tabs.find( '.fl-tabs-labels .fl-tabs-label[data-index=' + tabIndex + ']' ); 1136 1137 label[0].click(); 1138 FLBuilderLayout._scrollToElement(element); 1139 }, 100 ); 1140 } 1141 } 1142 } 1143 catch( e ) {} 1144 } 1145 }, 1146 1147 /** 1148 * Adds a visually-hidden "(opens in new tab)" notice to links 1149 * inside a builder layout that target a new tab, and removes it 1150 * from ones that don't. Runs on the live DOM only, across every 1151 * module, so the notice is never part of any saved content. Covers 1152 * both real anchors (`target` attribute) and non-anchor elements 1153 * like the Box module's `role="link"` div, which carries the same 1154 * intent via a `data-link-target` attribute instead. 1155 * 1156 * @since 2.12 1157 * @access private 1158 * @method _initLinkNotices 1159 */ 1160 _initLinkNotices: function() 1161 { 1162 $( '.fl-builder-content a[target], .fl-builder-content [role="link"][data-link-target]' ).each( function() { 1163 var link = $( this ), 1164 notice = link.children( 'span.fl-new-tab-notice' ), 1165 target = link.attr( 'target' ) || link.attr( 'data-link-target' ); 1166 1167 if ( '_blank' === target ) { 1168 if ( 0 === notice.length ) { 1169 link.append( '<span class="fl-new-tab-notice sr-only">' + FLBuilderLayoutConfig.i18n.opensInNewTab + '</span>' ); 1170 } 1171 } else if ( notice.length > 0 ) { 1172 notice.remove(); 1173 } 1174 }); 1175 }, 1176 1177 /** 1178 * Initializes all anchor links on the page for smooth scrolling. 1179 * 1180 * @since 1.4.9 1181 * @access private 1182 * @method _initAnchorLinks 1183 */ 1184 _initAnchorLinks: function() 1185 { 1186 $( 'a, [role="link"]' ).each( FLBuilderLayout._initAnchorLink ); 1187 }, 1188 1189 /** 1190 * Initializes a single anchor link for smooth scrolling. 1191 * 1192 * @since 1.4.9 1193 * @access private 1194 * @method _initAnchorLink 1195 */ 1196 _initAnchorLink: function() 1197 { 1198 var link = $( this ), 1199 href = link.data( 'url' ) ? link.data( 'url' ) : link.attr( 'href' ), 1200 target = link.data( 'url' ) ? new URL( href, window.location.href ) : this, 1201 loc = window.location, 1202 id = null, 1203 element = null, 1204 flNode = false; 1205 1206 if ( 'undefined' != typeof href && href.indexOf( '#' ) > -1 && link.closest('svg').length < 1 ) { 1207 1208 if ( loc.pathname.replace( /^\//, '' ) == target.pathname.replace( /^\//, '' ) && loc.hostname == target.hostname ) { 1209 1210 try { 1211 1212 id = href.split( '#' ).pop(); 1213 // If there is no ID then we have nowhere to look 1214 // Fixes a quirk in jQuery and FireFox 1215 if( ! id ) { 1216 return; 1217 } 1218 element = $( '#' + id ); 1219 1220 if ( element.length > 0 ) { 1221 flNode = element.hasClass( 'fl-row' ) || element.hasClass( 'fl-col' ) || element.hasClass( 'fl-module' ); 1222 if ( !element.hasClass( 'fl-no-scroll' ) && ( link.hasClass( 'fl-scroll-link' ) || flNode ) ) { 1223 $( link ).on( 'click', FLBuilderLayout._scrollToElementOnLinkClick ); 1224 } 1225 if ( element.hasClass( 'fl-accordion-item' ) ) { 1226 $( link ).on( 'click', FLBuilderLayout._scrollToAccordionOnLinkClick ); 1227 } 1228 if ( element.hasClass( 'fl-tabs-panel' ) ) { 1229 $( link ).on( 'click', FLBuilderLayout._scrollToTabOnLinkClick ); 1230 } 1231 } 1232 } 1233 catch( e ) {} 1234 } 1235 } 1236 }, 1237 1238 /** 1239 * Scrolls to an element when an anchor link is clicked. 1240 * 1241 * @since 1.4.9 1242 * @access private 1243 * @method _scrollToElementOnLinkClick 1244 * @param {Object} e An event object. 1245 * @param {Function} callback A function to call when the scroll is complete. 1246 */ 1247 _scrollToElementOnLinkClick: function( e, callback ) 1248 { 1249 var attribute = $( this ).data( 'url' ) ? $( this ).data( 'url' ) : $( this ).attr( 'href' ); 1250 var element = $( '#' + attribute.split( '#' ).pop() ); 1251 1252 FLBuilderLayout._scrollToElement( element, callback ); 1253 1254 e.preventDefault(); 1255 }, 1256 1257 /** 1258 * Scrolls to an element. 1259 * 1260 * @since 1.6.4.5 1261 * @access private 1262 * @method _scrollToElement 1263 * @param {Object} element The element to scroll to. 1264 * @param {Function} callback A function to call when the scroll is complete. 1265 */ 1266 _scrollToElement: function( element, callback ) 1267 { 1268 var config = FLBuilderLayoutConfig.anchorLinkAnimations, 1269 dest = 0, 1270 win = $( window ), 1271 doc = $( document ); 1272 1273 if ( element.length > 0 ) { 1274 1275 if ( 'fixed' === element.css('position') || 'fixed' === element.parent().css('position') ) { 1276 dest = element.position().top; 1277 } 1278 else if ( element.offset().top > doc.height() - win.height() ) { 1279 dest = doc.height() - win.height(); 1280 } 1281 else { 1282 dest = element.offset().top - config.offset; 1283 } 1284 1285 $( 'html, body' ).stop( true ).animate( { scrollTop: dest }, config.duration, config.easing, function() { 1286 1287 if ( 'undefined' != typeof callback ) { 1288 callback(); 1289 } 1290 1291 if ( undefined != element.attr( 'id' ) && window.location.hash !== '#' + element.attr( 'id' ) ) { 1292 1293 var firefox_version = window.navigator.userAgent.match( /Firefox\/(\d+)\./ ), 1294 firefox_version = firefox_version ? parseInt( firefox_version[1], 10 ) : null; 1295 1296 if ( firefox_version !== null && firefox_version < 135 ) { 1297 window.location.hash = element.attr( 'id' ); 1298 } else { 1299 if ( history.pushState ) { 1300 history.pushState( null, null, '#' + element.attr( 'id' ) ); 1301 } else { 1302 window.location.hash = element.attr( 'id' ); 1303 } 1304 } 1305 } 1306 } ); 1307 } 1308 }, 1309 1310 /** 1311 * Scrolls to an accordion item when a link is clicked. 1312 * 1313 * @since 1.5.9 1314 * @access private 1315 * @method _scrollToAccordionOnLinkClick 1316 * @param {Object} e An event object. 1317 */ 1318 _scrollToAccordionOnLinkClick: function( e ) 1319 { 1320 var element = $( '#' + $( this ).attr( 'href' ).split( '#' ).pop() ); 1321 1322 if ( element.length > 0 ) { 1323 1324 var callback = function() { 1325 if ( element ) { 1326 element.find( '.fl-accordion-button' ).trigger( 'click' ); 1327 element = false;
1328 } 1329 }; 1330 1331 FLBuilderLayout._scrollToElementOnLinkClick.call( this, e, callback ); 1332 } 1333 }, 1334 1335 /** 1336 * Scrolls to a tab panel when a link is clicked. 1337 * 1338 * @since 1.5.9 1339 * @access private 1340 * @method _scrollToTabOnLinkClick 1341 * @param {Object} e An event object. 1342 */ 1343 _scrollToTabOnLinkClick: function( e ) 1344 { 1345 var element = $( '#' + $( this ).attr( 'href' ).split( '#' ).pop() ), 1346 tabs = null, 1347 label = null, 1348 responsiveLabel = null; 1349 1350 if ( element.length > 0 ) { 1351 1352 tabs = element.closest( '.fl-tabs' ); 1353 responsiveLabel = element.find( '.fl-tabs-panel-label' ); 1354 tabIndex = responsiveLabel.data( 'index' ); 1355 label = tabs.find( '.fl-tabs-labels .fl-tabs-label[data-index=' + tabIndex + ']' ); 1356 1357 if ( responsiveLabel.is( ':visible' ) ) { 1358 1359 var callback = function() { 1360 if ( element ) { 1361 responsiveLabel.trigger( $.Event( 'click', { which: 1 } ) ); 1362 } 1363 }; 1364 1365 FLBuilderLayout._scrollToElementOnLinkClick.call( this, e, callback ); 1366 } 1367 else { 1368 label[0].click(); 1369 FLBuilderLayout._scrollToElement( element ); 1370 } 1371 1372 e.preventDefault(); 1373 } 1374 }, 1375 1376 /** 1377 * Initializes all builder forms on a page. 1378 * 1379 * @since 1.5.4 1380 * @access private 1381 * @method _initForms 1382 */ 1383 _initForms: function() 1384 { 1385 if ( ! FLBuilderLayout._hasPlaceholderSupport ) { 1386 $( '.fl-form-field input' ).each( FLBuilderLayout._initFormFieldPlaceholderFallback ); 1387 } 1388 1389 $( '.fl-form-field input' ).on( 'focus', FLBuilderLayout._clearFormFieldError ); 1390 }, 1391 1392 /** 1393 * Checks to see if the current device has HTML5 1394 * placeholder support. 1395 * 1396 * @since 1.5.4 1397 * @access private 1398 * @method _hasPlaceholderSupport 1399 * @return {Boolean} 1400 */ 1401 _hasPlaceholderSupport: function() 1402 { 1403 var input = document.createElement( 'input' ); 1404 1405 return 'undefined' != input.placeholder; 1406 }, 1407 1408 /** 1409 * Initializes the fallback for when placeholders aren't supported. 1410 * 1411 * @since 1.5.4 1412 * @access private 1413 * @method _initFormFieldPlaceholderFallback 1414 */ 1415 _initFormFieldPlaceholderFallback: function() 1416 { 1417 var field = $( this ), 1418 val = field.val(), 1419 placeholder = field.attr( 'placeholder' ); 1420 1421 if ( 'undefined' != placeholder && '' === val ) { 1422 field.val( placeholder ); 1423 field.on( 'focus', FLBuilderLayout._hideFormFieldPlaceholderFallback ); 1424 field.on( 'blur', FLBuilderLayout._showFormFieldPlaceholderFallback ); 1425 } 1426 }, 1427 1428 /** 1429 * Hides a fallback placeholder on focus. 1430 * 1431 * @since 1.5.4 1432 * @access private 1433 * @method _hideFormFieldPlaceholderFallback 1434 */ 1435 _hideFormFieldPlaceholderFallback: function() 1436 { 1437 var field = $( this ), 1438 val = field.val(), 1439 placeholder = field.attr( 'placeholder' ); 1440 1441 if ( val == placeholder ) { 1442 field.val( '' ); 1443 } 1444 }, 1445 1446 /** 1447 * Shows a fallback placeholder on blur. 1448 * 1449 * @since 1.5.4 1450 * @access private 1451 * @method _showFormFieldPlaceholderFallback 1452 */ 1453 _showFormFieldPlaceholderFallback: function() 1454 { 1455 var field = $( this ), 1456 val = field.val(), 1457 placeholder = field.attr( 'placeholder' ); 1458 1459 if ( '' === val ) { 1460 field.val( placeholder ); 1461 } 1462 }, 1463 1464 /** 1465 * Clears a form field error message. 1466 * 1467 * @since 1.5.4 1468 * @access private 1469 * @method _clearFormFieldError 1470 */ 1471 _clearFormFieldError: function() 1472 { 1473 var field = $( this ); 1474 1475 field.removeAttr( 'aria-invalid' ); 1476 field.removeClass( 'fl-form-error' ); 1477 const message = field.attr('aria-describedby'); 1478 message ? $( '#' + message ).hide() : field.siblings( '.fl-form-error-message' ).hide(); 1479 }, 1480 1481 /** 1482 * Init Row Shape Layer's height. 1483 * 1484 * @since 2.5.3 1485 * @access private 1486 * @method _initRowShapeLayerHeight 1487 */ 1488 _initRowShapeLayerHeight: function () { 1489 FLBuilderLayout._adjustRowShapeLayerHeight(); 1490 $( window ).on( 'resize', FLBuilderLayout._adjustRowShapeLayerHeight ); 1491 }, 1492 1493 /** 1494 * Set parent column width of nested columns when it's zero or blank. 1495 * 1496 * @since 2.7.4 1497 * @access private 1498 * @method _initNestedColsWidth 1499 */ 1500 _initNestedColsWidth: function() 1501 { 1502 var nestedCols = $( '.fl-col-has-cols' ); 1503 1504 if ( nestedCols.length <= 0 ) { 1505 return; 1506 } 1507 1508 $( nestedCols ).each( function(index, col ){ 1509 if ( $( col ).width() <= 0 ) { 1510 $( col ).css( 'width', FLBuilderLayoutConfig.emptyColWidth ); 1511 } 1512 }); 1513 }, 1514 1515 /** 1516 * Adjust Row Shape Layer's height to fix to remove the fine line that appears on certain screen sizes. 1517 * 1518 * @since 2.5.3 1519 * @access private 1520 * @method _adjustRowShapeLayerHeight 1521 */
1522 _adjustRowShapeLayerHeight: function() { 1523 var rowShapeLayers = $('.fl-builder-shape-layer'); 1524 1525 $( rowShapeLayers ).each(function (index) { 1526 var rowShapeLayer = $(this), 1527 shape = $(rowShapeLayer).find('svg'), 1528 height = shape.height(), 1529 excludeShapes = '.fl-builder-shape-circle, .fl-builder-shape-dot-cluster, .fl-builder-shape-topography, .fl-builder-shape-rect'; 1530 1531 if ( ! rowShapeLayer.is( excludeShapes ) ) { 1532 $(shape).css('height', Math.ceil( height ) ); 1533 } 1534 }); 1535 }, 1536 _string_to_slug: function( str ) { 1537 str = str.replace(/^\s+|\s+$/g, ''); // trim 1538 if ( 'undefined' == typeof window._fl_string_to_slug_regex ) { 1539 regex = new RegExp('[^a-zA-Z0-9\'":() !.,-_|]', 'g'); 1540 } else { 1541 regex = new RegExp('[^' + window._fl_string_to_slug_regex + '\'":\(\) !.,-_|\\\p{Letter}]', 'ug'); 1542 } 1543 str = str.replace(regex, '') // remove invalid chars 1544 .replace(/\s+/g, ' '); // collapse whitespace and replace by a space 1545 return str; 1546 }, 1547 _reorderMenu: function() { 1548 if ( $('#wp-admin-bar-fl-builder-frontend-edit-link-default li').length > 1 ) { 1549 $( '#wp-admin-bar-fl-builder-frontend-duplicate-link' ) 1550 .appendTo('#wp-admin-bar-fl-builder-frontend-edit-link-default') 1551 .css( 'padding-top', '5px' ) 1552 .css( 'border-top', '2px solid #1D2125' ) 1553 .css( 'margin-top', '5px' ) 1554 } 1555 } 1556 }; 1557 1558 /* Initializes the builder layout. */ 1559 $(function(){ 1560 FLBuilderLayout.init(); 1561 }); 1562 1563})(jQuery); 1564(function($){ 1565 1566 if(typeof FLBuilderLayoutModules !== 'undefined') { 1567 return; 1568 } 1569 1570 /** 1571 * Helper class with generic logic for builder modules. 1572 * If generic module code needs to work in both the block 1573 * editor and builder, it should go here. Otherwise, it 1574 * should go on FLBuilderLayout. 1575 * 1576 * @since 2.9 1577 */ 1578 FLBuilderLayoutModules = { 1579 1580 /** 1581 * Initializes builder module logic. 1582 * 1583 * @since 2.9 1584 * @method init 1585 */ 1586 init: function() 1587 { 1588 // Only init if the builder isn't active. 1589 if ( 0 === $('.fl-builder-edit').length ) { 1590 1591 // Init module animations. 1592 FLBuilderLayoutModules._initModuleAnimations(); 1593 } 1594 }, 1595 1596 /** 1597 * Initializes module animations. 1598 * 1599 * @since 1.1.9 1600 * @access private 1601 * @method _initModuleAnimations 1602 */ 1603 _initModuleAnimations: function() 1604 { 1605 if(typeof jQuery.fn.waypoint !== 'undefined') { 1606 $('.fl-animation').each( function() { 1607 var node = $( this ), 1608 nodeTop = node.offset().top, 1609 winHeight = $( window ).height(), 1610 bodyHeight = $( 'body' ).height(), 1611 waypoint = FLBuilderLayoutConfig.waypoint, 1612 offset = '80%'; 1613 1614 if ( typeof waypoint.offset !== undefined ) { 1615 offset = FLBuilderLayoutConfig.waypoint.offset + '%'; 1616 } 1617 1618 if ( bodyHeight - nodeTop < winHeight * 0.2 ) { 1619 offset = '100%'; 1620 } 1621 1622 node.waypoint({ 1623 offset: offset, 1624 handler: FLBuilderLayoutModules._doModuleAnimation 1625 }); 1626 } ); 1627 } 1628 }, 1629 1630 /** 1631 * Runs a module animation. 1632 * 1633 * @since 1.1.9 1634 * @access private 1635 * @method _doModuleAnimation 1636 */ 1637 _doModuleAnimation: function() 1638 { 1639 var module = 'undefined' == typeof this.element ? $(this) : $(this.element), 1640 delay = parseFloat(module.data('animation-delay')), 1641 duration = parseFloat(module.data('animation-duration')); 1642 1643 if ( ! isNaN( duration ) ) { 1644 module.css( 'animation-duration', duration + 's' ); 1645 } 1646 1647 if(!isNaN(delay) && delay > 0) { 1648 setTimeout(function(){ 1649 module.addClass('fl-animated'); 1650 }, delay * 1000); 1651 } else { 1652 setTimeout(function(){ 1653 module.addClass('fl-animated'); 1654 }, 1); 1655 } 1656 } 1657 }; 1658 1659 /* Initializes builder module logic. */ 1660 $(function(){ 1661 FLBuilderLayoutModules.init(); 1662 }); 1663 1664})(jQuery); 1665 1666/* Start Global JS */ 1667const addToCart = document.querySelector('#singleCartBTN .add_to_cart_button'); 1668 1669if (addToCart) { 1670 addToCart.innerHTML = "buy now"; 1671 addToCart.style.color = 'rgba(255, 255, 255, 1)'; 1672 addToCart.addEventListener('mouseover', function(e) { 1673 e.target.style.color = '#ffca08'; 1674 }); 1675 addToCart.addEventListener('mouseout', function(e) { 1676 e.target.style.color = '#ffffff'; 1677 }); 1678} 1679/* End Global JS */ 1680 1681 1682;(function($) { 1683 1684 FLBuilderTabs = function( settings ) 1685 { 1686 this.settings = settings; 1687 this.nodeClass = '.fl-node-' + settings.id; 1688 this.tabsOnMobile = settings.tabsOnMobile; 1689 this.activeTab = settings.activeTab; 1690 this._init(); 1691 }; 1692 1693 FLBuilderTabs.prototype = { 1694 1695 settings : {}, 1696 nodeClass : '', 1697 1698 _init: function() 1699 { 1700 var win = $(window); 1701 1702 $(this.nodeClass + ' .fl-tabs-labels .fl-tabs-label').click($.proxy(this._labelClick, this)); 1703 $(this.nodeClass + ' .fl-tabs-labels .fl-tabs-label').on('keydown', $.proxy(this._labelClick, this)); 1704 $(this.nodeClass + ' .fl-tabs-panels .fl-tabs-label').click($.proxy(this._responsiveLabelClick, this)); 1705 $(this.nodeClass + ' .fl-tabs-panels .fl-tabs-label').on('keydown', $.proxy(this._responsiveLabelClick, this)); 1706 1707 win.on('resize', $.proxy( this._setupTabs, this)); 1708
1709 if($(this.nodeClass + ' .fl-tabs-vertical').length > 0) { 1710 this._resize(); 1711 win.off('resize' + this.nodeClass); 1712 win.on('resize' + this.nodeClass, $.proxy(this._resize, this)); 1713 } 1714 1715 FLBuilderLayout.preloadAudio( this.nodeClass + ' .fl-tabs-panel-content' ); 1716 this._setupTabs(); 1717 }, 1718 1719 _labelClick: function(e) 1720 { 1721 var label = $(e.target).closest('.fl-tabs-label'), 1722 index = label.data('index'), 1723 wrap = label.closest('.fl-tabs'), 1724 allIcons = wrap.find('.fl-tabs-panels .fl-tabs-label .fas'), 1725 icon = wrap.find('.fl-tabs-panels .fl-tabs-label[data-index="' + index + '"] .fas'); 1726 1727 // Click or keyboard (enter or space) input? 1728 if(!this._validClick(e)) { 1729 return; 1730 } 1731 1732 // Toggle the responsive icons. 1733 allIcons.addClass('fa-plus'); 1734 icon.removeClass('fa-plus'); 1735 1736 // Toggle the tabs. 1737 wrap.find('.fl-tabs-labels:first .fl-tab-active').removeClass('fl-tab-active').attr('aria-selected', 'false'); 1738 wrap.find('.fl-tabs-panels:first > .fl-tabs-panel > .fl-tab-active').removeClass('fl-tab-active').attr('aria-selected', 'false'); 1739 wrap.find('.fl-tabs-panels:first > .fl-tabs-panel > .fl-tabs-panel-content').attr('aria-hidden', 'true').css('display', ''); 1740 1741 wrap.find('.fl-tabs-labels:first .fl-tabs-label[data-index="' + index + '"]').addClass('fl-tab-active').attr('aria-selected', 'true'); 1742 wrap.find('.fl-tabs-panels:first > .fl-tabs-panel > .fl-tabs-panel-label[data-index="' + index + '"]').addClass('fl-tab-active').attr('aria-selected', 'true'); 1743 wrap.find('.fl-tabs-panels:first > .fl-tabs-panel > .fl-tabs-panel-content[data-index="' + index + '"]').addClass('fl-tab-active').attr('aria-hidden', 'false'); 1744 1745 // Gallery module support. 1746 FLBuilderLayout.refreshGalleries( wrap.find('.fl-tabs-panel-content[data-index="' + index + '"]') ); 1747 1748 // Grid layout support (uses Masonry) 1749 FLBuilderLayout.refreshGridLayout( wrap.find('.fl-tabs-panel-content[data-index="' + index + '"]') ); 1750 1751 // Post Carousel support (uses BxSlider) 1752 FLBuilderLayout.reloadSlider( wrap.find('.fl-tabs-panel-content[data-index="' + index + '"]') ); 1753 1754 // WP audio shortcode support 1755 FLBuilderLayout.resizeAudio( wrap.find('.fl-tabs-panel-content[data-index="' + index + '"]') ); 1756 1757 // Slideshow module support. 1758 FLBuilderLayout.resizeSlideshow(); 1759 1760 // Reload Google Map embed. 1761 FLBuilderLayout.reloadGoogleMap( wrap.find('.fl-tabs-panel-content[data-index="' + index + '"]') ); 1762 1763 e.preventDefault(); 1764 }, 1765 1766 _responsiveLabelClick: function(e) 1767 { 1768 var label = $(e.target).closest('.fl-tabs-label'), 1769 wrap = label.closest('.fl-tabs'), 1770 index = label.data('index'), 1771 content = label.siblings('.fl-tabs-panel-content'), 1772 activeContent = wrap.find('.fl-tabs-panel-content.fl-tab-active'), 1773 allIcons = wrap.find('.fl-tabs-panels .fl-tabs-label > .fas'), 1774 icon = label.find('.fas'); 1775 1776 // Click or keyboard (enter or space) input? 1777 if(!this._validClick(e)) { 1778 return; 1779 } 1780 1781 if( label.hasClass( 'fl-tab-active' ) || wrap.hasClass( 'fl-tabs-animation' ) ) { 1782 return; 1783 } 1784 1785 // Toggle the icons. 1786 allIcons.addClass('fa-plus'); 1787 icon.removeClass('fa-plus'); 1788 1789 // Run the animations. 1790 wrap.addClass('fl-tabs-animation'); 1791 activeContent.slideUp('normal'); 1792 1793 content.slideDown('normal', function(){ 1794 1795 wrap.find('.fl-tab-active').removeClass('fl-tab-active').attr('aria-selected', 'false'); 1796 wrap.find('.fl-tabs-panels:first > .fl-tabs-panel > .fl-tabs-panel-content').attr('aria-hidden', 'true'); 1797 wrap.find('.fl-tabs-label[data-index="' + index + '"]').addClass('fl-tab-active').attr('aria-selected', 'true'); 1798 wrap.find('.fl-tabs-panels:first > .fl-tabs-panel > .fl-tabs-panel-content[data-index="' + index + '"]').attr('aria-hidden', 'false'); 1799 content.addClass('fl-tab-active'); 1800 wrap.removeClass('fl-tabs-animation'); 1801 1802 // Gallery module support. 1803 FLBuilderLayout.refreshGalleries( content ); 1804 1805 // Grid layout support (uses Masonry) 1806 FLBuilderLayout.refreshGridLayout( content ); 1807 1808 // Post Carousel support (uses BxSlider) 1809 FLBuilderLayout.reloadSlider( content ); 1810 1811 // WP audio shortcode support 1812 FLBuilderLayout.resizeAudio( content ); 1813 1814 // Reload Google Map embed. 1815 FLBuilderLayout.reloadGoogleMap( wrap.find('.fl-tabs-panel-content[data-index="' + index + '"]') ); 1816 1817 if(label.offset().top < $(window).scrollTop() + 100) { 1818 $('html, body').animate({ scrollTop: label.offset().top - 100 }, 500, 'swing'); 1819 } 1820 }); 1821 }, 1822 1823 _resize: function() 1824 {
1825 $(this.nodeClass + ' .fl-tabs-vertical').each($.proxy(this._resizeVertical, this)); 1826 }, 1827 1828 _resizeVertical: function(e) 1829 { 1830 var wrap = $(this.nodeClass + ' .fl-tabs-vertical'), 1831 labels = wrap.find('.fl-tabs-labels'), 1832 panels = wrap.find('.fl-tabs-panels'); 1833 1834 panels.css('min-height', labels.height() + 'px'); 1835 }, 1836 1837 _validClick: function(e) 1838 { 1839 return (e.which == 1 || e.which == 13 || e.which == 32) ? true : false; 1840 }, 1841 1842 _setupTabs: function() { 1843 index = this.activeTab - 1; 1844 if ( index < 0 ) { 1845 index = 0; 1846 } 1847 // $( this.nodeClass ).find('.fl-tabs-panel-content[data-index="' + index + '"]').addClass('fl-tab-active'); 1848 var winWidth = $(window).width(), 1849 activeTabContent = $( this.nodeClass + ' .fl-tabs-panel-content.fl-tab-active' ), 1850 activeTabPanel = activeTabContent.parent(), 1851 activeTabLabelIcon = activeTabPanel.find('.fl-tabs-label > i'), 1852 smallBreakPoint = FLBuilderLayoutConfig.breakpoints.small; 1853 1854 if ( winWidth <= smallBreakPoint && 'close-all' == this.tabsOnMobile ) { 1855 activeTabContent.hide(); 1856 activeTabLabelIcon.addClass('fa-plus'); 1857 activeTabPanel.find('.fl-tab-active').removeClass('fl-tab-active').attr('aria-hidden', 'true'); 1858 } else if ( winWidth >= smallBreakPoint ) { 1859 activeTabContent.show(); 1860 activeTabLabelIcon.removeClass('fa-plus'); 1861 } 1862 }, 1863 }; 1864 1865})(jQuery); 1866(function($) { 1867 1868 $(function() { 1869 1870 new FLBuilderTabs({ 1871 id: '5f8de49eea527', 1872 tabsOnMobile: 'open-active', 1873 activeTab: '1', 1874 }); 1875 }); 1876 1877})(jQuery); 1878 1879/* Start Global Node Custom JS */ 1880 1881/* End Global Node Custom JS */ 1882 1883 1884/* Start Layout Custom JS */ 1885 1886/* End Layout Custom JS */ 1887 1888 1889 1890;/* eslint-disable no-undef */ 1891(function($) { 1892 1893 /** 1894 * Class for Menu Module 1895 * 1896 * @since 1.6.1 1897 */ 1898 FLBuilderMenu = function( settings ){ 1899 1900 // set params 1901 this.nodeId = settings.id; 1902 this.nodeClass = '.fl-node-' + settings.id; 1903 this.wrapperClass = this.nodeClass + ' .fl-menu'; 1904 this.type = settings.type; 1905 this.mobileToggle = settings.mobile; 1906 this.mobileBelowRow = settings.mobileBelowRow; 1907 this.mobileFlyout = settings.mobileFlyout; 1908 this.breakPoints = settings.breakPoints; 1909 this.mobileBreakpoint = settings.mobileBreakpoint; 1910 this.currentBrowserWidth = $( window ).width(); 1911 this.postId = settings.postId; 1912 this.mobileStacked = settings.mobileStacked; 1913 this.submenuIcon = settings.submenuIcon; 1914 this.flyoutWidth = settings.flyoutWidth; 1915 1916 // initialize the menu 1917 this._initMenu(); 1918 1919 // check if viewport is resizing 1920 $( window ).on( 'resize', $.proxy( function() { 1921 1922 var width = $( window ).width(); 1923 1924 // if screen width is resized, reload the menu 1925 if( width != this.currentBrowserWidth ){ 1926 1927 this.currentBrowserWidth = width; 1928 this._initMenu( true ); 1929 this._clickOrHover( true ); 1930 } 1931 1932 this._resizeFlyoutMenuPanel(); 1933 }, this ) ); 1934 1935 $( window ).on( 'scroll', $.proxy( function() { 1936 this._resizeFlyoutMenuPanel(); 1937 }, this ) ); 1938 1939 // Move focus to the first menu item if the layout is set to BelowRow 1940 $( this.wrapperClass ).on( 'keydown', '.fl-menu-mobile-toggle, .menu-item:first a:first', $.proxy( function ( event ) { 1941 // Make sure it is a tab key with both the menu toggle enabled & below row layout set 1942 if ( event.key !== 'Tab' || ! this._isMenuToggle() || ! this._isMobileBelowRowEnabled() ) { 1943 return; 1944 } 1945 // Trigger focus on the mobile toggle button if the user is shift tabbing from the first menu item 1946 if ( event.shiftKey && $( event.currentTarget ).is( 'a' ) ) { 1947 event.preventDefault(); 1948 $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ).trigger( 'focus' ); 1949 } 1950 // Trigger focus on the first menu item if the user is tabbing from the mobile toggle button 1951 else if ( ! event.shiftKey && $( event.currentTarget ).hasClass( 'fl-menu-mobile-toggle' ) && $( event.currentTarget ).hasClass( 'fl-active' ) ) { 1952 event.preventDefault(); 1953 $( this.wrapperClass ).find( '.menu-item' ).first().find( 'a' ).first().trigger( 'focus' ); 1954 } 1955 }, this ) ); 1956 1957 // Close mobile menu or accordion when tabbing out from the first or last menu item 1958 $( this.wrapperClass ).on( 'focusout', $.proxy( function ( event ) { 1959 if ( $( this.wrapperClass + ' nav' ).has( $( event.relatedTarget ) ).length === 0 ) { 1960 if ( this.type === 'accordion' ) { 1961 this._toggleSubmenu( $( event.target ).parents( '.fl-has-submenu' ).last(), false ); 1962 } 1963 else { 1964 this._clickOrHover( true ); 1965 } 1966 const mobileToggle = $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ); 1967 if ( this._isMenuToggle() && mobileToggle.hasClass( 'fl-active' ) && ! $( event.relatedTarget ).is( mobileToggle ) && 'expanded' !== this.mobileToggle ) { 1968 mobileToggle.trigger( 'click' ); 1969 } 1970 } 1971 }, this ) ); 1972 1973 }; 1974 1975 FLBuilderMenu.prototype = { 1976 nodeClass : '', 1977 wrapperClass: '', 1978 type : '', 1979 breakPoints : {}, 1980 $submenus : null, 1981 1982 /** 1983 * Check if the screen size fits a mobile viewport. 1984 * 1985 * @since 1.6.1 1986 * @return bool 1987 */ 1988 _isMobile: function(){ 1989 return this.currentBrowserWidth <= this.breakPoints.small ? true : false;
1990 }, 1991 1992 /** 1993 * Check if the device is connected to a mouse. 1994 * 1995 * @since 2.9.0 1996 * @return bool 1997 */ 1998 _isMouseAvailable: function(){ 1999 return window.matchMedia("(pointer: fine) and (hover: hover)").matches; 2000 }, 2001 2002 /** 2003 * Check if the screen size fits a medium viewport. 2004 * 2005 * @since 1.10.5 2006 * @return bool 2007 */ 2008 _isMedium: function(){ 2009 return this.currentBrowserWidth <= this.breakPoints.medium ? true : false; 2010 }, 2011 2012 /** 2013 * Check if the screen size fits a large viewport. 2014 * 2015 * @since 1.10.5 2016 * @return bool 2017 */ 2018 _isLarge: function(){ 2019 return this.currentBrowserWidth <= this.breakPoints.large ? true : false; 2020 }, 2021 2022 /** 2023 * Check if the menu should toggle for the current viewport base on the selected breakpoint. 2024 * 2025 * @see this._isMobile() 2026 * @see this._isMedium() 2027 * @since 1.10.5 2028 * @return bool 2029 */ 2030 _isMenuToggle: function(){ 2031 if ( ( 'always' == this.mobileBreakpoint 2032 || ( this._isMobile() && 'mobile' == this.mobileBreakpoint ) 2033 || ( this._isMedium() && 'medium-mobile' == this.mobileBreakpoint ) 2034 || ( this._isLarge() && 'large-mobile' == this.mobileBreakpoint ) 2035 ) && ( $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ).is(':visible') || 'expanded' == this.mobileToggle ) ) { 2036 return true; 2037 } 2038 2039 return false; 2040 }, 2041 2042 /** 2043 * Initialize the toggle logic for the menu. 2044 * 2045 * @see this._isMenuToggle() 2046 * @see this._menuOnCLick() 2047 * @see this._clickOrHover() 2048 * @see this._submenuOnRight() 2049 * @see this._submenuRowZindexFix() 2050 * @see this._toggleForMobile() 2051 * @since 1.6.1 2052 * @return void 2053 */ 2054 _initMenu: function( resized = false ){ 2055 this._setupSubmenu(); 2056 if ( ! resized ) { 2057 this._menuOnFocus(); 2058 this._menuOnClick(); 2059 this._menuOnEscape(); 2060 } 2061 if ( $( this.nodeClass ).length && this.type == 'horizontal' ) { 2062 this._initMegaMenus(); 2063 } 2064 2065 if( this._isMenuToggle() || this.type == 'accordion' ){ 2066 2067 $( this.wrapperClass ).off( 'mouseenter mouseleave' ); 2068 this._clickOrHover(); 2069 2070 } else { 2071 this._submenuOnRight(); 2072 this._submenuRowZindexFix(); 2073 } 2074 2075 if( this.mobileToggle != 'expanded' ){ 2076 this._toggleForMobile(); 2077 } 2078 2079 if( $( this.wrapperClass ).find( '.fl-menu-search-item' ).length ){ 2080 this._toggleMenuSearch(); 2081 } 2082 2083 if( $( this.wrapperClass ).find( '.fl-menu-cart-item').length ){ 2084 this._wooUpdateParams(); 2085 } 2086 }, 2087 2088 /** 2089 * Whether the toggle behavior for submenus is through hovering or not. 2090 * 2091 * @since 2.5 2092 * @return void 2093 */ 2094 _setupSubmenu: function() { 2095 if ( ! this._isMouseAvailable() || this._isMenuToggle() || this.type === 'accordion' ) { 2096 $( this.wrapperClass ).addClass( 'no-hover' ); 2097 } 2098 else { 2099 $( this.wrapperClass ).removeClass( 'no-hover' ); 2100 } 2101 }, 2102 2103 /** 2104 * Logic for menu items when focused. 2105 * 2106 * @since 1.9.0 2107 * @return void 2108 */ 2109 _menuOnFocus: function(){ 2110 $( this.wrapperClass ).on( 'focus', 'a:not(.fl-button), .fl-menu-toggle', $.proxy( function( event ) { 2111 const focusedMenuItem = $( event.currentTarget ).closest( '.menu-item' ); 2112 const blurredMenuItem = $( event.relatedTarget ).closest( '.menu-item' ); 2113 // In case the blurred & focused items are siblings 2114 if ( focusedMenuItem.closest( 'ul' ).is( blurredMenuItem.closest( 'ul' ) ) ) { 2115 // Check if the focus event is not between the link & submenu icon of the same menu item 2116 if ( ! focusedMenuItem.is( blurredMenuItem ) ) { 2117 this._toggleSubmenu( blurredMenuItem, false ); 2118 } 2119 } 2120 // In case neither items are child & parent nor vice versa 2121 else if ( focusedMenuItem.has( blurredMenuItem ).length === blurredMenuItem.has( focusedMenuItem ).length ) { 2122 // Get the blurred item parent & is the sibling of the focused item 2123 blurredMenuItem.parents( '.fl-has-submenu' ).each( ( _, parent ) => { 2124 if ( focusedMenuItem.closest( 'ul' ).is( $( parent ).closest( 'ul' ) ) ) { 2125 this._toggleSubmenu( $( parent ), false ); 2126 return false; 2127 } 2128 } ); 2129 } 2130 // In case the blurred item is a child of the focused item
2131 else if ( focusedMenuItem.has( blurredMenuItem ).length ) { 2132 if ( this.type !== 'accordion' && this.submenuIcon === 'none' ) { 2133 this._toggleSubmenu( blurredMenuItem, false ); 2134 } 2135 } 2136 }, this ) ); 2137 }, 2138 2139 /** 2140 * Logic for menu items when Escape key is pressed. 2141 * 2142 * @since 2.7.1 2143 * @return void 2144 */ 2145 _menuOnEscape: function(){ 2146 $( this.wrapperClass ).on( 'keydown', $.proxy( function( event ){ 2147 if ( event.key !== 'Escape' ) return; 2148 const menuItem = $( event.target ).closest( '.menu-item' ); 2149 const mobileToggle = $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ); 2150 // In case there is a focused menu item 2151 if ( menuItem.length || $( event.target ).hasClass( 'fl-menu-mobile-close' ) ) { 2152 // Close the search form if it is open 2153 if ( menuItem.hasClass( 'fl-menu-search-item' ) && $( this.wrapperClass ).find('.fl-search-form-input-wrap').is( ':visible' ) ) { 2154 $( this.wrapperClass ).find('.fl-menu-search-item .fl-button:is(a, button:not([type="submit"]))').trigger( 'focus' ); 2155 } 2156 // Close all the submenus of the focused menu item 2157 else if ( menuItem.hasClass( 'fl-has-submenu' ) && menuItem.find( '.sub-menu' ).first().is( ':visible' ) ) { 2158 this._toggleSubmenu( menuItem, false ); 2159 } 2160 else { 2161 // Shift focus to the parent while closing all its submenus 2162 const parentMenuItem = menuItem.parents( '.fl-has-submenu' ).not( '.mega-menu.hide-heading, .mega-menu-disabled.hide-heading' ).first(); 2163 // Close the mobile menu if there is no parent menu item and the mobile toggle is active 2164 if ( parentMenuItem.length === 0 && this._isMenuToggle() && mobileToggle.hasClass( 'fl-active' ) ) { 2165 // Blur to trigger the focus-out event for closing the menu 2166 mobileToggle.trigger( 'blur' ).trigger( 'focus' ); 2167 } 2168 // Close the parent submenu and shift focus to its link 2169 else if ( parentMenuItem.length !== 0 ) { 2170 this._toggleSubmenu( parentMenuItem, false ); 2171 parentMenuItem.find( 'a' ).first().trigger( 'focus' ); 2172 } 2173 } 2174 } 2175 // Close the mobile menu if the mobile toggle is active 2176 else if ( $( event.target ).hasClass( 'fl-menu-mobile-toggle' ) && mobileToggle.hasClass( 'fl-active' ) ) { 2177 // Blur to trigger the focus-out event for closing the menu 2178 mobileToggle.trigger( 'blur' ).trigger( 'focus' ); 2179 } 2180 }, this ) ); 2181 }, 2182 2183 /** 2184 * Logic for menu items when clicked. 2185 * 2186 * @since 1.6.1 2187 * @return void 2188 */ 2189 _menuOnClick: function(){ 2190 // Fallback for span elements and fake links to interact with the keyboard 2191 $( this.wrapperClass ).on( 'keydown', 'a:not(.fl-button, [href]), span.fl-menu-toggle', $.proxy( function( event ) { 2192 if ( event.key === 'Enter' || event.key === ' ' ) { 2193 event.preventDefault(); 2194 $( event.currentTarget ).trigger( 'click' ); 2195 } 2196 }, this ) ); 2197 $( this.wrapperClass ).on( 'click', 'a, .fl-menu-toggle', $.proxy( function( event ) { 2198 // Only allow mouse clicks with accordion & mobile menus 2199 if ( this._isMouseAvailable() && ! this._isMenuToggle() && event.detail && this.type !== 'accordion' ) return; 2200 // Links only open & do not toggle submenus if there is either a submenu icon or an accordion layout 2201 if ( $( event.currentTarget ).is( 'a' ) && ( this.submenuIcon !== 'none' || this.type === 'accordion' ) ) return; 2202 const menuItem = $( event.currentTarget ).closest( '.menu-item, .fl-menu-logo' ); 2203 const menuLink = menuItem.find( 'a' ).first().attr( 'href' ); 2204 const submenuHidden = menuItem.find( '.sub-menu' ).first().is( ':hidden' ); 2205 if ( typeof menuLink === 'undefined' || menuLink === '#' || submenuHidden ) { 2206 event.preventDefault(); 2207 } 2208 if ( $( event.currentTarget ).hasClass( 'fl-menu-toggle' ) || submenuHidden ) { 2209 this._toggleSubmenu( menuItem, submenuHidden ); 2210 } 2211 }, this ) ); 2212 }, 2213 2214 /** 2215 * Controls toggling the visibility of all submenu items for desktop & mobile. 2216 * 2217 * @since 2.10 2218 * @return void 2219 */ 2220 _toggleSubmenu: function ( menuItem, opened ) { 2221 const togglingClass = this._isMenuToggle() || this.type === 'accordion' ? 'fl-active' : 'focus'; 2222 const toggleElement = this.submenuIcon === 'none' ? 'a' : '.fl-menu-toggle'; 2223 const hiddenMenu = '.mega-menu.hide-heading, .mega-menu-disabled.hide-heading'; 2224 if ( opened && menuItem.hasClass( 'fl-has-submenu' ) && ! menuItem.is( hiddenMenu ) ) { 2225 menuItem.addClass( togglingClass ); 2226 menuItem.find( toggleElement ).first().attr( 'aria-expanded', true ); 2227 if ( this._isMenuToggle() || this.type === 'accordion' ) { 2228 menuItem.find( '.sub-menu' ).first().filter( ':hidden' ).slideDown(); 2229 } 2230 } 2231 else { 2232 menuItem.parent().find( '.menu-item' ).removeClass( togglingClass ); 2233 menuItem.parent().find( '.fl-has-submenu' ).not( hiddenMenu ).find( toggleElement ).attr( 'aria-expanded', false ); 2234 if ( this._isMenuToggle() || this.type === 'accordion' ) {
2235 menuItem.find( '.sub-menu' ).filter( ':visible' ).slideUp(); 2236 } 2237 } 2238 }, 2239 2240 /** 2241 * Changes general styling and behavior of menus based on mobile / desktop viewport. 2242 * 2243 * @see this._isMenuToggle() 2244 * @since 1.6.1 2245 * @return void 2246 */ 2247 _clickOrHover: function( clear = false ){ 2248 const selector = this._isMobileBelowRowEnabled() ? this.nodeClass + '-clone' : this.nodeClass; 2249 this.$submenus = this.$submenus || $( selector ).find( '.sub-menu' ); 2250 2251 const className = this._isMenuToggle() || this.type === 'accordion' ? 'fl-active' : 'focus'; 2252 const toggleElement = this.submenuIcon === 'none' ? 'a' : '.fl-menu-toggle'; 2253 const hiddenMenu = '.mega-menu.hide-heading, .mega-menu-disabled.hide-heading'; 2254 $( selector ).find( '.fl-has-submenu' ).not( hiddenMenu ).each( function() { 2255 if( clear || ! $ ( this ).hasClass( className ) ){ 2256 if ( clear ) $( this ).removeClass( className ); 2257 $( this ).find( toggleElement ).first().attr( 'aria-expanded', false ); 2258 if ( className === 'fl-active' ) { 2259 $( this ).find( '.sub-menu' ).fadeOut(); 2260 } else if ( className === 'focus' ) { 2261 $( this ).find( '.sub-menu' ).css( { 2262 'display' : '', 2263 'opacity' : '' 2264 } ); 2265 } 2266 } 2267 } ); 2268 }, 2269 2270 /** 2271 * Logic to prevent submenus to go outside viewport boundaries. 2272 * 2273 * @since 1.6.1 2274 * @return void 2275 */ 2276 _submenuOnRight: function(){ 2277 2278 $( this.wrapperClass ) 2279 .on( 'mouseenter focus', '.fl-has-submenu', $.proxy( function( e ){ 2280 2281 if( $ ( e.currentTarget ).find('.sub-menu').length === 0 ) { 2282 return; 2283 } 2284 2285 var $link = $( e.currentTarget ), 2286 $parent = $link.parent(), 2287 $subMenu = $link.find( '.sub-menu' ), 2288 subMenuWidth = $subMenu.width(), 2289 subMenuPos = 0, 2290 bodyWidth = $( 'body' ).width(); 2291 2292 if( $link.closest( '.fl-menu-submenu-right' ).length !== 0) { 2293 2294 $link.addClass( 'fl-menu-submenu-right' ); 2295 2296 } else if( $( 'body' ).hasClass( 'rtl' ) ) { 2297 2298 subMenuPos = $parent.is( '.sub-menu' ) ? 2299 $parent.offset().left - subMenuWidth: 2300 $link.offset().left - $link.width() - subMenuWidth; 2301 2302 if( subMenuPos <= 0 ) { 2303 $link.addClass( 'fl-menu-submenu-right' ); 2304 } 2305 2306 } else { 2307 2308 subMenuPos = $parent.is( '.sub-menu' ) ? 2309 $parent.offset().left + $parent.width() + subMenuWidth : 2310 $link.offset().left + $link.width() + subMenuWidth; 2311 2312 if( subMenuPos > bodyWidth ) { 2313 $link.addClass('fl-menu-submenu-right'); 2314 } 2315 } 2316 }, this ) ) 2317 .on( 'mouseleave', '.fl-has-submenu', $.proxy( function( e ){ 2318 $( e.currentTarget ).removeClass( 'fl-menu-submenu-right' ); 2319 }, this ) ); 2320 2321 }, 2322 2323 /** 2324 * Logic to prevent submenus to go behind the next overlay row. 2325 * 2326 * @since 1.10.9 2327 * @return void 2328 */ 2329 _submenuRowZindexFix: function(){ 2330 2331 $( this.wrapperClass ) 2332 .on( 'mouseenter', 'ul.menu > .fl-has-submenu', $.proxy( function( e ){ 2333 2334 if( $ ( e.currentTarget ).find('.sub-menu').length === 0 ) { 2335 return; 2336 } 2337 2338 $( this.nodeClass ) 2339 .closest( '.fl-row' ) 2340 .find( '.fl-row-content' ) 2341 .css( 'z-index', '10' ); 2342 2343 }, this ) ) 2344 .on( 'mouseleave', 'ul.menu > .fl-has-submenu', $.proxy( function(){ 2345 2346 $( this.nodeClass ) 2347 .closest( '.fl-row' ) 2348 .find( '.fl-row-content' ) 2349 .css( 'z-index', '' ); 2350 2351 }, this ) ); 2352 }, 2353 2354 /** 2355 * Logic for the mobile menu button. 2356 * 2357 * @since 1.6.1 2358 * @return void 2359 */ 2360 _toggleForMobile: function(){ 2361 2362 var $wrapper = null, 2363 $menu = null, 2364 self = this; 2365 2366 $( this.wrapperClass ).find( '.fl-menu-mobile-toggle' ).attr('aria-controls', $( this.wrapperClass ).find( 'ul.menu' ).attr('id')); 2367 2368 if( this._isMenuToggle() ){ 2369 2370 if ( this._isMobileBelowRowEnabled() ) { 2371 this._placeMobileMenuBelowRow(); 2372 $wrapper = $( this.wrapperClass ); 2373 $menu = $( this.nodeClass + '-clone' ); 2374 $menu.find( 'ul.menu' ).show(); 2375 } 2376 else { 2377 $wrapper = $( this.wrapperClass ); 2378 $menu = $wrapper.find( '.menu' ); 2379 } 2380 2381 if( !$wrapper.find( '.fl-menu-mobile-toggle' ).hasClass( 'fl-active' ) && ! self.mobileFlyout ){ 2382 $menu.css({ display: 'none' }); 2383 } 2384 2385 // Flayout Menu 2386 if ( self.mobileFlyout ) { 2387 this._initFlyoutMenu(); 2388 } 2389 2390 $wrapper.on( 'click', '.fl-menu-mobile-toggle', function( e ){ 2391 e.stopImmediatePropagation(); 2392 2393 $( this ).toggleClass( 'fl-active' ); 2394 2395 if ( self.mobileFlyout ) { 2396 self._toggleFlyoutMenu(); 2397 const flyoutWrapper = $( '.fl-menu-mobile-flyout' ); 2398 if ( $( this ).hasClass( 'fl-active' ) ) { 2399 flyoutWrapper.attr( 'aria-hidden', false ); 2400 flyoutWrapper.find( 'a[href], button, input, select, textarea, span.fl-menu-toggle, [tabindex="-1"]' ).attr( 'tabindex', 0 ); 2401 } 2402 else { 2403 flyoutWrapper.attr( 'aria-hidden', true ); 2404 flyoutWrapper.find( 'a[href], button, input, select, textarea, span.fl-menu-toggle, [tabindex]:not
2404([tabindex="-1"])' ).attr( 'tabindex', -1 ); 2405 } 2406 } 2407 else { 2408 var targetMenu = null; 2409 2410 if ( self.mobileBelowRow ) { 2411 var $closestCol = $( this ).parents( '.fl-col, .fl-module-box' ), 2412 $closestColGroup = $closestCol.length ? $closestCol.parent( '.fl-col-group' ) : null; 2413 targetMenu = $closestCol.length ? $closestCol.last().next( '.fl-menu-mobile-clone' ) : null; 2414 2415 if ( $closestColGroup.length ) { 2416 if ( $closestColGroup.hasClass( 'fl-col-group-responsive-reversed' ) ) { 2417 $closestColGroup.find( '.fl-menu-mobile-clone' ).css( 'order', -1 ); 2418 } else if ( $closestColGroup ) { 2419 $closestColGroup.find( '.fl-menu-mobile-clone' ).css( 'order', 2 ); 2420 } 2421 } 2422 } else { 2423 targetMenu = $( this ).closest( '.fl-menu' ).find( 'ul.menu' ); 2424 } 2425 2426 if ( targetMenu.length ) { 2427 $menu = $( targetMenu ); 2428 } 2429 2430 $menu.slideToggle(); 2431 } 2432 2433 e.stopPropagation(); 2434 } ); 2435 2436 // Hide active menu when click on anchor link ID that exists on a page 2437 $menu.off().on( 'click', '.menu-item > a[href*="#"]:not([href="#"])', function(){ 2438 var $href = $(this).attr('href'), 2439 $targetID = $href.split('#')[1], 2440 element = $('#' + $targetID); 2441 if ( $('body').find(element).length > 0 ) { 2442 $( this ).toggleClass( 'fl-active' ); 2443 FLBuilderLayout._scrollToElement( element ); 2444 if ( ! self._isMenuToggle() ) { 2445 $menu.slideToggle(); 2446 } 2447 } 2448 }); 2449 } 2450 else { 2451 2452 if ( this._isMobileBelowRowEnabled() ) { 2453 this._removeMenuFromBelowRow(); 2454 } 2455 2456 $wrapper = $( this.wrapperClass ), 2457 $menu = $wrapper.find( 'ul.menu' ); 2458 $wrapper.find( '.fl-menu-mobile-toggle' ).removeClass( 'fl-active' ); 2459 $menu.css({ display: '' }); 2460 2461 if ( ! this._isMobileBelowRowEnabled() ) { 2462 $menu.off( 'click', '.menu-item > a[href*="#"]:not([href="#"])' ); 2463 } 2464 2465 if ( this.mobileFlyout && $wrapper.find( '.fl-menu-mobile-flyout' ).length > 0 ) { 2466 $( 'body' ).css( 'margin', '' ); 2467 $( '.fl-builder-ui-pinned-content-transform' ).css( 'transform', '' ); 2468 $menu.unwrap(); 2469 $wrapper.find( '.fl-menu-mobile-close' ).remove(); 2470 $wrapper.find( '.fl-menu-mobile-opacity' ).remove(); 2471 } 2472 } 2473 }, 2474 2475 /** 2476 * Init any mega menus that exist. 2477 * 2478 * @see this._isMenuToggle() 2479 * @since 1.10.4 2480 * @return void 2481 */ 2482 _initMegaMenus: function(){ 2483 2484 var module = $( this.nodeClass ), 2485 rowContent = module.closest( '.fl-row-content' ), 2486 rowWidth = rowContent.width(), 2487 megas = module.find( '.mega-menu' ), 2488 disabled = module.find( '.mega-menu-disabled' ), 2489 isToggle = this._isMenuToggle(); 2490 2491 if ( isToggle ) { 2492 megas.removeClass( 'mega-menu' ).addClass( 'mega-menu-disabled' ); 2493 module.find( 'li.mega-menu-disabled > ul.sub-menu' ).css( 'width', '' ); 2494 rowContent.css( 'position', '' ); 2495 } else { 2496 disabled.removeClass( 'mega-menu-disabled' ).addClass( 'mega-menu' ); 2497 module.find( 'li.mega-menu > ul.sub-menu' ).css( 'width', rowWidth + 'px' ); 2498 rowContent.css( 'position', 'relative' ); 2499 } 2500 }, 2501 2502 /** 2503 * Check to see if Below Row should be enabled. 2504 * 2505 * @since 1.11 2506 * @return boolean 2507 */ 2508 _isMobileBelowRowEnabled: function() { 2509 return this.mobileBelowRow && ( $( this.nodeClass ).parents( '.fl-col, .fl-module-box' ).length ); 2510 }, 2511 2512 /** 2513 * Logic for putting the mobile menu below the menu's 2514 * column so it spans the full width of the page. 2515 * 2516 * @since 1.10 2517 * @return void 2518 */ 2519 _placeMobileMenuBelowRow: function(){ 2520 2521 if ( $( this.nodeClass + '-clone' ).length ) { 2522 return; 2523 } 2524 2525 var module = $( this.nodeClass ), 2526 clone = null, 2527 col = module.parents( '.fl-col, .fl-module-box' ).last(); 2528 2529 if ( module.length < 1 ) { 2530 return; 2531 } 2532 2533 clone = ( module.length > 1 ) ? $( module[0] ).clone() : module.clone(); 2534 module.find( 'ul.menu' ).remove(); 2535 clone.addClass( ( this.nodeClass + '-clone' ).replace( '.', '' ) ); 2536 clone.addClass( 'fl-menu-mobile-clone' ); 2537 clone.find( '.fl-menu-mobile-toggle' ).remove(); 2538 col.after( clone ); 2539 2540 // Removes animation when enabled 2541 if ( module.hasClass( 'fl-animation' ) ) { 2542 clone.removeClass( 'fl-animation' ); 2543 } 2544 2545 this._menuOnFocus(); 2546 this._menuOnClick(); 2547 this._menuOnEscape(); 2548 }, 2549 2550 /** 2551 * Logic for removing the mobile menu from below the menu's 2552 * column and putting it back in the main wrapper. 2553 * 2554 * @since 1.10 2555 * @return void 2556 */ 2557 _removeMenuFromBelowRow: function(){ 2558 2559 if ( ! $( this.nodeClass + '-clone' ).length ) { 2560 return; 2561 } 2562 2563 var module = $( this.nodeClass ), 2564 clone = $( this.nodeClass + '-clone' ), 2565 menu = clone.find( 'ul.menu' ), 2566 nav = module.find( 'nav' ); 2567
2568 if ( nav.length ) { 2569 nav.append( menu ); 2570 } else { 2571 module.find( '.fl-menu-mobile-toggle' ).after( menu ); 2572 } 2573 clone.remove(); 2574 menu.find( 'a' ).each( FLBuilderLayout._initAnchorLink ); 2575 }, 2576 2577 /** 2578 * Logic for Flyout responsive menu. 2579 * 2580 * @since 2.2 2581 * @return void 2582 */ 2583 _initFlyoutMenu: function(){ 2584 var wrapper = $( this.wrapperClass ), 2585 menu = wrapper.find( 'ul.menu' ), 2586 button = wrapper.find( '.fl-menu-mobile-toggle' ); 2587 2588 if ( 0 === wrapper.find( '.fl-menu-mobile-flyout' ).length ) { 2589 menu.wrap( '<div class="fl-menu-mobile-flyout" aria-hidden="true"></div>' ); 2590 } 2591 2592 if ( 0 === wrapper.find( '.fl-menu-mobile-close' ).length ) { 2593 var close = window.fl_responsive_close || 'Close' 2594 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>' ); 2595 } 2596 2597 // Push with opacity 2598 if ( wrapper.hasClass( 'fl-menu-responsive-flyout-push-opacity' ) && 0 === wrapper.find( '.fl-menu-mobile-opacity' ).length ) { 2599 wrapper.append( '<div class="fl-menu-mobile-opacity"></div>' ); 2600 } 2601 2602 wrapper.off( 'click', '.fl-menu-mobile-opacity, .fl-menu-mobile-close' ).on( 'click', '.fl-menu-mobile-opacity, .fl-menu-mobile-close', function(e){ 2603 button.trigger('focus').trigger( 'click' ); 2604 e.stopPropagation(); 2605 }); 2606 2607 if ( 'undefined' !== typeof FLBuilder ) { 2608 FLBuilder.addHook('restartEditingSession', function(){ 2609 $( '.fl-builder-ui-pinned-content-transform' ).css( 'transform', '' ); 2610 2611 // Toggle active menu 2612 if ( button.hasClass( 'fl-active' ) ) { 2613 button.trigger( 'click' ); 2614 } 2615 }); 2616 } 2617 $( '.fl-menu-mobile-flyout' ).find( 'a[href], button, input, select, textarea, span.fl-menu-toggle, [tabindex]:not([tabindex="-1"])' ).attr( 'tabindex', -1 ); 2618 }, 2619 2620 /** 2621 * Logic to enable/disable the Flyout menu on button click. 2622 * 2623 * @since 2.2 2624 * @return void 2625 */ 2626 _toggleFlyoutMenu: function(){ 2627 var wrapper = $( this.wrapperClass ), 2628 button = wrapper.find( '.fl-menu-mobile-toggle' ), 2629 position = wrapper.hasClass( 'fl-flyout-right' ) ? 'right' : 'left', 2630 pushMenu = wrapper.hasClass( 'fl-menu-responsive-flyout-push' ) || wrapper.hasClass( 'fl-menu-responsive-flyout-push-opacity' ), 2631 opacity = wrapper.find( '.fl-menu-mobile-opacity' ), 2632 marginPos = {}, 2633 fixedPos = {}, 2634 fixedHeader = $('header, header > div'); 2635 2636 this._resizeFlyoutMenuPanel(); 2637 2638 // Fix the push menu when builder ui panel is pinned 2639 if ( $( '.fl-builder-ui-pinned-content-transform' ).length > 0 && ! $( 'body' ).hasClass( 'fl-builder-edit' ) ) { 2640 $( '.fl-builder-ui-pinned-content-transform' ).css( 'transform', 'none' ); 2641 } 2642 2643 if ( pushMenu ) { 2644 marginPos[ 'margin-' + position ] = button.hasClass( 'fl-active' ) ? this.flyoutWidth + 'px' : '0px'; 2645 $( 'body' ).animate( marginPos, 200); 2646 2647 // Fixed header 2648 if ( fixedHeader.length > 0 ) { 2649 fixedPos[ position] = button.hasClass( 'fl-active' ) ? this.flyoutWidth + 'px' : '0px'; 2650 fixedHeader.each(function(){ 2651 if ( 'fixed' == $( this ).css( 'position' ) ) { 2652 $( this ).css({ 'transition' : 'none' }); 2653 $( this ).animate( fixedPos, 200 ); 2654 } 2655 }); 2656 } 2657 } 2658 2659 if ( opacity.length > 0 && button.hasClass( 'fl-active' ) ) { 2660 opacity.show(); 2661 } 2662 else { 2663 opacity.hide(); 2664 } 2665 }, 2666 2667 /** 2668 * Resize or reposition the Flyout Menu Panel. 2669 * 2670 * @since 2.8.1 2671 * @return void 2672 */ 2673 _resizeFlyoutMenuPanel: function(){ 2674 const wrapper = $( this.wrapperClass ); 2675 const wrapFlyout = wrapper.find( '.fl-menu-mobile-flyout' ); 2676 2677 if ( wrapFlyout.length > 0 ) { 2678 wrapFlyout.css( this._getFlyoutMenuPanelPosition() ); 2679 } 2680 }, 2681 2682 /** 2683 * Compute the Flyout Menu Panel's position on the screen. 2684 * 2685 * @since 2.8.1 2686 * @return object 2687 */ 2688 _getFlyoutMenuPanelPosition: function() { 2689 var wrapper = $( this.wrapperClass ), 2690 button = wrapper.find( '.fl-menu-mobile-toggle' ), 2691 side = wrapper.hasClass( 'fl-flyout-right' ) ? 'right' : 'left', 2692 winHeight = $(window).outerHeight(), 2693 winTop = $(window).scrollTop(), 2694 adminBarHeight = $( '#wpadminbar' ).length ? $( '#wpadminbar' ).height() : 0, 2695 flyoutPosition = {}; 2696 2697 flyoutPosition[ side ] = '-' + ( parseInt( this.flyoutWidth ) + 15 ) + 'px'; 2698 if ( ! button.hasClass( 'fl-active' ) ) { 2699 return flyoutPosition; 2700 } 2701 2702 flyoutPosition[ side ] = '0px'; 2703 flyoutPosition[ 'height' ] = winHeight + 'px'; 2704 flyoutPosition[ 'top' ] = '0px'; 2705 2706 if ( adminBarHeight > 0 ) { 2707 const diff = adminBarHeight - winTop; 2708 flyoutPosition[ 'top' ] = diff <= 0 ? '0px' : (diff) + 'px'; 2709 } 2710 2711 return flyoutPosition; 2712 }, 2713 2714 /** 2715 * Shows or hides the nav search form. 2716 * 2717 * @since 2.5 2718 * @method _toggleMenuSearch 2719 */ 2720 _toggleMenuSearch: function(){ 2721 const form = $( this.wrapperClass ).find('.fl-menu-search-item .fl-search-form-input-wrap');
2722 const button = $( this.wrapperClass ).find('.fl-menu-search-item .fl-button:is(a, button:not([type="submit"]))'); 2723 form.on('submit', () => form.stop().fadeToggle('fast')); 2724 // Close the search form when focus is lost 2725 form.on('focusout', (event) => { 2726 if (form.is(':visible') && form.has($(event.relatedTarget)).length === 0) { 2727 button.trigger('click'); 2728 } 2729 }); 2730 button.attr('aria-expanded', false); 2731 // Close other open submenus when focusing on the search button 2732 button.on('focus', (event) => { 2733 const parentMenus = $(event.relatedTarget).parents('.menu-item.fl-has-submenu'); 2734 if (event.relatedTarget && parentMenus.length) { 2735 this._toggleSubmenu(parentMenus, false); 2736 } 2737 }); 2738 button.on('click', (event) => { 2739 // Prevent toggling when clicking with mouse while the form is already open 2740 if (event.detail && event.type === 'click' && form.is(':visible')) return; 2741 form.stop().fadeToggle('fast', () => { 2742 button.attr('aria-expanded', form.is(':visible')); 2743 if(form.is(':visible')) { 2744 form.find('.fl-search-text').focus(); 2745 } 2746 }); 2747 }); 2748 }, 2749 2750 /** 2751 * Adds menu node and post ID to WooCommerce ajax URL requests. 2752 * 2753 * @since 2.5 2754 * @return void 2755 */ 2756 _wooUpdateParams: function() { 2757 if ( 'undefined' !== typeof wc_cart_fragments_params ) { 2758 wc_cart_fragments_params.wc_ajax_url += '&fl-menu-node='+ this.nodeId +'&post-id='+ this.postId; 2759 } 2760 if ( 'undefined' !== typeof wc_add_to_cart_params ) { 2761 wc_add_to_cart_params.wc_ajax_url += '&fl-menu-node='+ this.nodeId +'&post-id='+ this.postId; 2762 } 2763 }, 2764 }; 2765 2766})(jQuery); 2767 2768(function($) { 2769 2770 $(function() { 2771 2772 new FLBuilderMenu({ 2773 id: '5b4e4504e6e84', 2774 type: 'horizontal', 2775 mobile: 'expanded', 2776 mobileBelowRow: false, 2777 mobileFlyout: false, 2778 breakPoints: { 2779 large: 1200, 2780 medium: 992, 2781 small: 768 }, 2782 mobileBreakpoint: 'mobile', 2783 postId : '793', 2784 mobileStacked: true, 2785 submenuIcon: 'none', 2786 flyoutWidth: '250', 2787 }); 2788 2789 }); 2790 2791})(jQuery); 2792 2793/* Start Layout Custom JS */ 2794 2795/* End Layout Custom JS */ 2796 2797 2798jQuery(function($) { 2799 2800 $(function() { 2801 $( '.fl-node-5da892bbc0b4e .fl-photo-img' ) 2802 .on( 'mouseenter', function( e ) { 2803 $( this ).data( 'title', $( this ).attr( 'title' ) ).removeAttr( 'title' ); 2804 } ) 2805 .on( 'mouseleave', function( e ){ 2806 $( this ).attr( 'title', $( this ).data( 'title' ) ).data( 'title', null ); 2807 } ); 2808 }); 2809 window._fl_string_to_slug_regex = 'a-zA-Z0-9'; 2810}); 2811 2812(function($) { 2813 2814 $(function() { 2815 2816 new FLBuilderMenu({ 2817 id: '5da892bbc0b4a', 2818 type: 'horizontal', 2819 mobile: 'hamburger', 2820 mobileBelowRow: true, 2821 mobileFlyout: false, 2822 breakPoints: { 2823 large: 1200, 2824 medium: 992, 2825 small: 768 }, 2826 mobileBreakpoint: 'medium-mobile', 2827 postId : '15756', 2828 mobileStacked: true, 2829 submenuIcon: 'none', 2830 flyoutWidth: '250', 2831 }); 2832 2833 }); 2834 2835})(jQuery); 2836 2837/* Start Layout Custom JS */ 2838 2839/* End Layout Custom JS */ 2840 2841(function($){ 2842 2843 /** 2844 * Helper class for header layout logic. 2845 * 2846 * @since 1.0 2847 * @class FLThemeBuilderHeaderLayout 2848 */ 2849 FLThemeBuilderHeaderLayout = { 2850 2851 /** 2852 * A reference to the window object for this page. 2853 * 2854 * @since 1.0 2855 * @property {Object} win 2856 */ 2857 win : null, 2858 2859 /** 2860 * A reference to the body object for this page. 2861 * 2862 * @since 1.0 2863 * @property {Object} body 2864 */ 2865 body : null, 2866 2867 /** 2868 * A reference to the header object for this page. 2869 * 2870 * @since 1.0 2871 * @property {Object} header 2872 */ 2873 header : null, 2874 2875 /** 2876 * Whether this header overlays the content or not. 2877 * 2878 * @since 1.0 2879 * @property {Boolean} overlay 2880 */ 2881 overlay : false, 2882 2883 /** 2884 * Whether the page has the WP admin bar or not. 2885 * 2886 * @since 1.0 2887 * @property {Boolean} hasAdminBar 2888 */ 2889 hasAdminBar : false, 2890 2891 /** 2892 * Breakpoint for when the sticky header should apply. 2893 * 2894 * @since 1.4 2895 * @property {String} stickyOn 2896 */ 2897 stickyOn: '', 2898 2899 /** 2900 * A reference of the sticky and shrink header breakpoint. 2901 * 2902 * @since 1.2.5 2903 * @property {Number} breakpointWidth 2904 */ 2905 breakpointWidth: 0, 2906 2907 /** 2908 * Whether this header can be pinned from page load. 2909 * 2910 * @since 1.6.1 2911 * @property {Boolean} pinnable 2912 */ 2913 pinnable : false, 2914 2915 /** 2916 * Initializes header layout logic. 2917 * 2918 * @since 1.0 2919 * @method init 2920 */ 2921 init: function() 2922 {
2923 var editing = $( 'html.fl-builder-edit' ).length, 2924 header = $( '.fl-builder-content[data-type=header]' ), 2925 menuModule = header.find( '.fl-module-menu' ), 2926 breakpoint = null, 2927 adminBarHeight = 0; 2928 2929 if ( ! editing && header.length ) { 2930 2931 header.imagesLoaded( $.proxy( function() { 2932 2933 this.win = $( window ); 2934 this.body = $( 'body' ); 2935 this.header = header.eq( 0 ); 2936 this.overlay = !! Number( header.attr( 'data-overlay' ) ); 2937 this.hasAdminBar = !! $( 'body.admin-bar' ).length; 2938 this.stickyOn = this.header.data( 'sticky-on' ); 2939 breakpoint = this.header.data( 'sticky-breakpoint' ); 2940 2941 if ( '' == this.stickyOn ) { 2942 if ( typeof FLBuilderLayoutConfig.breakpoints[ breakpoint ] !== undefined ) { 2943 this.breakpointWidth = FLBuilderLayoutConfig.breakpoints[ breakpoint ]; 2944 } 2945 else { 2946 this.breakpointWidth = FLBuilderLayoutConfig.breakpoints.medium; 2947 } 2948 } 2949 2950 if ( Number( header.attr( 'data-sticky' ) ) ) { 2951 2952 this.header.data( 'original-top', this.header.offset().top ); 2953 2954 // The pin decision is made once here, while the header offset and the admin bar height are read from the same layout. 2955 adminBarHeight = $( '#wpadminbar' ).length ? $( '#wpadminbar' ).outerHeight() : 0; 2956 this.pinnable = this.overlay && this.header.data( 'original-top' ) <= adminBarHeight; 2957 2958 this.win.on( 'resize', $.throttle( 500, $.proxy( this._initSticky, this ) ) ); 2959 this._initSticky(); 2960 2961 } 2962 2963 }, this ) ); 2964 } 2965 }, 2966 2967 /** 2968 * Initializes sticky logic for a header. 2969 * 2970 * @since 1.0 2971 * @access private 2972 * @method _initSticky 2973 */ 2974 _initSticky: function( e ) 2975 { 2976 var header = $('.fl-builder-content[data-type=header]'), 2977 windowSize = this.win.width(), 2978 makeSticky = false; 2979 2980 makeSticky = this._makeWindowSticky( windowSize ); 2981 if ( makeSticky || ( this.breakpointWidth > 0 && windowSize >= this.breakpointWidth ) ) { 2982 this.win.on( 'scroll.fl-theme-builder-header-sticky', $.proxy( this._doSticky, this ) ); 2983 this._initPin(); 2984
2985 // Apply the sticky state on initial load when the page is 2986 // already scrolled past the header — e.g. when the browser 2987 // restores the scroll position on refresh. Overlay headers are 2988 // out of scope here; this mirrors the shrink-on-load behavior 2989 // in _initShrink(). 2990 if ( ! e && ! this.overlay && ! this.header.hasClass( 'fl-theme-builder-header-sticky' ) ) { 2991 var loadWinTop = this.win.scrollTop(), 2992 loadHeaderTop = Math.floor( this.header.data( 'original-top' ) ); 2993 2994 if ( this.hasAdminBar && windowSize > 600 ) { 2995 loadWinTop += Math.floor( $( '#wpadminbar' ).outerHeight() ); 2996 } 2997 2998 if ( loadWinTop > loadHeaderTop ) { 2999 this.header.addClass( 'fl-theme-builder-header-sticky' ); 3000 this.header.addClass( 'fl-theme-builder-header-scrolled' ); 3001 this._adjustHeaderHeight(); 3002 this._adjustStickyHeaderWidth(); 3003 } 3004 } 3005 3006 // 3007 // Check if Event Type is 'resize' then invoke this._doSticky() 3008 // only if the 'fl-theme-builder-header-sticky' class is already present. 3009 // 3010 if ( e && 'resize' === e.type ) { 3011 if ( this.header.hasClass( 'fl-theme-builder-header-sticky' ) ) { 3012 this._doSticky( e ); 3013 } 3014 this._adjustStickyHeaderWidth(); 3015 } 3016 3017 if ( Number( header.attr( 'data-shrink' ) ) ) { 3018 this.header.data( 'original-height', this.header.outerHeight() ); 3019 this.win.on( 'resize', $.throttle( 500, $.proxy( this._initShrink, this ) ) ); 3020 this._initShrink(); 3021 } 3022 3023 this._initFlyoutMenuFix( e ); 3024 } else { 3025 this.win.off( 'scroll.fl-theme-builder-header-sticky' ); 3026 this.win.off( 'resize.fl-theme-builder-header-sticky' ); 3027 3028 this.header.removeClass( 'fl-theme-builder-header-sticky' ); 3029 this.header.removeClass( 'fl-theme-builder-header-pinned' ); 3030 this.header.removeAttr( 'style' ); 3031 this.header.parent().css( 'padding-top', '0' ); 3032 } 3033 }, 3034 3035 /** 3036 * Pins an overlay header with fixed positioning from page load, avoiding a position change that a scroll handler cannot apply before the compositor paints. 3037 * 3038 * @since 1.6.1 3039 * @access private 3040 * @method _initPin 3041 */ 3042 _initPin: function() 3043 { 3044 this.header.toggleClass( 'fl-theme-builder-header-pinned', this.pinnable ); 3045 3046 if ( ! this.pinnable ) { 3047 return; 3048 } 3049 3050 // Fixed positioning resolves the width against the viewport, so boxed layouts require their maximum width immediately. 3051 this._adjustStickyHeaderWidth(); 3052 3053 // The header is visible from the first paint, so the scrolled state must reflect any restored scroll position. 3054 if ( this.win.scrollTop() > Math.floor( this.header.data( 'original-top' ) ) ) { 3055 this.header.addClass( 'fl-theme-builder-header-scrolled' ); 3056 } 3057 }, 3058 3059 /** 3060 * Check if Header should be sticky at a particular Window size. 3061 * 3062 * @since 1.4 3063 * @access private 3064 * @param widowSize 3065 * @method _makeWindowSticky 3066 */ 3067 _makeWindowSticky: function ( windowSize ) 3068 { 3069 var makeSticky = false; 3070 3071 switch (this.stickyOn) { 3072 case 'xl': 3073 makeSticky = windowSize > FLBuilderLayoutConfig.breakpoints['large']; 3074 break; 3075 case '': // Default 3076 case 'desktop': 3077 makeSticky = windowSize >= FLBuilderLayoutConfig.breakpoints['medium']; 3078 break; 3079 case 'desktop-medium': 3080 makeSticky = windowSize > FLBuilderLayoutConfig.breakpoints['small']; 3081 break; 3082 case 'large': 3083 makeSticky = windowSize > FLBuilderLayoutConfig.breakpoints['medium'] && windowSize <= FLBuilderLayoutConfig.breakpoints['large']; 3084 break; 3085 case 'large-medium': 3086 makeSticky = windowSize > FLBuilderLayoutConfig.breakpoints['small'] && windowSize <= FLBuilderLayoutConfig.breakpoints['large']; 3087 break; 3088 case 'medium': 3089 makeSticky = ( windowSize <= FLBuilderLayoutConfig.breakpoints['medium'] && windowSize > FLBuilderLayoutConfig.breakpoints['small'] ); 3090 break; 3091 case 'medium-mobile': 3092 makeSticky = (windowSize <= FLBuilderLayoutConfig.breakpoints['medium']); 3093 break; 3094 case 'mobile': 3095 makeSticky = (windowSize <= FLBuilderLayoutConfig.breakpoints['small']); 3096 break; 3097 case 'all':
3098 makeSticky = true; 3099 break; 3100 } 3101 3102 return makeSticky; 3103 }, 3104 3105 /** 3106 * Sticks the header when the page is scrolled. 3107 * 3108 * @since 1.0 3109 * @access private 3110 * @method _doSticky 3111 */ 3112 _doSticky: function( e ) 3113 { 3114 var winTop = Math.floor( this.win.scrollTop() ), 3115 headerTop = Math.floor( this.header.data( 'original-top' ) ), 3116 hasStickyClass = this.header.hasClass( 'fl-theme-builder-header-sticky' ), 3117 hasScrolledClass = this.header.hasClass( 'fl-theme-builder-header-scrolled' ), 3118 beforeHeader = this.header.prevAll( '.fl-builder-content' ), 3119 bodyTopPadding = parseInt( jQuery('body').css('padding-top') ), 3120 winBarHeight = $('#wpadminbar').length ? $('#wpadminbar').outerHeight() : 0, 3121 headerHeight = 0; 3122 3123 if ( isNaN( bodyTopPadding ) ) { 3124 bodyTopPadding = 0; 3125 } 3126 3127 if ( this.hasAdminBar && this.win.width() > 600 ) { 3128 winTop += Math.floor( winBarHeight ); 3129 } 3130 3131 if ( winTop > headerTop ) { 3132 if ( ! hasStickyClass ) { 3133 if ( e && ( 'scroll' === e.type || 'smartscroll' === e.type ) ) { 3134 this.header.addClass( 'fl-theme-builder-header-sticky' ); 3135 if ( this.overlay && beforeHeader.length ) { 3136 this.header.css( 'top', winBarHeight); 3137 } 3138 } 3139 3140 if ( ! this.overlay ) { 3141 this._adjustHeaderHeight(); 3142 } 3143 } 3144 } 3145 else if ( hasStickyClass ) { 3146 this.header.removeClass( 'fl-theme-builder-header-sticky' ); 3147 this.header.removeAttr( 'style' ); 3148 this.header.parent().css( 'padding-top', '0' ); 3149 } 3150 3151 this._adjustStickyHeaderWidth(); 3152 3153 if ( winTop > headerTop ) { 3154 if ( ! hasScrolledClass ) { 3155 this.header.addClass( 'fl-theme-builder-header-scrolled' ); 3156 } 3157 } else if ( hasScrolledClass ) { 3158 this.header.removeClass( 'fl-theme-builder-header-scrolled' ); 3159 } 3160 3161 this._flyoutMenuFix( e ); 3162 }, 3163 3164 /** 3165 * Initializes flyout menu fixes on sticky header. 3166 * 3167 * @since 1.4.1 3168 * @method _initFlyoutMenuFix 3169 */ 3170 _initFlyoutMenuFix: function( e ) { 3171 var header = this.header, 3172 menuModule = header.closest( '.fl-menu' ), 3173 flyoutMenu = menuModule.find( '.fl-menu-mobile-flyout' ), 3174 isPushMenu = menuModule.hasClass( 'fl-menu-responsive-flyout-push' ) || menuModule.hasClass( 'fl-menu-responsive-flyout-push-opacity' ), 3175 isOverlay = menuModule.hasClass( 'fl-menu-responsive-flyout-overlay' ), 3176 flyoutPos = menuModule.hasClass( 'fl-flyout-right' ) ? 'right' : 'left', 3177 flyoutParent = header.parent().is( 'header' ) ? header.parent().parent() : header.parent(); 3178 isFullWidth = this.win.width() === header.width(), 3179 flyoutLayout = '', 3180 activePos = 250, 3181 headerPos = 0; 3182 3183 if ( ! flyoutMenu.length ) { 3184 return; 3185 } 3186 3187 if ( this.win.width() > header.parent().width() ) { 3188 headerPos = ( this.win.width() - header.width() ) / 2; 3189 } 3190 3191 if ( isOverlay ) { 3192 activePos = headerPos; 3193 } 3194 else if ( isPushMenu ) { 3195 activePos = activePos + headerPos; 3196 } 3197 flyoutMenu.data( 'activePos', activePos ); 3198 3199 if ( isPushMenu ) { 3200 flyoutLayout = 'push-' + flyoutPos; 3201 } 3202 else if ( isOverlay ) { 3203 flyoutLayout = 'overlay-' + flyoutPos; 3204 } 3205 3206 if ( isPushMenu && ! $( 'html' ).hasClass( 'fl-theme-builder-has-flyout-menu' ) ) { 3207 $( 'html' ).addClass( 'fl-theme-builder-has-flyout-menu' ); 3208 } 3209 3210 if ( ! flyoutParent.hasClass( 'fl-theme-builder-flyout-menu-' + flyoutLayout ) ) { 3211 flyoutParent.addClass( 'fl-theme-builder-flyout-menu-' + flyoutLayout ); 3212 } 3213 3214 if ( ! header.hasClass( 'fl-theme-builder-flyout-menu-overlay' ) && isOverlay ) { 3215 header.addClass( 'fl-theme-builder-flyout-menu-overlay' ); 3216 } 3217 3218 if ( ! header.hasClass( 'fl-theme-builder-header-full-width' ) && isFullWidth ) { 3219 header.addClass( 'fl-theme-builder-header-full-width' ); 3220 } 3221 else if ( ! isFullWidth ) { 3222 header.removeClass( 'fl-theme-builder-header-full-width' ); 3223 } 3224 3225 menuModule.on( 'click', '.fl-menu-mobile-toggle', $.proxy( function( event ){ 3226 if ( menuModule.find( '.fl-menu-mobile-toggle.fl-active' ).length ) {
3227 $( 'html' ).addClass( 'fl-theme-builder-flyout-menu-active' ); 3228 event.stopImmediatePropagation(); 3229 } 3230 else { 3231 $( 'html' ).removeClass( 'fl-theme-builder-flyout-menu-active' ); 3232 } 3233 3234 this._flyoutMenuFix( event ); 3235 }, this ) ); 3236 }, 3237 3238 /** 3239 * Fix flyout menu inside the sticky header. 3240 * 3241 * @since 1.4.1 3242 * @method _flyoutMenuFix 3243 */ 3244 _flyoutMenuFix: function( e ){ 3245 var header = this.header, 3246 menuModule = $( e.target ).closest( '.fl-menu' ), 3247 flyoutMenu = menuModule.find( '.fl-menu-mobile-flyout' ), 3248 flyoutPos = menuModule.hasClass( 'fl-flyout-right' ) ? 'right' : 'left', 3249 menuOpacity = menuModule.find( '.fl-menu-mobile-opacity' ), 3250 isScroll = 'undefined' !== typeof e && 'scroll' === e.handleObj.type, 3251 activePos = 'undefined' !== typeof flyoutMenu.data( 'activePos' ) ? flyoutMenu.data( 'activePos' ) : 0, 3252 headerPos = ( this.win.width() - header.width() ) / 2, 3253 inactivePos = headerPos > 0 ? activePos + 4 : 254; 3254 3255 if ( ! flyoutMenu.length ) { 3256 return; 3257 } 3258 3259 if ( this.overlay ) { 3260 return; 3261 } 3262 3263 if( $( '.fl-theme-builder-flyout-menu-active' ).length ) { 3264 3265 if ( isScroll && ! flyoutMenu.hasClass( 'fl-menu-disable-transition' ) ) { 3266 flyoutMenu.addClass( 'fl-menu-disable-transition' ); 3267 } 3268 3269 if ( header.hasClass( 'fl-theme-builder-header-sticky' ) ) { 3270 if ( ! isScroll ) { 3271 setTimeout( $.proxy( function(){ 3272 flyoutMenu.css( flyoutPos, '-' + activePos + 'px' ); 3273 }, this ), 1 ); 3274 } 3275 else { 3276 flyoutMenu.css( flyoutPos, '-' + activePos + 'px' ); 3277 } 3278 } 3279 else { 3280 flyoutMenu.css( flyoutPos, '0px' ); 3281 } 3282 } 3283 else { 3284 if ( flyoutMenu.hasClass( 'fl-menu-disable-transition' ) ) { 3285 flyoutMenu.removeClass( 'fl-menu-disable-transition' ); 3286 } 3287 3288 if ( header.hasClass( 'fl-theme-builder-flyout-menu-overlay' ) && headerPos > 0 && headerPos < 250 ) { 3289 if ( header.hasClass( 'fl-theme-builder-header-sticky' ) ) { 3290 inactivePos = headerPos + 254; 3291 } 3292 else { 3293 inactivePos = 254; 3294 } 3295 } 3296 3297 if ( e && e.type === 'resize' ) { 3298 inactivePos = headerPos + 254; 3299 } 3300 3301 flyoutMenu.css( flyoutPos, '-' + inactivePos + 'px' ); 3302 } 3303 3304 if ( e && menuModule.is('.fl-menu-responsive-flyout-overlay') && $.infinitescroll ) { 3305 e.stopImmediatePropagation(); 3306 } 3307 3308 if( menuOpacity.length ) { 3309 if ( header.hasClass( 'fl-theme-builder-header-sticky' ) ) { 3310 if ( '0px' === menuOpacity.css( 'left' ) ) { 3311 menuOpacity.css( 'left', '-' + headerPos + 'px' ); 3312 } 3313 } 3314 else { 3315 menuOpacity.css( 'left', '' ); 3316 } 3317 } 3318 }, 3319 3320 /** 3321 * Adjust sticky header width if BB Theme Boxed Layout is used. 3322 * 3323 * @since 1.4 3324 * @access private 3325 * @method _adjustStickyHeaderWidth 3326 */ 3327 _adjustStickyHeaderWidth: function () { 3328 if ( $('body').hasClass( 'fl-fixed-width' ) ) { 3329 var parentWidth = this.header.parent().width(); 3330 3331 // Better if this is set in the stylesheet file. 3332 if ( this.win.width() >= 992 ) { 3333 this.header.css({ 3334 'margin': '0 auto', 3335 'max-width': parentWidth, 3336 }); 3337 } 3338 else { 3339 this.header.css({ 3340 'margin': '', 3341 'max-width': '', 3342 }); 3343 } 3344 } 3345 }, 3346 3347 /** 3348 * Adjust Sticky Header Height 3349 * 3350 * @since 1.4 3351 * @access private 3352 * @method _adjustHeaderHeight 3353 */ 3354 _adjustHeaderHeight: function () { 3355 var beforeHeader = this.header.prevAll('.fl-builder-content'), 3356 beforeHeaderHeight = 0, 3357 beforeHeaderFix = 0, 3358 headerHeight = Math.floor( this.header.outerHeight() ), 3359 bodyTopPadding = parseInt( $( 'body' ).css( 'padding-top' ) ), 3360 wpAdminBarHeight = 0, 3361 totalHeaderHeight = 0; 3362 3363 if ( isNaN( bodyTopPadding ) ) { 3364 bodyTopPadding = 0; 3365 } 3366 3367 if ( beforeHeader.length ) { 3368 $.each( beforeHeader, function() { 3369 beforeHeaderHeight += Math.floor( $(this).outerHeight() ); 3370 }); 3371 // Subtract this value from the header parent's top padding. 3372 beforeHeaderFix = 2; 3373 } 3374 3375 if ( this.hasAdminBar && this.win.width() <= 600 ) { 3376 wpAdminBarHeight = Math.floor( $('#wpadminbar').outerHeight() ); 3377 } 3378 3379 totalHeaderHeight = Math.floor( beforeHeaderHeight + headerHeight); 3380 3381 if ( headerHeight > 0 ) { 3382 var headerParent = this.header.parent(), 3383 headerParentTopPadding = 0; 3384 3385 // If the header's parent container is the BODY tag ignore its top padding. 3386 if ( $( headerParent ).is('body') ) { 3387 headerParentTopPadding = Math.floor( headerHeight - wpAdminBarHeight ); 3388 } else { 3389 headerParentTopPadding = Math.floor( headerHeight - bodyTopPadding - wpAdminBarHeight ); 3390 } 3391 3392 $( headerParent ).css( 'padding-top', ( headerParentTopPadding - beforeHeaderFix ) + 'px' ); 3393 3394 this.header.css({ 3395 '-webkit-transform': 'translate(0px, -' + totalHeaderHeight + 'px)', 3396 '-ms-transform': 'translate(0px, -' + totalHeaderHeight + 'px)', 3397 'transform': 'translate(0px, -' + totalHeaderHeight + 'px)' 3398 }); 3399 3400 } 3401 3402 }, 3403 3404 /** 3405 * Initializes shrink logic for a header. 3406 * 3407 * @since 1.0 3408 * @access private 3409 * @method _initShrink 3410 */ 3411 _initShrink: function( e ) 3412 { 3413 if ( this.win.width() >= this.breakpointWidth ) {
3414 this.win.on( 'scroll.fl-theme-builder-header-shrink', $.proxy( this._doShrink, this ) ); 3415 this._setImageMaxHeight(); 3416 3417 if ( this.win.scrollTop() > 0 ){ 3418 this._doShrink(); 3419 } 3420 3421 } else { 3422 this.header.parent().css( 'padding-top', '0' ); 3423 this.win.off( 'scroll.fl-theme-builder-header-shrink' ); 3424 this._removeShrink(); 3425 this._removeImageMaxHeight(); 3426 } 3427 }, 3428 3429 /** 3430 * Shrinks the header when the page is scrolled. 3431 * 3432 * @since 1.0 3433 * @access private 3434 * @method _doShrink 3435 */ 3436 _doShrink: function( e ) 3437 { 3438 var winTop = this.win.scrollTop(), 3439 headerTop = this.header.data('original-top'), 3440 headerHeight = this.header.data('original-height'), 3441 shrinkImageHeight = this.header.data('shrink-image-height'), 3442 windowSize = this.win.width(), 3443 makeSticky = this._makeWindowSticky( windowSize ), 3444 hasClass = this.header.hasClass( 'fl-theme-builder-header-shrink' ); 3445 3446 3447 if ( this.hasAdminBar ) { 3448 winTop += 32; 3449 } 3450 3451 if ( makeSticky && ( winTop > headerTop + headerHeight ) ) { 3452 if ( ! hasClass ) { 3453 3454 this.header.addClass( 'fl-theme-builder-header-shrink' ); 3455 3456 // Shrink images but don't include lightbox and menu images. 3457 this.header.find('img').each( function( i ) { 3458 var image = $( this ), 3459 maxMegaMenu = image.closest( '.max-mega-menu' ).length, 3460 imageInLightbox = image.closest( '.fl-button-lightbox-content' ).length, 3461 imageInNavMenu = image.closest( 'li.menu-item' ).length; 3462 3463 if ( ! ( imageInLightbox || imageInNavMenu || maxMegaMenu ) ) { 3464 image.css( 'max-height', shrinkImageHeight ); 3465 } 3466 3467 }); 3468 3469 this.header.find( '.fl-row-content-wrap' ).each( function() { 3470 3471 var row = $( this ); 3472 3473 if ( parseInt( row.css( 'padding-bottom' ) ) > 5 ) { 3474 row.addClass( 'fl-theme-builder-header-shrink-row-bottom' ); 3475 } 3476 3477 if ( parseInt( row.css( 'padding-top' ) ) > 5 ) { 3478 row.addClass( 'fl-theme-builder-header-shrink-row-top' ); 3479 } 3480 } ); 3481 3482 this.header.find( '.fl-module' ).each( function() { 3483 3484 var module = $( this ).find( '.fl-module-content' ).length ? $( this ).find( '.fl-module-content' ) : $( this ); 3485 3486 if ( parseInt( module.css( 'margin-bottom' ) ) > 5 ) { 3487 module.addClass( 'fl-theme-builder-header-shrink-module-bottom' ); 3488 } 3489 3490 if ( parseInt( module.css( 'margin-top' ) ) > 5 ) { 3491 module.addClass( 'fl-theme-builder-header-shrink-module-top' ); 3492 } 3493 } ); 3494 } 3495 } else if (hasClass) { 3496 this.header.find( 'img' ).css( 'max-height', '' ); 3497 this._removeShrink(); 3498 } 3499 3500 // Fixes Shrink header issue with BB Theme when window is scrolled then resized and back. 3501 if ( 'undefined' === typeof( e ) && $('body').hasClass( 'fl-fixed-width' ) ) { 3502 if ( ! this.overlay ) { 3503 this._adjustHeaderHeight(); 3504 } 3505 } 3506 3507 }, 3508 3509 /** 3510 * Removes the header shrink effect. 3511 * 3512 * @since 1.0 3513 * @access private 3514 * @method _removeShrink 3515 */ 3516 _removeShrink: function() 3517 { 3518 var rows = this.header.find( '.fl-row-content-wrap' ), 3519 modules = this.header.find('.fl-module, .fl-module-content'); 3520 3521 rows.removeClass( 'fl-theme-builder-header-shrink-row-bottom' ); 3522 rows.removeClass( 'fl-theme-builder-header-shrink-row-top' ); 3523 modules.removeClass( 'fl-theme-builder-header-shrink-module-bottom' ); 3524 modules.removeClass( 'fl-theme-builder-header-shrink-module-top' ); 3525 this.header.removeClass( 'fl-theme-builder-header-shrink' ); 3526 }, 3527 3528 /** 3529 * Adds max height to images in modules for smooth scrolling. 3530 * 3531 * @since 1.1.1 3532 * @access private 3533 * @method _setImageMaxHeight 3534 */ 3535 _setImageMaxHeight: function() 3536 { 3537 var head = $( 'head' ), 3538 stylesId = 'fl-header-styles-' + this.header.data( 'post-id' ), 3539 styles = '', 3540 images = this.header.find( '.fl-module img' ); 3541 3542 if ( $( '#' + stylesId ).length ) { 3543 return; 3544 } 3545 3546 images.each( function( i ) { 3547 var image = $( this ), 3548 height = image.height(), 3549 node = image.closest( '.fl-module' ).data( 'node' ), 3550 className = 'fl-node-' + node + '-img-' + i, 3551 maxMegaMenu = image.closest( '.max-mega-menu' ).length, 3552 imageInLightbox = image.closest( '.fl-button-lightbox-content' ).length, 3553 imageInNavMenu = image.closest( 'li.menu-item' ).length; 3554 3555 if ( ! ( imageInLightbox || imageInNavMenu || maxMegaMenu ) ) { 3556 image.addClass( className ); 3557 styles += '.' + className + ' { max-height: ' + ( height ? height : image[0].height ) + 'px }'; 3558 } 3559 3560 } ); 3561
3562 if ( '' !== styles ) { 3563 head.append( '<style id="' + stylesId + '">' + styles + '</style>' ); 3564 } 3565 }, 3566 3567 /** 3568 * Removes max height on images in modules for smooth scrolling. 3569 * 3570 * @since 1.1.1 3571 * @access private 3572 * @method _removeImageMaxHeight 3573 */ 3574 _removeImageMaxHeight: function() 3575 { 3576 $( '#fl-header-styles-' + this.header.data( 'post-id' ) ).remove(); 3577 }, 3578 }; 3579 3580 $( function() { FLThemeBuilderHeaderLayout.init(); } ); 3581 3582})(jQuery); 3583
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.