Skip to content

Instantly share code, notes, and snippets.

@Pross
Created September 4, 2019 19:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pross/a10148a322a5a99a8f5799fdbfd38f97 to your computer and use it in GitHub Desktop.
Save Pross/a10148a322a5a99a8f5799fdbfd38f97 to your computer and use it in GitHub Desktop.
bb-plugin/js/fl-builder-layout.js
(function($){
if(typeof FLBuilderLayout != 'undefined') {
return;
}
/**
* Helper class with generic logic for a builder layout.
*
* @class FLBuilderLayout
* @since 1.0
*/
FLBuilderLayout = {
/**
* Initializes a builder layout.
*
* @since 1.0
* @method init
*/
init: function()
{
// Destroy existing layout events.
FLBuilderLayout._destroy();
// Init CSS classes.
FLBuilderLayout._initClasses();
// Init backgrounds.
FLBuilderLayout._initBackgrounds();
// Only init if the builder isn't active.
if ( 0 === $('.fl-builder-edit').length ) {
// Init module animations.
FLBuilderLayout._initModuleAnimations();
// Init anchor links.
FLBuilderLayout._initAnchorLinks();
// Init the browser hash.
FLBuilderLayout._initHash();
// Init forms.
FLBuilderLayout._initForms();
}
},
/**
* Public method for refreshing Wookmark or MosaicFlow galleries
* within an element.
*
* @since 1.7.4
* @method refreshGalleries
*/
refreshGalleries: function( element )
{
var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
mfContent = $element.find( '.fl-mosaicflow-content' ),
wmContent = $element.find( '.fl-gallery' ),
mfObject = null;
if ( mfContent ) {
mfObject = mfContent.data( 'mosaicflow' );
if ( mfObject ) {
mfObject.columns = $( [] );
mfObject.columnsHeights = [];
mfContent.data( 'mosaicflow', mfObject );
mfContent.mosaicflow( 'refill' );
}
}
if ( wmContent ) {
wmContent.trigger( 'refreshWookmark' );
}
},
/**
* Public method for refreshing Masonry within an element
*
* @since 1.8.1
* @method refreshGridLayout
*/
refreshGridLayout: function( element )
{
var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
msnryContent = $element.find('.masonry');
if ( msnryContent.length ) {
msnryContent.masonry('layout');
}
},
/**
* Public method for reloading BxSlider within an element
*
* @since 1.8.1
* @method reloadSlider
*/
reloadSlider: function( element )
{
var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
bxContent = $element.find('.bx-viewport > div').eq(0),
bxObject = null;
if ( bxContent.length ) {
bxObject = bxContent.data( 'bxSlider');
if ( bxObject ) {
bxObject.reloadSlider();
}
}
},
/**
* Public method for resizing WP audio player
*
* @since 1.8.2
* @method resizeAudio
*/
resizeAudio: function( element )
{
var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
audioPlayers = $element.find('.wp-audio-shortcode.mejs-audio'),
player = null,
mejsPlayer = null,
rail = null,
railWidth = 400;
if ( audioPlayers.length && typeof mejs !== 'undefined' ) {
audioPlayers.each(function(){
player = $(this);
mejsPlayer = mejs.players[player.attr('id')];
rail = player.find('.mejs-controls .mejs-time-rail');
var innerMejs = player.find('.mejs-inner'),
total = player.find('.mejs-controls .mejs-time-total');
if ( typeof mejsPlayer !== 'undefined' ) {
railWidth = Math.ceil(player.width() * 0.8);
if ( innerMejs.length ) {
rail.css('width', railWidth +'px!important');
//total.width(rail.width() - 10);
mejsPlayer.options.autosizeProgress = true;
// webkit has trouble doing this without a delay
setTimeout(function () {
mejsPlayer.setControlsSize();
}, 50);
player.find('.mejs-inner').css({
visibility: 'visible',
height: 'inherit'
});
}
}
});
}
},
/**
* Public method for preloading WP audio player when it's inside the hidden element
*
* @since 1.8.2
* @method preloadAudio
*/
preloadAudio: function(element)
{
var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
contentWrap = $element.closest('.fl-accordion-item'),
audioPlayers = $element.find('.wp-audio-shortcode.mejs-audio');
if ( ! contentWrap.hasClass('fl-accordion-item-active') && audioPlayers.find('.mejs-inner').length ) {
audioPlayers.find('.mejs-inner').css({
visibility : 'hidden',
height: 0
});
}
},
/**
* Public method for resizing slideshow momdule within the tab
*
* @since 1.10.5
* @method resizeSlideshow
*/
resizeSlideshow: function(){
if(typeof YUI !== 'undefined') {
YUI().use('node-event-simulate', function(Y) {
Y.one(window).simulate("resize");
});
}
},
/**
* Public method for reloading an embedded Google Map within the tabs or hidden element.
*
* @since 2.2
* @method reloadGoogleMap
*/
reloadGoogleMap: function(element){
var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
googleMap = $element.find( 'iframe[src*="google.com/maps"]' );
if ( googleMap.length ) {
googleMap.attr( 'src', function(i, val) {
return val;
});
}
},
/**
* Unbinds builder layout events.
*
* @since 1.0
* @access private
* @method _destroy
*/
_destroy: function()
{
var win = $(window);
win.off('scroll.fl-bg-parallax');
win.off('resize.fl-bg-video');
},
/**
* Checks to see if the current device has touch enabled.
*
* @since 1.0
* @access private
* @method _isTouch
* @return {Boolean}
*/
_isTouch: function()
{
if(('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch)) {
return true;
}
return false;
},
/**
* Checks to see if the current device is mobile.
*
* @since 1.7
* @access private
* @method _isMobile
* @return {Boolean}
*/
_isMobile: function()
{
return /Mobile|Android|Silk\/|Kindle|BlackBerry|Opera Mini|Opera Mobi|webOS/i.test( navigator.userAgent );
},
/**
* Initializes builder body classes.
*
* @since 1.0
* @access private
* @method _initClasses
*/
_initClasses: function()
{
var body = $( 'body' ),
ua = navigator.userAgent;
// Add the builder body class.
if ( ! body.hasClass( 'archive' ) && $( '.fl-builder-content-primary' ).length > 0 ) {
body.addClass('fl-builder');
}
// Add the builder touch body class.
if(FLBuilderLayout._isTouch()) {
body.addClass('fl-builder-touch');
}
// Add the builder mobile body class.
if(FLBuilderLayout._isMobile()) {
body.addClass('fl-builder-mobile');
}
// IE11 body class.
if ( ua.indexOf( 'Trident/7.0' ) > -1 && ua.indexOf( 'rv:11.0' ) > -1 ) {
body.addClass( 'fl-builder-ie-11' );
}
},
/**
* Initializes builder node backgrounds that require
* additional JavaScript logic such as parallax.
*
* @since 1.1.4
* @access private
* @method _initBackgrounds
*/
_initBackgrounds: function()
{
var win = $(window);
// Init parallax backgrounds.
if($('.fl-row-bg-parallax').length > 0 && !FLBuilderLayout._isMobile()) {
FLBuilderLayout._scrollParallaxBackgrounds();
FLBuilderLayout._initParallaxBackgrounds();
win.on('scroll.fl-bg-parallax', FLBuilderLayout._scrollParallaxBackgrounds);
}
// Init video backgrounds.
if($('.fl-bg-video').length > 0) {
FLBuilderLayout._initBgVideos();
FLBuilderLayout._resizeBgVideos();
win.on('resize.fl-bg-video', FLBuilderLayout._resizeBgVideos);
}
},
/**
* Initializes all parallax backgrounds in a layout.
*
* @since 1.1.4
* @access private
* @method _initParallaxBackgrounds
*/
_initParallaxBackgrounds: function()
{
$('.fl-row-bg-parallax').each(FLBuilderLayout._initParallaxBackground);
},
/**
* Initializes a single parallax background.
*
* @since 1.1.4
* @access private
* @method _initParallaxBackgrounds
*/
_initParallaxBackground: function()
{
var row = $(this),
content = row.find('> .fl-row-content-wrap'),
src = row.data('parallax-image'),
loaded = row.data('parallax-loaded'),
img = new Image();
if(loaded) {
return;
}
else if(typeof src != 'undefined') {
$(img).on('load', function() {
content.css('background-image', 'url(' + src + ')');
row.data('parallax-loaded', true);
});
img.src = src;
}
},
/**
* Fires when the window is scrolled to adjust
* parallax backgrounds.
*
* @since 1.1.4
* @access private
* @method _scrollParallaxBackgrounds
*/
_scrollParallaxBackgrounds: function()
{
$('.fl-row-bg-parallax').each(FLBuilderLayout._scrollParallaxBackground);
},
/**
* Fires when the window is scrolled to adjust
* a single parallax background.
*
* @since 1.1.4
* @access private
* @method _scrollParallaxBackground
*/
_scrollParallaxBackground: function()
{
var win = $(window),
row = $(this),
content = row.find('> .fl-row-content-wrap'),
speed = row.data('parallax-speed'),
offset = content.offset(),
yPos = -((win.scrollTop() - offset.top) / speed);
content.css('background-position', 'center ' + yPos + 'px');
},
/**
* Initializes all video backgrounds.
*
* @since 1.6.3.3
* @access private
* @method _initBgVideos
*/
_initBgVideos: function()
{
$('.fl-bg-video').each(FLBuilderLayout._initBgVideo);
},
/**
* Initializes a video background.
*
* @since 1.6.3.3
* @access private
* @method _initBgVideo
*/
_initBgVideo: function()
{
var wrap = $( this ),
width = wrap.data( 'width' ),
height = wrap.data( 'height' ),
mp4 = wrap.data( 'mp4' ),
youtube = wrap.data( 'youtube'),
vimeo = wrap.data( 'vimeo'),
mp4Type = wrap.data( 'mp4-type' ),
webm = wrap.data( 'webm' ),
webmType = wrap.data( 'webm-type' ),
fallback = wrap.data( 'fallback' ),
loaded = wrap.data( 'loaded' ),
videoMobile = wrap.data( 'video-mobile' ),
fallbackTag = '',
videoTag = null,
mp4Tag = null,
webmTag = null;
// Return if the video has been loaded for this row.
if ( loaded ) {
return;
}
videoTag = $( '<video autoplay loop muted playsinline></video>' );
/**
* Add poster image (fallback image)
*/
if( 'undefined' != typeof fallback && '' != fallback ) {
videoTag.attr( 'poster', 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' )
videoTag.css( 'background', 'transparent url("' + fallback + '") no-repeat center center' )
videoTag.css( 'background-size', 'cover' )
videoTag.css( 'height', '100%' )
}
// MP4 Source Tag
if ( 'undefined' != typeof mp4 && '' != mp4 ) {
mp4Tag = $( '<source />' );
mp4Tag.attr( 'src', mp4 );
mp4Tag.attr( 'type', mp4Type );
videoTag.append( mp4Tag );
}
// WebM Source Tag
if ( 'undefined' != typeof webm && '' != webm ) {
webmTag = $( '<source />' );
webmTag.attr( 'src', webm );
webmTag.attr( 'type', webmType );
videoTag.append( webmTag );
}
// Check what video player we are going to load in a row
if ( ! FLBuilderLayout._isMobile() || ( FLBuilderLayout._isMobile() && "yes" == videoMobile ) ) {
if ( 'undefined' != typeof youtube ) {
FLBuilderLayout._initYoutubeBgVideo.apply( this );
}
else if ( 'undefined' != typeof vimeo ) {
FLBuilderLayout._initVimeoBgVideo.apply( this );
}
else {
wrap.append( videoTag );
}
}
// Mark this video as loaded.
wrap.data('loaded', true);
},
/**
* Initializes Youtube video background
*
* @since 1.9
* @access private
* @method _initYoutubeBgVideo
*/
_initYoutubeBgVideo: function()
{
var playerWrap = $(this),
videoId = playerWrap.data('video-id'),
videoPlayer = playerWrap.find('.fl-bg-video-player'),
enableAudio = playerWrap.data('enable-audio'),
audioButton = playerWrap.find('.fl-bg-video-audio'),
startTime = 'undefined' !== typeof playerWrap.data('start') ? playerWrap.data('start') : 0,
endTime = 'undefined' !== typeof playerWrap.data('end') ? playerWrap.data('end') : 0,
loop = 'undefined' !== typeof playerWrap.data('loop') ? playerWrap.data('loop') : 1,
stateCount = 0,
player;
if ( videoId ) {
FLBuilderLayout._onYoutubeApiReady( function( YT ) {
setTimeout( function() {
player = new YT.Player( videoPlayer[0], {
videoId: videoId,
events: {
onReady: function(event) {
if ( "no" === enableAudio || FLBuilderLayout._isMobile() ) {
event.target.mute();
}
else if ( "yes" === enableAudio && event.target.isMuted ) {
event.target.unMute();
}
// Store an instance to a parent
playerWrap.data('YTPlayer', player);
FLBuilderLayout._resizeYoutubeBgVideo.apply(playerWrap);
// Queue the video.
event.target.playVideo();
if ( audioButton.length > 0 && ! FLBuilderLayout._isMobile() ) {
audioButton.on( 'click', {button: audioButton, player: player}, FLBuilderLayout._toggleBgVideoAudio );
}
},
onStateChange: function( event ) {
// Manual check if video is not playable in some browsers.
// StateChange order: [-1, 3, -1]
if ( stateCount < 4 ) {
stateCount++;
}
// Comply with the audio policy in some browsers like Chrome and Safari.
if ( stateCount > 1 && (-1 === event.data || 2 === event.data) && "yes" === enableAudio ) {
player.mute();
player.playVideo();
audioButton.show();
}
if ( event.data === YT.PlayerState.ENDED && 1 === loop ) {
if ( startTime > 0 ) {
player.seekTo( startTime );
}
else {
player.playVideo();
}
}
},
onError: function(event) {
console.info('YT Error: ' + event.data)
FLBuilderLayout._onErrorYoutubeVimeo(playerWrap)
}
},
playerVars: {
playsinline: FLBuilderLayout._isMobile() ? 1 : 0,
controls: 0,
showinfo: 0,
rel : 0,
start: startTime,
end: endTime,
}
} );
}, 1 );
} );
}
},
/**
* On youtube or vimeo error show the fallback image if available.
* @since 2.0.7
*/
_onErrorYoutubeVimeo: function(playerWrap) {
fallback = playerWrap.data('fallback') || false
if( ! fallback ) {
return false;
}
playerWrap.find('iframe').remove()
fallbackTag = $( '<div></div>' );
fallbackTag.addClass( 'fl-bg-video-fallback' );
fallbackTag.css( 'background-image', 'url(' + playerWrap.data('fallback') + ')' );
playerWrap.append( fallbackTag );
},
/**
* Check if Youtube API has been downloaded
*
* @since 1.9
* @access private
* @method _onYoutubeApiReady
* @param {Function} callback Method to call when YT API has been loaded
*/
_onYoutubeApiReady: function( callback ) {
if ( window.YT && YT.loaded ) {
callback( YT );
} else {
// If not ready check again by timeout..
setTimeout( function() {
FLBuilderLayout._onYoutubeApiReady( callback );
}, 350 );
}
},
/**
* Initializes Vimeo video background
*
* @since 1.9
* @access private
* @method _initVimeoBgVideo
*/
_initVimeoBgVideo: function()
{
var playerWrap = $(this),
videoId = playerWrap.data('video-id'),
videoPlayer = playerWrap.find('.fl-bg-video-player'),
enableAudio = playerWrap.data('enable-audio'),
audioButton = playerWrap.find('.fl-bg-video-audio'),
player,
width = playerWrap.outerWidth();
if ( typeof Vimeo !== 'undefined' && videoId ) {
player = new Vimeo.Player(videoPlayer[0], {
id : videoId,
loop : true,
title : false,
portrait : false,
background : true,
autopause : false,
muted : true
});
playerWrap.data('VMPlayer', player);
if ( "no" === enableAudio ) {
player.setVolume(0);
}
else if ("yes" === enableAudio ) {
// Chrome and Safari have audio policy restrictions for autoplay videos.
if ( $.browser.safari || $.browser.chrome ) {
player.setVolume(0);
audioButton.show();
}
else {
player.setVolume(1);
}
}
player.play().catch(function(error) {
FLBuilderLayout._onErrorYoutubeVimeo(playerWrap)
});
if ( audioButton.length > 0 ) {
audioButton.on( 'click', {button: audioButton, player: player}, FLBuilderLayout._toggleBgVideoAudio );
}
}
},
/**
* Mute / unmute audio on row's video background.
* It works for both Youtube and Vimeo.
*
* @since 2.1.3
* @access private
* @method _toggleBgVideoAudio
* @param {Object} e Method arguments
*/
_toggleBgVideoAudio: function( e ) {
var player = e.data.player,
control = e.data.button.find('.fl-audio-control');
if ( control.hasClass( 'fa-volume-off' ) ) {
// Unmute
control
.removeClass( 'fa-volume-off' )
.addClass( 'fa-volume-up' );
e.data.button.find( '.fa-times' ).hide();
if ( 'function' === typeof player.unMute ) {
player.unMute();
}
else {
player.setVolume( 1 );
}
}
else {
// Mute
control
.removeClass( 'fa-volume-up' )
.addClass( 'fa-volume-off' );
e.data.button.find( '.fa-times' ).show();
if ( 'function' === typeof player.unMute ) {
player.mute();
}
else {
player.setVolume( 0 );
}
}
},
/**
* Fires when there is an error loading a video
* background source and shows the fallback.
*
* @since 1.6.3.3
* @access private
* @method _videoBgSourceError
* @param {Object} e An event object
* @deprecated 2.0.3
*/
_videoBgSourceError: function( e )
{
var source = $( e.target ),
wrap = source.closest( '.fl-bg-video' ),
vid = wrap.find( 'video' ),
fallback = wrap.data( 'fallback' ),
fallbackTag = '';
source.remove();
if ( vid.find( 'source' ).length ) {
// Don't show the fallback if we still have other sources to check.
return;
} else if ( '' !== fallback ) {
fallbackTag = $( '<div></div>' );
fallbackTag.addClass( 'fl-bg-video-fallback' );
fallbackTag.css( 'background-image', 'url(' + fallback + ')' );
wrap.append( fallbackTag );
vid.remove();
}
},
/**
* Fires when the window is resized to resize
* all video backgrounds.
*
* @since 1.1.4
* @access private
* @method _resizeBgVideos
*/
_resizeBgVideos: function()
{
$('.fl-bg-video').each( function() {
FLBuilderLayout._resizeBgVideo.apply( this );
if ( $( this ).parent().find( 'img' ).length > 0 ) {
$( this ).parent().imagesLoaded( $.proxy( FLBuilderLayout._resizeBgVideo, this ) );
}
} );
},
/**
* Fires when the window is resized to resize
* a single video background.
*
* @since 1.1.4
* @access private
* @method _resizeBgVideo
*/
_resizeBgVideo: function()
{
if ( 0 === $( this ).find( 'video' ).length && 0 === $( this ).find( 'iframe' ).length ) {
return;
}
var wrap = $(this),
wrapHeight = wrap.outerHeight(),
wrapWidth = wrap.outerWidth(),
vid = wrap.find('video'),
vidHeight = wrap.data('height'),
vidWidth = wrap.data('width'),
newWidth = wrapWidth,
newHeight = Math.round(vidHeight * wrapWidth/vidWidth),
newLeft = 0,
newTop = 0,
iframe = wrap.find('iframe');
if ( vid.length ) {
if(vidHeight === '' || typeof vidHeight === 'undefined' || vidWidth === '' || typeof vidWidth === 'undefined') {
vid.css({
'left' : '0px',
'top' : '0px',
'width' : newWidth + 'px'
});
// Try to set the actual video dimension on 'loadedmetadata' when using URL as video source
vid.on('loadedmetadata', FLBuilderLayout._resizeOnLoadedMeta);
}
else {
if(newHeight < wrapHeight) {
newHeight = wrapHeight;
newWidth = Math.round(vidWidth * wrapHeight/vidHeight);
newLeft = -((newWidth - wrapWidth)/2);
}
else {
newTop = -((newHeight - wrapHeight)/2);
}
vid.css({
'left' : newLeft + 'px',
'top' : newTop + 'px',
'height' : newHeight + 'px',
'width' : newWidth + 'px'
});
}
}
else if ( iframe.length ) {
// Resize Youtube video player within iframe tag
if ( typeof wrap.data('youtube') !== 'undefined' ) {
FLBuilderLayout._resizeYoutubeBgVideo.apply(this);
}
}
},
/**
* Fires when video meta has been loaded.
* This will be Triggered when width/height attributes were not specified during video background resizing.
*
* @since 1.8.5
* @access private
* @method _resizeOnLoadedMeta
*/
_resizeOnLoadedMeta: function(){
var video = $(this),
wrapHeight = video.parent().outerHeight(),
wrapWidth = video.parent().outerWidth(),
vidWidth = video[0].videoWidth,
vidHeight = video[0].videoHeight,
newHeight = Math.round(vidHeight * wrapWidth/vidWidth),
newWidth = wrapWidth,
newLeft = 0,
newTop = 0;
if(newHeight < wrapHeight) {
newHeight = wrapHeight;
newWidth = Math.round(vidWidth * wrapHeight/vidHeight);
newLeft = -((newWidth - wrapWidth)/2);
}
else {
newTop = -((newHeight - wrapHeight)/2);
}
video.parent().data('width', vidWidth);
video.parent().data('height', vidHeight);
video.css({
'left' : newLeft + 'px',
'top' : newTop + 'px',
'width' : newWidth + 'px',
'height' : newHeight + 'px'
});
},
/**
* Fires when the window is resized to resize
* a single Youtube video background.
*
* @since 1.9
* @access private
* @method _resizeYoutubeBgVideo
*/
_resizeYoutubeBgVideo: function()
{
var wrap = $(this),
wrapWidth = wrap.outerWidth(),
wrapHeight = wrap.outerHeight(),
player = wrap.data('YTPlayer'),
video = player ? player.getIframe() : null,
aspectRatioSetting = '16:9', // Medium
aspectRatioArray = aspectRatioSetting.split( ':' ),
aspectRatio = aspectRatioArray[0] / aspectRatioArray[1],
ratioWidth = wrapWidth / aspectRatio,
ratioHeight = wrapHeight * aspectRatio,
isWidthFixed = wrapWidth / wrapHeight > aspectRatio,
width = isWidthFixed ? wrapWidth : ratioHeight,
height = isWidthFixed ? ratioWidth : wrapHeight;
if ( video ) {
$(video).width( width ).height( height );
}
},
/**
* Initializes module animations.
*
* @since 1.1.9
* @access private
* @method _initModuleAnimations
*/
_initModuleAnimations: function()
{
if(typeof jQuery.fn.waypoint !== 'undefined') {
$('.fl-animation').each( function() {
var node = $( this ),
nodeTop = node.offset().top,
winHeight = $( window ).height(),
bodyHeight = $( 'body' ).height(),
waypoint = FLBuilderLayoutConfig.waypoint,
offset = '80%';
if ( typeof waypoint.offset !== undefined ) {
offset = FLBuilderLayoutConfig.waypoint.offset + '%';
}
if ( bodyHeight - nodeTop < winHeight * 0.2 ) {
offset = '100%';
}
node.waypoint({
offset: offset,
handler: FLBuilderLayout._doModuleAnimation
});
} );
}
},
/**
* Runs a module animation.
*
* @since 1.1.9
* @access private
* @method _doModuleAnimation
*/
_doModuleAnimation: function()
{
var module = 'undefined' == typeof this.element ? $(this) : $(this.element),
delay = parseFloat(module.data('animation-delay')),
duration = parseFloat(module.data('animation-duration'));
if ( ! isNaN( duration ) ) {
module.css( 'animation-duration', duration + 's' );
}
if(!isNaN(delay) && delay > 0) {
setTimeout(function(){
module.addClass('fl-animated');
}, delay * 1000);
} else {
setTimeout(function(){
module.addClass('fl-animated');
}, 1);
}
},
/**
* Opens a tab or accordion item if the browser hash is set
* to the ID of one on the page.
*
* @since 1.6.0
* @access private
* @method _initHash
*/
_initHash: function()
{
var hash = window.location.hash.replace( '#', '' ).split( '/' ).shift(),
element = null,
tabs = null,
responsiveLabel = null,
tabIndex = null,
label = null;
if ( '' !== hash ) {
try {
element = $( '#' + hash );
if ( element.length > 0 ) {
if ( element.hasClass( 'fl-accordion-item' ) ) {
setTimeout( function() {
element.find( '.fl-accordion-button' ).trigger( 'click' );
}, 100 );
}
if ( element.hasClass( 'fl-tabs-panel' ) ) {
setTimeout( function() {
tabs = element.closest( '.fl-tabs' );
responsiveLabel = element.find( '.fl-tabs-panel-label' );
tabIndex = responsiveLabel.data( 'index' );
label = tabs.find( '.fl-tabs-labels .fl-tabs-label[data-index=' + tabIndex + ']' );
if ( responsiveLabel.is( ':visible' ) ) {
responsiveLabel.trigger( 'click' );
}
else {
label[0].click();
FLBuilderLayout._scrollToElement( element );
}
}, 100 );
}
}
}
catch( e ) {}
}
},
/**
* Initializes all anchor links on the page for smooth scrolling.
*
* @since 1.4.9
* @access private
* @method _initAnchorLinks
*/
_initAnchorLinks: function()
{
$( 'a' ).each( FLBuilderLayout._initAnchorLink );
},
/**
* Initializes a single anchor link for smooth scrolling.
*
* @since 1.4.9
* @access private
* @method _initAnchorLink
*/
_initAnchorLink: function()
{
var link = $( this ),
href = link.attr( 'href' ),
loc = window.location,
id = null,
element = null;
if ( 'undefined' != typeof href && href.indexOf( '#' ) > -1 && link.closest('svg').length < 1 ) {
if ( loc.pathname.replace( /^\//, '' ) == this.pathname.replace( /^\//, '' ) && loc.hostname == this.hostname ) {
try {
id = href.split( '#' ).pop();
// If there is no ID then we have nowhere to look
// Fixes a quirk in jQuery and FireFox
if( ! id ) {
return;
}
element = $( '#' + id );
if ( element.length > 0 ) {
if ( link.hasClass( 'fl-scroll-link' ) || element.hasClass( 'fl-row' ) || element.hasClass( 'fl-col' ) || element.hasClass( 'fl-module' ) ) {
$( link ).on( 'click', FLBuilderLayout._scrollToElementOnLinkClick );
}
if ( element.hasClass( 'fl-accordion-item' ) ) {
$( link ).on( 'click', FLBuilderLayout._scrollToAccordionOnLinkClick );
}
if ( element.hasClass( 'fl-tabs-panel' ) ) {
$( link ).on( 'click', FLBuilderLayout._scrollToTabOnLinkClick );
}
}
}
catch( e ) {}
}
}
},
/**
* Scrolls to an element when an anchor link is clicked.
*
* @since 1.4.9
* @access private
* @method _scrollToElementOnLinkClick
* @param {Object} e An event object.
* @param {Function} callback A function to call when the scroll is complete.
*/
_scrollToElementOnLinkClick: function( e, callback )
{
var element = $( '#' + $( this ).attr( 'href' ).split( '#' ).pop() );
FLBuilderLayout._scrollToElement( element, callback );
e.preventDefault();
},
/**
* Scrolls to an element.
*
* @since 1.6.4.5
* @access private
* @method _scrollToElement
* @param {Object} element The element to scroll to.
* @param {Function} callback A function to call when the scroll is complete.
*/
_scrollToElement: function( element, callback )
{
var config = FLBuilderLayoutConfig.anchorLinkAnimations,
dest = 0,
win = $( window ),
doc = $( document );
if ( element.length > 0 ) {
if ( element.offset().top > doc.height() - win.height() ) {
dest = doc.height() - win.height();
}
else {
dest = element.offset().top - config.offset;
}
$( 'html, body' ).animate( { scrollTop: dest }, config.duration, config.easing, function() {
if ( 'undefined' != typeof callback ) {
callback();
}
if ( undefined != element.attr( 'id' ) ) {
if ( history.pushState ) {
history.pushState( null, null, '#' + element.attr( 'id' ) );
}
else {
window.location.hash = element.attr( 'id' );
}
}
} );
}
},
/**
* Scrolls to an accordion item when a link is clicked.
*
* @since 1.5.9
* @access private
* @method _scrollToAccordionOnLinkClick
* @param {Object} e An event object.
*/
_scrollToAccordionOnLinkClick: function( e )
{
var element = $( '#' + $( this ).attr( 'href' ).split( '#' ).pop() );
if ( element.length > 0 ) {
var callback = function() {
if ( element ) {
element.find( '.fl-accordion-button' ).trigger( 'click' );
element = false;
}
};
FLBuilderLayout._scrollToElementOnLinkClick.call( this, e, callback );
}
},
/**
* Scrolls to a tab panel when a link is clicked.
*
* @since 1.5.9
* @access private
* @method _scrollToTabOnLinkClick
* @param {Object} e An event object.
*/
_scrollToTabOnLinkClick: function( e )
{
var element = $( '#' + $( this ).attr( 'href' ).split( '#' ).pop() ),
tabs = null,
label = null,
responsiveLabel = null;
if ( element.length > 0 ) {
tabs = element.closest( '.fl-tabs' );
responsiveLabel = element.find( '.fl-tabs-panel-label' );
tabIndex = responsiveLabel.data( 'index' );
label = tabs.find( '.fl-tabs-labels .fl-tabs-label[data-index=' + tabIndex + ']' );
if ( responsiveLabel.is( ':visible' ) ) {
var callback = function() {
if ( element ) {
responsiveLabel.trigger( 'click' );
element = false;
}
};
FLBuilderLayout._scrollToElementOnLinkClick.call( this, e, callback );
}
else {
label[0].click();
FLBuilderLayout._scrollToElement( element );
}
e.preventDefault();
}
},
/**
* Initializes all builder forms on a page.
*
* @since 1.5.4
* @access private
* @method _initForms
*/
_initForms: function()
{
if ( ! FLBuilderLayout._hasPlaceholderSupport ) {
$( '.fl-form-field input' ).each( FLBuilderLayout._initFormFieldPlaceholderFallback );
}
$( '.fl-form-field input' ).on( 'focus', FLBuilderLayout._clearFormFieldError );
},
/**
* Checks to see if the current device has HTML5
* placeholder support.
*
* @since 1.5.4
* @access private
* @method _hasPlaceholderSupport
* @return {Boolean}
*/
_hasPlaceholderSupport: function()
{
var input = document.createElement( 'input' );
return 'undefined' != input.placeholder;
},
/**
* Initializes the fallback for when placeholders aren't supported.
*
* @since 1.5.4
* @access private
* @method _initFormFieldPlaceholderFallback
*/
_initFormFieldPlaceholderFallback: function()
{
var field = $( this ),
val = field.val(),
placeholder = field.attr( 'placeholder' );
if ( 'undefined' != placeholder && '' === val ) {
field.val( placeholder );
field.on( 'focus', FLBuilderLayout._hideFormFieldPlaceholderFallback );
field.on( 'blur', FLBuilderLayout._showFormFieldPlaceholderFallback );
}
},
/**
* Hides a fallback placeholder on focus.
*
* @since 1.5.4
* @access private
* @method _hideFormFieldPlaceholderFallback
*/
_hideFormFieldPlaceholderFallback: function()
{
var field = $( this ),
val = field.val(),
placeholder = field.attr( 'placeholder' );
if ( val == placeholder ) {
field.val( '' );
}
},
/**
* Shows a fallback placeholder on blur.
*
* @since 1.5.4
* @access private
* @method _showFormFieldPlaceholderFallback
*/
_showFormFieldPlaceholderFallback: function()
{
var field = $( this ),
val = field.val(),
placeholder = field.attr( 'placeholder' );
if ( '' === val ) {
field.val( placeholder );
}
},
/**
* Clears a form field error message.
*
* @since 1.5.4
* @access private
* @method _clearFormFieldError
*/
_clearFormFieldError: function()
{
var field = $( this );
field.removeClass( 'fl-form-error' );
field.siblings( '.fl-form-error-message' ).hide();
}
};
/* Initializes the builder layout. */
$(function(){
FLBuilderLayout.init();
});
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment