fl-builder-responsive-editing.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  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. */
  24. refreshPreview: function()
  25. {
  26. var width;
  27. if ( ! $( '.fl-responsive-preview' ).length || 'default' == this._mode ) {
  28. return;
  29. }
  30. else if ( 'responsive' == this._mode ) {
  31. width = FLBuilderConfig.global.responsive_breakpoint >= 320 ? 320 : FLBuilderConfig.global.responsive_breakpoint;
  32. FLBuilderSimulateMediaQuery.update( width );
  33. }
  34. else if ( 'medium' == this._mode ) {
  35. width = FLBuilderConfig.global.medium_breakpoint >= 769 ? 769 : FLBuilderConfig.global.medium_breakpoint;
  36. FLBuilderSimulateMediaQuery.update( width );
  37. }
  38. FLBuilder._resizeLayout();
  39. },
  40. /**
  41. * Initializes responsive editing.
  42. *
  43. * @since 1.9
  44. * @access private
  45. * @method _init
  46. */
  47. _init: function()
  48. {
  49. this._bind();
  50. this._initMediaQueries();
  51. },
  52. /**
  53. * Bind events.
  54. *
  55. * @since 1.9
  56. * @access private
  57. * @method _bind
  58. */
  59. _bind: function()
  60. {
  61. FLBuilder.addHook( 'settings-form-init', this._initSettingsForms );
  62. FLBuilder.addHook( 'settings-lightbox-closed', this._clearPreview );
  63. $( 'body' ).delegate( '.fl-field-responsive-toggle', 'click', this._settingToggleClicked );
  64. },
  65. /**
  66. * Initializes faux media queries.
  67. *
  68. * @since 1.10
  69. * @access private
  70. * @method _initMediaQueries
  71. */
  72. _initMediaQueries: function()
  73. {
  74. // Don't simulate media queries for stylesheets that match these paths.
  75. FLBuilderSimulateMediaQuery.ignore( [
  76. FLBuilderConfig.pluginUrl,
  77. 'fl-theme-builder',
  78. '/wp-includes/',
  79. '/wp-admin/',
  80. 'admin-bar-inline-css',
  81. 'ace-tm',
  82. 'ace_editor.css'
  83. ] );
  84. // Reparse stylesheets that match these paths on each update.
  85. FLBuilderSimulateMediaQuery.reparse( [
  86. FLBuilderConfig.postId + '-layout-draft.css?',
  87. FLBuilderConfig.postId + '-layout-draft-partial.css?',
  88. FLBuilderConfig.postId + '-layout-preview.css?',
  89. FLBuilderConfig.postId + '-layout-preview-partial.css?',
  90. 'fl-builder-global-css',
  91. 'fl-builder-layout-css'
  92. ] );
  93. },
  94. /**
  95. * Switches to either mobile, tablet or desktop editing.
  96. *
  97. * @since 1.9
  98. * @access private
  99. * @method _switchTo
  100. */
  101. _switchTo: function( mode, callback )
  102. {
  103. var html = $( 'html' ),
  104. body = $( 'body' ),
  105. content = $( FLBuilder._contentClass ),
  106. preview = $( '.fl-responsive-preview' ),
  107. mask = $( '.fl-responsive-preview-mask' ),
  108. placeholder = $( '.fl-content-placeholder' ),
  109. width = null;
  110. // Save the new mode.
  111. FLBuilderResponsiveEditing._mode = mode;
  112. // Setup the preview.
  113. if ( 'default' == mode ) {
  114. if ( 0 === placeholder.length ) {
  115. return;
  116. }
  117. html.removeClass( 'fl-responsive-preview-enabled' );
  118. placeholder.after( content );
  119. placeholder.remove();
  120. preview.remove();
  121. mask.remove();
  122. }
  123. else if ( 0 === preview.length ) {
  124. html.addClass( 'fl-responsive-preview-enabled' );
  125. content.after( '<div class="fl-content-placeholder"></div>' );
  126. body.prepend( wp.template( 'fl-responsive-preview' )() );
  127. $( '.fl-responsive-preview' ).addClass( 'fl-preview-' + mode );
  128. $( '.fl-responsive-preview-content' ).append( content );
  129. }
  130. else {
  131. preview.removeClass( 'fl-preview-responsive fl-preview-medium' );
  132. preview.addClass( 'fl-preview-' + mode );
  133. }
  134. // Set the content width and apply media queries.
  135. if ( 'responsive' == mode ) {
  136. width = FLBuilderConfig.global.responsive_breakpoint >= 360 ? 360 : FLBuilderConfig.global.responsive_breakpoint;
  137. content.width( width );
  138. FLBuilderSimulateMediaQuery.update( width, callback );
  139. }
  140. else if ( 'medium' == mode ) {
  141. width = FLBuilderConfig.global.medium_breakpoint >= 769 ? 769 : FLBuilderConfig.global.medium_breakpoint;
  142. content.width( width );
  143. FLBuilderSimulateMediaQuery.update( width, callback );
  144. }
  145. else {
  146. content.width( '' );
  147. FLBuilderSimulateMediaQuery.update( null, callback );
  148. }
  149. // Set the content background color.
  150. this._setContentBackgroundColor();
  151. // Resize the layout.
  152. FLBuilder._resizeLayout();
  153. // Broadcast the switch.
  154. FLBuilder.triggerHook( 'responsive-editing-switched', mode );
  155. },
  156. /**
  157. * Sets the background color for the builder content
  158. * in a responsive preview.
  159. *
  160. * @since 1.9
  161. * @access private
  162. * @method _setContentBackgroundColor
  163. */
  164. _setContentBackgroundColor: function()
  165. {
  166. var content = $( FLBuilder._contentClass ),
  167. preview = $( '.fl-responsive-preview' ),
  168. placeholder = $( '.fl-content-placeholder' ),
  169. parents = placeholder.parents(),
  170. parent = null,
  171. color = '#fff',
  172. i = 0;
  173. if ( 0 === preview.length ) {
  174. content.css( 'background-color', '' );
  175. }
  176. else {
  177. for( ; i < parents.length; i++ ) {
  178. color = parents.eq( i ).css( 'background-color' );
  179. if ( color != 'rgba(0, 0, 0, 0)' ) {
  180. break;
  181. }
  182. }
  183. content.css( 'background-color', color );
  184. }
  185. },
  186. /**
  187. * Switches to the given mode and scrolls to an
  188. * active node if one is present.
  189. *
  190. * @since 1.9
  191. * @access private
  192. * @method _switchToAndScroll
  193. */
  194. _switchToAndScroll: function( mode )
  195. {
  196. var nodeId = $( '.fl-builder-settings' ).data( 'node' ),
  197. element = undefined === nodeId ? undefined : $( '.fl-node-' + nodeId );
  198. FLBuilderResponsiveEditing._switchTo( mode, function() {
  199. if ( undefined !== element && element ) {
  200. setTimeout( function(){
  201. var win = $( window ),
  202. content = $( '.fl-responsive-preview-content' );
  203. if ( ! content.length || win.height() < content.height() ) {
  204. $( 'html, body' ).animate( {
  205. scrollTop: element.offset().top - 100
  206. }, 250 );
  207. }
  208. else {
  209. scrollTo( 0, 0 );
  210. }
  211. }, 250 );
  212. }
  213. } );
  214. },
  215. /**
  216. * Clears the responsive editing preview and reverts
  217. * to the default view.
  218. *
  219. * @since 1.9
  220. * @access private
  221. * @method _clearPreview
  222. */
  223. _clearPreview: function()
  224. {
  225. FLBuilderResponsiveEditing._switchToAndScroll( 'default' );
  226. },
  227. /**
  228. * Callback for when the responsive toggle of a setting
  229. * is clicked.
  230. *
  231. * @since 1.9
  232. * @access private
  233. * @method _settingToggleClicked
  234. */
  235. _settingToggleClicked: function()
  236. {
  237. var toggle = $( this ),
  238. mode = toggle.data( 'mode' );
  239. if ( 'default' == mode ) {
  240. mode = 'medium';
  241. }
  242. else if ( 'medium' == mode ) {
  243. mode = 'responsive';
  244. }
  245. else {
  246. mode = 'default';
  247. }
  248. FLBuilderResponsiveEditing._switchAllSettingsTo( mode );
  249. toggle.siblings( '.fl-field-responsive-setting:visible' ).find( 'input' ).focus();
  250. },
  251. /**
  252. * Switches all responsive settings in a settings form
  253. * to the given mode.
  254. *
  255. * @since 1.9
  256. * @access private
  257. * @method _switchAllSettingsTo
  258. * @param {String} mode
  259. */
  260. _switchAllSettingsTo: function( mode )
  261. {
  262. var className = 'dashicons-desktop dashicons-tablet dashicons-smartphone';
  263. $( '.fl-field-responsive-toggle' ).removeClass( className );
  264. $( '.fl-field-responsive-setting' ).hide();
  265. if ( 'default' == mode ) {
  266. className = 'dashicons-desktop';
  267. }
  268. else if ( 'medium' == mode ) {
  269. className = 'dashicons-tablet';
  270. }
  271. else {
  272. className = 'dashicons-smartphone';
  273. }
  274. $( '.fl-field-responsive-toggle' ).addClass( className ).data( 'mode', mode );
  275. $( '.fl-field-responsive-setting-' + mode ).css( 'display', 'inline-block' );
  276. FLBuilderResponsiveEditing._switchToAndScroll( mode );
  277. },
  278. /**
  279. * Initializes responsive settings in settings forms.
  280. *
  281. * @since 1.9
  282. * @access private
  283. * @method _initSettingsForms
  284. */
  285. _initSettingsForms: function()
  286. {
  287. var self = FLBuilderResponsiveEditing;
  288. if ( Number( FLBuilderConfig.global.responsive_enabled ) ) {
  289. self._initFields( 'dimension', 'input', 'keyup', self._spacingFieldKeyup, [
  290. 'margin',
  291. 'padding'
  292. ] );
  293. self._initFields( 'dimension', 'input', 'keyup', self._textFieldKeyup );
  294. self._initFields( 'unit', 'input', 'keyup', self._textFieldKeyup );
  295. }
  296. self._switchAllSettingsTo( self._mode );
  297. },
  298. /**
  299. * Initializes responsive logic for fields when
  300. * an event occurs.
  301. *
  302. * @since 1.9
  303. * @access private
  304. * @method _initFields
  305. * @param {String} type
  306. * @param {String} selector
  307. * @param {String} event
  308. * @param {Function} callback
  309. * @param {Array} names
  310. */
  311. _initFields: function( type, selector, event, callback, names )
  312. {
  313. var form = $( '.fl-builder-settings' ),
  314. fields = form.find( '.fl-field' ).has( '.fl-field-responsive-setting' ),
  315. field = null,
  316. name = null,
  317. i = 0;
  318. for ( ; i < fields.length; i++ ) {
  319. field = fields.eq( i );
  320. name = field.attr( 'id' ).replace( 'fl-field-', '' );
  321. if ( 'object' == typeof names && $.inArray( name, names ) < 0 ) {
  322. continue;
  323. }
  324. if ( type != field.attr( 'data-type' ) ) {
  325. continue;
  326. }
  327. if ( undefined !== field.attr( 'data-responsive-init' ) ) {
  328. continue;
  329. }
  330. field.find( '.fl-field-responsive-setting-default ' + selector ).on( event, callback );
  331. field.find( '.fl-field-responsive-setting-medium ' + selector ).on( event, callback );
  332. field.find( '.fl-field-responsive-setting-responsive ' + selector ).on( event, callback );
  333. field.find( '.fl-field-responsive-setting-default ' + selector ).trigger( event );
  334. field.attr( 'data-responsive-init', 1 );
  335. }
  336. },
  337. /**
  338. * Returns the fields object for an element.
  339. *
  340. * @since 1.9
  341. * @access private
  342. * @method _getFields
  343. * @param {Object} element
  344. * @param {String} selector
  345. * @return {Object}
  346. */
  347. _getFields: function( element, selector )
  348. {
  349. var field = $( element ).closest( '.fl-field' );
  350. return {
  351. 'default' : field.find( '.fl-field-responsive-setting-default ' + selector ),
  352. 'medium' : field.find( '.fl-field-responsive-setting-medium ' + selector ),
  353. 'responsive' : field.find( '.fl-field-responsive-setting-responsive ' + selector )
  354. };
  355. },
  356. /**
  357. * Callback for the keyup event on a text field.
  358. *
  359. * @since 1.9
  360. * @access private
  361. * @method _textFieldKeyup
  362. */
  363. _textFieldKeyup: function()
  364. {
  365. var fields = FLBuilderResponsiveEditing._getFields( this, 'input' );
  366. fields.default.each( function( i ) {
  367. var defaultPlaceholder = fields.default.eq( i ).attr( 'placeholder' ),
  368. defaultValue = fields.default.eq( i ).val(),
  369. mediumValue = fields.medium.eq( i ).val();
  370. // Medium placeholder
  371. if ( '' == defaultValue ) {
  372. fields.medium.eq( i ).attr( 'placeholder', ( undefined === defaultPlaceholder ? '' : defaultPlaceholder ) );
  373. }
  374. else {
  375. fields.medium.eq( i ).attr( 'placeholder', defaultValue );
  376. }
  377. // Responsive placeholder
  378. if ( '' == mediumValue ) {
  379. fields.responsive.eq( i ).attr( 'placeholder', fields.medium.eq( i ).attr( 'placeholder' ) );
  380. }
  381. else {
  382. fields.responsive.eq( i ).attr( 'placeholder', mediumValue );
  383. }
  384. } );
  385. },
  386. /**
  387. * Callback for the keyup event on a spacing field.
  388. *
  389. * @since 1.9
  390. * @access private
  391. * @method _spacingFieldKeyup
  392. */
  393. _spacingFieldKeyup: function()
  394. {
  395. var form = $( '.fl-builder-settings' ),
  396. type = 'row',
  397. name = $( this ).closest( '.fl-field' ).attr( 'id' ).replace( 'fl-field-', '' ),
  398. fields = FLBuilderResponsiveEditing._getFields( this, 'input' ),
  399. config = FLBuilderConfig.global,
  400. configPrefix = null,
  401. defaultGlobalVal = null,
  402. mediumGlobalVal = null,
  403. responsiveGlobalVal = null;
  404. // Get the node type
  405. if ( form.hasClass( 'fl-builder-row-settings' ) ) {
  406. type = 'row';
  407. }
  408. else if ( form.hasClass( 'fl-builder-col-settings' ) ) {
  409. type = 'col';
  410. }
  411. else if ( form.hasClass( 'fl-builder-module-settings' ) ) {
  412. type = 'module';
  413. }
  414. // Spoof global column defaults since we don't have a setting for those.
  415. $.extend( config, {
  416. col_margins : 0,
  417. col_margins_medium : '',
  418. col_margins_responsive : '',
  419. col_padding : 0,
  420. col_padding_medium : '',
  421. col_padding_responsive : ''
  422. } );
  423. // Set the global values
  424. configPrefix = type + '_' + name + ( 'margin' === name ? 's' : '' );
  425. defaultGlobalVal = config[ configPrefix ];
  426. mediumGlobalVal = config[ configPrefix + '_medium' ];
  427. responsiveGlobalVal = config[ configPrefix + '_responsive' ];
  428. // Loop through the fields.
  429. fields.default.each( function( i ) {
  430. var dimension = fields.default.eq( i ).attr( 'name' ).split( '_' ).pop(),
  431. defaultVal = fields.default.eq( i ).val(),
  432. mediumVal = fields.medium.eq( i ).val(),
  433. responsiveVal = fields.responsive.eq( i ).val(),
  434. moduleGlobalVal = null,
  435. moduleResponsiveVal = null;
  436. // Medium value
  437. if ( '' === mediumGlobalVal ) {
  438. if ( '' !== defaultVal ) {
  439. fields.medium.eq( i ).attr( 'placeholder', defaultVal );
  440. }
  441. else if ( '' !== defaultGlobalVal ) {
  442. fields.medium.eq( i ).attr( 'placeholder', defaultGlobalVal );
  443. }
  444. }
  445. // Responsive value
  446. if ( '' === responsiveGlobalVal ) {
  447. if ( 'module' === type && Number( config.auto_spacing ) ) {
  448. moduleGlobalVal = '' === mediumGlobalVal ? Number( defaultGlobalVal ) : mediumGlobalVal;
  449. moduleResponsiveVal = '' === mediumVal ? Number( defaultVal ) : mediumVal;
  450. if ( '' !== moduleResponsiveVal && ( moduleResponsiveVal > moduleGlobalVal || moduleResponsiveVal < 0 ) ) {
  451. fields.responsive.eq( i ).attr( 'placeholder', moduleGlobalVal );
  452. }
  453. else if ( '' !== moduleResponsiveVal ) {
  454. fields.responsive.eq( i ).attr( 'placeholder', moduleResponsiveVal );
  455. }
  456. else {
  457. fields.responsive.eq( i ).attr( 'placeholder', moduleGlobalVal );
  458. }
  459. }
  460. else if ( ! Number( config.auto_spacing ) || ( 'padding' === name && 'top|bottom'.indexOf( dimension ) > -1 ) ) {
  461. if ( '' !== mediumVal ) {
  462. fields.responsive.eq( i ).attr( 'placeholder', mediumVal );
  463. }
  464. else if ( '' !== mediumGlobalVal ) {
  465. fields.responsive.eq( i ).attr( 'placeholder', mediumGlobalVal );
  466. }
  467. else if ( '' !== defaultVal ) {
  468. fields.responsive.eq( i ).attr( 'placeholder', defaultVal );
  469. }
  470. else if ( '' !== defaultGlobalVal ) {
  471. fields.responsive.eq( i ).attr( 'placeholder', defaultGlobalVal );
  472. }
  473. }
  474. }
  475. } );
  476. },
  477. };
  478. $( function() { FLBuilderResponsiveEditing._init() } );
  479. } )( jQuery );