fl-builder-layout.js 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274
  1. (function($){
  2. if(typeof FLBuilderLayout != 'undefined') {
  3. return;
  4. }
  5. /**
  6. * Helper class with generic logic for a builder layout.
  7. *
  8. * @class FLBuilderLayout
  9. * @since 1.0
  10. */
  11. FLBuilderLayout = {
  12. /**
  13. * Initializes a builder layout.
  14. *
  15. * @since 1.0
  16. * @method init
  17. */
  18. init: function()
  19. {
  20. // Destroy existing layout events.
  21. FLBuilderLayout._destroy();
  22. // Init CSS classes.
  23. FLBuilderLayout._initClasses();
  24. // Init backgrounds.
  25. FLBuilderLayout._initBackgrounds();
  26. // Only init if the builder isn't active.
  27. if ( 0 === $('.fl-builder-edit').length ) {
  28. // Init anchor links.
  29. FLBuilderLayout._initAnchorLinks();
  30. // Init the browser hash.
  31. FLBuilderLayout._initHash();
  32. // Init module animations.
  33. FLBuilderLayout._initModuleAnimations();
  34. // Init forms.
  35. FLBuilderLayout._initForms();
  36. }
  37. },
  38. /**
  39. * Public method for refreshing Wookmark or MosaicFlow galleries
  40. * within an element.
  41. *
  42. * @since 1.7.4
  43. * @method refreshGalleries
  44. */
  45. refreshGalleries: function( element )
  46. {
  47. var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
  48. mfContent = $element.find( '.fl-mosaicflow-content' ),
  49. wmContent = $element.find( '.fl-gallery' ),
  50. mfObject = null;
  51. if ( mfContent ) {
  52. mfObject = mfContent.data( 'mosaicflow' );
  53. if ( mfObject ) {
  54. mfObject.columns = $( [] );
  55. mfObject.columnsHeights = [];
  56. mfContent.data( 'mosaicflow', mfObject );
  57. mfContent.mosaicflow( 'refill' );
  58. }
  59. }
  60. if ( wmContent ) {
  61. wmContent.trigger( 'refreshWookmark' );
  62. }
  63. },
  64. /**
  65. * Public method for refreshing Masonry within an element
  66. *
  67. * @since 1.8.1
  68. * @method refreshGridLayout
  69. */
  70. refreshGridLayout: function( element )
  71. {
  72. var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
  73. msnryContent = $element.find('.masonry');
  74. if ( msnryContent.length ) {
  75. msnryContent.masonry('layout');
  76. }
  77. },
  78. /**
  79. * Public method for reloading BxSlider within an element
  80. *
  81. * @since 1.8.1
  82. * @method reloadSlider
  83. */
  84. reloadSlider: function( element )
  85. {
  86. var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
  87. bxContent = $element.find('.bx-viewport > div').eq(0),
  88. bxObject = null;
  89. if ( bxContent.length ) {
  90. bxObject = bxContent.data( 'bxSlider');
  91. if ( bxObject ) {
  92. bxObject.reloadSlider();
  93. }
  94. }
  95. },
  96. /**
  97. * Public method for resizing WP audio player
  98. *
  99. * @since 1.8.2
  100. * @method resizeAudio
  101. */
  102. resizeAudio: function( element )
  103. {
  104. var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
  105. audioPlayers = $element.find('.wp-audio-shortcode.mejs-audio'),
  106. player = null,
  107. mejsPlayer = null,
  108. rail = null,
  109. railWidth = 400;
  110. if ( audioPlayers.length && typeof mejs !== 'undefined' ) {
  111. audioPlayers.each(function(){
  112. player = $(this);
  113. mejsPlayer = mejs.players[player.attr('id')];
  114. rail = player.find('.mejs-controls .mejs-time-rail');
  115. var innerMejs = player.find('.mejs-inner'),
  116. total = player.find('.mejs-controls .mejs-time-total');
  117. if ( typeof mejsPlayer !== 'undefined' ) {
  118. railWidth = Math.ceil(player.width() * 0.8);
  119. if ( innerMejs.length ) {
  120. rail.css('width', railWidth +'px!important');
  121. //total.width(rail.width() - 10);
  122. mejsPlayer.options.autosizeProgress = true;
  123. // webkit has trouble doing this without a delay
  124. setTimeout(function () {
  125. mejsPlayer.setControlsSize();
  126. }, 50);
  127. player.find('.mejs-inner').css({
  128. visibility: 'visible',
  129. height: 'inherit'
  130. });
  131. }
  132. }
  133. });
  134. }
  135. },
  136. /**
  137. * Public method for preloading WP audio player when it's inside the hidden element
  138. *
  139. * @since 1.8.2
  140. * @method preloadAudio
  141. */
  142. preloadAudio: function(element)
  143. {
  144. var $element = 'undefined' == typeof element ? $( 'body' ) : $( element ),
  145. contentWrap = $element.closest('.fl-accordion-item'),
  146. audioPlayers = $element.find('.wp-audio-shortcode.mejs-audio');
  147. if ( ! contentWrap.hasClass('fl-accordion-item-active') && audioPlayers.find('.mejs-inner').length ) {
  148. audioPlayers.find('.mejs-inner').css({
  149. visibility : 'hidden',
  150. height: 0
  151. });
  152. }
  153. },
  154. /**
  155. * Public method for resizing slideshow momdule within the tab
  156. *
  157. * @since 1.10.5
  158. * @method resizeSlideshow
  159. */
  160. resizeSlideshow: function(){
  161. if(typeof YUI !== 'undefined') {
  162. YUI().use('node-event-simulate', function(Y) {
  163. Y.one(window).simulate("resize");
  164. });
  165. }
  166. },
  167. /**
  168. * Unbinds builder layout events.
  169. *
  170. * @since 1.0
  171. * @access private
  172. * @method _destroy
  173. */
  174. _destroy: function()
  175. {
  176. var win = $(window);
  177. win.off('scroll.fl-bg-parallax');
  178. win.off('resize.fl-bg-video');
  179. },
  180. /**
  181. * Checks to see if the current device has touch enabled.
  182. *
  183. * @since 1.0
  184. * @access private
  185. * @method _isTouch
  186. * @return {Boolean}
  187. */
  188. _isTouch: function()
  189. {
  190. if(('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch)) {
  191. return true;
  192. }
  193. return false;
  194. },
  195. /**
  196. * Checks to see if the current device is mobile.
  197. *
  198. * @since 1.7
  199. * @access private
  200. * @method _isMobile
  201. * @return {Boolean}
  202. */
  203. _isMobile: function()
  204. {
  205. return /Mobile|Android|Silk\/|Kindle|BlackBerry|Opera Mini|Opera Mobi|webOS/i.test( navigator.userAgent );
  206. },
  207. /**
  208. * Initializes builder body classes.
  209. *
  210. * @since 1.0
  211. * @access private
  212. * @method _initClasses
  213. */
  214. _initClasses: function()
  215. {
  216. var body = $( 'body' ),
  217. ua = navigator.userAgent;
  218. // Add the builder body class.
  219. if ( ! body.hasClass( 'archive' ) && $( '.fl-builder-content-primary' ).length > 0 ) {
  220. body.addClass('fl-builder');
  221. }
  222. // Add the builder touch body class.
  223. if(FLBuilderLayout._isTouch()) {
  224. body.addClass('fl-builder-touch');
  225. }
  226. // Add the builder mobile body class.
  227. if(FLBuilderLayout._isMobile()) {
  228. body.addClass('fl-builder-mobile');
  229. }
  230. // IE11 body class.
  231. if ( ua.indexOf( 'Trident/7.0' ) > -1 && ua.indexOf( 'rv:11.0' ) > -1 ) {
  232. body.addClass( 'fl-builder-ie-11' );
  233. }
  234. },
  235. /**
  236. * Initializes builder node backgrounds that require
  237. * additional JavaScript logic such as parallax.
  238. *
  239. * @since 1.1.4
  240. * @access private
  241. * @method _initBackgrounds
  242. */
  243. _initBackgrounds: function()
  244. {
  245. var win = $(window);
  246. // Init parallax backgrounds.
  247. if($('.fl-row-bg-parallax').length > 0 && !FLBuilderLayout._isMobile()) {
  248. FLBuilderLayout._scrollParallaxBackgrounds();
  249. FLBuilderLayout._initParallaxBackgrounds();
  250. win.on('scroll.fl-bg-parallax', FLBuilderLayout._scrollParallaxBackgrounds);
  251. }
  252. // Init video backgrounds.
  253. if($('.fl-bg-video').length > 0) {
  254. FLBuilderLayout._initBgVideos();
  255. FLBuilderLayout._resizeBgVideos();
  256. win.on('resize.fl-bg-video', FLBuilderLayout._resizeBgVideos);
  257. }
  258. },
  259. /**
  260. * Initializes all parallax backgrounds in a layout.
  261. *
  262. * @since 1.1.4
  263. * @access private
  264. * @method _initParallaxBackgrounds
  265. */
  266. _initParallaxBackgrounds: function()
  267. {
  268. $('.fl-row-bg-parallax').each(FLBuilderLayout._initParallaxBackground);
  269. },
  270. /**
  271. * Initializes a single parallax background.
  272. *
  273. * @since 1.1.4
  274. * @access private
  275. * @method _initParallaxBackgrounds
  276. */
  277. _initParallaxBackground: function()
  278. {
  279. var row = $(this),
  280. content = row.find('> .fl-row-content-wrap'),
  281. src = row.data('parallax-image'),
  282. loaded = row.data('parallax-loaded'),
  283. img = new Image();
  284. if(loaded) {
  285. return;
  286. }
  287. else if(typeof src != 'undefined') {
  288. $(img).on('load', function() {
  289. content.css('background-image', 'url(' + src + ')');
  290. row.data('parallax-loaded', true);
  291. });
  292. img.src = src;
  293. }
  294. },
  295. /**
  296. * Fires when the window is scrolled to adjust
  297. * parallax backgrounds.
  298. *
  299. * @since 1.1.4
  300. * @access private
  301. * @method _scrollParallaxBackgrounds
  302. */
  303. _scrollParallaxBackgrounds: function()
  304. {
  305. $('.fl-row-bg-parallax').each(FLBuilderLayout._scrollParallaxBackground);
  306. },
  307. /**
  308. * Fires when the window is scrolled to adjust
  309. * a single parallax background.
  310. *
  311. * @since 1.1.4
  312. * @access private
  313. * @method _scrollParallaxBackground
  314. */
  315. _scrollParallaxBackground: function()
  316. {
  317. var win = $(window),
  318. row = $(this),
  319. content = row.find('> .fl-row-content-wrap'),
  320. speed = row.data('parallax-speed'),
  321. offset = content.offset(),
  322. yPos = -((win.scrollTop() - offset.top) / speed);
  323. content.css('background-position', 'center ' + yPos + 'px');
  324. },
  325. /**
  326. * Initializes all video backgrounds.
  327. *
  328. * @since 1.6.3.3
  329. * @access private
  330. * @method _initBgVideos
  331. */
  332. _initBgVideos: function()
  333. {
  334. $('.fl-bg-video').each(FLBuilderLayout._initBgVideo);
  335. },
  336. /**
  337. * Initializes a video background.
  338. *
  339. * @since 1.6.3.3
  340. * @access private
  341. * @method _initBgVideo
  342. */
  343. _initBgVideo: function()
  344. {
  345. var wrap = $( this ),
  346. width = wrap.data( 'width' ),
  347. height = wrap.data( 'height' ),
  348. mp4 = wrap.data( 'mp4' ),
  349. youtube = wrap.data( 'youtube'),
  350. vimeo = wrap.data( 'vimeo'),
  351. mp4Type = wrap.data( 'mp4-type' ),
  352. webm = wrap.data( 'webm' ),
  353. webmType = wrap.data( 'webm-type' ),
  354. fallback = wrap.data( 'fallback' ),
  355. loaded = wrap.data( 'loaded' ),
  356. fallbackTag = '',
  357. videoTag = null,
  358. mp4Tag = null,
  359. webmTag = null;
  360. // Return if the video has been loaded for this row.
  361. if ( loaded ) {
  362. return;
  363. }
  364. videoTag = $( '<video autoplay loop muted playsinline></video>' );
  365. /**
  366. * Add poster image (fallback image)
  367. */
  368. if( 'undefined' != typeof fallback && '' != fallback ) {
  369. videoTag.attr( 'poster', 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' )
  370. videoTag.css( 'background', 'transparent url("' + fallback + '") no-repeat center center' )
  371. videoTag.css( 'background-size', 'cover' )
  372. videoTag.css( 'height', '100%' )
  373. }
  374. // MP4 Source Tag
  375. if ( 'undefined' != typeof mp4 && '' != mp4 ) {
  376. mp4Tag = $( '<source />' );
  377. mp4Tag.attr( 'src', mp4 );
  378. mp4Tag.attr( 'type', mp4Type );
  379. videoTag.append( mp4Tag );
  380. }
  381. // WebM Source Tag
  382. if ( 'undefined' != typeof webm && '' != webm ) {
  383. webmTag = $( '<source />' );
  384. webmTag.attr( 'src', webm );
  385. webmTag.attr( 'type', webmType );
  386. videoTag.append( webmTag );
  387. }
  388. // Check what video player we are going to load in a row
  389. if ( 'undefined' != typeof youtube && ! FLBuilderLayout._isMobile() ) {
  390. FLBuilderLayout._initYoutubeBgVideo.apply( this );
  391. }
  392. else if ( 'undefined' != typeof vimeo && ! FLBuilderLayout._isMobile() ) {
  393. FLBuilderLayout._initVimeoBgVideo.apply( this );
  394. }
  395. else {
  396. wrap.append( videoTag );
  397. }
  398. // Mark this video as loaded.
  399. wrap.data('loaded', true);
  400. },
  401. /**
  402. * Initializes Youtube video background
  403. *
  404. * @since 1.9
  405. * @access private
  406. * @method _initYoutubeBgVideo
  407. */
  408. _initYoutubeBgVideo: function()
  409. {
  410. var playerWrap = $(this),
  411. videoId = playerWrap.data('video-id'),
  412. videoPlayer = playerWrap.find('.fl-bg-video-player'),
  413. enableAudio = playerWrap.data('enable-audio'),
  414. audioButton = playerWrap.find('.fl-bg-video-audio'),
  415. startTime = 'undefined' !== typeof playerWrap.data('t') ? playerWrap.data('t') : 0,
  416. loop = 'undefined' !== typeof playerWrap.data('loop') ? playerWrap.data('loop') : 1,
  417. vidPlayed = false,
  418. didUnmute = false,
  419. stateCount = 0,
  420. player;
  421. if ( videoId ) {
  422. FLBuilderLayout._onYoutubeApiReady( function( YT ) {
  423. setTimeout( function() {
  424. player = new YT.Player( videoPlayer[0], {
  425. videoId: videoId,
  426. events: {
  427. onReady: function(event) {
  428. if ( "no" === enableAudio ) {
  429. event.target.mute();
  430. }
  431. else if ("yes" === enableAudio && event.target.isMuted ) {
  432. event.target.unMute();
  433. }
  434. // Store an instance to a parent
  435. playerWrap.data('YTPlayer', player);
  436. FLBuilderLayout._resizeYoutubeBgVideo.apply(playerWrap);
  437. // Queue the video.
  438. event.target.playVideo();
  439. if ( audioButton.length > 0 ) {
  440. audioButton.on( 'click', {button: audioButton, player: player}, FLBuilderLayout._toggleBgVideoAudio );
  441. }
  442. },
  443. onStateChange: function( event ) {
  444. // Manual check if video is not playable in some browsers.
  445. // StateChange order: [-1, 3, -1]
  446. if ( stateCount < 4 ) {
  447. stateCount++;
  448. }
  449. // Comply with the audio policy in some browsers like Chrome and Safari.
  450. if ( stateCount > 1 && -1 === event.data && "yes" === enableAudio ) {
  451. player.mute();
  452. player.playVideo();
  453. audioButton.show();
  454. }
  455. if ( event.data === YT.PlayerState.ENDED && 1 === loop ) {
  456. if ( startTime > 0 ) {
  457. player.seekTo( startTime );
  458. }
  459. else {
  460. player.playVideo();
  461. }
  462. }
  463. },
  464. onError: function(event) {
  465. console.info('YT Error: ' + event.data)
  466. FLBuilderLayout._onErrorYoutubeVimeo(playerWrap)
  467. }
  468. },
  469. playerVars: {
  470. controls: 0,
  471. showinfo: 0,
  472. rel : 0,
  473. start: startTime,
  474. }
  475. } );
  476. }, 1 );
  477. } );
  478. }
  479. },
  480. /**
  481. * On youtube or vimeo error show the fallback image if available.
  482. * @since 2.0.7
  483. */
  484. _onErrorYoutubeVimeo: function(playerWrap) {
  485. fallback = playerWrap.data('fallback') || false
  486. if( ! fallback ) {
  487. return false;
  488. }
  489. playerWrap.find('iframe').remove()
  490. fallbackTag = $( '<div></div>' );
  491. fallbackTag.addClass( 'fl-bg-video-fallback' );
  492. fallbackTag.css( 'background-image', 'url(' + playerWrap.data('fallback') + ')' );
  493. playerWrap.append( fallbackTag );
  494. },
  495. /**
  496. * Check if Youtube API has been downloaded
  497. *
  498. * @since 1.9
  499. * @access private
  500. * @method _onYoutubeApiReady
  501. * @param {Function} callback Method to call when YT API has been loaded
  502. */
  503. _onYoutubeApiReady: function( callback ) {
  504. if ( window.YT && YT.loaded ) {
  505. callback( YT );
  506. } else {
  507. // If not ready check again by timeout..
  508. setTimeout( function() {
  509. FLBuilderLayout._onYoutubeApiReady( callback );
  510. }, 350 );
  511. }
  512. },
  513. /**
  514. * Initializes Vimeo video background
  515. *
  516. * @since 1.9
  517. * @access private
  518. * @method _initVimeoBgVideo
  519. */
  520. _initVimeoBgVideo: function()
  521. {
  522. var playerWrap = $(this),
  523. videoId = playerWrap.data('video-id'),
  524. videoPlayer = playerWrap.find('.fl-bg-video-player'),
  525. enableAudio = playerWrap.data('enable-audio'),
  526. audioButton = playerWrap.find('.fl-bg-video-audio'),
  527. player,
  528. width = playerWrap.outerWidth();
  529. if ( typeof Vimeo !== 'undefined' && videoId ) {
  530. player = new Vimeo.Player(videoPlayer[0], {
  531. id : videoId,
  532. loop : true,
  533. title : false,
  534. portrait : false,
  535. background : true,
  536. autopause : false,
  537. muted : true
  538. });
  539. playerWrap.data('VMPlayer', player);
  540. if ( "no" === enableAudio ) {
  541. player.setVolume(0);
  542. }
  543. else if ("yes" === enableAudio ) {
  544. // Chrome and Safari have audio policy restrictions for autoplay videos.
  545. if ( $.browser.safari || $.browser.chrome ) {
  546. player.setVolume(0);
  547. audioButton.show();
  548. }
  549. else {
  550. player.setVolume(1);
  551. }
  552. }
  553. player.play().catch(function(error) {
  554. FLBuilderLayout._onErrorYoutubeVimeo(playerWrap)
  555. });
  556. if ( audioButton.length > 0 ) {
  557. audioButton.on( 'click', {button: audioButton, player: player}, FLBuilderLayout._toggleBgVideoAudio );
  558. }
  559. }
  560. },
  561. /**
  562. * Mute / unmute audio on row's video background.
  563. * It works for both Youtube and Vimeo.
  564. *
  565. * @since 2.1.3
  566. * @access private
  567. * @method _toggleBgVideoAudio
  568. * @param {Object} e Method arguments
  569. */
  570. _toggleBgVideoAudio: function( e ) {
  571. var player = e.data.player,
  572. control = e.data.button.find('.fl-audio-control');
  573. if ( control.hasClass( 'fa-volume-off' ) ) {
  574. // Unmute
  575. control
  576. .removeClass( 'fa-volume-off' )
  577. .addClass( 'fa-volume-up' );
  578. e.data.button.find( '.fa-times' ).hide();
  579. if ( 'function' === typeof player.unMute ) {
  580. player.unMute();
  581. }
  582. else {
  583. player.setVolume( 1 );
  584. }
  585. }
  586. else {
  587. // Mute
  588. control
  589. .removeClass( 'fa-volume-up' )
  590. .addClass( 'fa-volume-off' );
  591. e.data.button.find( '.fa-times' ).show();
  592. if ( 'function' === typeof player.unMute ) {
  593. player.mute();
  594. }
  595. else {
  596. player.setVolume( 0 );
  597. }
  598. }
  599. },
  600. /**
  601. * Fires when there is an error loading a video
  602. * background source and shows the fallback.
  603. *
  604. * @since 1.6.3.3
  605. * @access private
  606. * @method _videoBgSourceError
  607. * @param {Object} e An event object
  608. * @deprecated 2.0.3
  609. */
  610. _videoBgSourceError: function( e )
  611. {
  612. var source = $( e.target ),
  613. wrap = source.closest( '.fl-bg-video' ),
  614. vid = wrap.find( 'video' ),
  615. fallback = wrap.data( 'fallback' ),
  616. fallbackTag = '';
  617. source.remove();
  618. if ( vid.find( 'source' ).length ) {
  619. // Don't show the fallback if we still have other sources to check.
  620. return;
  621. } else if ( '' !== fallback ) {
  622. fallbackTag = $( '<div></div>' );
  623. fallbackTag.addClass( 'fl-bg-video-fallback' );
  624. fallbackTag.css( 'background-image', 'url(' + fallback + ')' );
  625. wrap.append( fallbackTag );
  626. vid.remove();
  627. }
  628. },
  629. /**
  630. * Fires when the window is resized to resize
  631. * all video backgrounds.
  632. *
  633. * @since 1.1.4
  634. * @access private
  635. * @method _resizeBgVideos
  636. */
  637. _resizeBgVideos: function()
  638. {
  639. $('.fl-bg-video').each( function() {
  640. FLBuilderLayout._resizeBgVideo.apply( this );
  641. if ( $( this ).parent().find( 'img' ).length > 0 ) {
  642. $( this ).parent().imagesLoaded( $.proxy( FLBuilderLayout._resizeBgVideo, this ) );
  643. }
  644. } );
  645. },
  646. /**
  647. * Fires when the window is resized to resize
  648. * a single video background.
  649. *
  650. * @since 1.1.4
  651. * @access private
  652. * @method _resizeBgVideo
  653. */
  654. _resizeBgVideo: function()
  655. {
  656. if ( 0 === $( this ).find( 'video' ).length && 0 === $( this ).find( 'iframe' ).length ) {
  657. return;
  658. }
  659. var wrap = $(this),
  660. wrapHeight = wrap.outerHeight(),
  661. wrapWidth = wrap.outerWidth(),
  662. vid = wrap.find('video'),
  663. vidHeight = wrap.data('height'),
  664. vidWidth = wrap.data('width'),
  665. newWidth = wrapWidth,
  666. newHeight = Math.round(vidHeight * wrapWidth/vidWidth),
  667. newLeft = 0,
  668. newTop = 0,
  669. iframe = wrap.find('iframe');
  670. if ( vid.length ) {
  671. if(vidHeight === '' || typeof vidHeight === 'undefined' || vidWidth === '' || typeof vidWidth === 'undefined') {
  672. vid.css({
  673. 'left' : '0px',
  674. 'top' : '0px',
  675. 'width' : newWidth + 'px'
  676. });
  677. // Try to set the actual video dimension on 'loadedmetadata' when using URL as video source
  678. vid.on('loadedmetadata', FLBuilderLayout._resizeOnLoadedMeta);
  679. }
  680. else {
  681. if(newHeight < wrapHeight) {
  682. newHeight = wrapHeight;
  683. newWidth = Math.round(vidWidth * wrapHeight/vidHeight);
  684. newLeft = -((newWidth - wrapWidth)/2);
  685. }
  686. else {
  687. newTop = -((newHeight - wrapHeight)/2);
  688. }
  689. vid.css({
  690. 'left' : newLeft + 'px',
  691. 'top' : newTop + 'px',
  692. 'height' : newHeight + 'px',
  693. 'width' : newWidth + 'px'
  694. });
  695. }
  696. }
  697. else if ( iframe.length ) {
  698. // Resize Youtube video player within iframe tag
  699. if ( typeof wrap.data('youtube') !== 'undefined' ) {
  700. FLBuilderLayout._resizeYoutubeBgVideo.apply(this);
  701. }
  702. }
  703. },
  704. /**
  705. * Fires when video meta has been loaded.
  706. * This will be Triggered when width/height attributes were not specified during video background resizing.
  707. *
  708. * @since 1.8.5
  709. * @access private
  710. * @method _resizeOnLoadedMeta
  711. */
  712. _resizeOnLoadedMeta: function(){
  713. var video = $(this),
  714. wrapHeight = video.parent().outerHeight(),
  715. wrapWidth = video.parent().outerWidth(),
  716. vidWidth = video[0].videoWidth,
  717. vidHeight = video[0].videoHeight,
  718. newHeight = Math.round(vidHeight * wrapWidth/vidWidth),
  719. newWidth = wrapWidth,
  720. newLeft = 0,
  721. newTop = 0;
  722. if(newHeight < wrapHeight) {
  723. newHeight = wrapHeight;
  724. newWidth = Math.round(vidWidth * wrapHeight/vidHeight);
  725. newLeft = -((newWidth - wrapWidth)/2);
  726. }
  727. else {
  728. newTop = -((newHeight - wrapHeight)/2);
  729. }
  730. video.parent().data('width', vidWidth);
  731. video.parent().data('height', vidHeight);
  732. video.css({
  733. 'left' : newLeft + 'px',
  734. 'top' : newTop + 'px',
  735. 'width' : newWidth + 'px',
  736. 'height' : newHeight + 'px'
  737. });
  738. },
  739. /**
  740. * Fires when the window is resized to resize
  741. * a single Youtube video background.
  742. *
  743. * @since 1.9
  744. * @access private
  745. * @method _resizeYoutubeBgVideo
  746. */
  747. _resizeYoutubeBgVideo: function()
  748. {
  749. var wrap = $(this),
  750. wrapWidth = wrap.outerWidth(),
  751. wrapHeight = wrap.outerHeight(),
  752. player = wrap.data('YTPlayer'),
  753. video = player ? player.getIframe() : null,
  754. aspectRatioSetting = '16:9', // Medium
  755. aspectRatioArray = aspectRatioSetting.split( ':' ),
  756. aspectRatio = aspectRatioArray[0] / aspectRatioArray[1],
  757. ratioWidth = wrapWidth / aspectRatio,
  758. ratioHeight = wrapHeight * aspectRatio,
  759. isWidthFixed = wrapWidth / wrapHeight > aspectRatio,
  760. width = isWidthFixed ? wrapWidth : ratioHeight,
  761. height = isWidthFixed ? ratioWidth : wrapHeight;
  762. if ( video ) {
  763. $(video).width( width ).height( height );
  764. }
  765. },
  766. /**
  767. * Initializes module animations.
  768. *
  769. * @since 1.1.9
  770. * @access private
  771. * @method _initModuleAnimations
  772. */
  773. _initModuleAnimations: function()
  774. {
  775. if(typeof jQuery.fn.waypoint !== 'undefined' && !FLBuilderLayout._isMobile()) {
  776. $('.fl-animation').each( function() {
  777. var node = $( this ),
  778. nodeTop = node.offset().top,
  779. winHeight = $( window ).height(),
  780. bodyHeight = $( 'body' ).height(),
  781. waypoint = FLBuilderLayoutConfig.waypoint,
  782. offset = '80%';
  783. if ( typeof waypoint.offset !== undefined ) {
  784. offset = FLBuilderLayoutConfig.waypoint.offset + '%';
  785. }
  786. if ( bodyHeight - nodeTop < winHeight * 0.2 ) {
  787. offset = '100%';
  788. }
  789. node.waypoint({
  790. offset: offset,
  791. handler: FLBuilderLayout._doModuleAnimation
  792. });
  793. } );
  794. }
  795. },
  796. /**
  797. * Runs a module animation.
  798. *
  799. * @since 1.1.9
  800. * @access private
  801. * @method _doModuleAnimation
  802. */
  803. _doModuleAnimation: function()
  804. {
  805. var module = 'undefined' == typeof this.element ? $(this) : $(this.element),
  806. delay = parseFloat(module.data('animation-delay'));
  807. if(!isNaN(delay) && delay > 0) {
  808. setTimeout(function(){
  809. module.addClass('fl-animated');
  810. }, delay * 1000);
  811. }
  812. else {
  813. module.addClass('fl-animated');
  814. }
  815. },
  816. /**
  817. * Opens a tab or accordion item if the browser hash is set
  818. * to the ID of one on the page.
  819. *
  820. * @since 1.6.0
  821. * @access private
  822. * @method _initHash
  823. */
  824. _initHash: function()
  825. {
  826. var hash = window.location.hash.replace( '#', '' ).split( '/' ).shift(),
  827. element = null,
  828. tabs = null,
  829. responsiveLabel = null,
  830. tabIndex = null,
  831. label = null;
  832. if ( '' !== hash ) {
  833. try {
  834. element = $( '#' + hash );
  835. if ( element.length > 0 ) {
  836. if ( element.hasClass( 'fl-accordion-item' ) ) {
  837. setTimeout( function() {
  838. element.find( '.fl-accordion-button' ).trigger( 'click' );
  839. }, 100 );
  840. }
  841. if ( element.hasClass( 'fl-tabs-panel' ) ) {
  842. setTimeout( function() {
  843. tabs = element.closest( '.fl-tabs' );
  844. responsiveLabel = element.find( '.fl-tabs-panel-label' );
  845. tabIndex = responsiveLabel.data( 'index' );
  846. label = tabs.find( '.fl-tabs-labels .fl-tabs-label[data-index=' + tabIndex + ']' );
  847. if ( responsiveLabel.is( ':visible' ) ) {
  848. responsiveLabel.trigger( 'click' );
  849. }
  850. else {
  851. label[0].click();
  852. FLBuilderLayout._scrollToElement( element );
  853. }
  854. }, 100 );
  855. }
  856. }
  857. }
  858. catch( e ) {}
  859. }
  860. },
  861. /**
  862. * Initializes all anchor links on the page for smooth scrolling.
  863. *
  864. * @since 1.4.9
  865. * @access private
  866. * @method _initAnchorLinks
  867. */
  868. _initAnchorLinks: function()
  869. {
  870. $( 'a' ).each( FLBuilderLayout._initAnchorLink );
  871. },
  872. /**
  873. * Initializes a single anchor link for smooth scrolling.
  874. *
  875. * @since 1.4.9
  876. * @access private
  877. * @method _initAnchorLink
  878. */
  879. _initAnchorLink: function()
  880. {
  881. var link = $( this ),
  882. href = link.attr( 'href' ),
  883. loc = window.location,
  884. id = null,
  885. element = null;
  886. if ( 'undefined' != typeof href && href.indexOf( '#' ) > -1 && link.closest('svg').length < 1 ) {
  887. if ( loc.pathname.replace( /^\//, '' ) == this.pathname.replace( /^\//, '' ) && loc.hostname == this.hostname ) {
  888. try {
  889. id = href.split( '#' ).pop();
  890. // If there is no ID then we have nowhere to look
  891. // Fixes a quirk in jQuery and FireFox
  892. if( ! id ) {
  893. return;
  894. }
  895. element = $( '#' + id );
  896. if ( element.length > 0 ) {
  897. if ( link.hasClass( 'fl-scroll-link' ) || element.hasClass( 'fl-row' ) || element.hasClass( 'fl-col' ) || element.hasClass( 'fl-module' ) ) {
  898. $( link ).on( 'click', FLBuilderLayout._scrollToElementOnLinkClick );
  899. }
  900. if ( element.hasClass( 'fl-accordion-item' ) ) {
  901. $( link ).on( 'click', FLBuilderLayout._scrollToAccordionOnLinkClick );
  902. }
  903. if ( element.hasClass( 'fl-tabs-panel' ) ) {
  904. $( link ).on( 'click', FLBuilderLayout._scrollToTabOnLinkClick );
  905. }
  906. }
  907. }
  908. catch( e ) {}
  909. }
  910. }
  911. },
  912. /**
  913. * Scrolls to an element when an anchor link is clicked.
  914. *
  915. * @since 1.4.9
  916. * @access private
  917. * @method _scrollToElementOnLinkClick
  918. * @param {Object} e An event object.
  919. * @param {Function} callback A function to call when the scroll is complete.
  920. */
  921. _scrollToElementOnLinkClick: function( e, callback )
  922. {
  923. var element = $( '#' + $( this ).attr( 'href' ).split( '#' ).pop() );
  924. FLBuilderLayout._scrollToElement( element, callback );
  925. e.preventDefault();
  926. },
  927. /**
  928. * Scrolls to an element.
  929. *
  930. * @since 1.6.4.5
  931. * @access private
  932. * @method _scrollToElement
  933. * @param {Object} element The element to scroll to.
  934. * @param {Function} callback A function to call when the scroll is complete.
  935. */
  936. _scrollToElement: function( element, callback )
  937. {
  938. var config = FLBuilderLayoutConfig.anchorLinkAnimations,
  939. dest = 0,
  940. win = $( window ),
  941. doc = $( document );
  942. if ( element.length > 0 ) {
  943. if ( element.offset().top > doc.height() - win.height() ) {
  944. dest = doc.height() - win.height();
  945. }
  946. else {
  947. dest = element.offset().top - config.offset;
  948. }
  949. $( 'html, body' ).animate( { scrollTop: dest }, config.duration, config.easing, function() {
  950. if ( 'undefined' != typeof callback ) {
  951. callback();
  952. }
  953. if ( undefined != element.attr( 'id' ) ) {
  954. if ( history.pushState ) {
  955. history.pushState( null, null, '#' + element.attr( 'id' ) );
  956. }
  957. else {
  958. window.location.hash = element.attr( 'id' );
  959. }
  960. }
  961. } );
  962. }
  963. },
  964. /**
  965. * Scrolls to an accordion item when a link is clicked.
  966. *
  967. * @since 1.5.9
  968. * @access private
  969. * @method _scrollToAccordionOnLinkClick
  970. * @param {Object} e An event object.
  971. */
  972. _scrollToAccordionOnLinkClick: function( e )
  973. {
  974. var element = $( '#' + $( this ).attr( 'href' ).split( '#' ).pop() );
  975. if ( element.length > 0 ) {
  976. var callback = function() {
  977. if ( element ) {
  978. element.find( '.fl-accordion-button' ).trigger( 'click' );
  979. element = false;
  980. }
  981. };
  982. FLBuilderLayout._scrollToElementOnLinkClick.call( this, e, callback );
  983. }
  984. },
  985. /**
  986. * Scrolls to a tab panel when a link is clicked.
  987. *
  988. * @since 1.5.9
  989. * @access private
  990. * @method _scrollToTabOnLinkClick
  991. * @param {Object} e An event object.
  992. */
  993. _scrollToTabOnLinkClick: function( e )
  994. {
  995. var element = $( '#' + $( this ).attr( 'href' ).split( '#' ).pop() ),
  996. tabs = null,
  997. label = null,
  998. responsiveLabel = null;
  999. if ( element.length > 0 ) {
  1000. tabs = element.closest( '.fl-tabs' );
  1001. responsiveLabel = element.find( '.fl-tabs-panel-label' );
  1002. tabIndex = responsiveLabel.data( 'index' );
  1003. label = tabs.find( '.fl-tabs-labels .fl-tabs-label[data-index=' + tabIndex + ']' );
  1004. if ( responsiveLabel.is( ':visible' ) ) {
  1005. var callback = function() {
  1006. if ( element ) {
  1007. responsiveLabel.trigger( 'click' );
  1008. element = false;
  1009. }
  1010. };
  1011. FLBuilderLayout._scrollToElementOnLinkClick.call( this, e, callback );
  1012. }
  1013. else {
  1014. label[0].click();
  1015. FLBuilderLayout._scrollToElement( element );
  1016. }
  1017. e.preventDefault();
  1018. }
  1019. },
  1020. /**
  1021. * Initializes all builder forms on a page.
  1022. *
  1023. * @since 1.5.4
  1024. * @access private
  1025. * @method _initForms
  1026. */
  1027. _initForms: function()
  1028. {
  1029. if ( ! FLBuilderLayout._hasPlaceholderSupport ) {
  1030. $( '.fl-form-field input' ).each( FLBuilderLayout._initFormFieldPlaceholderFallback );
  1031. }
  1032. $( '.fl-form-field input' ).on( 'focus', FLBuilderLayout._clearFormFieldError );
  1033. },
  1034. /**
  1035. * Checks to see if the current device has HTML5
  1036. * placeholder support.
  1037. *
  1038. * @since 1.5.4
  1039. * @access private
  1040. * @method _hasPlaceholderSupport
  1041. * @return {Boolean}
  1042. */
  1043. _hasPlaceholderSupport: function()
  1044. {
  1045. var input = document.createElement( 'input' );
  1046. return 'undefined' != input.placeholder;
  1047. },
  1048. /**
  1049. * Initializes the fallback for when placeholders aren't supported.
  1050. *
  1051. * @since 1.5.4
  1052. * @access private
  1053. * @method _initFormFieldPlaceholderFallback
  1054. */
  1055. _initFormFieldPlaceholderFallback: function()
  1056. {
  1057. var field = $( this ),
  1058. val = field.val(),
  1059. placeholder = field.attr( 'placeholder' );
  1060. if ( 'undefined' != placeholder && '' === val ) {
  1061. field.val( placeholder );
  1062. field.on( 'focus', FLBuilderLayout._hideFormFieldPlaceholderFallback );
  1063. field.on( 'blur', FLBuilderLayout._showFormFieldPlaceholderFallback );
  1064. }
  1065. },
  1066. /**
  1067. * Hides a fallback placeholder on focus.
  1068. *
  1069. * @since 1.5.4
  1070. * @access private
  1071. * @method _hideFormFieldPlaceholderFallback
  1072. */
  1073. _hideFormFieldPlaceholderFallback: function()
  1074. {
  1075. var field = $( this ),
  1076. val = field.val(),
  1077. placeholder = field.attr( 'placeholder' );
  1078. if ( val == placeholder ) {
  1079. field.val( '' );
  1080. }
  1081. },
  1082. /**
  1083. * Shows a fallback placeholder on blur.
  1084. *
  1085. * @since 1.5.4
  1086. * @access private
  1087. * @method _showFormFieldPlaceholderFallback
  1088. */
  1089. _showFormFieldPlaceholderFallback: function()
  1090. {
  1091. var field = $( this ),
  1092. val = field.val(),
  1093. placeholder = field.attr( 'placeholder' );
  1094. if ( '' === val ) {
  1095. field.val( placeholder );
  1096. }
  1097. },
  1098. /**
  1099. * Clears a form field error message.
  1100. *
  1101. * @since 1.5.4
  1102. * @access private
  1103. * @method _clearFormFieldError
  1104. */
  1105. _clearFormFieldError: function()
  1106. {
  1107. var field = $( this );
  1108. field.removeClass( 'fl-form-error' );
  1109. field.siblings( '.fl-form-error-message' ).hide();
  1110. }
  1111. };
  1112. /* Initializes the builder layout. */
  1113. $(function(){
  1114. FLBuilderLayout.init();
  1115. });
  1116. })(jQuery);