fl-builder-responsive-editing.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. ( function( $ ) {
  2. /**
  3. * Helper for handling responsive editing logic.
  4. *
  5. * @since 1.9
  6. * @class FLBuilderResponsiveEditing
  7. */
  8. FLBuilderResponsiveEditing = {
  9. /**
  10. * The current editing mode we're in.
  11. *
  12. * @since 1.9
  13. * @private
  14. * @property {String} _mode
  15. */
  16. _mode: 'default',
  17. /**
  18. * Refreshes the media queries for the responsive preview
  19. * if necessary.
  20. *
  21. * @since 1.9
  22. * @method refreshPreview
  23. * @param {Function} callback
  24. */
  25. refreshPreview: function( callback )
  26. {
  27. var width;
  28. if ( $( '.fl-responsive-preview' ).length && 'default' !== this._mode ) {
  29. if ( 'responsive' == this._mode ) {
  30. width = FLBuilderConfig.global.responsive_breakpoint >= 320 ? 320 : FLBuilderConfig.global.responsive_breakpoint;
  31. FLBuilderSimulateMediaQuery.update( width, callback );
  32. }
  33. else if ( 'medium' == this._mode ) {
  34. width = FLBuilderConfig.global.medium_breakpoint >= 769 ? 769 : FLBuilderConfig.global.medium_breakpoint;
  35. FLBuilderSimulateMediaQuery.update( width, callback );
  36. }
  37. else if ( 'large' == this._mode ) {
  38. width = FLBuilderConfig.global.large_breakpoint >= 1200 ? 1200 : FLBuilderConfig.global.large_breakpoint;
  39. FLBuilderSimulateMediaQuery.update( width, callback );
  40. }
  41. FLBuilder._resizeLayout();
  42. } else if ( callback ) {
  43. callback();
  44. }
  45. },
  46. /**
  47. * Initializes responsive editing.
  48. *
  49. * @since 1.9
  50. * @access private
  51. * @method _init
  52. */
  53. _init: function()
  54. {
  55. this._bind();
  56. if ( ! FLBuilder.UIIFrame.isEnabled() ) {
  57. this._initMediaQueries();
  58. }
  59. },
  60. /**
  61. * Bind events.
  62. *
  63. * @since 1.9
  64. * @access private
  65. * @method _bind
  66. */
  67. _bind: function()
  68. {
  69. FLBuilder.addHook( 'endEditingSession', this._clearPreview );
  70. FLBuilder.addHook( 'didEnterRevisionPreview', this._clearPreview );
  71. FLBuilder.addHook( 'responsiveEditing', this._menuToggleClicked );
  72. FLBuilder.addHook( 'preview-init', this._switchAllSettingsToCurrentMode );
  73. FLBuilder.addHook( 'responsive-editing-switched', this._updateSizeText );
  74. $( 'body', window.parent.document ).on( 'click', '.fl-field-responsive-toggle', this._settingToggleClicked );
  75. $( 'body', window.parent.document ).on( 'click', '.fl-responsive-preview-message button', this._previewToggleClicked );
  76. },
  77. /**
  78. * Initializes faux media queries.
  79. *
  80. * @since 1.10
  81. * @access private
  82. * @method _initMediaQueries
  83. */
  84. _initMediaQueries: function()
  85. {
  86. // Don't simulate media queries for stylesheets that match these paths.
  87. FLBuilderSimulateMediaQuery.ignore(
  88. [
  89. FLBuilderConfig.pluginUrl,
  90. FLBuilderConfig.relativePluginUrl
  91. ]
  92. );
  93. var ignorelist = $.map( FLBuilderConfig.responsiveIgnore, function( value, index ) {
  94. return [ value ];
  95. } );
  96. FLBuilderSimulateMediaQuery.ignore( ignorelist );
  97. // Reparse stylesheets that match these paths on each update.
  98. FLBuilderSimulateMediaQuery.reparse( [
  99. FLBuilderConfig.postId + '-layout-draft.css',
  100. FLBuilderConfig.postId + '-layout-draft-partial.css',
  101. FLBuilderConfig.postId + '-layout-preview.css',
  102. FLBuilderConfig.postId + '-layout-preview-partial.css',
  103. FLBuilderConfig.postId + '-inline-css',
  104. 'fl-builder-global-css',
  105. 'fl-builder-layout-css'
  106. ] );
  107. },
  108. /**
  109. * Updates the size text in the preview UI.
  110. */
  111. _updateSizeText: function() {
  112. var show_size = $('.fl-responsive-preview-message .size' ),
  113. large = ( '1' === FLBuilderConfig.global.responsive_preview ) ? FLBuilderConfig.global.large_breakpoint : 1200,
  114. medium = ( '1' === FLBuilderConfig.global.responsive_preview ) ? FLBuilderConfig.global.medium_breakpoint : 769,
  115. responsive = ( '1' === FLBuilderConfig.global.responsive_preview ) ? FLBuilderConfig.global.responsive_breakpoint : 360,
  116. size_text = '';
  117. if ( $('.fl-responsive-preview').hasClass('fl-preview-responsive') ) {
  118. size_text = FLBuilderStrings.mobile + ' ' + responsive + 'px';
  119. } else if ( $('.fl-responsive-preview').hasClass('fl-preview-medium') ) {
  120. size_text = FLBuilderStrings.medium + ' ' + medium + 'px';
  121. } else if ( $('.fl-responsive-preview').hasClass('fl-preview-large') ) {
  122. size_text = FLBuilderStrings.large + ' ' + large + 'px';
  123. }
  124. show_size.html('').html(size_text)
  125. },
  126. /**
  127. * Switches to either mobile, tablet or desktop editing.
  128. *
  129. * @since 1.9
  130. * @access private
  131. * @method _switchTo
  132. */
  133. _switchTo: function( mode, callback )
  134. {
  135. var html = $( 'html' ).add( 'html', window.parent.document ),
  136. body = $( 'body' ),
  137. content = $( FLBuilder._contentClass ),
  138. preview = $( '.fl-responsive-preview' ),
  139. mask = $( '.fl-responsive-preview-mask' ),
  140. placeholder = $( '.fl-content-placeholder' ),
  141. width = null;
  142. // Save the new mode.
  143. FLBuilderResponsiveEditing._mode = mode;
  144. // Run the legacy UI, otherwise, use the iframe UI.
  145. if ( ! FLBuilder.UIIFrame.isEnabled() ) {
  146. // Setup the preview.
  147. if ( 'default' == mode ) {
  148. if ( 0 === placeholder.length ) {
  149. return;
  150. }
  151. html.removeClass( 'fl-responsive-preview-enabled' );
  152. placeholder.after( content );
  153. placeholder.remove();
  154. preview.remove();
  155. mask.remove();
  156. }
  157. else if ( 0 === preview.length ) {
  158. html.addClass( 'fl-responsive-preview-enabled' );
  159. content.after( '<div class="fl-content-placeholder"></div>' );
  160. body.prepend( wp.template( 'fl-responsive-preview' )() );
  161. $( '.fl-responsive-preview' ).addClass( 'fl-preview-' + mode );
  162. $( '.fl-responsive-preview-content' ).append( content );
  163. }
  164. else {
  165. preview.removeClass( 'fl-preview-responsive fl-preview-medium' );
  166. preview.addClass( 'fl-preview-' + mode );
  167. }
  168. // Set the content width and apply media queries.
  169. if ( 'responsive' == mode ) {
  170. width = ( '1' !== FLBuilderConfig.global.responsive_preview && FLBuilderConfig.global.responsive_breakpoint >= 360 ) ? 360 : FLBuilderConfig.global.responsive_breakpoint;
  171. content.width( width );
  172. FLBuilderSimulateMediaQuery.update( width, callback );
  173. FLBuilderResponsiveEditing._setMarginPaddingPlaceholders();
  174. }
  175. else if ( 'medium' == mode ) {
  176. width = ( '1' !== FLBuilderConfig.global.responsive_preview && FLBuilderConfig.global.medium_breakpoint >= 769 ) ? 769 : FLBuilderConfig.global.medium_breakpoint;
  177. content.width( width );
  178. FLBuilderSimulateMediaQuery.update( width, callback );
  179. FLBuilderResponsiveEditing._setMarginPaddingPlaceholders();
  180. }
  181. else if ( 'large' == mode ) {
  182. width = ( '1' !== FLBuilderConfig.global.responsive_preview && FLBuilderConfig.global.large_breakpoint >= 1200 ) ? 1200 : FLBuilderConfig.global.large_breakpoint;
  183. content.width( width );
  184. FLBuilderSimulateMediaQuery.update( width, callback );
  185. FLBuilderResponsiveEditing._setMarginPaddingPlaceholders();
  186. }
  187. else {
  188. content.width( '' );
  189. FLBuilderSimulateMediaQuery.update( null, callback );
  190. }
  191. // Set the content background color.
  192. this._setContentBackgroundColor();
  193. } else if ( callback ) {
  194. callback();
  195. }
  196. // Resize the layout.
  197. FLBuilder._resizeLayout();
  198. // Preview all responsive settings.
  199. this._setMarginPaddingPlaceholders();
  200. this._previewFields();
  201. // Broadcast the switch.
  202. FLBuilder.triggerHook( 'responsive-editing-switched', mode );
  203. },
  204. /**
  205. * Sets the background color for the builder content
  206. * in a responsive preview.
  207. *
  208. * @since 1.9
  209. * @access private
  210. * @method _setContentBackgroundColor
  211. */
  212. _setContentBackgroundColor: function()
  213. {
  214. var content = $( FLBuilder._contentClass ),
  215. preview = $( '.fl-responsive-preview' ),
  216. placeholder = $( '.fl-content-placeholder' ),
  217. parents = placeholder.parents(),
  218. parent = null,
  219. color = '#fff',
  220. i = 0;
  221. if ( 0 === preview.length ) {
  222. content.css( 'background-color', '' );
  223. }
  224. else {
  225. for( ; i < parents.length; i++ ) {
  226. color = parents.eq( i ).css( 'background-color' );
  227. if ( color != 'rgba(0, 0, 0, 0)' ) {
  228. break;
  229. }
  230. }
  231. content.css( 'background-color', color );
  232. }
  233. },
  234. /**
  235. * Switches to the given mode and scrolls to an
  236. * active node if one is present.
  237. *
  238. * @since 1.9
  239. * @access private
  240. * @method _switchToAndScroll
  241. */
  242. _switchToAndScroll: function( mode )
  243. {
  244. var nodeId = $( '.fl-builder-settings', window.parent.document ).data( 'node' ),
  245. element = undefined === nodeId ? undefined : $( '.fl-node-' + nodeId );
  246. FLBuilderResponsiveEditing._switchTo( mode, function() {
  247. if ( undefined !== element && element ) {
  248. var win = $( window ),
  249. content = $( '.fl-responsive-preview-content' );
  250. if ( content.length ) {
  251. content.scrollTop( 0 );
  252. content.scrollTop( element.offset().top - 150 );
  253. } else {
  254. $( 'html, body' ).scrollTop( element.offset().top - 100 );
  255. }
  256. }
  257. $('.fl-row-bg-parallax').each(function(){
  258. var row = $(this),
  259. content = row.find('> .fl-row-content-wrap'),
  260. rowImages = {
  261. 'default': row.data('parallax-image'),
  262. 'medium': row.data('parallax-image-medium'),
  263. 'responsive': row.data('parallax-image-responsive'),
  264. };
  265. if ( undefined !== rowImages[mode] ) {
  266. content.css('background-image', 'url(' + rowImages[mode] + ')');
  267. }
  268. });
  269. } );
  270. },
  271. /**
  272. * Switches all responsive settings in a settings form
  273. * to the given mode.
  274. *
  275. * @since 1.9
  276. * @access private
  277. * @method _switchAllSettingsTo
  278. * @param {String} mode
  279. */
  280. _switchAllSettingsTo: function( mode )
  281. {
  282. var className = 'dashicons-desktop dashicons-laptop dashicons-tablet dashicons-smartphone';
  283. $( '.fl-field-responsive-toggle' ).removeClass( className );
  284. $( '.fl-field-responsive-setting' ).hide();
  285. if ( 'default' == mode ) {
  286. className = 'dashicons-desktop';
  287. }
  288. else if ( 'large' == mode ) {
  289. className = 'dashicons-laptop';
  290. }
  291. else if ( 'medium' == mode ) {
  292. className = 'dashicons-tablet';
  293. }
  294. else {
  295. className = 'dashicons-smartphone';
  296. }
  297. $( '.fl-field-responsive-toggle' ).addClass( className ).data( 'mode', mode );
  298. $( '.fl-field-responsive-setting-' + mode ).css( 'display', 'inline-block' );
  299. FLBuilder._toggleForm()
  300. },
  301. /**
  302. * Switches all responsive settings in a settings form
  303. * to the current mode.
  304. *
  305. * @since 2.2
  306. * @access private
  307. * @method _switchAllSettingsToCurrentMode
  308. */
  309. _switchAllSettingsToCurrentMode: function()
  310. {
  311. var self = FLBuilderResponsiveEditing;
  312. self._switchAllSettingsTo( self._mode );
  313. if( 'default' != self._mode ) {
  314. self._setMarginPaddingPlaceholders();
  315. }
  316. FLBuilder.triggerHook( 'responsive-editing-switched', self._mode );
  317. },
  318. /**
  319. * Set Placeholders for Padding and Margin
  320. *
  321. * @since 2.4
  322. * @access private
  323. * @method _setMarginPaddingPlaceholders
  324. */
  325. _setMarginPaddingPlaceholders: function()
  326. {
  327. var self = FLBuilderResponsiveEditing;
  328. var sizes = [ 'default', 'large', 'medium', 'responsive' ];
  329. var sides = ['top', 'left', 'bottom', 'right'];
  330. var inputs = {
  331. padding: {},
  332. margin: {}
  333. };
  334. sizes.forEach( function ( size ) {
  335. inputs.padding[ size ] = {};
  336. inputs.margin[ size ] = {};
  337. sides.forEach( function ( side ) {
  338. var name = 'default' === size ? side : side + '_' + size;
  339. inputs.padding[ size ][ side ] = $( '#fl-field-padding .fl-field-responsive-setting-' + size + ' input[name="padding_' + name + '"]' );
  340. inputs.margin[ size ][ side ] = $( '#fl-field-margin .fl-field-responsive-setting-' + size + ' input[name="margin_' + name + '"]' );
  341. } );
  342. } );
  343. sides.forEach( function( side ) {
  344. self._setSpacingInputPlaceholder( inputs, 'padding', 'large', 'default', side )
  345. self._setSpacingInputPlaceholder( inputs, 'padding', 'medium', 'large', side )
  346. self._setSpacingInputPlaceholder( inputs, 'padding', 'responsive', 'medium', side )
  347. self._setSpacingInputPlaceholder( inputs, 'margin', 'large', 'default', side )
  348. self._setSpacingInputPlaceholder( inputs, 'margin', 'medium', 'large', side )
  349. self._setSpacingInputPlaceholder( inputs, 'margin', 'responsive', 'medium', side )
  350. } );
  351. self._setAutoSpacingPlaceholders( inputs );
  352. },
  353. /**
  354. * Sets the placeholder for a single spacing input.
  355. *
  356. * @since 2.6
  357. * @access private
  358. * @method _setSpacingInputPlaceholder
  359. */
  360. _setSpacingInputPlaceholder: function( inputs, property, size, fallbackSize, side )
  361. {
  362. var input = inputs[ property ][ size ][ side ];
  363. var fallback = inputs[ property ][ fallbackSize ][ side ];
  364. if ( ! input.attr( 'placeholder' ) || input.data( 'has-custom-placeholder' ) ) {
  365. input.data( 'has-custom-placeholder', true );
  366. if ( '' !== fallback.val() ) {
  367. input.attr( 'placeholder', fallback.val() );
  368. } else if ( fallback.attr( 'placeholder' ) ) {
  369. input.attr( 'placeholder', fallback.attr( 'placeholder' ) );
  370. }
  371. }
  372. },
  373. /**
  374. * @since 2.7
  375. * @access private
  376. * @method _setAutoSpacingPlaceholders
  377. */
  378. _setAutoSpacingPlaceholders: function( inputs )
  379. {
  380. var settings = FLBuilderConfig.global;
  381. var isAuto = '1' === settings.auto_spacing;
  382. var isRow = !! $( '.fl-builder-row-settings' ).length;
  383. var isCol = !! $( '.fl-builder-col-settings' ).length;
  384. var isModule = !! $( '.fl-builder-module-settings' ).length;
  385. if ( ! isAuto || isModule ) {
  386. return;
  387. }
  388. var type = isRow ? 'row' : 'column';
  389. if ( '' === settings[ type + '_padding_right_responsive' ] ) {
  390. inputs.padding.responsive.right.attr( 'placeholder', '0' );
  391. }
  392. if ( '' === settings[ type + '_padding_left_responsive' ] ) {
  393. inputs.padding.responsive.left.attr( 'placeholder', '0' );
  394. }
  395. if ( '' === settings[ type + '_margins_top_responsive' ] ) {
  396. inputs.margin.responsive.top.attr( 'placeholder', '0' );
  397. }
  398. if ( '' === settings[ type + '_margins_right_responsive' ] ) {
  399. inputs.margin.responsive.right.attr( 'placeholder', '0' );
  400. }
  401. if ( '' === settings[ type + '_margins_bottom_responsive' ] ) {
  402. inputs.margin.responsive.bottom.attr( 'placeholder', '0' );
  403. }
  404. if ( '' === settings[ type + '_margins_left_responsive' ] ) {
  405. inputs.margin.responsive.left.attr( 'placeholder', '0' );
  406. }
  407. },
  408. /**
  409. * Callback for when the responsive toggle of a setting
  410. * is clicked.
  411. *
  412. * @since 1.9
  413. * @access private
  414. * @method _settingToggleClicked
  415. */
  416. _settingToggleClicked: function()
  417. {
  418. var toggle = $( this ),
  419. mode = toggle.data( 'mode' );
  420. if ( 'default' == mode ) {
  421. mode = 'large';
  422. }
  423. else if ( 'large' == mode ) {
  424. mode = 'medium';
  425. }
  426. else if ( 'medium' == mode ) {
  427. mode = 'responsive';
  428. }
  429. else {
  430. mode = 'default';
  431. }
  432. FLBuilderResponsiveEditing._switchAllSettingsTo( mode );
  433. FLBuilderResponsiveEditing._switchToAndScroll( mode );
  434. toggle.siblings( '.fl-field-responsive-setting:visible' ).find( 'input' ).focus();
  435. },
  436. /**
  437. * Callback for when the main menu item is clicked.
  438. *
  439. * @since 2.2
  440. * @access private
  441. * @method _menuToggleClicked
  442. */
  443. _menuToggleClicked: function()
  444. {
  445. var mode = FLBuilderResponsiveEditing._mode;
  446. if ( 'default' == mode ) {
  447. mode = 'large';
  448. } else if ( 'large' == mode ) {
  449. mode = 'medium';
  450. } else if ( 'medium' == mode ) {
  451. mode = 'responsive';
  452. } else {
  453. mode = 'default';
  454. }
  455. FLBuilder.MainMenu.hide();
  456. FLBuilderResponsiveEditing._switchAllSettingsTo( mode );
  457. FLBuilderResponsiveEditing._switchToAndScroll( mode );
  458. },
  459. /**
  460. * Callback for when the switch buttons of the responsive
  461. * preview header are clicked.
  462. *
  463. * @since 2.2
  464. * @access private
  465. * @method _previewToggleClicked
  466. */
  467. _previewToggleClicked: function()
  468. {
  469. var mode = $( this ).data( 'mode' );
  470. FLBuilderResponsiveEditing._switchAllSettingsTo( mode );
  471. FLBuilderResponsiveEditing._switchToAndScroll( mode );
  472. },
  473. /**
  474. * Clears the responsive editing preview and reverts
  475. * to the default view.
  476. *
  477. * @since 1.9
  478. * @access private
  479. * @method _clearPreview
  480. */
  481. _clearPreview: function()
  482. {
  483. FLBuilderResponsiveEditing._switchToAndScroll( 'default' );
  484. },
  485. /**
  486. * Callback for when the responsive preview changes
  487. * to live preview CSS for responsive fields.
  488. *
  489. * @since 1.9
  490. * @access private
  491. * @method _previewFields
  492. */
  493. _previewFields: function()
  494. {
  495. var mode = FLBuilderResponsiveEditing._mode,
  496. form = $( '.fl-builder-settings:visible' );
  497. if ( 0 === form.length || undefined === form.attr( 'data-node' ) ) {
  498. return;
  499. }
  500. FLBuilder.triggerHook( 'responsive-editing-before-preview-fields', mode );
  501. form.find( '.fl-builder-settings-tab' ).each( function() {
  502. var tab = $( this );
  503. tab.css( 'display', 'block' );
  504. tab.find( '.fl-field-responsive-setting-' + mode + ':visible' ).each( function() {
  505. var field = $( this ),
  506. parent = field.closest( '.fl-field' ),
  507. type = parent.data( 'type' ),
  508. preview = parent.data( 'preview' ),
  509. hasConnection = parent.find( '.fl-field-connection-visible' ).length;
  510. if ( 'refresh' == preview.type ) {
  511. return;
  512. }
  513. if ( hasConnection ) {
  514. if ( 'photo' === type && 'default' !== mode ) {
  515. field.find( '.fl-photo-remove' ).trigger( 'click' );
  516. }
  517. } else{
  518. field.find( 'input' ).trigger( 'keyup' );
  519. field.find( 'select' ).trigger( 'change' );
  520. }
  521. } );
  522. tab.css( 'display', '' );
  523. } );
  524. FLBuilder.triggerHook( 'responsive-editing-after-preview-fields', mode );
  525. },
  526. };
  527. $( function() { FLBuilderResponsiveEditing._init() } );
  528. } )( jQuery );