(function($){ /** * The main builder interface class. * * @since 1.0 * @class FLBuilder */ FLBuilder = { /** * An instance of FLBuilderPreview for working * with the current live preview. * * @since 1.3.3 * @property {FLBuilderPreview} preview */ preview : null, /** * An instance of FLLightbox for displaying a list * of actions a user can take such as publish or cancel. * * @since 1.0 * @access private * @property {FLLightbox} _actionsLightbox */ _actionsLightbox : null, /** * An array of AJAX data that needs to be requested * after the current request has finished. * * @since 2.2 * @property {Array} _ajaxQueue */ _ajaxQueue : [], /** * A reference to the current AJAX request object. * * @since 2.2 * @property {Object} _ajaxRequest */ _ajaxRequest : null, /** * An object that holds data for column resizing. * * @since 1.6.4 * @access private * @property {Object} _colResizeData */ _colResizeData : null, /** * A flag for whether a column is being resized or not. * * @since 1.6.4 * @access private * @property {Boolean} _colResizing */ _colResizing : false, /** * The CSS class of the main content wrapper for the * current layout that is being worked on. * * @since 1.0 * @access private * @property {String} _contentClass */ _contentClass : false, /** * Whether dragging has been enabled or not. * * @since 1.0 * @access private * @property {Boolean} _dragEnabled */ _dragEnabled : false, /** * Whether an element is currently being dragged or not. * * @since 1.0 * @access private * @property {Boolean} _dragging */ _dragging : false, /** * The initial scroll top of the window when a drag starts. * Used to reset the scroll top when a drag is cancelled. * * @since 1.0 * @access private * @property {Boolean} _dragging */ _dragInitialScrollTop : 0, /** * The URL to redirect to when a user leaves the builder. * * @since 1.0 * @access private * @property {String} _exitUrl */ _exitUrl : null, /** * An instance of FLBuilderAJAXLayout for rendering * the layout via AJAX. * * @since 1.7 * @property {FLBuilderAJAXLayout} _layout */ _layout : null, /** * An array of layout data that needs to be rendered * after the current rendered is finished. * * @since 2.2 * @property {Array} _layoutQueue */ _layoutQueue : [], /** * A cached copy of custom layout CSS that is used to * revert changes if the cancel button is clicked. * * @since 1.7 * @property {String} _layoutSettingsCSSCache */ _layoutSettingsCSSCache : null, /** * A timeout for throttling custom layout CSS changes. * * @since 1.7 * @property {Object} _layoutSettingsCSSTimeout */ _layoutSettingsCSSTimeout : null, /** * An instance of FLLightbox for displaying settings. * * @since 1.0 * @access private * @property {FLLightbox} _lightbox */ _lightbox : null, /** * A timeout for refreshing the height of lightbox scrollbars * in case the content changes from dynamic settings. * * @since 1.0 * @access private * @property {Object} _lightboxScrollbarTimeout */ _lightboxScrollbarTimeout : null, /** * An array that's used to cache which module settings * CSS and JS assets have already been loaded so they * are only loaded once. * * @since 1.0 * @access private * @property {Array} _loadedModuleAssets */ _loadedModuleAssets : [], /** * An object used to store module settings helpers. * * @since 1.0 * @access private * @property {Object} _moduleHelpers */ _moduleHelpers : {}, /** * An instance of wp.media used to select multiple photos. * * @since 1.0 * @access private * @property {Object} _multiplePhotoSelector */ _multiplePhotoSelector : null, /** * A jQuery reference to a group that a new column * should be added to once it's finished rendering. * * @since 2.0 * @access private * @property {Object} _newColParent */ _newColParent : null, /** * The position a column should be added to within * a group once it finishes rendering. * * @since 2.0 * @access private * @property {Number} _newColPosition */ _newColPosition : 0, /** * A jQuery reference to a row that a new column group * should be added to once it's finished rendering. * * @since 1.0 * @access private * @property {Object} _newColGroupParent */ _newColGroupParent : null, /** * The position a column group should be added to within * a row once it finishes rendering. * * @since 1.0 * @access private * @property {Number} _newColGroupPosition */ _newColGroupPosition : 0, /** * A jQuery reference to a new module's parent. * * @since 1.7 * @access private * @property {Object} _newModuleParent */ _newModuleParent : null, /** * The position a new module should be added at once * it finishes rendering. * * @since 1.7 * @access private * @property {Number} _newModulePosition */ _newModulePosition : 0, /** * The position a row should be added to within * the layout once it finishes rendering. * * @since 1.0 * @access private * @property {Number} _newRowPosition */ _newRowPosition : 0, /** * The ID of a template that the user has selected. * * @since 1.0 * @access private * @property {Number} _selectedTemplateId */ _selectedTemplateId : null, /** * The type of template that the user has selected. * Possible values are "core" or "user". * * @since 1.0 * @access private * @property {String} _selectedTemplateType */ _selectedTemplateType : null, /** * An instance of wp.media used to select a single photo. * * @since 1.0 * @access private * @property {Object} _singlePhotoSelector */ _singlePhotoSelector : null, /** * An instance of wp.media used to select a single video. * * @since 1.0 * @access private * @property {Object} _singleVideoSelector */ _singleVideoSelector : null, /** * An instance of wp.media used to select a multiple audio. * * @since 1.0 * @access private * @property {Object} _multipleAudiosSelector */ _multipleAudiosSelector : null, /** * @since 2.2.5 */ _codeDisabled: false, /** * Misc data container * @since 2.6 * @access private */ _sandbox: {}, /** * Flag whether to clear preview or not * @access private */ _publishAndRemain: false, _shapesEdited: false, /** * Initializes the builder interface. * * @since 1.0 * @access private * @method _init */ _init: function() { FLBuilder.UIIFrame.init(); FLBuilder._initJQueryReadyFix(); FLBuilder._initGlobalErrorHandling(); FLBuilder._initPostLock(); FLBuilder._initClassNames(); FLBuilder._initMediaUploader(); FLBuilder._initOverflowFix(); FLBuilder._initScrollbars(); FLBuilder._initLightboxes(); FLBuilder._initDropTargets(); FLBuilder._initSortables(); FLBuilder._initStrings(); FLBuilder._initSanityChecks(); FLBuilder._initTipTips(); FLBuilder._initTinyMCE(); FLBuilder._bindEvents(); FLBuilder._bindOverlayEvents(); FLBuilder._setupEmptyLayout(); FLBuilder._highlightEmptyCols(); FLBuilder._checkEnv(); FLBuilder._initColorScheme(); FLBuilder.addHook('didInitUI', FLBuilder._showTourOrTemplates.bind(FLBuilder) ); FLBuilder.addHook('endEditingSession', FLBuilder._doStats.bind(this) ); FLBuilder.triggerHook('init'); }, /** * Prevent errors thrown in jQuery's ready function * from breaking subsequent ready calls. * * @since 1.4.6 * @access private * @method _initJQueryReadyFix */ _initJQueryReadyFix: function() { if ( FLBuilderConfig.debug ) { return; } jQuery.fn.oldReady = jQuery.fn.ready; jQuery.fn.ready = function( fn ) { return jQuery.fn.oldReady( function() { try { if ( 'function' == typeof fn ) { fn( $ ); } } catch ( e ){ FLBuilder.logError( e ); } }); }; }, _initSanityChecks: function() { if ( FLBuilderConfig.uploadPath && typeof FLBuilderLayout === 'undefined' ) { url = 'wp-admin -> Settings -> Media'; FLBuilder.alert( 'Critcal Error
Please go to ' + url + ' and make sure uploads folder settings is blank
'); $('.fl-builder-alert-close', window.parent.document).hide() } }, /** * Try to prevent errors from third party plugins * from breaking the builder. * * @since 1.4.6 * @access private * @method _initGlobalErrorHandling */ _initGlobalErrorHandling: function() { if ( FLBuilderConfig.debug ) { return; } window.onerror = function( message, file, line, col, error ) { FLBuilder.logGlobalError( message, file, line, col, error ); return true; }; }, /** * Send a wp.heartbeat request to lock editing of this * post so it can only be edited by the current user. * * @since 1.0.6 * @access private * @method _initPostLock */ _initPostLock: function() { if(typeof wp.heartbeat != 'undefined') { wp.heartbeat.interval(120); wp.heartbeat.enqueue('fl_builder_post_lock', { post_id: FLBuilderConfig.postId }); } }, /** * Initializes html and body classes as well as the * builder content class for this post. * * @since 1.0 * @access private * @method _initClassNames */ _initClassNames: function() { var html = $( 'html' ).add( 'html', window.parent.document ), body = $( 'body' ).add( 'body', window.parent.document ); html.addClass( 'fl-builder-edit' ); body.addClass( 'fl-builder' ); if ( FLBuilderConfig.simpleUi ) { body.addClass( 'fl-builder-simple' ); } FLBuilder._contentClass = '.fl-builder-content-' + FLBuilderConfig.postId; $( FLBuilder._contentClass ).addClass( 'fl-builder-content-editing' ); }, /** * Initializes the WordPress media uploader so any files * uploaded will be attached to the current post. * * @since 1.2.2 * @access private * @method _initMediaUploader */ _initMediaUploader: function() { wp.media.model.settings.post.id = FLBuilderConfig.postId; }, /** * Third party themes that set their content wrappers to * overflow:hidden break builder overlays. We set them * to overflow:visible while editing. * * @since 1.0 * @access private * @method _initOverflowFix */ _initOverflowFix: function() { $(FLBuilder._contentClass).parents().css('overflow', 'visible'); }, /** * Initializes Nano Scroller scrollbars for the * builder interface. * * @since 1.0 * @access private * @method _initScrollbars */ _initScrollbars: function() { var scrollers = $('.fl-nanoscroller', window.parent.document).nanoScroller({ documentContext: window.parent.document, alwaysVisible: true, preventPageScrolling: true, paneClass: 'fl-nanoscroller-pane', sliderClass: 'fl-nanoscroller-slider', contentClass: 'fl-nanoscroller-content' }), settingsScroller = scrollers.filter('.fl-builder-settings-fields'), pane = settingsScroller.find('.fl-nanoscroller-pane'); if ( pane.length ) { var display = pane.get(0).style.display; var content = settingsScroller.find('.fl-nanoscroller-content'); if ( display === "none" ) { content.removeClass('has-scrollbar'); } else { content.addClass('has-scrollbar'); } } }, /** * Initializes jQuery sortables for drag and drop. * * @since 1.0 * @access private * @method _initSortables */ _initSortables: function() { var defaults = { frame: null, appendTo: FLBuilder._contentClass, scroll: true, cursor: 'move', cursorAt: { left: 85, top: 20 }, distance: 1, helper: FLBuilder._blockDragHelper, start : FLBuilder._blockDragStart, sort: FLBuilder._blockDragSort, change: FLBuilder._blockDragChange, stop: FLBuilder._blockDragStop, placeholder: 'fl-builder-drop-zone', tolerance: 'intersect' }, rowConnections = '', columnConnections = '', moduleConnections = ''; // Adjust defaults for the iFrame UI. if ( FLBuilder.UIIFrame.isEnabled() ) { defaults.frame = $( '#fl-builder-ui-iframe', window.parent.document ); defaults.appendTo = $( 'body', window.parent.document ); defaults.scroll = false; } // Module Connections. if ( 'row' == FLBuilderConfig.userTemplateType ) { moduleConnections = FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-content'; } else if ( 'column' == FLBuilderConfig.userTemplateType ) { moduleConnections = FLBuilder._contentClass + ' .fl-col-group-drop-target, ' + FLBuilder._contentClass + ' .fl-col-drop-target, ' + FLBuilder._contentClass + ' .fl-col-content'; } else { moduleConnections = FLBuilder._contentClass + ' .fl-row-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col:not(.fl-builder-node-loading):not(.fl-node-global) .fl-col-content'; } // Column Connections. if ( 'row' == FLBuilderConfig.userTemplateType ) { columnConnections = FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target'; } else { columnConnections = FLBuilder._contentClass + ' .fl-row-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target'; } // Row Connections. if ( FLBuilderConfig.nestedColumns ) { rowConnections = moduleConnections; } else if ( 'row' == FLBuilderConfig.userTemplateType ) { rowConnections = FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target'; } else { rowConnections = FLBuilder._contentClass + ' .fl-row-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-group-drop-target, ' + FLBuilder._contentClass + ' .fl-row:not(.fl-builder-node-loading) .fl-col-drop-target'; } // Row layouts from the builder panel. $('.fl-builder-rows', window.parent.document).sortable($.extend({}, defaults, { connectWith: rowConnections, items: '.fl-builder-block-row', stop: FLBuilder._rowDragStop })); // Row templates from the builder panel. $('.fl-builder-row-templates', window.parent.document).sortable($.extend({}, defaults, { connectWith: FLBuilder._contentClass + ' .fl-row-drop-target', items: '.fl-builder-block-row-template:not(.fl-builder-block-disabled)', stop: FLBuilder._nodeTemplateDragStop })); // Saved rows from the builder panel. $('.fl-builder-saved-rows', window.parent.document).sortable($.extend({}, defaults, { cancel: '.fl-builder-node-template-actions, .fl-builder-node-template-edit, .fl-builder-node-template-delete', connectWith: FLBuilder._contentClass + ' .fl-row-drop-target', items: '.fl-builder-block-saved-row', stop: FLBuilder._nodeTemplateDragStop })); // Saved columns from the builder panel. $('.fl-builder-saved-columns', window.parent.document).sortable($.extend({}, defaults, { cancel: '.fl-builder-node-template-actions, .fl-builder-node-template-edit, .fl-builder-node-template-delete', connectWith: columnConnections, items: '.fl-builder-block-saved-column', stop: FLBuilder._nodeTemplateDragStop })); // Modules from the builder panel. $('.fl-builder-modules, .fl-builder-widgets', window.parent.document).sortable($.extend({}, defaults, { connectWith: moduleConnections, items: '.fl-builder-block-module:not(.fl-builder-block-disabled)', stop: FLBuilder._moduleDragStop })); // Module templates from the builder panel. $('.fl-builder-module-templates', window.parent.document).sortable($.extend({}, defaults, { connectWith: moduleConnections, items: '.fl-builder-block-module-template', stop: FLBuilder._nodeTemplateDragStop })); // Saved modules from the builder panel. $('.fl-builder-saved-modules', window.parent.document).sortable($.extend({}, defaults, { cancel: '.fl-builder-node-template-actions, .fl-builder-node-template-edit, .fl-builder-node-template-delete', connectWith: moduleConnections, items: '.fl-builder-block-saved-module', stop: FLBuilder._nodeTemplateDragStop })); // Rows $('.fl-row-sortable-proxy', window.parent.document).sortable($.extend({}, defaults, { connectWith: FLBuilder._contentClass + ' .fl-row-drop-target', helper: FLBuilder._rowDragHelper, start: FLBuilder._rowDragStart, stop: FLBuilder._rowDragStop })); // Columns $('.fl-col-sortable-proxy', window.parent.document).sortable($.extend({}, defaults, { connectWith: moduleConnections, helper: FLBuilder._colDragHelper, start: FLBuilder._colDragStart, stop: FLBuilder._colDragStop })); // Modules $('.fl-module-sortable-proxy', window.parent.document).sortable($.extend({}, defaults, { connectWith: moduleConnections, helper: FLBuilder._moduleDragHelper, start: FLBuilder._moduleDragStart, stop: FLBuilder._moduleDragStop })); $(FLBuilder._contentClass + ' .fl-col-content').sortable($.extend({}, defaults, { cancel: '.fl-module, .fl-col-group', handle: '.fl-module-sortable-proxy', })); // Drop targets $(FLBuilder._contentClass + ' .fl-row-drop-target').sortable( defaults ); $(FLBuilder._contentClass + ' .fl-col-group-drop-target').sortable( defaults ); $(FLBuilder._contentClass + ' .fl-col-drop-target').sortable( defaults ); }, /** * Refreshes the items for all jQuery sortables so any * new items will be recognized. * * @since 2.2 * @access private * @method _refreshSortables */ _refreshSortables: function() { var sortables = $( '.ui-sortable' ).add( '.ui-sortable', window.parent.document ); sortables.sortable( 'refresh' ); sortables.sortable( 'refreshPositions' ); }, /** * Initializes text translation * * @since 1.0 * @access private * @method _initStrings */ _initStrings: function() { $.validator.messages.required = FLBuilderStrings.validateRequiredMessage; }, /** * Binds most of the events for the builder interface. * * @since 1.0 * @access private * @method _bindEvents */ _bindEvents: function() { var isTouch = FLBuilderLayout._isTouch(); /* Links */ $excludedLinks = $('.fl-builder-bar a, .fl-builder--content-library-panel a, .fl-page-nav .nav a'); // links in ui shouldn't be disabled. $('a').not($excludedLinks).on('click', FLBuilder._preventDefault); $('.fl-page-nav .nav a').on('click', FLBuilder._headerLinkClicked); $('body').on( 'click', '.fl-builder-content a', FLBuilder._preventDefault); $('body').on( 'mouseup', 'button.fl-builder-button', this._buttonMouseUp.bind(this) ); /* Heartbeat */ $(document).on('heartbeat-tick', FLBuilder._initPostLock); /* Unload Warning */ $(window.parent).on('beforeunload', FLBuilder._warnBeforeUnload); /* Lite Version */ $('body', window.parent.document).on( 'click', '.fl-builder-blocks-pro-expand', FLBuilder._toggleProModules); $('body', window.parent.document).on( 'click', '.fl-builder-upgrade-button', FLBuilder._upgradeClicked); /* Panel */ $('.fl-builder-panel-actions .fl-builder-panel-close', window.parent.document).on('click', FLBuilder._closePanel); $('body', window.parent.document).on( 'mousedown', '.fl-builder-node-template-actions', FLBuilder._stopPropagation); $('body', window.parent.document).on( 'mousedown', '.fl-builder-node-template-edit', FLBuilder._stopPropagation); $('body', window.parent.document).on( 'mousedown', '.fl-builder-node-template-delete', FLBuilder._stopPropagation); $('body', window.parent.document).on( 'click', '.fl-builder-node-template-edit', FLBuilder._editNodeTemplateClicked); $('body', window.parent.document).on( 'click', '.fl-builder-node-template-delete', FLBuilder._deleteNodeTemplateClicked); $('body', window.parent.document).on( 'mousedown', '.fl-builder-block:not(.fl-builder-block-disabled)', FLBuilder._blockDragInit ); $('body', window.parent.document).on('mouseup', FLBuilder._blockDragCancel); /* Actions Lightbox */ $('body', window.parent.document).on( 'click', '.fl-builder-actions .fl-builder-cancel-button', FLBuilder._cancelButtonClicked); /* Tools Actions */ $('body', window.parent.document).on( 'click', '.fl-builder-layout-settings .fl-builder-settings-save', FLBuilder._saveLayoutSettingsClicked); $('body', window.parent.document).on( 'click', '.fl-builder-layout-settings .fl-builder-settings-cancel', FLBuilder._cancelLayoutSettingsClicked); $('body', window.parent.document).on( 'click', '.fl-builder-global-settings .fl-builder-settings-save', FLBuilder._saveGlobalSettingsClicked); $('body', window.parent.document).on( 'click', '.fl-builder-global-settings .fl-builder-settings-cancel', FLBuilder._cancelLayoutSettingsClicked); /* Template Panel Tab */ $('body', window.parent.document).on( 'click', '.fl-user-template', FLBuilder._userTemplateClicked); $('body', window.parent.document).on( 'click', '.fl-user-template-edit', FLBuilder._editUserTemplateClicked); $('body', window.parent.document).on( 'click', '.fl-user-template-delete', FLBuilder._deleteUserTemplateClicked); $('body', window.parent.document).on( 'click', '.fl-builder-template-replace-button', FLBuilder._templateReplaceClicked); $('body', window.parent.document).on( 'click', '.fl-builder-template-append-button', FLBuilder._templateAppendClicked); $('body', window.parent.document).on( 'click', '.fl-builder-template-actions .fl-builder-cancel-button', FLBuilder._templateCancelClicked); /* User Template Settings */ $('body', window.parent.document).on( 'click', '.fl-builder-user-template-settings .fl-builder-settings-save', FLBuilder._saveUserTemplateSettings); /* Welcome Actions */ $('body', window.parent.document).on( 'click', '.fl-builder-no-tour-button', FLBuilder._noTourButtonClicked); $('body', window.parent.document).on( 'click', '.fl-builder-yes-tour-button', FLBuilder._yesTourButtonClicked); /* Alert Lightbox */ $('body', window.parent.document).on( 'click', '.fl-builder-alert-close', FLBuilder._alertClose); /* General Overlays */ $('body').on( 'contextmenu', '.fl-block-overlay', FLBuilder._onContextmenu); /* Overlay Submenus */ $('body').on( 'click touchend', '.fl-builder-has-submenu', FLBuilder._submenuParentClicked); $('body').on( 'mouseenter', '.fl-builder-submenu-hover', FLBuilder._hoverMenuParentMouseEnter); $('body').on( 'mouseleave', '.fl-builder-submenu-hover', FLBuilder._hoverMenuParentMouseLeave); $('body').on( 'click touchend', '.fl-builder-has-submenu a', FLBuilder._submenuChildClicked); $('body').on( 'mouseenter', '.fl-builder-submenu', FLBuilder._submenuMouseenter); $('body').on( 'mouseleave', '.fl-builder-submenu', FLBuilder._submenuMouseleave); $('body').on( 'mouseenter', '.fl-builder-submenu .fl-builder-has-submenu', FLBuilder._submenuNestedParentMouseenter); /* Rows */ $('body').on( 'click touchend', '.fl-row-overlay .fl-block-remove', FLBuilder._deleteRowClicked); $('body').on( 'click touchend', '.fl-row-overlay .fl-block-copy', FLBuilder._rowCopyClicked); $('body').on( 'mousedown', '.fl-row-overlay .fl-block-move', FLBuilder._rowDragInit); $('body').on( 'touchstart', '.fl-row-overlay .fl-block-move', FLBuilder._rowDragInitTouch); $('body').on( 'click touchend', '.fl-row-overlay .fl-block-settings', FLBuilder._rowSettingsClicked); $('body').on( 'click touchend', '.fl-row-quick-copy', FLBuilder._rowCopySettingsClicked); $('body').on( 'click touchend', '.fl-row-quick-paste', FLBuilder._rowPasteSettingsClicked); $('body', window.parent.document).on( 'click', '.fl-builder-row-settings .fl-builder-settings-save', FLBuilder._saveSettings); // Row touch or mouse specific events. if ( isTouch ) { $('body').on( 'touchend', '.fl-row-overlay', FLBuilder._rowSettingsClicked); } else { $('body').on( 'click', '.fl-row-overlay', FLBuilder._rowSettingsClicked); } /* Rows Submenu */ $('body').on( 'click touchend', '.fl-block-col-submenu .fl-block-row-reset', FLBuilder._resetRowWidthClicked); /* Columns */ $('body').on( 'mousedown', '.fl-col-overlay .fl-block-move', FLBuilder._colDragInit); $('body').on( 'touchstart', '.fl-col-overlay .fl-block-move', FLBuilder._colDragInitTouch); $('body').on( 'click touchend', '.fl-block-col-copy', FLBuilder._copyColClicked); $('body').on( 'click touchend', '.fl-col-overlay .fl-block-remove', FLBuilder._deleteColClicked); $('body').on( 'click touchend', '.fl-col-overlay .fl-block-settings', FLBuilder._colSettingsClicked); $('body').on( 'click touchend', '.fl-col-quick-copy', FLBuilder._colCopySettingsClicked); $('body').on( 'click touchend', '.fl-col-quick-paste', FLBuilder._colPasteSettingsClicked); $('body', window.parent.document).on( 'click', '.fl-builder-col-settings .fl-builder-settings-save', FLBuilder._saveSettings); // Column touch or mouse specific events. if ( isTouch ) { $('body').on( 'touchend', '.fl-col-overlay', FLBuilder._colSettingsClicked); } else { $('body').on( 'click', '.fl-col-overlay', FLBuilder._colSettingsClicked); } /* Columns Submenu */ $('body').on( 'mousedown', '.fl-block-col-submenu .fl-block-col-move', FLBuilder._colDragInit); $('body').on( 'touchstart', '.fl-block-col-submenu .fl-block-col-move', FLBuilder._colDragInitTouch); $('body').on( 'click touchend', '.fl-block-col-submenu .fl-block-col-edit', FLBuilder._colSettingsClicked); $('body').on( 'click touchend', '.fl-block-col-submenu .fl-block-col-delete', FLBuilder._deleteColClicked); $('body').on( 'click touchend', '.fl-block-col-submenu .fl-block-col-reset', FLBuilder._resetColumnWidthsClicked); $('body').on( 'mouseenter', '.fl-block-col-submenu li', FLBuilder._showColHighlightGuide); $('body').on( 'mouseleave', '.fl-block-col-submenu li', FLBuilder._removeColHighlightGuides); /* Columns Submenu (Parent Column) */ $('body').on( 'mousedown', '.fl-block-col-submenu .fl-block-col-move-parent', FLBuilder._colDragInit); $('body').on( 'touchstart', '.fl-block-col-submenu .fl-block-col-move-parent', FLBuilder._colDragInitTouch); $('body').on( 'click touchend', '.fl-block-col-submenu .fl-block-col-edit-parent', FLBuilder._colSettingsClicked); /* Modules */ $('body').on( 'click touchend', '.fl-module-overlay .fl-block-remove', FLBuilder._deleteModuleClicked); $('body').on( 'click touchend', '.fl-module-overlay .fl-block-copy', FLBuilder._moduleCopyClicked); $('body').on( 'mousedown', '.fl-module-overlay .fl-block-move', FLBuilder._moduleDragInit); $('body').on( 'touchstart', '.fl-module-overlay .fl-block-move', FLBuilder._moduleDragInitTouch); $('body').on( 'click touchend', '.fl-module-overlay .fl-block-settings', FLBuilder._moduleSettingsClicked); $('body').on( 'click touchend', '.fl-module-quick-copy', FLBuilder._moduleCopySettingsClicked); $('body').on( 'click touchend', '.fl-module-quick-paste', FLBuilder._modulePasteSettingsClicked); $('body').on( 'click touchend', '.fl-module-overlay .fl-block-col-settings', FLBuilder._colSettingsClicked); $('body', window.parent.document).on( 'click', '.fl-builder-module-settings .fl-builder-settings-save', FLBuilder._saveModuleClicked); // Module touch or mouse specific events. if ( isTouch ) { $('body').on( 'touchend', '.fl-module-overlay', FLBuilder._moduleSettingsClicked); } else { $('body').on( 'click', '.fl-module-overlay', FLBuilder._moduleSettingsClicked); } /* Node Templates */ $('body', window.parent.document).on( 'click', '.fl-builder-settings-save-as', FLBuilder._showNodeTemplateSettings); $('body', window.parent.document).on( 'click', '.fl-builder-node-template-settings .fl-builder-settings-save', FLBuilder._saveNodeTemplate); /* Settings */ $('body', window.parent.document).on( 'click', '.fl-builder-settings-tabs a', FLBuilder._settingsTabClicked); $('body', window.parent.document).on( 'show', '.fl-builder-settings-tabs a', FLBuilder._calculateSettingsTabsOverflow); $('body', window.parent.document).on( 'hide', '.fl-builder-settings-tabs a', FLBuilder._calculateSettingsTabsOverflow); $('body', window.parent.document).on( 'click', '.fl-builder-settings-cancel', FLBuilder._settingsCancelClicked); /* Settings Tabs Overflow menu */ $('body', window.parent.document).on( 'click', '.fl-builder-settings-tabs-overflow-menu > a', FLBuilder._settingsTabsToOverflowMenuItemClicked.bind(this)); $('body', window.parent.document).on( 'click', '.fl-builder-settings-tabs-more', FLBuilder._toggleTabsOverflowMenu.bind(this) ); $('body', window.parent.document).on( 'click', '.fl-builder-settings-tabs-overflow-click-mask', FLBuilder._hideTabsOverflowMenu.bind(this)); /* Tooltips */ $('body', window.parent.document).on( 'mouseover', '.fl-help-tooltip-icon', FLBuilder._showHelpTooltip); $('body', window.parent.document).on( 'mouseout', '.fl-help-tooltip-icon', FLBuilder._hideHelpTooltip); /* Multiple Fields */ $('body', window.parent.document).on( 'click', '.fl-builder-field-add', FLBuilder._addFieldClicked); $('body', window.parent.document).on( 'click', '.fl-builder-field-copy', FLBuilder._copyFieldClicked); $('body', window.parent.document).on( 'click', '.fl-builder-field-delete', FLBuilder._deleteFieldClicked); /* Photo Fields */ $('body', window.parent.document).on( 'click', '.fl-photo-field .fl-photo-select', FLBuilder._selectSinglePhoto); $('body', window.parent.document).on( 'click', '.fl-photo-field .fl-photo-edit', FLBuilder._selectSinglePhoto); $('body', window.parent.document).on( 'click', '.fl-photo-field .fl-photo-replace', FLBuilder._selectSinglePhoto); $('body', window.parent.document).on( 'click', '.fl-photo-field .fl-photo-remove', FLBuilder._singlePhotoRemoved); /* Multiple Photo Fields */ $('body', window.parent.document).on( 'click', '.fl-multiple-photos-field .fl-multiple-photos-select', FLBuilder._selectMultiplePhotos); $('body', window.parent.document).on( 'click', '.fl-multiple-photos-field .fl-multiple-photos-edit', FLBuilder._selectMultiplePhotos); $('body', window.parent.document).on( 'click', '.fl-multiple-photos-field .fl-multiple-photos-add', FLBuilder._selectMultiplePhotos); /* Video Fields */ $('body', window.parent.document).on( 'click', '.fl-video-field .fl-video-select', FLBuilder._selectSingleVideo); $('body', window.parent.document).on( 'click', '.fl-video-field .fl-video-replace', FLBuilder._selectSingleVideo); $('body', window.parent.document).on( 'click', '.fl-video-field .fl-video-remove', FLBuilder._singleVideoRemoved); /* Multiple Audio Fields */ $('body', window.parent.document).on( 'click', '.fl-multiple-audios-field .fl-multiple-audios-select', FLBuilder._selectMultipleAudios); $('body', window.parent.document).on( 'click', '.fl-multiple-audios-field .fl-multiple-audios-edit', FLBuilder._selectMultipleAudios); $('body', window.parent.document).on( 'click', '.fl-multiple-audios-field .fl-multiple-audios-add', FLBuilder._selectMultipleAudios); /* Icon Fields */ $('body', window.parent.document).on( 'click', '.fl-icon-field .fl-icon-select', FLBuilder._selectIcon); $('body', window.parent.document).on( 'click', '.fl-icon-field .fl-icon-replace', FLBuilder._selectIcon); $('body', window.parent.document).on( 'click', '.fl-icon-field .fl-icon-remove', FLBuilder._removeIcon); /* Settings Form Fields */ $('body', window.parent.document).on( 'click', '.fl-form-field .fl-form-field-edit', FLBuilder._formFieldClicked); $('body', window.parent.document).on( 'click', '.fl-form-field-settings .fl-builder-settings-save', FLBuilder._saveFormFieldClicked); /* Layout Fields */ $('body', window.parent.document).on( 'click', '.fl-layout-field-option', FLBuilder._layoutFieldClicked); /* Links Fields */ $('body', window.parent.document).on( 'click', '.fl-link-field-select', FLBuilder._linkFieldSelectClicked); $('body', window.parent.document).on( 'click', '.fl-link-field-search-cancel', FLBuilder._linkFieldSelectCancelClicked); /* Loop Settings Fields */ $('body', window.parent.document).on( 'change', '.fl-loop-data-source-select select[name=data_source]', FLBuilder._loopDataSourceChange); $('body', window.parent.document).on( 'change', '.fl-custom-query select[name=post_type]', FLBuilder._customQueryPostTypeChange); $('body', window.parent.document).on( 'change', '.fl-custom-query select[name="post_type[]"]', FLBuilder._customQueryPostTypesChange); /* Text Fields - Add Predefined Value Selector */ $('body', window.parent.document).on( 'change', '.fl-text-field-add-value', FLBuilder._textFieldAddValueSelectChange); /* Number Fields */ $('body', window.parent.document).on( 'focus', '.fl-field input[type=number]', FLBuilder._onNumberFieldFocus ); $('body', window.parent.document).on( 'blur', '.fl-field input[type=number]', FLBuilder._onNumberFieldBlur ); // Live Preview FLBuilder.addHook( 'didCompleteAJAX', FLBuilder._refreshSettingsPreviewReference ); FLBuilder.addHook( 'didRenderLayoutComplete', FLBuilder._refreshSettingsPreviewReference ); }, /** * Remove events when ending the edit session * @since 2.0 * @access private */ _unbindEvents: function() { $('a').off('click', FLBuilder._preventDefault); $('.fl-page-nav .nav a').off('click', FLBuilder._headerLinkClicked); $('body').undelegate('.fl-builder-content a', 'click', FLBuilder._preventDefault); }, /** * Rebind events when restarting the edit session * @since 2.1.2.3 * @access private */ _rebindEvents: function() { $('a').on('click', FLBuilder._preventDefault); $('.fl-page-nav .nav a').on('click', FLBuilder._headerLinkClicked); $('body').on( 'click', '.fl-builder-content a', FLBuilder._preventDefault); }, /** * Binds the events for overlays that appear when * mousing over a row, column or module. * * @since 1.0 * @access private * @method _bindOverlayEvents */ _bindOverlayEvents: function() { var content = $(FLBuilder._contentClass); content.on( 'mouseenter touchstart', '.fl-row', FLBuilder._rowMouseenter); content.on( 'mouseleave', '.fl-row', FLBuilder._rowMouseleave); content.on( 'mouseleave', '.fl-row-overlay', FLBuilder._rowMouseleave); content.on( 'mouseenter touchstart', '.fl-col', FLBuilder._colMouseenter); content.on( 'mouseleave', '.fl-col', FLBuilder._colMouseleave); content.on( 'mouseenter touchstart', '.fl-module', FLBuilder._moduleMouseenter); content.on( 'mouseleave', '.fl-module', FLBuilder._moduleMouseleave); }, /** * Unbinds the events for overlays that appear when * mousing over a row, column or module. * * @since 1.0 * @access private * @method _destroyOverlayEvents */ _destroyOverlayEvents: function() { var content = $(FLBuilder._contentClass); content.undelegate('.fl-row', 'mouseenter touchstart', FLBuilder._rowMouseenter); content.undelegate('.fl-row', 'mouseleave', FLBuilder._rowMouseleave); content.undelegate('.fl-row-overlay', 'mouseleave', FLBuilder._rowMouseleave); content.undelegate('.fl-col', 'mouseenter touchstart', FLBuilder._colMouseenter); content.undelegate('.fl-col', 'mouseleave', FLBuilder._colMouseleave); content.undelegate('.fl-module', 'mouseenter touchstart', FLBuilder._moduleMouseenter); content.undelegate('.fl-module', 'mouseleave', FLBuilder._moduleMouseleave); }, /** * Hides overlays when the contextmenu event is fired on them. * This allows us to inspect the actual node in the console * instead of getting the overlay. * * @since 2.2 * @access private * @method _onContextmenu * @param {Object} e The event object. */ _onContextmenu: function( e ) { $( this ).hide(); }, /** * Prevents the default action for an event. * * @since 1.6.3 * @access private * @method _preventDefault * @param {Object} e The event object. */ _preventDefault: function( e ) { e.preventDefault(); }, /** * Prevents propagation of an event. * * @since 1.6.3 * @access private * @method _stopPropagation * @param {Object} e The event object. */ _stopPropagation: function( e ) { e.stopPropagation(); }, /** * Launches the builder for another page if a link in the * builder theme header is clicked. * * @since 1.3.9 * @access private * @method _headerLinkClicked * @param {Object} e The event object. */ _headerLinkClicked: function(e) { var link = $(this), href = link.attr('href'); // ignore links with a #hash if( this.hash ) { return; } e.preventDefault(); if ( FLBuilderConfig.isUserTemplate ) { return; } FLBuilder._exitUrl = href.indexOf('?') > -1 ? href : href + '?fl_builder'; FLBuilder.triggerHook('triggerDone'); }, /** * Warns the user that their changes won't be saved if * they leave the page while editing settings. * * @since 1.0.6 * @access private * @method _warnBeforeUnload * @return {String} The warning message. */ _warnBeforeUnload: function() { var rowSettings = $('.fl-builder-row-settings', window.parent.document).length > 0, colSettings = $('.fl-builder-col-settings', window.parent.document).length > 0, moduleSettings = $('.fl-builder-module-settings', window.parent.document).length > 0; if(rowSettings || colSettings || moduleSettings) { return FLBuilderStrings.unloadWarning; } }, /* Lite Version ----------------------------------------------------------*/ /** * Opens a new window with the upgrade URL when the * upgrade button is clicked. * * @since 1.0 * @access private * @method _upgradeClicked */ _upgradeClicked: function() { window.parent.open(FLBuilderConfig.upgradeUrl); }, /** * Toggles the pro module section in lite. * * @since 2.4 */ _toggleProModules: function() { var button = $( '.fl-builder-blocks-pro-expand', window.parent.document ), closed = $( '.fl-builder-blocks-pro-closed', window.parent.document ), open = $( '.fl-builder-blocks-pro-open', window.parent.document ); button.toggleClass( 'fl-builder-blocks-pro-expand-rotate' ); if ( closed.length ) { closed.removeClass( 'fl-builder-blocks-pro-closed' ); closed.addClass( 'fl-builder-blocks-pro-open' ); } else { open.removeClass( 'fl-builder-blocks-pro-open' ); open.addClass( 'fl-builder-blocks-pro-closed' ); } }, /** * Shows the the pro message lightbox. * * @since 2.4 */ _showProMessage: function( feature ) { if ( ! FLBuilderConfig.lite ) { return } var alert = new FLLightbox({ className: 'fl-builder-pro-lightbox', destroyOnClose: true }), template = wp.template( 'fl-pro-lightbox' ); alert.open( template( { feature : feature } ) ); }, /* TipTips ----------------------------------------------------------*/ /** * Initializes tooltip help messages. * * @since 1.1.9 * @access private * @method _initTipTips */ _initTipTips: function() { var classname = '.fl-tip:not(.fl-has-tip)', tips = $( classname ).add( classname, window.parent.document ); tips.each( function(){ var ele = $( this ); ele.addClass( 'fl-has-tip' ); if ( undefined == ele.attr( 'data-title' ) ) { ele.attr( 'data-title', ele.attr( 'title' ) ); } } ) if ( ! FLBuilderLayout._isTouch() ) { tips.tipTip( { defaultPosition : 'top', delay : 300, maxWidth : 'auto' } ); } }, /** * Removes all tooltip help messages from the screen. * * @since 1.1.9 * @access private * @method _hideTipTips */ _hideTipTips: function() { $('#tiptip_holder').stop().hide(); $('#tiptip_holder', window.parent.document).stop().hide(); }, /* Submenus ----------------------------------------------------------*/ /** * Callback for when the parent of a submenu is clicked. * * @since 1.6.4 * @access private * @method _submenuParentClicked * @param {Object} e The event object. */ _submenuParentClicked: function( e ) { var body = $( 'body' ), parent = $( this ), submenu = parent.find( '.fl-builder-submenu' ); if ( parent.hasClass( 'fl-builder-submenu-open' ) ) { body.removeClass( 'fl-builder-submenu-open' ); parent.removeClass( 'fl-builder-submenu-open' ); parent.removeClass( 'fl-builder-submenu-right' ); } else { if( parent.offset().left + submenu.width() > $( window ).width() ) { parent.addClass( 'fl-builder-submenu-right' ); } body.addClass( 'fl-builder-submenu-open' ); parent.addClass( 'fl-builder-submenu-open' ); } submenu.closest('.fl-row-overlay').addClass('fl-row-menu-active'); FLBuilder._hideTipTips(); e.preventDefault(); e.stopPropagation(); }, /** * Callback for when the parent of a submenu is hovered. * * @since 2.6 * @access private * @method _hoverMenuParentMouseEnter * @param {Object} e The event object. */ _hoverMenuParentMouseEnter: function (e) { e.stopPropagation(); var body = $('body'), parent = $(this), submenu = parent.find('.fl-builder-submenu'); // remove classes first $('.fl-builder-submenu-right').removeClass('fl-builder-submenu-right'); $('.fl-builder-submenu-open').removeClass('fl-builder-submenu-open'); $('.fl-row-menu-active').removeClass('fl-row-menu-active'); // determine align if (parent.offset().left + submenu.width() > $(window).width()) { parent.addClass('fl-builder-submenu-right'); } // add classes parent.closest('.fl-row-overlay').addClass('fl-row-menu-active'); body.addClass('fl-builder-submenu-open'); parent.addClass('fl-builder-submenu-open'); }, /** * Callback for when the parent of a submenu is unhovered. * * @since 2.6 * @access private * @method _hoverMenuParentMouseLeave * @param {Object} e The event object. */ _hoverMenuParentMouseLeave: function (e) { // remove classes $('.fl-builder-submenu-right').removeClass('fl-builder-submenu-right'); $('.fl-builder-submenu-open').removeClass('fl-builder-submenu-open'); $('.fl-row-menu-active').removeClass('fl-row-menu-active'); }, /** * Callback for when the child of a submenu is clicked. * * @since 1.6.4 * @access private * @method _submenuChildClicked * @param {Object} e The event object. */ _submenuChildClicked: function( e ) { var body = $( 'body' ), parent = $( this ).parents( '.fl-builder-has-submenu' ); if ( ! parent.parents( '.fl-builder-has-submenu' ).length ) { body.removeClass( 'fl-builder-submenu-open' ); parent.removeClass( 'fl-builder-submenu-open' ); } }, /** * Callback for when the mouse enters a submenu. * * @since 1.6.4 * @access private * @method _submenuMouseenter * @param {Object} e The event object. */ _submenuMouseenter: function( e ) { if ($(this).parent().hasClass('fl-builder-submenu-hover')) { return; } var menu = $( this ), timeout = menu.data( 'timeout' ); if ( 'undefined' != typeof timeout ) { clearTimeout( timeout ); } }, /** * Callback for when the mouse leaves a submenu. * * @since 1.6.4 * @access private * @method _submenuMouseleave * @param {Object} e The event object. */ _submenuMouseleave: function( e ) { if ($(this).parent().hasClass('fl-builder-submenu-hover')) { return; } var body = $( 'body' ), menu = $( this ), timeout = setTimeout( function() { body.removeClass( 'fl-builder-submenu-open' ); menu.closest( '.fl-builder-has-submenu' ).removeClass( 'fl-builder-submenu-open' ); }, 500 ); menu.closest('.fl-row-overlay').removeClass('fl-row-menu-active'); menu.data( 'timeout', timeout ); }, /** * Callback for when the mouse enters the parent * of a nested submenu. * * @since 1.9 * @access private * @method _submenuNestedParentMouseenter * @param {Object} e The event object. */ _submenuNestedParentMouseenter: function( e ) { var parent = $( this ); var submenu = parent.find( '.fl-builder-submenu' ); var winWidth = $( window ).width(); var leftSpace = parent.offset().left; var rightSpace = winWidth - ( parent.width() + leftSpace ); var menusWidth = parent.width() + leftSpace + submenu.width(); if ( menusWidth > winWidth && submenu.width() < leftSpace ) { parent.addClass( 'fl-builder-submenu-right' ); } else if ( submenu.width() > rightSpace ) { var left = parent.width() - ( submenu.width() - rightSpace ); submenu.css( 'left', left ); } }, /** * Closes all open submenus. * * @since 1.9 * @access private * @method _closeAllSubmenus */ _closeAllSubmenus: function() { $( '.fl-builder-submenu-open' ).removeClass( 'fl-builder-submenu-open' ); }, /* Bar ----------------------------------------------------------*/ /** * Fires blur on mouse up to avoid focus ring when clicked with mouse. * * @since 2.0 * @access private * @method _buttonMouseUp * @param {Event} e * @return void */ _buttonMouseUp: function(e) { $(e.currentTarget).blur(); }, /* Panel ----------------------------------------------------------*/ /** * Closes the builder's content panel. * * @since 1.0 * @access private * @method _closePanel */ _closePanel: function() { FLBuilder.triggerHook('hideContentPanel'); }, /** * Opens the builder's content panel. * * @since 1.0 * @access private * @method _showPanel */ _showPanel: function() { FLBuilder.triggerHook('showContentPanel'); }, /** * Toggle the panel open or closed. * * @since 2.0 * @access private * @method _togglePanel */ _togglePanel: function() { FLBuilder.triggerHook('toggleContentPanel'); }, /* Save Actions ----------------------------------------------------------*/ /** * Publish the current layout * * @since 2.0 * @access private * @method _publishLayout * @param {Boolean} shouldExit Whether or not builder should exit after publish * @param {Boolean} openLightbox Whether or not to keep the lightboxes open. * @return void */ _publishLayout: function( shouldExit, openLightbox ) { // Save existing settings first if any exist. Don't proceed if it fails. if ( ! FLBuilder._triggerSettingsSave( openLightbox, true ) ) { return; } if ( _.isUndefined( shouldExit ) ) { var shouldExit = true; } const actions = FL.Builder.data.getLayoutActions() const callback = FLBuilder._onPublishComplete.bind( FLBuilder, shouldExit ) actions.saveLayout( true, shouldExit, callback ) }, /** * Publishes the layout when the publish button is clicked. * * @since 1.0 * @access private * @param bool whether or not builder should exit after publish * @method _publishButtonClicked */ _publishButtonClicked: function( shouldExit ) { FLBuilder._publishLayout( shouldExit ); }, /** * Fires on successful ajax publish. * * @since 2.0 * @access private * @param bool whether or not builder should exit after publish * @return void */ _onPublishComplete: function( shouldExit ) { if ( shouldExit ) { if ( FLBuilderConfig.shouldRefreshOnPublish ) { FLBuilder._exit(); } else { FLBuilder._exitWithoutRefresh(); } } // Change the admin bar status dot to green if it isn't already $('#wp-admin-bar-fl-builder-frontend-edit-link .fl-builder-admin-bar-status-dot').css('color', '#6bc373'); FLBuilder.triggerHook( 'didPublishLayout', { shouldExit: shouldExit, } ); }, /** * Exits the builder when the save draft button is clicked. * * @since 1.0 * @access private * @method _draftButtonClicked */ _draftButtonClicked: function() { FLBuilder.showAjaxLoader(); const actions = FL.Builder.data.getLayoutActions() actions.saveDraft() }, /** * Clears changes to the layout when the discard draft button * is clicked. * * @since 1.0 * @access private * @method _discardButtonClicked */ _discardButtonClicked: function() { var result = confirm(FLBuilderStrings.discardMessage); if(result) { FLBuilder.showAjaxLoader(); const actions = FL.Builder.data.getLayoutActions() actions.discardDraft() } else { FLBuilder.triggerHook('didCancelDiscard'); } }, /** * Closes the actions lightbox when the cancel button is clicked. * * @since 1.0 * @access private * @method _cancelButtonClicked */ _cancelButtonClicked: function() { FLBuilder._exitUrl = null; FLBuilder._actionsLightbox.close(); }, /** * Redirects the user to the _exitUrl if defined, otherwise * it redirects the user to the current post without the * builder active. * * @since 1.0 * @since 1.5.7 Closes the window if we're in a child window. * @access private * @method _exit */ _exit: function() { var href = window.parent.location.href; try { var flbuilder = typeof window.parent.opener.FLBuilder != 'undefined' } catch(err) { var flbuilder = false } if ( FLBuilderConfig.isUserTemplate && typeof window.parent.opener != 'undefined' && window.parent.opener ) { if ( flbuilder ) { if ( 'undefined' === typeof FLBuilderGlobalNodeId ) { window.parent.opener.FLBuilder._updateLayout(); } else { window.parent.opener.FLBuilder._updateNode( FLBuilderGlobalNodeId ); } } window.parent.close(); } else { if ( FLBuilder._exitUrl ) { href = FLBuilder._exitUrl; } else { href = href.replace( '&fl_builder_ui', '' ); href = href.replace( '?fl_builder&', '?' ); href = href.replace( '?fl_builder', '' ); href = href.replace( '&fl_builder', '' ); } window.parent.location.href = href; } }, /** * Allow the editing session to end but don't redirect to any url. * * @since 2.0 * @return void */ _exitWithoutRefresh: function() { var href = window.parent.location.href; try { var flbuilder = typeof window.parent.opener.FLBuilder != 'undefined' } catch(err) { var flbuilder = false } if ( FLBuilderConfig.isUserTemplate && flbuilder && window.opener ) { if ( flbuilder ) { if ( 'undefined' === typeof FLBuilderGlobalNodeId ) { window.parent.opener.FLBuilder._updateLayout(); } else { window.parent.opener.FLBuilder._updateNode( FLBuilderGlobalNodeId ); } } window.parent.close(); } else { FLBuilder.triggerHook('endEditingSession'); } }, /* Tools Actions ----------------------------------------------------------*/ /** * Duplicates the current post and builder layout. * * @since 1.0 * @access private * @method _duplicateLayoutClicked */ _duplicateLayoutClicked: function() { FLBuilder.showAjaxLoader(); FLBuilder.ajax({ action: 'duplicate_post' }, FLBuilder._duplicateLayoutComplete); }, /** * Redirects the user to the post edit screen of a * duplicated post when duplication is complete. * * @since 1.0 * @access private * @method _duplicatePageComplete * @param {Number} The ID of the duplicated post. */ _duplicateLayoutComplete: function(response) { var adminUrl = FLBuilderConfig.adminUrl; window.parent.location.href = adminUrl + 'post.php?post='+ response +'&action=edit'; }, /* Layout Settings ----------------------------------------------------------*/ /** * Shows the layout settings lightbox when the layout * settings button is clicked. * * @since 1.7 * @access private * @method _layoutSettingsClicked */ _layoutSettingsClicked: function() { FLBuilderSettingsForms.render( { id : 'layout', className : 'fl-builder-layout-settings', settings : FLBuilderSettingsConfig.settings.layout }, function() { FLBuilder._layoutSettingsInitCSS(); } ); }, /** * Initializes custom layout CSS for live preview. * * @since 1.7 * @access private * @method _layoutSettingsInitCSS */ _layoutSettingsInitCSS: function() { var css = $( '.fl-builder-settings #fl-field-css textarea:not(.ace_text-input)', window.parent.document ); css.on( 'change', FLBuilder._layoutSettingsCSSChanged ); FLBuilder._layoutSettingsCSSCache = css.val(); }, /** * Sets a timeout for throttling custom layout CSS changes. * * @since 1.7 * @access private * @method _layoutSettingsCSSChanged */ _layoutSettingsCSSChanged: function() { if ( FLBuilder._layoutSettingsCSSTimeout ) { clearTimeout( FLBuilder._layoutSettingsCSSTimeout ); } FLBuilder._layoutSettingsCSSTimeout = setTimeout( $.proxy( FLBuilder._layoutSettingsCSSDoChange, this ), 600 ); }, /** * Updates the custom layout CSS when changes are made in the editor. * * @since 1.7 * @access private * @method _layoutSettingsCSSDoChange */ _layoutSettingsCSSDoChange: function() { var form = $( '.fl-builder-settings', window.parent.document ), textarea = $( this ), field = textarea.parents( '#fl-field-css' ); if ( field.find( '.ace_error' ).length > 0 ) { return; } else if ( form.hasClass( 'fl-builder-layout-settings' ) ) { $( '#fl-builder-layout-css' ).html( textarea.val() ); } else { $( '#fl-builder-global-css' ).html( textarea.val() ); } FLBuilder._layoutSettingsCSSTimeout = null; }, /** * Saves the layout settings when the save button is clicked. * * @since 1.7 * @access private * @method _saveLayoutSettingsClicked */ _saveLayoutSettingsClicked: function() { var form = $( this ).closest( '.fl-builder-settings' ), data = form.serializeArray(), settings = {}, i = 0; for( ; i < data.length; i++) { settings[ data[ i ].name ] = data[ i ].value; } FLBuilder.showAjaxLoader(); FLBuilder._lightbox.close(); FLBuilder._layoutSettingsCSSCache = null; const actions = FL.Builder.data.getLayoutActions() actions.saveLayoutSettings( settings ) }, /** * Reverts changes made when the cancel button for the layout * settings has been clicked. * * @since 1.7 * @access private * @method _cancelLayoutSettingsClicked */ _cancelLayoutSettingsClicked: function() { var form = $( '.fl-builder-settings', window.parent.document ); if ( form.hasClass( 'fl-builder-layout-settings' ) ) { $( '#fl-builder-layout-css' ).html( FLBuilder._layoutSettingsCSSCache ); } else { $( '#fl-builder-global-css' ).html( FLBuilder._layoutSettingsCSSCache ); } FLBuilder._layoutSettingsCSSCache = null; }, /** * Completes the ajax call for saving layout settings. * * @since 2.5 * @access private * @method _saveLayoutSettingsComplete * @param {Object} the settings object */ _saveLayoutSettingsComplete: function( settings ) { FLBuilder.triggerHook( 'didSaveLayoutSettingsComplete', settings ) FLBuilder._updateLayout() }, /* Global Settings ----------------------------------------------------------*/ /** * Shows the global builder settings lightbox when the global * settings button is clicked. * * @since 1.0 * @access private * @method _globalSettingsClicked */ _globalSettingsClicked: function() { const settings = FLBuilderSettingsConfig.settings.global settings.color_scheme = FL.Builder.data.getSystemState().colorScheme FLBuilderSettingsForms.render( { id : 'global', className : 'fl-builder-global-settings', settings : settings }, function() { FLBuilder._layoutSettingsInitCSS(); FLBuilder.original_shapes = FLBuilderSettingsConfig.settings.global.shape_form; } ); }, /** * Saves the global settings when the save button is clicked. * * @since 1.0 * @access private * @method _saveGlobalSettingsClicked */ _saveGlobalSettingsClicked: function() { var form = $(this).closest('.fl-builder-settings'), valid = form.validate().form(), settings = FLBuilder._getSettings( form ); if(valid) { FLBuilder.showAjaxLoader(); FLBuilder._layoutSettingsCSSCache = null; const actions = FL.Builder.data.getLayoutActions() actions.saveGlobalSettings( settings ) FLBuilder._lightbox.close(); // if number of global shapes has changed then refresh. if ( 'undefined' != typeof FLBuilder.original_shapes && FLBuilder.original_shapes.length !== settings.shape_form.length ) { FLBuilder._shapesEdited = true; } } }, /** * Saves the global settings when the save button is clicked. * * @since 1.0 * @access private * @method _saveGlobalSettingsComplete * @param {String} response */ _saveGlobalSettingsComplete: function( response ) { FLBuilder.triggerHook( 'didSaveGlobalSettingsComplete', FLBuilder._jsonParse( response ) ); FLBuilder._updateLayout(); if ( FLBuilder._shapesEdited === true ) { window.parent.location.reload(true); } }, /* Template Selector ----------------------------------------------------------*/ /** * Shows the template selector when the builder is launched * if the current layout is empty. * * @since 1.0 * @access private * @method _initTemplateSelector */ _initTemplateSelector: function() { var rows = $(FLBuilder._contentClass).find('.fl-row'), layoutHasContent = ( rows.length > 0 ); if( ! layoutHasContent ) { FLBuilder.ContentPanel.show('modules'); } }, /** * Show options for inserting or appending a template when a template is selected. * This logic was moved from `_templateClicked` to unbind it from the specific event. * * @since 2.0 * @access private * @method _requestTemplateInsert */ _requestTemplateInsert: function(index, type) { // if there are existing rows in the layout if( FLBuilder.layoutHasContent() ) { // If the template is blank, no need to ask if(index == 0) { if( confirm( FLBuilderStrings.changeTemplateMessage ) ) { FLBuilder._lightbox._node.hide(); FLBuilder._applyTemplate(0, false, type); } } // present options Replace or Append else { FLBuilder._selectedTemplateId = index; FLBuilder._selectedTemplateType = type; FLBuilder._showTemplateActions(); FLBuilder._lightbox._node.hide(); } } // if there are no rows, just insert the template. else { FLBuilder._applyTemplate(index, false, type); } }, /** * Shows the actions lightbox for replacing and appending templates. * * @since 1.1.9 * @access private * @method _showTemplateActions */ _showTemplateActions: function() { var buttons = []; buttons[ 10 ] = { 'key': 'template-replace', 'label': FLBuilderStrings.templateReplace }; buttons[ 20 ] = { 'key': 'template-append', 'label': FLBuilderStrings.templateAppend }; FLBuilder._showActionsLightbox({ 'className': 'fl-builder-template-actions', 'title': FLBuilderStrings.actionsLightboxTitle, 'buttons': buttons }); }, /** * Replaces the current layout with a template when the replace * button is clicked. * * @since 1.1.9 * @access private * @method _templateReplaceClicked */ _templateReplaceClicked: function() { if(confirm(FLBuilderStrings.changeTemplateMessage)) { FLBuilder._actionsLightbox.close(); FLBuilder._applyTemplate(FLBuilder._selectedTemplateId, false, FLBuilder._selectedTemplateType); } }, /** * Append a template to the current layout when the append * button is clicked. * * @since 1.1.9 * @access private * @method _templateAppendClicked */ _templateAppendClicked: function() { FLBuilder._actionsLightbox.close(); FLBuilder._applyTemplate(FLBuilder._selectedTemplateId, true, FLBuilder._selectedTemplateType); }, /** * Shows the template selector when the cancel button of * the template actions lightbox is clicked. * * @since 1.1.9 * @access private * @method _templateCancelClicked */ _templateCancelClicked: function() { FLBuilder.triggerHook( 'showContentPanel' ); }, /** * Applys a template to the current layout by either appending * it or replacing the current layout with it. * * @since 1.1.9 * @access private * @method _applyTemplate * @param {Number} id The template id. * @param {Boolean} append Whether the new template should be appended or not. * @param {String} type The type of template. Either core or user. */ _applyTemplate: function( id, append, type ) { append = typeof append === 'undefined' || ! append ? '0' : '1' type = typeof type === 'undefined' ? 'core' : type FLBuilder._lightbox.close(); FLBuilder.showAjaxLoader(); const actions = FL.Builder.data.getLayoutActions() actions.applyTemplate( id, append, type ) FLBuilder.triggerHook('didApplyTemplate'); }, /** * Callback for when applying a template completes. * @since 2.0 * @access private * @method _applyTemplateComplete * @param {String} response */ _applyTemplateComplete: function( response ) { var data = FLBuilder._jsonParse( response ); FLBuilder._renderLayout( data.layout ); FLBuilder.triggerHook( 'didApplyTemplateComplete', data.config ); }, /** * Callback for when applying a user template completes. * @since 1.9.5 * @access private * @method _applyUserTemplateComplete * @param {string} response */ _applyUserTemplateComplete: function( response ) { var data = FLBuilder._jsonParse( response ); if ( null !== data.layout_css ) { $( '#fl-builder-layout-css' ).html( data.layout_css ); } FLBuilder._renderLayout( data.layout ); FLBuilder.triggerHook( 'didApplyTemplateComplete', data.config ); }, /* User Template Settings ----------------------------------------------------------*/ /** * Shows the settings for saving a user defined template * when the save template button is clicked. * * @since 1.1.3 * @access private * @method _saveUserTemplateClicked */ _saveUserTemplateClicked: function() { if ( FLBuilderConfig.lite ) { FLBuilder._showProMessage( 'Saving Templates' ); return; } FLBuilderSettingsForms.render( { id : 'user_template', className : 'fl-builder-user-template-settings', rules : { name: { required: true } } } ); }, /** * Saves user template settings when the save button is clicked. * * @since 1.1.9 * @access private * @method _saveUserTemplateSettings */ _saveUserTemplateSettings: function() { var form = $(this).closest('.fl-builder-settings'), valid = form.validate().form(), settings = FLBuilder._getSettings(form); if(valid) { const actions = FL.Builder.data.getLayoutActions() actions.saveUserTemplateSettings( settings ) FLBuilder._lightbox.close(); } }, /** * Shows a success alert when user template settings have saved. * * @since 1.1.9 * @access private * @method _saveUserTemplateSettingsComplete */ _saveUserTemplateSettingsComplete: function(data) { if ( !data ) return; var data = FLBuilder._jsonParse(data); FLBuilderConfig.contentItems.template.push(data); FLBuilder.triggerHook('contentItemsChanged'); }, /** * Callback for when a user clicks a user defined template in * the template selector. * * @since 1.1.3 * @access private * @method _userTemplateClicked */ _userTemplateClicked: function() { var id = $(this).attr('data-id'); if($(FLBuilder._contentClass).children('.fl-row').length > 0) { if(id == 'blank') { if(confirm(FLBuilderStrings.changeTemplateMessage)) { FLBuilder._lightbox._node.hide(); FLBuilder._applyTemplate('blank', false, 'user'); } } else { FLBuilder._selectedTemplateId = id; FLBuilder._selectedTemplateType = 'user'; FLBuilder._showTemplateActions(); FLBuilder._lightbox._node.hide(); } } else { FLBuilder._applyTemplate(id, false, 'user'); } }, /** * Launches the builder in a new tab to edit a user * defined template when the edit link is clicked. * * @since 1.1.3 * @access private * @method _editUserTemplateClicked * @param {Object} e The event object. */ _editUserTemplateClicked: function(e) { e.preventDefault(); e.stopPropagation(); window.parent.open($(this).attr('href')); }, /** * Deletes a user defined template when the delete link is clicked. * * @since 1.1.3 * @access private * @method _deleteUserTemplateClicked * @param {Object} e The event object. */ _deleteUserTemplateClicked: function(e) { var template = $( this ).closest( '.fl-user-template' ), id = template.attr( 'data-id' ), all = $( '.fl-user-template[data-id=' + id + ']', window.parent.document ), parent = null, index = null, i = null, item = null; if ( confirm( FLBuilderStrings.deleteTemplate ) ) { const actions = FL.Builder.data.getLayoutActions() actions.deleteUserTemplate( id ) // Remove the item from library for(i in FLBuilderConfig.contentItems.template) { item = FLBuilderConfig.contentItems.template[i]; if (item.postId == id) { index = i; } } if (!_.isNull(index)) { FLBuilderConfig.contentItems.template.splice(index, 1); FLBuilder.triggerHook('contentItemsChanged'); } } e.stopPropagation(); }, /* Help Tour ----------------------------------------------------------*/ /** * Shows the help tour or template selector when the builder * is launched. * * @since 1.4.9 * @access private * @method _showTourOrTemplates */ _showTourOrTemplates: function() { if ( ! FLBuilderConfig.simpleUi && ! FLBuilderConfig.isUserTemplate ) { if ( FLBuilderConfig.help.tour && FLBuilderConfig.newUser ) { FLBuilder._showTourLightbox(); } else { FLBuilder._initTemplateSelector(); } } }, /** * Save browser stats when builder is loaded. * @since 2.1.6 */ _doStats: function() { if( 1 == FLBuilderConfig.statsEnabled ) { args = { 'screen-width': screen.width, 'screen-height': screen.height, 'pixel-ratio': window.devicePixelRatio, 'user-agent': window.navigator.userAgent, 'isrtl': FLBuilderConfig.isRtl } FLBuilder.ajax({ action: 'save_browser_stats', browser_data: args }); } }, /** * Shows the actions lightbox with a welcome message for new * users asking if they would like to take the tour. * * @since 1.4.9 * @access private * @method _showTourLightbox */ _showTourLightbox: function() { var template = wp.template( 'fl-tour-lightbox' ); FLBuilder._actionsLightbox.open( template() ); }, /** * Closes the actions lightbox and shows the template selector * if a new user declines the tour. * * @since 1.4.9 * @access private * @method _noTourButtonClicked */ _noTourButtonClicked: function() { FLBuilder._actionsLightbox.close(); FLBuilder._initTemplateSelector(); }, /** * Closes the actions lightbox and starts the tour when a new user * decides to take the tour. * * @since 1.4.9 * @access private * @method _yesTourButtonClicked */ _yesTourButtonClicked: function() { FLBuilder._actionsLightbox.close(); FLBuilderTour.start(); }, /** * Starts the help tour. * * @since 1.4.9 * @access private * @method _startHelpTour */ _startHelpTour: function() { FLBuilder._actionsLightbox.close(); FLBuilderTour.start(); }, /* Layout ----------------------------------------------------------*/ /** * Shows a message to drop a row or module to get started * if the layout is empty. * * @since 1.0 * @access private * @method _setupEmptyLayout */ _setupEmptyLayout: function() { var content = $(FLBuilder._contentClass); if ( FLBuilderConfig.isUserTemplate && 'module' == FLBuilderConfig.userTemplateType ) { return; } else if ( FLBuilderConfig.isUserTemplate && 'column' == FLBuilderConfig.userTemplateType ) { return; } else { content.removeClass('fl-builder-empty'); content.find('.fl-builder-empty-message').remove(); if ( ! content.find( '.fl-row, .fl-builder-block' ).length ) { content.addClass('fl-builder-empty'); content.append(' '); FLBuilder._initSortables(); } } }, /** * Sends an AJAX request to re-render a single node. * * @since 2.0 * @access private * @method _updateNode * @param {String} nodeId * @param {Function} callback */ _updateNode: function( nodeId, callback ) { if ( ! $( '.fl-node-' + nodeId ).length ) { return; } FLBuilder._showNodeLoading( nodeId ); const actions = FL.Builder.data.getLayoutActions() actions.renderNode( nodeId, callback ) }, /** * Sends an AJAX request to render the layout and is typically * used as a callback to many of the builder's save operations. * * @since 1.0 * @access private * @method _updateLayout */ _updateLayout: function() { FLBuilder.showAjaxLoader(); const actions = FL.Builder.data.getLayoutActions() actions.renderLayout() // Refresh Node Data actions.fetchLayout() }, /** * Removes the current layout and renders a new layout using * the provided data. Will render a node instead of the layout * if data.partial is true. * * @since 1.0 * @access private * @method _renderLayout * @param {Object} data The layout data. May also be a JSON encoded string. * @param {Function} callback A function to call when the layout has finished rendering. */ _renderLayout: function( data, callback ) { if ( FLBuilder._layout ) { FLBuilder._layoutQueue.push( { data: data, callback: callback, } ); } else { FLBuilder._layout = new FLBuilderAJAXLayout( data, callback ); } }, /** * Called by the layout's JavaScript file once it's loaded * to finish rendering the layout. * * @since 1.0 * @access private * @method _renderLayoutComplete */ _renderLayoutComplete: function() { if ( FLBuilder._layout ) { FLBuilder._layout._complete(); FLBuilder._layout = null; } if ( FLBuilder._layoutQueue.length ) { var item = FLBuilder._layoutQueue.shift(); FLBuilder._layout = new FLBuilderAJAXLayout( item.data, item.callback ); } }, /** * Trigger the resize event on the window so elements * in the layout that rely on JavaScript know to resize. * * @since 1.0 * @access private * @method _resizeLayout */ _resizeLayout: function() { $(window).trigger('resize'); if(typeof YUI !== 'undefined') { YUI().use('node-event-simulate', function(Y) { Y.one(window).simulate("resize"); }); } }, /** * Checks to see if any rows exist in the layout, or if it is blank. * * @since 2.0 * @method layoutHasContent * @return {Boolean} */ layoutHasContent: function() { if( $(FLBuilder._contentClass).children('.fl-row').length > 0) { return true; } else { return false; } }, /** * Initializes MediaElements.js audio and video players. * * @since 1.0 * @access private * @method _initMediaElements */ _initMediaElements: function() { var settings = {}; if(typeof $.fn.mediaelementplayer != 'undefined') { if(typeof _wpmejsSettings !== 'undefined') { settings.pluginPath = _wpmejsSettings.pluginPath; } $('.wp-audio-shortcode, .wp-video-shortcode').not('.mejs-container').mediaelementplayer(settings); } }, /* Generic Drag and Drop ----------------------------------------------------------*/ /** * Inserts drop targets for nodes such as rows, columns * and column groups since making those all sortables * makes sorting really jumpy. * * @since 1.9 * @access private * @method _initDropTargets */ _initDropTargets: function() { var notGlobal = 'row' == FLBuilderConfig.userTemplateType ? '' : ':not(.fl-node-global)', rows = $( FLBuilder._contentClass + ' .fl-row' ), row = null, groups = $( FLBuilder._contentClass + ' .fl-row' + notGlobal ).find( '.fl-col-group' ), group = null, cols = null, rootCol = 'column' == FLBuilderConfig.userTemplateType ? $( FLBuilder._contentClass + '> .fl-col' ).eq(0) : null, i = 0; // Remove old drop targets. $( '.fl-col-drop-target' ).remove(); $( '.fl-col-group-drop-target' ).remove(); $( '.fl-row-drop-target' ).remove(); // Row drop targets. $( FLBuilder._contentClass ).append( '' ); rows.prepend( '' ); rows.append( '' ); // Add group drop targets to empty rows. for ( ; i < rows.length; i++ ) { row = rows.eq( i ); if ( 0 === row.find( '.fl-col-group' ).length ) { row.find( '.fl-row-content' ).prepend( '' ); } } // Add drop target to root parent column. if ( rootCol && 0 === groups.length ) { groups = rootCol.find( '.fl-col-group' ); rootCol.append( '' ); rootCol.append( '' ); } // Loop through the column groups. for ( i = 0; i < groups.length; i++ ) { group = groups.eq( i ); cols = group.find( '> .fl-col' ); // Column group drop targets. if ( ! group.hasClass( 'fl-col-group-nested' ) ) { group.append( '' ); group.append( '' ); } // Column drop targets. cols.append( '' ); cols.append( '' ); } }, /** * Returns a helper element for a drag operation. * * @since 1.0 * @access private * @method _blockDragHelper * @param {Object} e The event object. * @param {Object} item The item being dragged. * @return {Object} The helper element. */ _blockDragHelper: function (e, item) { var helper = item.clone(); item.clone().insertAfter(item); helper.addClass('fl-builder-block-drag-helper'); return helper; }, /** * Initializes a drag operation. * * @since 1.0 * @access private * @method _blockDragInit * @param {Object} e The event object. */ _blockDragInit: function( e ) { var target = $( e.currentTarget ), node = null, scrollTop = $( window ).scrollTop(), initialPos = 0; // Set the _dragEnabled flag. FLBuilder._dragEnabled = true; // Save the initial scroll position. FLBuilder._dragInitialScrollTop = scrollTop; // Get the node to scroll to once the node highlights have affected the body height. if ( target.closest( '[data-node]' ).length > 0 ) { // Set the node to a node instance being dragged. node = target.closest( '[data-node]' ); // Mark this node as initialized for dragging. node.addClass( 'fl-node-drag-init' ); } else if ( target.hasClass( 'fl-builder-block' ) ) { // Set the node to the first visible row instance. $( '.fl-row' ).each( function() { if ( node === null && $( this ).offset().top - scrollTop > 0 ) { node = $( this ); } } ); } // Get the initial scroll position of the node. if ( node !== null ) { initialPos = node.offset().top - scrollTop; } // Setup the UI for dragging. FLBuilder._highlightRowsAndColsForDrag( target ); FLBuilder._adjustColHeightsForDrag(); FLBuilder._disableGlobalRows(); FLBuilder._disableGlobalCols(); FLBuilder._destroyOverlayEvents(); FLBuilder._initSortables(); $( 'body' ).addClass( 'fl-builder-dragging' ); $( 'body', window.parent.document ).addClass( 'fl-builder-dragging' ); $( '.fl-builder-empty-message' ).hide(); $( '.fl-sortable-disabled' ).removeClass( 'fl-sortable-disabled' ); // Remove all action overlays if this isn't a touch for a proxy item. if ( 'touchstart' !== e.type && ! $( e.target ).hasClass( 'fl-sortable-proxy-item ' ) ) { FLBuilder._removeAllOverlays(); } // Scroll to the node that is dragging. if ( initialPos > 0 ) { scrollTo( 0, node.offset().top - initialPos ); } FLBuilder.triggerHook('didInitDrag'); }, /** * Callback that fires when dragging starts. * * @since 1.0 * @access private * @method _blockDragStart * @param {Object} e The event object. * @param {Object} ui An object with additional info for the drag. */ _blockDragStart: function(e, ui) { // Let the builder know dragging has started. FLBuilder._dragging = true; // Removed the drag init class as we're done with that. $( '.fl-node-drag-init' ).removeClass( 'fl-node-drag-init' ); FLBuilder.triggerHook('didStartDrag'); }, /** * Callback that fires when an element that is being * dragged is sorted. * * @since 1.0 * @access private * @method _blockDragSort * @param {Object} e The event object. * @param {Object} ui An object with additional info for the drag. */ _blockDragSort: function(e, ui) { var parent = ui.placeholder.parent(), title = FLBuilderStrings.insert; // Prevent sorting? if ( FLBuilder._blockPreventSort( ui.item, parent ) ) { return; } // Find the placeholder title. if(parent.hasClass('fl-col-content')) { if(ui.item.hasClass('fl-builder-block-row')) { title = ui.item.find('.fl-builder-block-title').text(); } else if(ui.item.hasClass('fl-col-sortable-proxy-item')) { title = FLBuilderStrings.column; } else if(ui.item.hasClass('fl-builder-block-module')) { title = ui.item.find('.fl-builder-block-title').text(); } else if(ui.item.hasClass('fl-builder-block-saved-module') || ui.item.hasClass('fl-builder-block-module-template')) { title = ui.item.find('.fl-builder-block-title').text(); } else { title = ui.item.data('name'); } } else if(parent.hasClass('fl-col-drop-target')) { title = ''; } else if (parent.hasClass('fl-col-group-drop-target')) { title = ''; } else if(parent.hasClass('fl-row-drop-target')) { if(ui.item.hasClass('fl-builder-block-row')) { title = ui.item.find('.fl-builder-block-title').text(); } else if(ui.item.hasClass('fl-builder-block-saved-row')) { title = ui.item.find('.fl-builder-block-title').text(); } else if(ui.item.hasClass('fl-builder-block-saved-column')) { title = ui.item.find('.fl-builder-block-title').text(); } else if(ui.item.hasClass('fl-row-sortable-proxy-item')) { title = FLBuilderStrings.row; } else { title = FLBuilderStrings.newRow; } } // Set the placeholder title. ui.placeholder.html(title); // Add the global class? if ( ui.item.hasClass( 'fl-node-global' ) || ui.item.hasClass( 'fl-builder-block-global' ) || $( '.fl-node-dragging' ).hasClass( 'fl-node-global' ) ) { ui.placeholder.addClass( 'fl-builder-drop-zone-global' ); } else { ui.placeholder.removeClass( 'fl-builder-drop-zone-global' ); } }, /** * Callback that fires when an element that is being * dragged position changes. * * What we're doing here keeps it from appearing jumpy when draging * between columns. Without this you'd see the placeholder jump into * a column position briefly when you didn't intend for it to. * * @since 1.9 * @access private * @method _blockDragChange * @param {Object} e The event object. * @param {Object} ui An object with additional info for the drag. */ _blockDragChange: function( e, ui ) { ui.placeholder.css( 'opacity', '0' ); ui.placeholder.animate( { 'opacity': '1' }, 100 ); }, /** * Prevents sorting of items that shouldn't be sorted into * specific areas. * * @since 1.9 * @access private * @method _blockPreventSort * @param {Object} item The item being sorted. * @param {Object} parent The new parent. */ _blockPreventSort: function( item, parent ) { var prevent = false, isRowBlock = item.hasClass( 'fl-builder-block-row' ), isCol = item.hasClass( 'fl-col-sortable-proxy-item' ), isModuleItem = item.hasClass('fl-builder-block-module'), isParentColGlobal = parent.closest('.fl-col[data-template-url]').hasClass('fl-node-global'), isParentCol = parent.hasClass( 'fl-col-content' ), isColTarget = parent.hasClass( 'fl-col-drop-target' ), group = parent.parents( '.fl-col-group:not(.fl-col-group-nested)' ), nestedGroup = parent.parents( '.fl-col-group-nested' ); // Prevent columns in nested columns. if ( ( isRowBlock || isCol ) && isParentCol && nestedGroup.length > 0 ) { prevent = true; } // Prevent 1 column from being nested in an empty column. if ( isParentCol && ! parent.find( '.fl-module, .fl-col' ).length ) { if ( isRowBlock && '1-col' == item.data( 'cols' ) ) { prevent = true; } else if ( isCol ) { prevent = true; } } // Prevent 5 or 6 columns from being nested. if ( isRowBlock && isParentCol && $.inArray( item.data( 'cols' ), [ '5-cols', '6-cols' ] ) > -1 ) { prevent = true; } // Prevent columns with nested columns from being dropped in nested columns. if ( isCol && $( '.fl-node-dragging' ).find( '.fl-col-group-nested' ).length > 0 ) { if ( isParentCol || ( isColTarget && nestedGroup.length > 0 ) ) { prevent = true; } } // Prevent more than 12 columns. if ( isColTarget && group.length > 0 && 0 === nestedGroup.length && group.find( '> .fl-col:visible' ).length > 11 ) { prevent = true; } // Prevent more than 4 nested columns. if ( isColTarget && nestedGroup.length > 0 && nestedGroup.find( '.fl-col:visible' ).length > 3 ) { prevent = true; } // Prevent module from being dropped to a Global Col except when editing a column template. if ( isModuleItem && isParentColGlobal && 'column' !== FLBuilderConfig.userTemplateType ) { prevent = true; } // Add the disabled class if we are preventing a sort. if ( prevent ) { parent.addClass( 'fl-sortable-disabled' ); } return prevent; }, /** * Cleans up when a drag operation has stopped. * * @since 1.0 * @access private * @method _blockDragStop * @param {Object} e The event object. * @param {Object} ui An object with additional info for the drag. */ _blockDragStop: function( e, ui ) { var scrollTop = $( window ).scrollTop(), parent = ui.item.parent(), initialPos = null; // Get the node to scroll to once removing the node highlights affects the body height. if ( parent.hasClass( 'fl-drop-target' ) && parent.closest( '[data-node]' ).length ) { parent = parent.closest( '[data-node]' ); initialPos = parent.offset().top - scrollTop; } else { initialPos = parent.offset().top - scrollTop; } // Show the panel if a block was dropped back in. if ( parent.hasClass( 'fl-builder-blocks-section-content' ) ) { FLBuilder._showPanel(); } // Finish dragging. FLBuilder._dragEnabled = false; FLBuilder._dragging = false; FLBuilder._bindOverlayEvents(); FLBuilder._removeEmptyRowAndColHighlights(); FLBuilder._highlightEmptyCols(); FLBuilder._enableGlobalRows(); FLBuilder._enableGlobalCols(); FLBuilder._setupEmptyLayout(); $( 'body' ).removeClass( 'fl-builder-dragging' ); $( 'body', window.parent.document ).removeClass( 'fl-builder-dragging' ); // Scroll the page back to where it was. scrollTo( 0, parent.offset().top - initialPos ); FLBuilder.triggerHook('didStopDrag'); }, /** * Cleans up when a drag operation has canceled. * * @since 1.0 * @access private * @method _blockDragCancel */ _blockDragCancel: function() { if ( FLBuilder._dragEnabled && ! FLBuilder._dragging ) { FLBuilder._dragEnabled = false; FLBuilder._dragging = false; FLBuilder._bindOverlayEvents(); FLBuilder._removeEmptyRowAndColHighlights(); FLBuilder._highlightEmptyCols(); FLBuilder._enableGlobalRows(); FLBuilder._setupEmptyLayout(); $( 'body' ).removeClass( 'fl-builder-dragging' ); $( 'body', window.parent.document ).removeClass( 'fl-builder-dragging' ); $( '.fl-node-drag-init' ).removeClass( 'fl-node-drag-init' ); $( '.fl-node-dragging' ).removeClass( 'fl-node-dragging' ); scrollTo( 0, FLBuilder._dragInitialScrollTop ); FLBuilder.triggerHook('didCancelDrag'); } }, /** * Reorders a node within its parent. * * @since 1.9 * @access private * @method _reorderNode * @param {String} nodeId The node ID of the node. * @param {Number} position The new position. */ _reorderNode: function( nodeId, position ) { const actions = FL.Builder.getActions() actions.moveNode( nodeId, position ) }, /** * Handle completion after reorder ajax. * * @since 2.5 * @access private * @method _reorderNodeComplete * @param Object ajax response */ _reorderNodeComplete: function( response ) { var data = FLBuilder._jsonParse( response ); var hook = 'didMove' + data.nodeType.charAt(0).toUpperCase() + data.nodeType.slice(1); FLBuilder.triggerHook( 'didMoveNode', data ); FLBuilder.triggerHook( hook, data ); }, /** * Moves a node to a new parent. * * @since 1.9 * @access private * @method _moveNode * @param {String} newParent The node ID of the new parent. * @param {String} nodeId The node ID of the node. * @param {Number} position The new position. */ _moveNode: function( newParent, nodeId, position ) { const actions = FL.Builder.getActions() actions.moveNode( nodeId, position, newParent ) }, /** * Finishes a move node operation. * * @since 2.5 * @access private * @method _moveNodeComplete * @param Object ajax response */ _moveNodeComplete: function( response ) { const data = FLBuilder._jsonParse( response ); const hook = 'didMove' + data.nodeType.charAt( 0 ).toUpperCase() + data.nodeType.slice( 1 ); FLBuilder.triggerHook( 'didMoveNode', data ); FLBuilder.triggerHook( hook, data ); }, /** * Removes all node overlays and hides any tooltip helpies. * * @since 1.0 * @access private * @method _removeAllOverlays */ _removeAllOverlays: function() { FLBuilder._removeRowOverlays(); FLBuilder._removeColOverlays(); FLBuilder._removeColHighlightGuides(); FLBuilder._removeModuleOverlays(); FLBuilder._hideTipTips(); FLBuilder._closeAllSubmenus(); }, /** * Appends a node action overlay to the layout. * * @since 1.6.3.3 * @access private * @method _appendOverlay * @param {Object} node A jQuery reference to the node this overlay is associated with. * @param {Object} template A rendered wp.template. * @return {Object} The overlay element. */ _appendOverlay: function( node, template ) { var overlayPos = 0, overlay = null, isRow = node.hasClass( 'fl-row' ), nodeId = node.attr('data-node'), content = isRow ? node.find( '> .fl-row-content-wrap' ) : node.find( '> .fl-node-content' ), margins = { 'top' : parseInt( content.css( 'margin-top' ), 10 ), 'bottom' : parseInt( content.css( 'margin-bottom' ), 10 ) }; // Append the template. node.append( template ); // Add the active class to the node. node.addClass( 'fl-block-overlay-active' ); FL.Builder.data.getOutlinePanelActions().setFocusNode( nodeId ); // Init TipTips FLBuilder._initTipTips(); // Get a reference to the overlay. overlay = node.find( '> .fl-block-overlay' ); // Adjust the overlay positions to account for negative margins. if ( margins.top < 0 ) { overlayPos = parseInt( overlay.css( 'top' ), 10 ); overlayPos = isNaN( overlayPos ) ? 0 : overlayPos; overlay.css( 'top', ( margins.top + overlayPos ) + 'px' ); } if ( margins.bottom < 0 ) { overlayPos = parseInt( overlay.css( 'bottom' ), 10 ); overlayPos = isNaN( overlayPos ) ? 0 : overlayPos; overlay.css( 'bottom', ( margins.bottom + overlayPos ) + 'px' ); } return overlay; }, /** * Builds the overflow menu for an overlay if necessary. * * @since 1.9 * @access private * @method _buildOverlayOverflowMenu * @param {Object} overlay The overlay object. */ _buildOverlayOverflowMenu: function( overlay ) { var header = overlay.find( '.fl-block-overlay-header' ), actions = overlay.find( '.fl-block-overlay-actions' ), hasRules = overlay.find( '.fl-block-has-rules' ), original = actions.data( 'original' ), actionsWidth = 0, items = null, itemsWidth = 0, item = null, i = 0, visibleItems = [], overflowItems = [], menuData = [], template = wp.template( 'fl-overlay-overflow-menu' ); // Use the original copy if we have one. if ( undefined != original ) { actions.after( original ); actions.remove(); actions = original; } // Save a copy of the original actions. actions.data( 'original', actions.clone() ); // Get the actions width and items. Subtract any padding plus 2px (8px) actionsWidth = Math.floor(actions[0].getBoundingClientRect().width) - 8; items = actions.find( ' > i, > span.fl-builder-has-submenu' ); // Add the width of the visibility rules indicator if there is one. if ( hasRules.length && actionsWidth + hasRules.outerWidth() > header.outerWidth() ) { itemsWidth += hasRules.outerWidth(); } // Find visible and overflow items. for( ; i < items.length; i++ ) { item = items.eq( i ); itemsWidth += Math.floor(item[0].getBoundingClientRect().width); if ( itemsWidth > actionsWidth ) { overflowItems.push( item ); item.remove(); } else { visibleItems.push( item ); } } // Build the menu if we have overflow items. if ( overflowItems.length > 0 ) { if( visibleItems.length > 0 ) { overflowItems.unshift( visibleItems.pop().remove() ); } for( i = 0; i < overflowItems.length; i++ ) { if ( overflowItems[ i ].is( '.fl-builder-has-submenu' ) ) { menuData.push( { type : 'submenu', label : overflowItems[ i ].find( '.fa, .fas, .far' ).data( 'title' ), submenu : overflowItems[ i ].find( '.fl-builder-submenu' )[0].outerHTML } ); } else { menuData.push( { type : 'action', label : overflowItems[ i ].data( 'title' ), className : overflowItems[ i ].removeClass( function( i, c ) { return c.replace( /fl-block-([^\s]+)/, '' ); } ).attr( 'class' ) } ); } } actions.append( template( menuData ) ); FLBuilder._initTipTips(); } }, /* Rows ----------------------------------------------------------*/ /** * Removes all row overlays from the page. * * @since 1.0 * @access private * @method _removeRowOverlays */ _removeRowOverlays: function() { $('.fl-row').removeClass('fl-block-overlay-active'); $('.fl-row-overlay').remove(); $('.fl-module').removeClass('fl-module-adjust-height'); $('body').removeClass( 'fl-builder-row-resizing' ); FLBuilder._closeAllSubmenus(); FL.Builder.data.getOutlinePanelActions().setFocusNode( false ); }, /** * Removes all row overlays from the page. * * @since 1.0 * @access private * @method _removeRowOverlays */ _disableGlobalRows: function() { if ( 'row' == FLBuilderConfig.userTemplateType ) { return; } $('.fl-row.fl-node-global').addClass( 'fl-node-disabled' ); }, /** * Removes all global column overlays from the page. * * @since 2.1 * @access private * @method _disableGlobalCols */ _disableGlobalCols: function() { if ( 'column' == FLBuilderConfig.userTemplateType ) { return; } $('.fl-row:not(.fl-node-global) .fl-col.fl-node-global').addClass( 'fl-node-disabled' ); }, /** * Removes all row overlays from the page. * * @since 1.0 * @access private * @method _removeRowOverlays */ _enableGlobalRows: function() { if ( 'row' == FLBuilderConfig.userTemplateType ) { return; } $( '.fl-node-disabled' ).removeClass( 'fl-node-disabled' ); }, /** * Re-enable global column from the page. * * @since 2.1 * @access private * @method _enableGlobalCols */ _enableGlobalCols: function() { if ( 'column' == FLBuilderConfig.userTemplateType ) { return; } $( '.fl-node-disabled' ).removeClass( 'fl-node-disabled' ); }, /** * Shows an overlay with actions when the mouse enters a row. * * @since 1.0 * @access private * @method _rowMouseenter */ _rowMouseenter: function() { if ( 'undefined' == typeof FLBuilderSettingsConfig.nodes ) { return; } var row = $( this ), id = row.attr('data-node'), rowTop = row.offset().top, childTop = null, overlay = null, template = wp.template( 'fl-row-overlay' ), mode = FLBuilderResponsiveEditing._mode, settings = FLBuilderSettingsConfig.nodes[ id ]; if ( row.closest( '.fl-builder-node-loading' ).length ) { return; } else if ( ! row.hasClass( 'fl-block-overlay-active' ) ) { // Remove existing overlays. FLBuilder._removeRowOverlays(); // Append the overlay. overlay = FLBuilder._appendOverlay( row, template( { node : id, global : row.hasClass( 'fl-node-global' ), hasRules : row.hasClass( 'fl-node-has-rules' ), rulesTextRow : row.attr('data-rules-text'), rulesTypeRow : row.attr('data-rules-type'), nodeLabel : settings?.node_label, } ) ); // Adjust the overlay position if covered by negative margin content. row.find( '.fl-node-content:visible' ).each( function(){ var top = $( this ).offset().top; childTop = ( null === childTop || childTop > top ) ? top : childTop; } ); if ( null !== childTop && childTop < rowTop ) { overlay.css( 'top', ( childTop - rowTop - 30 ) + 'px' ); } // Put action headers on the bottom if they're hidden. if ( ( 'default' === mode && overlay.offset().top < 43 ) || ( 'default' !== mode && 0 === row.index() ) ) { overlay.addClass( 'fl-row-overlay-header-bottom' ); } // Adjust the height of modules if needed. row.find( '.fl-module' ).each( function(){ var module = $( this ); if ( module.outerHeight( true ) < 20 ) { module.addClass( 'fl-module-adjust-height' ); } } ); // Build the overlay overflow menu if needed. FLBuilder._buildOverlayOverflowMenu( overlay ); } }, /** * Removes overlays when the mouse leaves a row. * * @since 1.0 * @access private * @method _rowMouseleave * @param {Object} e The event object. */ _rowMouseleave: function(e) { var target = $( e.target ), toElement = $(e.toElement) || $(e.relatedTarget), isOverlay = toElement.hasClass('fl-row-overlay'), isOverlayChild = toElement.closest('.fl-row-overlay').length > 0, isTipTip = toElement.is('#tiptip_holder'), isTipTipChild = toElement.closest('#tiptip_holder').length > 0; if ( target.closest( '.fl-block-col-resize' ).length ) { return; } if ( isOverlay || isOverlayChild || isTipTip || isTipTipChild ) { return; } FLBuilder._removeRowOverlays(); }, /** * Returns a helper element for row drag operations. * * @since 1.0 * @access private * @method _rowDragHelper * @return {Object} The helper element. */ _rowDragHelper: function() { return $('' + FLBuilderStrings.noScriptWarn.heading + '
'; if ( FLBuilderConfig.userCaps.global_unfiltered_html ) { msg += '' + FLBuilderStrings.noScriptWarn.global + '
'; } else { msg += '' + FLBuilderStrings.noScriptWarn.message + '
'; } msg += ''; msg += '' + FLBuilderStrings.noScriptWarn.footer + '
'; FLBuilderSettingsForms.hideLightboxLoader() FLBuilder.alert( msg ); data = $.parseJSON(response); if ( '' !== data.diff ) { $('.fl-diff', window.parent.document).html( data.diff ); $('.fl-diff', window.parent.document).prepend( '' + FLBuilderStrings.codeErrorDetected + '
'); $('.fl-diff .diff-deletedline', window.parent.document).each(function(){ if ( $(this).find('del').length < 1 ) { $(this).css('background-color', 'rgb(255, 192, 203, 0.7)').css('padding', '10px').css('border', '1px solid pink'); } else { $(this).find('del').css('background-color', 'rgb(255, 192, 203, 0.7)').css('border', '1px solid pink'); } }); console.log( '============' ); console.log( 'key: ' + data.key ); console.log( 'value: ' + data.value ); console.log( 'parsed: ' + data.parsed ); console.log( '============' ); } } } ); } }, /** * Renders a new layout when the settings for the current * form have finished saving. * * @since 1.0 * @access private * @method _saveSettingsComplete * @param {Boolean} render Whether the layout should render after saving. * @param {String} response The layout data from the server. */ _saveSettingsComplete: function( render, response ) { var data = FLBuilder._jsonParse( response ), type = data.layout.nodeType, moduleType = data.layout.moduleType, hook = 'didSave' + type.charAt(0).toUpperCase() + type.slice(1) + 'SettingsComplete', preview = FLBuilder.preview, callback = function() { if (preview && data.layout.partial && data.layout.nodeId === preview.nodeId && !FLBuilder._publishAndRemain ) { preview.clear(); FLBuilder.preview = null; } FLBuilder._publishAndRemain = false; }; if ( true === render ) { FLBuilder._renderLayout( data.layout, callback ); } else { callback(); } FLBuilder.triggerHook( 'didSaveNodeSettingsComplete', { nodeId : data.node_id, nodeType : type, moduleType : moduleType, settings : data.settings } ); FLBuilder.triggerHook( hook, { nodeId : data.node_id, nodeType : type, moduleType : moduleType, settings : data.settings } ); }, /** * Triggers a click on the settings save button so all save * logic runs for any form that is currently in the lightbox. * * @since 2.0 * @access private * @method _triggerSettingsSave * @param {Boolean} disableClose * @param {Boolean} showAlert * @param {Boolean} destroy * @return {Boolean} */ _triggerSettingsSave: function( disableClose, showAlert, destroy ) { var form = FLBuilder._lightbox._node.find( 'form.fl-builder-settings' ), lightboxId = FLBuilder._lightbox._node.data( 'instance-id' ), lightbox = FLLightbox._instances[ lightboxId ], nested = $( '.fl-lightbox-wrap[data-parent]:visible', window.parent.document ), changed = false, valid = true; disableClose = _.isUndefined( disableClose ) ? false : disableClose; showAlert = _.isUndefined( showAlert ) ? false : showAlert; destroy = _.isUndefined( destroy ) ? ! disableClose : destroy; // prevent clearing preview. if (!destroy) { FLBuilder._publishAndRemain = true; } if ( form.length ) { // Save any nested settings forms. if ( nested.length ) { // Save the form. nested.find( '.fl-builder-settings-save' ).trigger( 'click' ); // Don't proceed if not saved. if ( nested.find( 'label.error' ).length || $( '.fl-builder-alert-lightbox:visible', window.parent.document ).length ) { valid = false; } } // Do a validation check of the main form to see if we should save. if ( valid && ! form.validate({ignore: '.fl-ignore-validation'}).form() ) { valid = false; } // Check to see if the main settings have changed. changed = FLBuilderSettingsForms.settingsHaveChanged(); // Save the main settings form if it has changes. if ( valid && changed ) { // Disable lightbox close? if ( disableClose ) { lightbox.disableClose(); } // Save the form. form.find( '.fl-builder-settings-save' ).trigger( 'click' ); // Enable lightbox close if it was disabled. if ( disableClose ) { lightbox.enableClose(); } // Don't proceed if not saved. if ( form.find( 'label.error' ).length || $( '.fl-builder-alert-lightbox:visible', window.parent.document ).length ) { valid = false; } } // Destroy the settings form? if ( destroy ) { FLBuilder._destroySettingsForms(); // Destroy the preview if settings don't have changes. if ( ! changed && FLBuilder.preview ) { FLBuilder.preview.clear(); FLBuilder.preview = null; } } else { // cache current settings FLBuilderSettingsForms.cacheCurrentSettings(); } // Close the main lightbox if it doesn't have changes and closing isn't disabled. if ( ! changed && ! disableClose ) { lightbox.close(); } } if ( ! valid ) { FLBuilder.triggerHook( 'didFailSettingsSave' ); FLBuilder._toggleSettingsTabErrors(); if ( showAlert && ! $( '.fl-builder-alert-lightbox:visible', window.parent.document ).length ) { FLBuilder.alert( FLBuilderStrings.settingsHaveErrors ); } } else { FLBuilder.triggerHook( 'didTriggerSettingsSave' ); } // Reset preview clearing FLBuilder._publishAndRemain = false; return valid; }, /** * Refreshes preview references for a node's settings panel * in case they have been broken by work in the layout. * * @since 2.0 * @access private * @method _refreshSettingsPreviewReference */ _refreshSettingsPreviewReference: function() { if ( FLBuilder.preview ) { FLBuilder.preview._initElementsAndClasses(); } }, /* Nested Settings Forms ----------------------------------------------------------*/ /** * Opens a nested settings lightbox. * * @since 1.10 * @access private * @method _openNestedSettings * @return object The settings lightbox object. */ _openNestedSettings: function( settings ) { if ( settings.className && -1 === settings.className.indexOf( 'fl-builder-settings-lightbox' ) ) { settings.className += ' fl-builder-settings-lightbox'; } settings = $.extend( { className: 'fl-builder-lightbox fl-builder-settings-lightbox', destroyOnClose: true, resizable: true }, settings ); var parentBoxWrap = $( '.fl-lightbox-wrap:visible', window.parent.document ), parentBox = parentBoxWrap.find( '.fl-lightbox' ), nestedBoxObj = new FLLightbox( settings ), nestedBoxWrap = nestedBoxObj._node, nestedBox = nestedBoxWrap.find( '.fl-lightbox' ); parentBoxWrap.hide(); nestedBoxWrap.attr( 'data-parent', parentBoxWrap.attr( 'data-instance-id' ) ); nestedBox.attr( 'style', parentBox.attr( 'style' ) ); nestedBoxObj.on( 'resized', FLBuilder._calculateSettingsTabsOverflow ); nestedBoxObj.open( '' ); return nestedBoxObj; }, /** * Opens the active nested settings lightbox. * * @since 1.10 * @access private * @method _closeNestedSettings */ _closeNestedSettings: function() { var nestedBoxWrap = $( '.fl-builder-lightbox[data-parent]:visible', window.parent.document ), nestedBox = nestedBoxWrap.find( '.fl-lightbox' ), nestedBoxId = nestedBoxWrap.attr( 'data-instance-id' ), nestedBoxObj = FLLightbox._instances[ nestedBoxId ], parentBoxId = nestedBoxWrap.attr( 'data-parent' ), parentBoxWrap = $( '[data-instance-id="' + parentBoxId + '"]', window.parent.document ), parentBox = parentBoxWrap.find( '.fl-lightbox' ), parentBoxForm = parentBoxWrap.find('form'), parentBoxObj = FLLightbox._instances[ parentBoxId ]; if ( ! nestedBoxObj ) { return } nestedBoxObj.on( 'close', function() { parentBox.attr( 'style', nestedBox.attr( 'style' ) ); parentBoxWrap.show(); parentBoxObj._resize(); parentBoxWrap.find( 'label.error' ).remove(); parentBoxForm.validate().hideErrors(); FLBuilder._toggleSettingsTabErrors(); FLBuilder._initMultipleFields(); } ); nestedBoxObj.close(); }, /* Tooltips ----------------------------------------------------------*/ /** * Shows a help tooltip in the settings lightbox. * * @since 1.0 * @access private * @method _showHelpTooltip */ _showHelpTooltip: function() { $(this).siblings('.fl-help-tooltip-text').fadeIn(); }, /** * Hides a help tooltip in the settings lightbox. * * @since 1.0 * @access private * @method _hideHelpTooltip */ _hideHelpTooltip: function() { $(this).siblings('.fl-help-tooltip-text').fadeOut(); }, /** * Setup section toggling * * @since 2.2 * @access private * @method _initSection * @return void */ _initSection: function() { var wrap = $(this), button = wrap.find('.fl-builder-settings-section-header'); button.on('click', function() { wrap.toggleClass('fl-builder-settings-section-collapsed') }); }, /* Align Fields ----------------------------------------------------------*/ /** * Initializes all button group fields within a settings form. * * @since 2.2 * @access private * @method _initButtonGroupFields */ _initButtonGroupFields: function() { $( '.fl-builder-settings:visible', window.parent.document ) .find( '.fl-button-group-field' ).each( FLBuilder._initButtonGroupField ); }, /** * Initializes a button group field within a settings form. * * @since 2.2 * @access private * @method _initButtonGroupField */ _initButtonGroupField: function() { var wrap = $( this ), options = wrap.find( '.fl-button-group-field-option' ), multiple = wrap.data( 'multiple' ), min = wrap.data( 'min' ), max = wrap.data( 'max' ), input = wrap.find( 'input' ), value = function( format ) { var val = []; options.each(function(i, option) { if ($(option).attr('data-selected') === '1') { val.push($(option).attr('data-value')); } }) if ( 'array' == format ) { return val; } return val.join(','); }; options.on( 'click', function() { var option = $( this ), length = value( 'array' ).length; if ( '1' == option.attr( 'data-selected' ) ) { if ( false == min ) { option.attr( 'data-selected', '0' ); } else { if ( length - 1 >= min ) { option.attr( 'data-selected', '0' ); } } } else { // Unset other options if ( multiple !== true ) { options.attr( 'data-selected', '0' ); } if ( false == max ) { option.attr( 'data-selected', '1' ); } else { if ( length + 1 <= max ) { option.attr( 'data-selected', '1' ); } } } input.val( value( '' ) ).trigger( 'change' ); } ); // Handle value being changed externally input.on( 'change', function( e ) { var value = input.val().split(','); // Unset other options if (multiple !== true) { options.attr('data-selected', '0' ); } $.each(value, function(i, val) { var option = options.filter( '[data-value="' + val + '"]' ); // Set the matching one. option.attr( 'data-selected', '1' ); }); }); }, /* Compound Fields ----------------------------------------------------------*/ /** * Initializes all compound fields within a settings form. * * @since 2.2 * @access private * @method _initCompoundFields */ _initCompoundFields: function() { $( '.fl-builder-settings:visible', window.parent.document ) .find( '.fl-compound-field' ).each( FLBuilder._initCompoundField ); }, /** * Initializes a compound field within a settings form. * * @since 2.2 * @access private * @method _initCompoundField */ _initCompoundField: function() { var wrap = $( this ), sections = wrap.find( '.fl-compound-field-section' ), toggles = wrap.find( '.fl-compound-field-section-toggle' ), dimensions = wrap.find( '.fl-compound-field-setting' ).has( '.fl-dimension-field-units' ); sections.each( function() { var section = $( this ); if ( ! section.find( '.fl-compound-field-section-toggle' ).length ) { section.addClass( 'fl-compound-field-section-visible' ); } } ); toggles.on( 'click', function() { var toggle = $( this ), field = toggle.closest( '.fl-field' ), section = toggle.closest( '.fl-compound-field-section' ), className = '.' + section.attr( 'class' ).split( ' ' ).join( '.' ); field.find( className ).toggleClass( 'fl-compound-field-section-visible' ); } ); // Init linking for compound dimension fields. dimensions.each( function() { var field = $( this ), label = field.find( '.fl-compound-field-label' ), icon = ''; if ( ! label.length || field.find( '.fl-shadow-field' ).length ) { return; } label.append( icon ); } ); }, /* Auto Suggest Fields ----------------------------------------------------------*/ /** * Initializes all auto suggest fields within a settings form. * * @since 1.2.3 * @access private * @method _initAutoSuggestFields */ _initAutoSuggestFields: function() { var fields = $('.fl-builder-settings:visible .fl-suggest-field', window.parent.document), field = null, values = null, name = null, data = []; fields.each( function() { field = $( this ); if ( '' !== field.attr( 'data-value' ) ) { FLBuilderSettingsForms.showFieldLoader( field ); data.push( { name : field.attr( 'name' ), value : field.attr( 'data-value' ), action : field.attr( 'data-action' ), data : field.attr( 'data-action-data' ), } ); } } ); if ( data.length ) { FLBuilder.ajax( { action: 'get_autosuggest_values', fields: data }, function( response ) { values = FLBuilder._jsonParse( response ); for ( name in values ) { $( '.fl-suggest-field[name="' + name + '"]', window.parent.document ) .attr( 'data-value', values[ name ] ); } fields.each( FLBuilder._initAutoSuggestField ); } ); } else { fields.each( FLBuilder._initAutoSuggestField ); } }, /** * Initializes a single auto suggest field. * * @since 1.2.3 * @access private * @method _initAutoSuggestField */ _initAutoSuggestField: function() { var field = $(this); field.autoSuggest(FLBuilder._ajaxUrl({ 'fl_action' : 'fl_builder_autosuggest', 'fl_as_action' : field.data('action'), 'fl_as_action_data' : field.data('action-data'), '_wpnonce' : FLBuilderConfig.ajaxNonce }), $.extend({}, { asHtmlID : field.attr('name'), selectedItemProp : 'name', searchObjProps : 'name', minChars : 2, keyDelay : 1000, fadeOut : false, usePlaceholder : true, emptyText : FLBuilderStrings.noResultsFound, showResultListWhenNoMatch : true, preFill : field.data('value'), queryParam : 'fl_as_query', afterSelectionAdd : FLBuilder._updateAutoSuggestField, afterSelectionRemove : FLBuilder._updateAutoSuggestField, selectionLimit : field.data('limit'), canGenerateNewSelections : false }, field.data( 'args' ))); FLBuilderSettingsForms.hideFieldLoader( field ); }, /** * Updates the value of an auto suggest field. * * @since 1.2.3 * @access private * @method _initAutoSuggestField * @param {Object} element The auto suggest field. * @param {Object} item The current selection. * @param {Array} selections An array of selected values. */ _updateAutoSuggestField: function(element, item, selections) { var that = this; $(this).siblings('.as-values').val(selections.join(',')).trigger('change'); // sortable stuff. $(this).parents( '.as-selections').sortable({ items : ':not(.as-original)', 'update': function( event, ui ) { var selected = []; set = that.parents( '.as-selections').find('li.as-selection-item'); $.each(set, function(i,n) { selected.push($(n).attr('data-value')); }); $(that).siblings('.as-values').val(selected.join(',')).trigger('change'); } }) }, /* Code Fields ----------------------------------------------------------*/ /** * SiteGround ForceSSL fix */ _CodeFieldSSLCheck: function() { $('body').append(''); if ( 'https://www.w3.org/2000/svg' === $('.sg-test').find('svg').attr('xmlns') ) { FLBuilder._codeDisabled = true; } $('.sg-test').remove() }, /** * Initializes all code fields in a settings form. * * @since 2.0 * @access private * @method _initCodeFields */ _initCodeFields: function() { if ( ! FLBuilder._codeDisabled ) { $( '.fl-builder-settings:visible', window.parent.document ) .find( '.fl-code-field' ).each( FLBuilder._initCodeField ); } }, /** * Initializes a single code field in a settings form. * * @since 2.0 * @access private * @method _initCodeField */ _initCodeField: function() { var field = $( this ), settings = field.closest( '.fl-builder-settings' ), textarea = field.find( 'textarea' ), editorId = textarea.attr( 'id' ), mode = textarea.data( 'editor' ), wrap = textarea.data( 'wrap' ), editDiv = $( '" + window.crash_vars.strings.troubleshoot + "
"; info += "" + window.crash_vars.strings.if_contact + "
"; info +="MacOS Users:
Chrome: View > Developer > JavaScript Console
Firefox: Tools > Web Developer > Browser Console
Safari: Develop > Show JavaScript console
Windows Users:
Chrome: Settings > More Tools > Developer > Console
Firefox: Menu/Settings > Web Developer > Web Console
Edge: Settings and More > More Tools > Console
" + window.crash_vars.strings.contact + "
" if ( FLBuilderConfig.MaxInputVars <= 3000 ) { info += '