fl-lightbox.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. (function($){
  2. /**
  3. * Custom lightbox for builder popups.
  4. *
  5. * @class FLLightbox
  6. * @since 1.0
  7. */
  8. FLLightbox = function( settings )
  9. {
  10. this._init( settings );
  11. };
  12. /**
  13. * Closes the lightbox of a child element that
  14. * is passed to this method.
  15. *
  16. * @since 1.0
  17. * @static
  18. * @method closeParent
  19. * @param {Object} child An HTML element or jQuery reference to an element.
  20. */
  21. FLLightbox.closeParent = function( child )
  22. {
  23. var instanceId = $( child ).closest( '.fl-lightbox-wrap' ).attr( 'data-instance-id' );
  24. if ( ! _.isUndefined( instanceId ) ) {
  25. FLLightbox._instances[ instanceId ].close();
  26. }
  27. };
  28. /**
  29. * Returns the classname for the resize control in lightbox headers.
  30. *
  31. * @since 2.0
  32. * @static
  33. * @method getResizableControlClass
  34. * @return {String}
  35. */
  36. FLLightbox.getResizableControlClass = function()
  37. {
  38. var resizable = $( '.fl-lightbox-resizable', window.parent.document ).eq( 0 ),
  39. className = 'far fa-window-maximize';
  40. if ( resizable.length && resizable.hasClass( 'fl-lightbox-width-full' ) ) {
  41. className = 'far fa-window-minimize';
  42. }
  43. return className;
  44. };
  45. /**
  46. * Unbinds events for all lightbox instances.
  47. *
  48. * @since 2.0
  49. * @static
  50. * @method unbindAll
  51. */
  52. FLLightbox.unbindAll = function()
  53. {
  54. var id;
  55. for ( id in FLLightbox._instances ) {
  56. FLLightbox._instances[ id ]._unbind();
  57. }
  58. };
  59. /**
  60. * Binds events for all lightbox instances.
  61. *
  62. * @since 2.0
  63. * @static
  64. * @method bindAll
  65. */
  66. FLLightbox.bindAll = function()
  67. {
  68. var id;
  69. for ( id in FLLightbox._instances ) {
  70. FLLightbox._instances[ id ]._bind();
  71. }
  72. };
  73. /**
  74. * Close all lightbox instances.
  75. *
  76. * @since 2.0
  77. * @static
  78. * @method closeAll
  79. */
  80. FLLightbox.closeAll = function()
  81. {
  82. var id;
  83. for ( id in FLLightbox._instances ) {
  84. FLLightbox._instances[ id ].close();
  85. }
  86. };
  87. /**
  88. * An object that stores a reference to each
  89. * lightbox instance that is created.
  90. *
  91. * @since 1.0
  92. * @static
  93. * @access private
  94. * @property {Object} _instances
  95. */
  96. FLLightbox._instances = {};
  97. /**
  98. * Prototype for new instances.
  99. *
  100. * @since 1.0
  101. * @property {Object} prototype
  102. */
  103. FLLightbox.prototype = {
  104. /**
  105. * A unique ID for this instance that's used to store
  106. * it in the static _instances object.
  107. *
  108. * @since 1.0
  109. * @access private
  110. * @property {String} _id
  111. */
  112. _id: null,
  113. /**
  114. * A jQuery reference to the main wrapper div.
  115. *
  116. * @since 1.0
  117. * @access private
  118. * @property {Object} _node
  119. */
  120. _node: null,
  121. /**
  122. * Flag for whether the lightbox is visible or not.
  123. *
  124. * @since 1.0
  125. * @access private
  126. * @property {Boolean} _visible
  127. */
  128. _visible: false,
  129. /**
  130. * Whether closing the lightbox is allowed or not.
  131. *
  132. * @since 2.0
  133. * @access private
  134. * @property {Boolean} _allowClosing
  135. */
  136. _allowClosing: true,
  137. /**
  138. * A timeout used to throttle the resize event.
  139. *
  140. * @since 1.0
  141. * @access private
  142. * @property {Object} _resizeTimer
  143. */
  144. _resizeTimer: null,
  145. /**
  146. * Default config object.
  147. *
  148. * @since 1.0
  149. * @access private
  150. * @property {Object} _defaults
  151. * @property {String} _defaults.className - A custom classname to add to the wrapper div.
  152. * @property {Boolean} _defaults.destroyOnClose - Flag for whether the instance should be destroyed when closed.
  153. * @property {Boolean} _defaults.resizable - Flag for Whether this instance should be resizable or not.
  154. */
  155. _defaults: {
  156. className: '',
  157. destroyOnClose: false,
  158. resizable: false
  159. },
  160. /**
  161. * Opens the lightbox. You can pass new content to this method.
  162. * If no content is passed, the previous content will be shown.
  163. *
  164. * @since 1.0
  165. * @method open
  166. * @param {String} content HTML content to add to the lightbox.
  167. */
  168. open: function(content)
  169. {
  170. var lightbox = this._node.find( '.fl-lightbox' ),
  171. isPinned = ( lightbox.closest( '.fl-builder-ui-pinned' ).length ),
  172. settings = this._getPositionSettings();
  173. if ( ! isPinned && settings && this._defaults.resizable ) {
  174. lightbox.css( settings );
  175. }
  176. this._bind();
  177. this._node.show();
  178. this._visible = true;
  179. if(typeof content !== 'undefined') {
  180. this.setContent(content);
  181. }
  182. else {
  183. this._resize();
  184. }
  185. this.trigger('open');
  186. FLBuilder.triggerHook('didShowLightbox', this);
  187. },
  188. /**
  189. * Closes the lightbox.
  190. *
  191. * @since 1.0
  192. * @method close
  193. */
  194. close: function()
  195. {
  196. var parent = this._node.data('parent');
  197. if ( ! this._allowClosing ) {
  198. return;
  199. }
  200. this.trigger('beforeCloseLightbox');
  201. this._unbind();
  202. this._node.hide();
  203. this._visible = false;
  204. this.trigger('close');
  205. FLBuilder.triggerHook('didHideLightbox');
  206. if ( this._defaults.resizable && _.isUndefined( parent ) ) {
  207. FLBuilder.triggerHook('didHideAllLightboxes');
  208. }
  209. if(this._defaults.destroyOnClose) {
  210. this.destroy();
  211. }
  212. },
  213. /**
  214. * Disables closing the lightbox.
  215. *
  216. * @since 2.0
  217. * @method disableClose
  218. */
  219. disableClose: function()
  220. {
  221. this._allowClosing = false;
  222. },
  223. /**
  224. * Enables closing the lightbox.
  225. *
  226. * @since 2.0
  227. * @method enableClose
  228. */
  229. enableClose: function()
  230. {
  231. this._allowClosing = true;
  232. },
  233. /**
  234. * Adds HTML content to the lightbox replacing any
  235. * previously added content.
  236. *
  237. * @since 1.0
  238. * @method setContent
  239. * @param {String} content HTML content to add to the lightbox.
  240. */
  241. setContent: function(content)
  242. {
  243. this._node.find('.fl-lightbox-content').html(content);
  244. this._resize();
  245. if( $( '.fl-builder-content-panel-button', window.parent.document ).length == 0 ) {
  246. $( '.fl-builder-panel-drag-handle', window.parent.document ).show();
  247. }
  248. },
  249. /**
  250. * Uses the jQuery empty function to remove lightbox
  251. * content and any related events.
  252. *
  253. * @since 1.0
  254. * @method empty
  255. */
  256. empty: function()
  257. {
  258. this._node.find('.fl-lightbox-content').empty();
  259. },
  260. /**
  261. * Bind an event to the lightbox.
  262. *
  263. * @since 1.0
  264. * @method on
  265. * @param {String} event The type of event to bind.
  266. * @param {Function} callback A callback to fire when the event is triggered.
  267. */
  268. on: function(event, callback)
  269. {
  270. this._node.on(event, callback);
  271. },
  272. /**
  273. * Unbind an event from the lightbox.
  274. *
  275. * @since 1.0
  276. * @method off
  277. * @param {String} event The type of event to unbind.
  278. * @param {Function} callback
  279. */
  280. off: function(event, callback)
  281. {
  282. this._node.off(event, callback);
  283. },
  284. /**
  285. * Trigger an event on the lightbox.
  286. *
  287. * @since 1.0
  288. * @method trigger
  289. * @param {String} event The type of event to trigger.
  290. * @param {Object} params Additional parameters to pass to the event.
  291. */
  292. trigger: function(event, params)
  293. {
  294. this._node.trigger(event, params);
  295. },
  296. /**
  297. * Destroy the lightbox by removing all elements, events
  298. * and object references.
  299. *
  300. * @since 1.0
  301. * @method destroy
  302. */
  303. destroy: function()
  304. {
  305. this._node.empty();
  306. this._node.remove();
  307. FLLightbox._instances[this._id] = 'undefined';
  308. try{ delete FLLightbox._instances[this._id]; } catch(e){}
  309. },
  310. /**
  311. * Initialize this lightbox instance.
  312. *
  313. * @since 1.0
  314. * @access private
  315. * @method _init
  316. * @param {Object} settings A setting object for this instance.
  317. */
  318. _init: function(settings)
  319. {
  320. var i = 0,
  321. prop = null;
  322. for(prop in FLLightbox._instances) {
  323. i++;
  324. }
  325. this._defaults = $.extend({}, this._defaults, settings);
  326. this._id = new Date().getTime() + i;
  327. FLLightbox._instances[this._id] = this;
  328. this._render();
  329. this._resizable();
  330. },
  331. /**
  332. * Renders the main wrapper.
  333. *
  334. * @since 1.0
  335. * @access private
  336. * @method _render
  337. */
  338. _render: function()
  339. {
  340. this._node = $( '<div class="fl-lightbox-wrap" data-instance-id="'+ this._id +'"><div class="fl-lightbox-mask"></div><div class="fl-lightbox"><div class="fl-lightbox-content-wrap"><div class="fl-lightbox-content"></div></div></div></div>' );
  341. this._node.addClass( this._defaults.className );
  342. $( 'body', window.parent.document ).append( this._node );
  343. },
  344. /**
  345. * Binds events for this instance.
  346. *
  347. * @since 1.0
  348. * @access private
  349. * @method _bind
  350. */
  351. _bind: function()
  352. {
  353. $( window.parent ).on( 'resize.fl-lightbox-' + this._id, this._delayedResize.bind( this ) );
  354. },
  355. /**
  356. * Unbinds events for this instance.
  357. *
  358. * @since 1.0
  359. * @access private
  360. * @method _unbind
  361. */
  362. _unbind: function()
  363. {
  364. $( window.parent ).off( 'resize.fl-lightbox-' + this._id );
  365. },
  366. /**
  367. * Enable resizing for the lightbox.
  368. *
  369. * @since 2.0
  370. * @method _resizable
  371. */
  372. _resizable: function()
  373. {
  374. var body = $( 'body', window.parent.document ),
  375. mask = this._node.find( '.fl-lightbox-mask' ),
  376. lightbox = this._node.find( '.fl-lightbox' ),
  377. resizable = $( '.fl-lightbox-resizable', window.parent.document ).eq( 0 ),
  378. rootEl = getComputedStyle(document.documentElement),
  379. minWidth = parseInt( rootEl.getPropertyValue('--fl-builder-panel-min-width') ),
  380. minHeight = parseInt( rootEl.getPropertyValue('--fl-builder-panel-min-height') );
  381. if ( this._defaults.resizable ) {
  382. mask.hide();
  383. lightbox.addClass( 'fl-lightbox-resizable' );
  384. lightbox.on( 'click', '.fl-lightbox-resize-toggle', this._resizeClicked.bind( this ) );
  385. lightbox.draggable( {
  386. cursor : 'move',
  387. handle : '.fl-lightbox-header',
  388. iframeFix : true
  389. } ).resizable( {
  390. handles : 'all',
  391. minHeight : minHeight,
  392. minWidth : minWidth,
  393. start : this._resizeStart.bind( this ),
  394. stop : this._resizeStop.bind( this )
  395. } );
  396. if ( resizable.length && resizable.hasClass( 'fl-lightbox-width-full' ) ) { // Setup nested
  397. lightbox.addClass( 'fl-lightbox-width-full' );
  398. lightbox.draggable( 'disable' );
  399. } else { // Setup the main parent lightbox
  400. this._restorePosition();
  401. }
  402. }
  403. else {
  404. mask.show();
  405. }
  406. this._resize();
  407. },
  408. /**
  409. * Resizes the lightbox after a delay.
  410. *
  411. * @since 1.0
  412. * @access private
  413. * @method _delayedResize
  414. */
  415. _delayedResize: function()
  416. {
  417. clearTimeout( this._resizeTimer );
  418. this._resizeTimer = setTimeout( this._resize.bind( this ), 250 );
  419. },
  420. /**
  421. * Resizes the lightbox.
  422. *
  423. * @since 1.0
  424. * @access private
  425. * @method _resize
  426. */
  427. _resize: function()
  428. {
  429. var lightbox = this._node.find( '.fl-lightbox' ),
  430. boxTop = parseInt( this._node.css( 'padding-top' ) ),
  431. boxBottom = parseInt( this._node.css( 'padding-bottom' ) ),
  432. boxLeft = parseInt( this._node.css( 'padding-left' ) ),
  433. boxRight = parseInt( this._node.css( 'padding-right' ) ),
  434. boxHeight = lightbox.height(),
  435. boxWidth = lightbox.width(),
  436. win = $( window.parent ),
  437. winHeight = win.height() - boxTop - boxBottom,
  438. winWidth = win.width() - boxLeft - boxRight,
  439. top = '0px';
  440. if ( ! this._defaults.resizable ) {
  441. if ( winHeight > boxHeight ) {
  442. top = ( ( winHeight - boxHeight - 46 ) / 2 ) + 'px';
  443. }
  444. lightbox.attr( 'style', '' ).css( 'margin', top + ' auto 0' );
  445. }
  446. else {
  447. if ( boxWidth < 600 ) {
  448. lightbox.addClass( 'fl-lightbox-width-slim' );
  449. } else {
  450. lightbox.removeClass( 'fl-lightbox-width-slim' );
  451. }
  452. if ( boxWidth < 450 ) {
  453. lightbox.addClass( 'fl-lightbox-width-micro' );
  454. } else {
  455. lightbox.removeClass( 'fl-lightbox-width-micro' );
  456. }
  457. this._resizeEditors();
  458. }
  459. this.trigger( 'resized' );
  460. },
  461. /**
  462. * Callback for when a user lightbox resize starts.
  463. *
  464. * @since 2.0
  465. * @access private
  466. * @method _resizeStart
  467. */
  468. _resizeStart: function()
  469. {
  470. $( 'body', window.parent.document ).addClass( 'fl-builder-resizable-is-resizing' );
  471. $( '.fl-builder-lightbox:visible', window.parent.document ).append( '<div class="fl-builder-resizable-iframe-fix"></div>' );
  472. FLBuilder._destroyOverlayEvents();
  473. FLBuilder._removeAllOverlays();
  474. },
  475. /**
  476. * Callback for when a user lightbox resize stops.
  477. *
  478. * @since 2.0
  479. * @access private
  480. * @method _resizeStop
  481. */
  482. _resizeStop: function( e, ui )
  483. {
  484. var lightbox = $( '.fl-lightbox-resizable:visible', window.parent.document );
  485. if ( parseInt( lightbox.css( 'top' ) ) < 0 ) {
  486. lightbox.css( 'top', '0' );
  487. }
  488. this._savePosition();
  489. $( 'body', window.parent.document ).removeClass( 'fl-builder-resizable-is-resizing' );
  490. $( '.fl-builder-resizable-iframe-fix', window.parent.document ).remove();
  491. FLBuilder._bindOverlayEvents();
  492. },
  493. /**
  494. * Resize to full or back to standard when the resize icon is clicked.
  495. *
  496. * @since 2.0
  497. * @access private
  498. * @method _expandLightbox
  499. */
  500. _resizeClicked: function()
  501. {
  502. var lightboxes = $( '.fl-lightbox-resizable', window.parent.document ),
  503. controls = lightboxes.find( '.fl-lightbox-resize-toggle' ),
  504. lightbox = this._node.find( '.fl-lightbox' );
  505. if ( lightbox.hasClass( 'fl-lightbox-width-full' ) ) {
  506. this._resizeExitFull();
  507. } else {
  508. this._resizeEnterFull();
  509. }
  510. this._resize();
  511. },
  512. /**
  513. * Resize to the full size lightbox.
  514. *
  515. * @since 2.0
  516. * @access private
  517. * @method _resizeEnterFull
  518. */
  519. _resizeEnterFull: function()
  520. {
  521. var lightboxes = $( '.fl-lightbox-resizable', window.parent.document ),
  522. controls = lightboxes.find( '.fl-lightbox-resize-toggle' ),
  523. lightbox = this._node.find( '.fl-lightbox' );
  524. controls.removeClass( 'fa-window-maximize' ).addClass( 'fa-window-minimize' );
  525. lightboxes.addClass( 'fl-lightbox-width-full' );
  526. lightboxes.draggable( 'disable' );
  527. lightboxes.resizable( 'disable' );
  528. },
  529. /**
  530. * Resize to the standard size lightbox.
  531. *
  532. * @since 2.0
  533. * @access private
  534. * @method _resizeEnterFull
  535. */
  536. _resizeExitFull: function()
  537. {
  538. var lightboxes = $( '.fl-lightbox-resizable', window.parent.document ),
  539. controls = lightboxes.find( '.fl-lightbox-resize-toggle' ),
  540. lightbox = this._node.find( '.fl-lightbox' );
  541. controls.removeClass( 'fa-window-minimize' ).addClass( 'fa-window-maximize' );
  542. lightboxes.removeClass( 'fl-lightbox-width-full' );
  543. lightboxes.draggable( 'enable' );
  544. lightboxes.resizable( 'enable' );
  545. },
  546. /**
  547. * Resizes text and code editor fields.
  548. *
  549. * @since 2.0
  550. * @method _resizeEditors
  551. */
  552. _resizeEditors: function()
  553. {
  554. $( '.fl-lightbox-resizable', window.parent.document ).each( function() {
  555. var lightbox = $( this ),
  556. fieldsHeight = lightbox.find( '.fl-builder-settings-fields' ).height(),
  557. editors = lightbox.find( '.mce-edit-area > iframe, textarea.wp-editor-area, .ace_editor' ),
  558. editor = null;
  559. if ( fieldsHeight < 350 ) {
  560. fieldsHeight = 350;
  561. }
  562. editors.each( function() {
  563. editor = $( this );
  564. if ( editor.hasClass( 'ace_editor' ) ) {
  565. editor.height( fieldsHeight - 60 );
  566. editor.closest( '.fl-field' ).data( 'editor' ).resize();
  567. } else if ( editor.closest( '.mce-container-body' ).find( '.mce-toolbar-grp .mce-toolbar.mce-last' ).is( ':visible' ) ) {
  568. editor.height( fieldsHeight - 175 );
  569. } else {
  570. editor.height( fieldsHeight - 150 );
  571. }
  572. } );
  573. } );
  574. },
  575. /**
  576. * Save the lightbox position for the current user.
  577. *
  578. * @since 2.0
  579. * @access private
  580. * @method _savePosition
  581. */
  582. _savePosition: function()
  583. {
  584. var lightbox = this._node.find( '.fl-lightbox' ),
  585. data = {
  586. width : lightbox.width(),
  587. height : lightbox.height(),
  588. top : parseInt( lightbox.css( 'top' ) ) < 0 ? '0px' : lightbox.css( 'top' ),
  589. left : lightbox.css( 'left' )
  590. };
  591. if ( lightbox.closest( '.fl-builder-ui-pinned' ).length ) {
  592. return;
  593. }
  594. FLBuilderConfig.userSettings.lightbox = data;
  595. FLBuilder.ajax( {
  596. action : 'save_lightbox_position',
  597. data : data
  598. } );
  599. },
  600. /**
  601. * Restores the lightbox position for the current user.
  602. *
  603. * @since 2.0
  604. * @access private
  605. * @method _restorePosition
  606. */
  607. _restorePosition: function()
  608. {
  609. var lightbox = this._node.find( '.fl-lightbox' ),
  610. settings = this._getPositionSettings();
  611. if ( settings ) {
  612. lightbox.css( settings );
  613. } else {
  614. lightbox.css( {
  615. top : 25,
  616. left : FLBuilderConfig.isRtl ? '-' + 25 : 25
  617. } );
  618. }
  619. },
  620. /**
  621. * Get the user settings for the lightbox position.
  622. *
  623. * Resize the height to 500px if the lightbox height is
  624. * taller than the window and the window is taller than
  625. * 546px (500px for lightbox min-height and 46px for the
  626. * builder bar height).
  627. *
  628. * @since 2.0
  629. * @access private
  630. * @method _getPositionSettings
  631. * @return {Object|Boolean}
  632. */
  633. _getPositionSettings: function()
  634. {
  635. var settings = FLBuilderConfig.userSettings.lightbox;
  636. if ( ! settings ) {
  637. return false;
  638. }
  639. var winHeight = window.parent.innerHeight,
  640. isRTL = FLBuilderConfig.isRtl,
  641. height = parseInt( settings.height ),
  642. top = parseInt( settings.top ),
  643. wleft = parseInt( settings.left ),
  644. wtop = parseInt( settings.top ),
  645. width = parseInt( settings.width );
  646. // settings are off the screen to the right
  647. if( ! isRTL && (wleft + width + 100) > screen.width ) {
  648. settings.left = screen.width - width - 250;
  649. }
  650. // settings are off the screen to the left
  651. if ( ! isRTL && wleft < 0 ) {
  652. settings.left = 50;
  653. }
  654. wleft = parseInt( settings.left );
  655. if ( isRTL && wleft > 0 ) {
  656. settings.left = -25;
  657. }
  658. if ( ( height > winHeight && winHeight > 546 ) || top + height > winHeight ) {
  659. if ( height > winHeight ) {
  660. settings.height = winHeight - 50;
  661. }
  662. settings.top = 0;
  663. }
  664. return settings;
  665. },
  666. };
  667. })(jQuery);