File: /var/www/vhosts/ensaco.es/httpdocs/wp-content/plugins/bb-plugin/js/fl-builder-preview.js
(function($){
/**
* Helper class for dealing with live previews.
*
* @class FLBuilderPreview
* @since 1.3.3
* @param {Object} config
*/
FLBuilderPreview = function( config )
{
// Set the preview ID.
this.id = new Date().getTime();
// Set the type.
this.type = config.type;
// Set the layout document.
this.layoutDoc = FLBuilderPreview.getLayoutDoc();
// Save the current state.
this._saveState();
// Initialize the preview.
if ( config.layout ) {
FLBuilder._renderLayout( config.layout, function() {
this._init();
if ( config.callback ) {
config.callback();
}
}.bind( this ) );
} else {
this._init();
if ( config.callback ) {
config.callback();
}
}
};
/**
* Stores all the fonts and weights of all font fields.
* This is used to render the stylesheet with Google Fonts.
*
* @since 1.6.3
* @access private
* @property {Array} _fontsList
*/
FLBuilderPreview._fontsList = {};
/**
* Returns a formatted selector string for a preview.
*
* @since 2.1
* @method getFormattedSelector
* @param {String} selector A CSS selector string.
* @return {String}
*/
FLBuilderPreview.getFormattedSelector = function( prefix, selector = '' )
{
var formatted = '',
pattern = /,(?![^()]*\))/,
parts = selector.split( pattern ),
i = 0;
for ( ; i < parts.length; i++ ) {
if ( parts[ i ].indexOf( '{node}' ) > -1 ) {
formatted += parts[ i ].replace( '{node}', prefix );
} else if ( parts[ i ].indexOf( '{node_id}' ) > -1 ) {
formatted += parts[ i ].replace( /{node_id}/g, this.nodeId );
} else {
formatted += prefix + ' ' + parts[ i ];
}
if ( i != parts.length - 1 ) {
formatted += ', ';
}
}
return formatted;
};
/**
* Returns the document object that contains the node layout.
* In the builder, this is always the current document, regardless
* of if the layout is in an iframe or not. The preview logic
* loads where the layout is rendered in the builder.
*
* @since 2.9
* @method getLayoutDoc
* @return {Object}
*/
FLBuilderPreview.getLayoutDoc = () => {
if ( FLBuilder.isBlockEditor() ) {
const iframe = document.querySelector( '[name="editor-canvas"]' )
return iframe ? iframe.contentDocument : document
}
return document
}
/**
* Prototype for new instances.
*
* @since 1.3.3
* @property {Object} prototype
*/
FLBuilderPreview.prototype = {
/**
* A unique ID for this preview.
*
* @since 1.3.3
* @property {String} id
*/
id : '',
/**
* The type of node that we are previewing.
*
* @since 1.3.3
* @property {String} type
*/
type : '',
/**
* The ID of node that we are previewing.
*
* @since 1.3.3
* @property {String} nodeId
*/
nodeId : null,
/**
* An object with data for each CSS class
* in the preview.
*
* @since 1.3.3
* @property {Object} classes
*/
classes : {},
/**
* An object with references to each element
* in the preview.
*
* @since 1.3.3
* @property {Object} elements
*/
elements : {},
/**
* An object that contains data for the current
* state of a layout before changes are made.
*
* @since 1.3.3
* @property {Object} state
*/
state : null,
/**
* The document object that contains the node layout.
*
* @since 2.9
* @property {Object} layoutDoc
*/
layoutDoc : null,
/**
* Node settings saved when the preview was initialized.
*
* @since 1.7
* @access private
* @property {Object} _savedSettings
*/
_savedSettings : null,
/**
* An instance of FLStyleSheet for the current preview.
*
* @since 1.3.3
* @access private
* @property {FLStyleSheet} _styleSheet
*/
_styleSheet : null,
/**
* An instance of FLStyleSheet for the large device preview.
*
* @since 2.6
* @access private
* @property {FLStyleSheet} _styleSheetLarge
*/
_styleSheetLarge : null,
/**
* An instance of FLStyleSheet for the medium device preview.
*
* @since 1.9
* @access private
* @property {FLStyleSheet} _styleSheetMedium
*/
_styleSheetMedium : null,
/**
* An instance of FLStyleSheet for the responsive device preview.
*
* @since 1.9
* @access private
* @property {FLStyleSheet} _styleSheet
*/
_styleSheetResponsive : null,
/**
* A timeout object for delaying the current preview refresh.
*
* @since 1.3.3
* @access private
* @property {Object} _timeout
*/
_timeout : null,
/**
* A timeout object for delaying when we show the loading
* graphic for refresh previews.
*
* @since 1.10
* @access private
* @property {Object} _loaderTimeout
*/
_loaderTimeout : null,
/**
* Stores the last classname for a classname preview.
*
* @since 1.3.3
* @access private
* @property {String} _lastClassName
*/
_lastClassName : null,
/**
* A reference to the AJAX object for a preview refresh.
*
* @since 1.3.3
* @access private
* @property {Object} _xhr
*/
_xhr : null,
/**
* Initializes a builder preview.
*
* @since 1.3.3
* @access private
* @method _init
*/
_init: function()
{
// Node Id
this.nodeId = $('form.fl-builder-settings:visible', window.parent.document).data('node');
// Save settings
this._saveSettings();
// Elements and Class Names
this._initElementsAndClasses();
// Create the preview stylesheets
this._createSheets();
this._bindEvents()
},
_bindEvents() {
// Responsive previews
this._initResponsivePreviews();
// Default field previews
this._initDefaultFieldPreviews();
// Init
switch(this.type) {
case 'row':
this._initRow();
break;
case 'col':
this._initColumn();
break;
case 'module':
case 'form':
this._initModule();
break;
}
FLBuilder.triggerHook( 'preview-init', this );
},
/**
* Saves the current settings to be checked to see if
* anything has changed when a preview is canceled.
*
* @since 1.7
* @access private
* @method _saveSettings
*/
_saveSettings: function()
{
var form = $('.fl-builder-settings-lightbox .fl-builder-settings', window.parent.document);
this._savedSettings = FLBuilder._getSettingsForChangedCheck( this.nodeId, form );
},
/**
* Checks to see if the settings have changed.
*
* @since 1.7
* @access private
* @method _settingsHaveChanged
* @return bool
*/
_settingsHaveChanged: function()
{
if ( FLBuilder._autoSuggestValueChanged ) {
FLBuilder._autoSuggestValueChanged = false;
return true;
}
var form = $('.fl-builder-settings-lightbox .fl-builder-settings', window.parent.document),
settings = FLBuilder._getSettings( form );
return JSON.stringify( this._savedSettings ) != JSON.stringify( settings );
},
/**
* Initializes the classname and element references
* for this preview.
*
* @since 1.3.3
* @access private
* @method _initElementsAndClasses
*/
_initElementsAndClasses: function()
{
var contentClass;
// Content Class
if(this.type == 'row') {
contentClass = '.fl-row-content-wrap';
}
else {
contentClass = '.fl-' + this.type + '-content';
}
let formSelector = '.fl-builder-settings-lightbox:visible form.fl-builder-settings'
// Class Names
$.extend(this.classes, {
settings : formSelector,
settingsHeader : formSelector + ' .fl-lightbox-header',
node : FLBuilder._contentClass + ' .fl-node-' + this.nodeId,
content : FLBuilder._contentClass + ' .fl-node-' + this.nodeId + ' > ' + contentClass
});
// Elements
$.extend(this.elements, {
settings : $(this.classes.settings, window.parent.document),
settingsHeader : $(this.classes.settingsHeader, window.parent.document),
node : $(this.classes.node, this.layoutDoc),
content : $(this.classes.content, this.layoutDoc)
});
},
/**
* Creates the stylesheets for default, medium
* and responsive previews.
*
* @since 1.9
* @method _createSheets
*/
_createSheets: function()
{
// We use different stylesheets in the block editor.
if ( FLBuilder.isBlockEditor() ) {
return;
}
this._destroySheets();
if ( ! this._styleSheet ) {
this._styleSheet = new FLStyleSheet( {
id : 'fl-builder-preview',
className : 'fl-builder-preview-style'
} );
}
if ( ! this._styleSheetLarge) {
this._styleSheetLarge = new FLStyleSheet( {
id : 'fl-builder-preview-large',
className : 'fl-builder-preview-style'
} );
this._styleSheetLarge.disable();
}
if ( ! this._styleSheetMedium ) {
this._styleSheetMedium = new FLStyleSheet( {
id : 'fl-builder-preview-medium',
className : 'fl-builder-preview-style'
} );
this._styleSheetMedium.disable();
}
if ( ! this._styleSheetResponsive ) {
this._styleSheetResponsive = new FLStyleSheet( {
id : 'fl-builder-preview-responsive',
className : 'fl-builder-preview-style'
} );
this._styleSheetResponsive.disable();
}
},
/**
* Destroys all preview sheets.
*
* @since 1.9
* @method _destroySheets
*/
_destroySheets: function()
{
if ( this._styleSheet ) {
this._styleSheet.destroy();
this._styleSheet = null;
}
if ( this._styleSheetLarge ) {
this._styleSheetLarge.destroy();
this._styleSheetLarge = null;
}
if ( this._styleSheetMedium ) {
this._styleSheetMedium.destroy();
this._styleSheetMedium = null;
}
if ( this._styleSheetResponsive ) {
this._styleSheetResponsive.destroy();
this._styleSheetResponsive = null;
}
$( '.fl-builder-preview-style' ).remove();
},
/**
* Disables preview styles for the current
* responsive editing mode.
*
* @since 2.2
* @method _disableStyles
*/
_disableStyles: function()
{
var mode = FLBuilderResponsiveEditing._mode,
config = FLBuilderConfig.global,
node = this.elements.node;
if ( 'responsive' === mode && this._styleSheetResponsive ) {
window?.FLBuilderSimulateMediaQuery?.disableStyles( config.responsive_breakpoint );
this._styleSheetResponsive.disable();
} else if ( 'large' === mode && this._styleSheetLarge ) {
window?.FLBuilderSimulateMediaQuery?.disableStyles( config.large_breakpoint );
this._styleSheetLarge.disable();
} else if ( 'medium' === mode && this._styleSheetMedium ) {
window?.FLBuilderSimulateMediaQuery?.disableStyles( config.medium_breakpoint );
this._styleSheetMedium.disable();
} else {
node.removeClass( function( i, className ) {
return ( className.match( /fl-node-[^\s]*/g ) || [] ).join( ' ' );
} );
}
},
/**
* Enables preview styles for the current
* responsive editing mode.
*
* @since 2.2
* @method _enableStyles
*/
_enableStyles: function()
{
var mode = FLBuilderResponsiveEditing._mode,
node = this.elements.node;
if ( 'responsive' === mode && this._styleSheetResponsive ) {
window?.FLBuilderSimulateMediaQuery?.enableStyles();
this._styleSheetResponsive.enable();
} else if ( 'medium' === mode && this._styleSheetMedium ) {
window?.FLBuilderSimulateMediaQuery?.enableStyles();
this._styleSheetMedium.enable();
} else if ( 'large' === mode && this._styleSheetLarge ) {
window?.FLBuilderSimulateMediaQuery?.enableStyles();
this._styleSheetLarge.enable();
} else {
node.addClass( 'fl-node-' + node.data( 'node' ) );
}
},
/**
* Attempt to find the default value for a CSS property.
*
* @since 2.2
* @method _getDefaultValue
* @param {String} selector
* @param {String} property
* @return {String}
*/
_getDefaultValue: function( selector, property )
{
var value = '',
element = $( selector, this.layoutDoc ),
node = element.closest( '[data-node]' ),
ignore = [ 'color', 'background-color', 'border-color', 'line-height', 'font-weight' ];
if ( 'width' === property ) {
value = 'auto';
} else if ( -1 === $.inArray( property, ignore ) && node.length ) {
this._disableStyles();
value = element.eq( element.length - 1 ).css( property );
this._enableStyles();
}
return value;
},
/**
* Updates a CSS rule for this preview.
*
* @since 1.3.3
* @method updateCSSRule
* @param {String} selector The CSS selector to update.
* @param {String} property The CSS property to update.
* @param {String} value The CSS value to update.
* @param {String|Boolean} responsive If this preview is responsive or not.
*/
updateCSSRule: function( selector, property, value, responsive )
{
var mode = FLBuilderResponsiveEditing._mode,
sheetKey = '';
// Get the default value if needed.
if ( '' === value || 'null' === value ) {
value = this._getDefaultValue( selector, property );
// Trigger a preview refresh to ensure the existing layout styles
// don't override preview styles when the value is empty.
this.delayPreview();
}
// Fire a hook for the block editor.
if ( FLBuilder.isBlockEditor() ) {
FLBuilder.triggerHook( 'updateCSSRule', [ selector, property, value, responsive ] );
return;
}
// Update the rule.
if ( responsive ) {
if ( 'string' === typeof responsive ) {
sheetKey = this.toUpperCaseWords( responsive );
} else {
sheetKey = 'default' === mode ? '' : this.toUpperCaseWords( mode );
}
this[ '_styleSheet' + sheetKey ].updateRule( selector, property, value );
} else {
this._styleSheet.updateRule( selector, property, value );
}
},
/**
* Runs a delay with a callback.
*
* @since 1.3.3
* @method delay
* @param {Number} length How long to wait before running the callback.
* @param {Function} callback A function to call when the delay is complete.
*/
delay: function(length, callback)
{
this._cancelDelay();
this._timeout = setTimeout(callback, length);
},
/**
* Cancels a preview refresh delay.
*
* @since 1.3.3
* @access private
* @method _cancelDelay
*/
_cancelDelay: function()
{
if(this._timeout !== null) {
clearTimeout(this._timeout);
}
if(this._loaderTimeout !== null) {
clearTimeout(this._loaderTimeout);
}
},
/**
* Converts a hex value to an array of RGB values.
*
* @since 1.3.3
* @method hexToRgb
* @param {String} hex
* @return {Array}
*/
hexToRgb: function(hex)
{
var bigInt = parseInt(hex, 16),
r = (bigInt >> 16) & 255,
g = (bigInt >> 8) & 255,
b = bigInt & 255;
return [r, g, b];
},
/**
* Returns a hex or rgb formatted value.
*
* @since 2.0.3
* @method hexOrRgb
* @param {String} value
* @return {String}
*/
hexOrRgb: function( value )
{
if ( value.indexOf( 'rgb' ) < 0 && value.indexOf( '#' ) < 0 ) {
value = '#' + value;
}
return value;
},
/**
* Parses a float or returns 0 if we don't have a number.
*
* @since 1.3.3
* @method parseFloat
* @param {Number} value
* @return {Number}
*/
parseFloat: function(value)
{
return isNaN(parseFloat(value)) ? 0 : parseFloat(value);
},
/* Responsive Previews
----------------------------------------------------------*/
/**
* Initializes logic for responsive previews.
*
* @since 1.9
* @method _initResponsivePreviews
*/
_initResponsivePreviews: function()
{
var namespace = '.preview-' + this.id;
FLBuilder.addHook( 'responsive-editing-switched' + namespace, $.proxy( this._responsiveEditingSwitched, this ) );
FLBuilder.addHook( 'responsive-editing-before-preview-fields' + namespace, $.proxy( this._responsiveEditingPreviewFields, this ) );
},
/**
* Destroys responsive preview events.
*
* @since 1.9
* @method _destroyResponsivePreviews
*/
_destroyResponsivePreviews: function()
{
var namespace = '.preview-' + this.id;
FLBuilder.removeHook( 'responsive-editing-switched' + namespace );
FLBuilder.removeHook( 'responsive-editing-before-preview-fields' + namespace );
},
/**
* Initializes logic for responsive previews.
*
* @since 1.9
* @method _responsiveEditingSwitched
*/
_responsiveEditingSwitched: function( e, mode )
{
if ( 'default' == mode ) {
this._styleSheetLarge.disable();
this._styleSheetMedium.disable();
this._styleSheetResponsive.disable();
}
else if ( 'large' == mode ) {
this._styleSheetLarge.enable();
this._styleSheetMedium.disable();
this._styleSheetResponsive.disable();
}
else if ( 'medium' == mode ) {
this._styleSheetLarge.enable();
this._styleSheetMedium.enable();
this._styleSheetResponsive.disable();
}
else if ( 'responsive' == mode ) {
this._styleSheetLarge.enable();
this._styleSheetMedium.enable();
this._styleSheetResponsive.enable();
}
},
/**
* Logic that needs to run before field previews are triggered
* after responsive editing mode switches.
*
* @since 2.2
* @method _responsiveEditingPreviewFields
*/
_responsiveEditingPreviewFields: function( e, mode )
{
if ( 'large' === mode || 'medium' === mode ) {
if ( 'col' === this.type && this.elements.node[0].style.width ) {
size = parseFloat( this.elements.node[0].style.width );
this.elements.size.val( size );
}
}
},
/**
* Deprecated. Use updateCSSRule instead.
*
* @since 1.9
*/
updateResponsiveCSSRule: function( selector, property, value )
{
this.updateCSSRule( selector, property, value, true );
},
/* States
----------------------------------------------------------*/
/**
* Saves the current state of a layout.
*
* @since 1.3.3
* @access private
* @method _saveState
*/
_saveState: function()
{
var post = FLBuilderConfig.postId,
css = $('link[href*="/cache/' + post + '"]').attr('href'),
js = $('script[src*="/cache/' + post + '"]').attr('src'),
html = $(FLBuilder._contentClass).html();
this.state = {
css : css,
js : js,
html : html
};
},
/**
* Runs a preview refresh for the current settings lightbox.
*
* @since 1.3.3
* @method preview
*/
preview: function()
{
var form = $('.fl-builder-settings-lightbox .fl-builder-settings', window.parent.document),
nodeId = form.attr('data-node'),
settings = FLBuilder._getSettings(form);
// Abort an existing preview request.
this._cancelPreview();
settings = FLBuilder._inputVarsCheck( settings );
if ( 'error' === settings ) {
return 0;
}
if ( FLBuilder.isBlockEditor() ) {
FLBuilder.triggerHook( 'requestPreview', settings );
this._renderPreviewComplete();
} else {
// Show the node as loading.
FLBuilder._showNodeLoading( nodeId );
// Make a new preview request.
this._xhr = FLBuilder.ajax({
action : 'render_layout',
node_id : this._getNodeIdForPreview( nodeId ),
node_preview_id : nodeId,
node_preview : settings
}, $.proxy(this._renderPreview, this));
}
},
/**
* Returns the ID of the node that should be previewed. For duplicate
* nodes with the same ID (as in loop modules), it will return the
* outer most parent with only one ID on the page. That way, all copies
* of the child modules get an updated preview with the new settings.
*
* This is different than `node_preview_id` which points to the node
* whose settings are currently being previewed.
*
* @since 2.9
* @param {String} nodeId
* @return {String}
*/
_getNodeIdForPreview: function( nodeId ) {
var node = $( '.fl-node-' + nodeId );
if ( 1 < node.length ) {
var parent = node.parents( '[data-node]' ).eq(0);
var parentId = parent.attr( 'data-node' );
return this._getNodeIdForPreview( parentId );
}
return nodeId;
},
/**
* Runs a preview refresh with a delay.
*
* @since 1.3.3
* @method delayPreview
*/
delayPreview: function(e)
{
var heading = typeof e == 'undefined' ? [] : $(e.target).closest('tr').find('th'),
widgetHeading = $('.fl-builder-widget-settings .fl-builder-settings-title', window.parent.document),
lightboxHeading = $('.fl-builder-settings .fl-lightbox-header', window.parent.document),
loaderSrc = FLBuilderLayoutConfig.paths.pluginUrl + 'img/ajax-loader-small.svg',
loader = $('<img class="fl-builder-preview-loader" src="' + loaderSrc + '" />');
this.delay(1000, $.proxy(this.preview, this));
this._loaderTimeout = setTimeout( function() {
$('.fl-builder-preview-loader', window.parent.document).remove();
if(heading.length > 0) {
heading.append(loader);
}
else if(widgetHeading.length > 0) {
widgetHeading.append(loader);
}
else if(lightboxHeading.length > 0) {
lightboxHeading.append(loader);
}
}, 1500 );
},
/**
* Cancels a preview refresh.
*
* @since 1.3.3
* @access private
* @method _cancelPreview
*/
_cancelPreview: function()
{
if(this._xhr) {
this._xhr.abort();
this._xhr = null;
}
},
/**
* Renders the response of a preview refresh.
*
* @since 1.3.3
* @access private
* @method _renderPreview
* @param {String} response The JSON encoded response.
*/
_renderPreview: function(response)
{
this._xhr = null;
FLBuilder._renderLayout(response, $.proxy(this._renderPreviewComplete, this));
},
/**
* Fires when a preview refresh has finished rendering.
*
* @since 1.3.3
* @access private
* @method _renderPreviewComplete
*/
_renderPreviewComplete: function()
{
// Refresh the preview styles.
this._createSheets();
// Refresh the elements.
this._initElementsAndClasses();
// Refresh preview config for element references.
this._initDefaultFieldPreviews();
// Clear the loader timeout.
if(this._loaderTimeout !== null) {
clearTimeout(this._loaderTimeout);
}
/**
* Remove the loading graphic
* 2.6.0.2 added 500ms delay to fix #2234
*/
setTimeout( function() {
$('.fl-builder-preview-loader', window.parent.document).remove();
}, 500 );
// Fire the preview rendered event.
$( FLBuilder._contentClass, this.layoutDoc ).trigger( 'fl-builder.preview-rendered' );
},
/**
* Reverts a preview to the state that was saved
* before the preview was initialized.
*
* @since 1.3.3
* @method revert
*/
revert: function()
{
var nodeId = this.nodeId;
if ( ! this._settingsHaveChanged() ) {
this.clear();
return;
}
if ( 'col' === this.type ) {
nodeId = this.elements.node.closest( '.fl-col-group' ).data( 'node' );
}
FLBuilder._updateNode( nodeId, function() {
this.clear();
}.bind( this ) );
},
/**
* Cancels a preview refresh.
*
* @since 1.3.3
* @method clear
*/
cancel: function()
{
this._cancelDelay();
this._cancelPreview();
},
/**
* Cancels a preview refresh and removes
* any stylesheet changes.
*
* @since 1.3.3
* @method clear
*/
clear: function()
{
// Canel any preview delays or requests.
this.cancel();
// Destroy the preview stylesheet.
this._destroySheets();
// Destroy responsive editing previews.
this._destroyResponsivePreviews();
},
/* Node Text Color Settings
----------------------------------------------------------*/
/**
* Initializes node text color previews.
*
* @since 1.3.3
* @access private
* @method _initNodeTextColor
*/
_initNodeTextColor: function()
{
// Elements
$.extend(this.elements, {
textColor : $(this.classes.settings + ' input[name=text_color]', window.parent.document),
linkColor : $(this.classes.settings + ' input[name=link_color]', window.parent.document),
hoverColor : $(this.classes.settings + ' input[name=hover_color]', window.parent.document),
headingColor : $(this.classes.settings + ' input[name=heading_color]', window.parent.document)
});
// Events
this.elements.textColor.on('change', $.proxy(this._textColorChange, this));
this.elements.linkColor.on('change', $.proxy(this._textColorChange, this));
this.elements.hoverColor.on('change', $.proxy(this._textColorChange, this));
this.elements.headingColor.on('change', $.proxy(this._textColorChange, this));
},
/**
* Fires when the text color field for a node
* is changed.
*
* @since 1.3.3
* @access private
* @method _textColorChange
* @param {Object} e An event object.
*/
_textColorChange: function(e)
{
var textColor = this.elements.textColor.val(),
linkColor = this.elements.linkColor.val(),
hoverColor = this.elements.hoverColor.val(),
headingColor = this.elements.headingColor.val();
linkColor = linkColor === '' ? textColor : linkColor;
hoverColor = hoverColor === '' ? textColor : hoverColor;
headingColor = headingColor === '' ? textColor : headingColor;
if ( textColor && textColor.indexOf( 'rgb' ) < 0 ) {
textColor = '#' + textColor;
}
if ( linkColor && linkColor.indexOf( 'rgb' ) < 0 ) {
linkColor = '#' + linkColor;
}
if ( hoverColor && hoverColor.indexOf( 'rgb' ) < 0 ) {
hoverColor = '#' + hoverColor;
}
if ( headingColor && headingColor.indexOf( 'rgb' ) < 0 ) {
headingColor = '#' + headingColor;
}
this.delay(50, $.proxy(function(){
// Update Text color.
if(textColor === '') {
this.updateCSSRule(this.classes.node, 'color', '');
}
else {
this.updateCSSRule(this.classes.node, 'color', textColor);
}
// Update Link Color
if ( linkColor === '' ) {
this.updateCSSRule(this.classes.node + ' a', 'color', '');
}
else {
this.updateCSSRule(this.classes.node + ' a', 'color', linkColor);
}
// Hover Color
if(hoverColor === '') {
this.updateCSSRule(this.classes.node + ' a:hover', 'color', '');
}
else {
this.updateCSSRule(this.classes.node + ' a:hover', 'color', hoverColor);
}
// Heading Color
if(headingColor === '') {
this.updateCSSRule(this.classes.node + ' h1', 'color', '');
this.updateCSSRule(this.classes.node + ' h2', 'color', '');
this.updateCSSRule(this.classes.node + ' h3', 'color', '');
this.updateCSSRule(this.classes.node + ' h4', 'color', '');
this.updateCSSRule(this.classes.node + ' h5', 'color', '');
this.updateCSSRule(this.classes.node + ' h6', 'color', '');
this.updateCSSRule(this.classes.node + ' h1 a', 'color', '');
this.updateCSSRule(this.classes.node + ' h2 a', 'color', '');
this.updateCSSRule(this.classes.node + ' h3 a', 'color', '');
this.updateCSSRule(this.classes.node + ' h4 a', 'color', '');
this.updateCSSRule(this.classes.node + ' h5 a', 'color', '');
this.updateCSSRule(this.classes.node + ' h6 a', 'color', '');
}
else {
this.updateCSSRule(this.classes.node + ' h1', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h2', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h3', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h4', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h5', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h6', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h1 a', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h2 a', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h3 a', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h4 a', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h5 a', 'color', headingColor);
this.updateCSSRule(this.classes.node + ' h6 a', 'color', headingColor);
}
}, this));
},
/* Node Bg Settings
----------------------------------------------------------*/
/**
* Initializes node background previews.
*
* @since 1.3.3
* @access private
* @method _initNodeBg
*/
_initNodeBg: function()
{
// Elements
$.extend(this.elements, {
bgType : $(this.classes.settings + ' select[name=bg_type]', window.parent.document),
bgColor : $(this.classes.settings + ' input[name=bg_color]', window.parent.document),
bgColorPicker : $(this.classes.settings + ' .fl-picker-bg_color', window.parent.document),
bgGradientType : $(this.classes.settings + ' select.fl-gradient-picker-type-select', window.parent.document),
bgVideoSource : $(this.classes.settings + ' select[name=bg_video_source]', window.parent.document),
bgVideo : $(this.classes.settings + ' input[name=bg_video]', window.parent.document),
bgVideoServiceUrl : $(this.classes.settings + ' input[name=bg_video_service_url]', window.parent.document),
bgVideoFallbackSrc : $(this.classes.settings + ' select[name=bg_video_fallback_src]', window.parent.document),
bgSlideshowSource : $(this.classes.settings + ' select[name=ss_source]', window.parent.document),
bgSlideshowPhotos : $(this.classes.settings + ' input[name=ss_photos]', window.parent.document),
bgSlideshowFeedUrl : $(this.classes.settings + ' input[name=ss_feed_url]', window.parent.document),
bgSlideshowSpeed : $(this.classes.settings + ' input[name=ss_speed]', window.parent.document),
bgSlideshowTrans : $(this.classes.settings + ' select[name=ss_transition]', window.parent.document),
bgSlideshowTransSpeed : $(this.classes.settings + ' input[name=ss_transitionDuration]', window.parent.document),
bgParallaxImageSrc : $(this.classes.settings + ' select[name=bg_parallax_image_src]', window.parent.document),
bgOverlayType : $(this.classes.settings + ' select[name=bg_overlay_type]', window.parent.document),
bgOverlayColor : $(this.classes.settings + ' input[name=bg_overlay_color]', window.parent.document),
bgOverlayGradient : $(this.classes.settings + ' #fl-field-bg_overlay_gradient select', window.parent.document),
});
// Events
this.elements.bgType.on( 'change', $.proxy(this._bgTypeChange, this));
this.elements.bgColor.on( 'change', $.proxy(this._bgColorChange, this));
this.elements.bgVideoServiceUrl.on( 'change', $.proxy(this._bgVideoChange, this));
this.elements.bgSlideshowSource.on( 'change', $.proxy(this._bgSlideshowChange, this));
this.elements.bgSlideshowPhotos.on( 'change', $.proxy(this._bgSlideshowChange, this));
this.elements.bgSlideshowFeedUrl.on( 'keyup', $.proxy(this._bgSlideshowChange, this));
this.elements.bgSlideshowSpeed.on( 'keyup', $.proxy(this._bgSlideshowChange, this));
this.elements.bgSlideshowTrans.on( 'change', $.proxy(this._bgSlideshowChange, this));
this.elements.bgSlideshowTransSpeed.on( 'keyup', $.proxy(this._bgSlideshowChange, this));
this.elements.bgParallaxImageSrc.on( 'change', $.proxy(this._bgParallaxChange, this));
this.elements.bgOverlayType.on( 'change', $.proxy(this._bgOverlayChange, this));
this.elements.bgOverlayColor.on( 'change', $.proxy(this._bgOverlayChange, this));
},
/**
* Fires when the background type field of
* a node changes.
*
* @since 1.3.3
* @access private
* @method _bgTypeChange
* @param {Object} e An event object.
*/
_bgTypeChange: function(e)
{
var val = this.elements.bgType.val(),
mode = FLBuilderResponsiveEditing._mode;
// Clear bg styles first.
this.elements.node.removeClass('fl-row-bg-video');
this.elements.node.removeClass('fl-row-bg-slideshow');
this.elements.node.removeClass('fl-row-bg-parallax');
this.elements.node.find('.fl-bg-video').remove();
this.elements.node.find('.fl-bg-slideshow').remove();
this.elements.content.css('background-image', '');
this.updateCSSRule(this.classes.content, 'background-color', 'transparent');
this.updateCSSRule(this.classes.content, 'background-image', 'none');
this.updateCSSRule(this.classes.content, 'background-image', 'none', 'medium');
this.updateCSSRule(this.classes.content, 'background-image', 'none', 'responsive');
// None
if(val == 'none') {
this._bgOverlayClear();
}
// Color
else if(val == 'color') {
this.elements.bgColor.trigger('change');
this._bgOverlayClear();
}
// Gradient
else if(val == 'gradient') {
this.elements.bgGradientType.trigger('change');
this._bgOverlayClear();
}
// Photo
else if(val == 'photo') {
this.elements.bgColor.trigger('change');
this.elements.settings.find( '[data-device="' + mode + '"] select[name*="bg_"]' ).trigger( 'change' );
}
// Video
else if(val == 'video') {
this.elements.bgColor.trigger('change');
this._bgVideoChange();
}
// Slideshow
else if(val == 'slideshow') {
this.elements.bgColor.trigger('change');
this._bgSlideshowChange();
}
// Parallax
else if(val == 'parallax') {
this.elements.bgColor.trigger('change');
this.elements.bgParallaxImageSrc.trigger('change');
}
},
/**
* Fires when the background color field of
* a node changes.
*
* @since 1.3.3
* @access private
* @method _bgColorChange
* @param {Object} e An event object.
*/
_bgColorChange: function(e)
{
var rgb, alpha, value;
if(this.elements.bgColor.val() === '') {
this.updateCSSRule(this.classes.content, 'background-color', 'transparent');
}
else {
value = this.hexOrRgb( this.elements.bgColor.val() );
this.delay(100, $.proxy(function(){
this.updateCSSRule(this.classes.content, 'background-color', value);
}, this));
}
},
/**
* Fires when the background video field of
* a node changes.
*
* @since 1.9.2
* @access private
* @method _bgVideoChange
* @param {Object} e An event object.
*/
_bgVideoChange: function(e)
{
var elements = this.elements,
source = elements.bgVideoSource.val(),
video = elements.bgVideo.val(),
videoUrl = elements.bgVideoServiceUrl.val(),
youtubePlayer = 'https://www.youtube.com/iframe_api',
vimeoPlayer = 'https://player.vimeo.com/api/player.js',
scriptTag = $( '<script>' );
// Only load the required API script library
if(source == 'video_service' && videoUrl != '') {
if (/^(?:(?:(?:https?:)?\/\/)?(?:www.)?(?:youtu(?:be.com|.be))\/(?:watch\?v\=|v\/|embed\/)?([\w\-]+))/i.test(videoUrl)
&& $( 'script[src*="youtube.com"', this.layoutDoc ).length < 1) {
scriptTag.attr('src', youtubePlayer);
}
else if(/^(http\:\/\/|https\:\/\/)?(www\.)?(vimeo\.com\/)([0-9]+)$/.test(videoUrl)
&& $( 'script[src*="vimeo.com"', this.layoutDoc ).length < 1) {
scriptTag.attr('src', vimeoPlayer);
}
scriptTag
.attr('type', 'text/javascript')
.appendTo('head');
this.delay(500, $.proxy(this.preview, this));
}
else if(video != '') {
this.preview();
}
},
/**
* Fires when the background slideshow field of
* a node changes.
*
* @since 1.3.3
* @access private
* @method _bgSlideshowChange
* @param {Object} e An event object.
*/
_bgSlideshowChange: function(e)
{
var elements = this.elements,
source = elements.bgSlideshowSource.val(),
photos = elements.bgSlideshowPhotos.val(),
feed = elements.bgSlideshowFeedUrl.val(),
speed = elements.bgSlideshowSpeed.val(),
transSpeed = elements.bgSlideshowTransSpeed.val();
if(source == 'wordpress' && photos === '') {
return;
}
else if(source == 'smugmug' && feed === '') {
return;
}
else if(isNaN(parseInt(speed))) {
return;
}
else if(isNaN(parseInt(transSpeed))) {
return;
}
this.delay(500, $.proxy(this.preview, this));
},
/**
* Fires when the background parallax field of
* a node changes.
*
* @since 1.3.3
* @access private
* @method _bgParallaxChange
* @param {Object} e An event object.
*/
_bgParallaxChange: function(e)
{
if(this.elements.bgParallaxImageSrc.val()) {
this.updateCSSRule(this.classes.content, {
'background-image' : 'url(' + this.elements.bgParallaxImageSrc.val() + ')',
'background-repeat' : 'no-repeat',
'background-position' : 'center center',
'background-attachment' : 'fixed',
'background-size' : 'cover'
});
}
},
/**
* Fires when the background overlay field of
* a node changes.
*
* @since 1.3.3
* @access private
* @method _bgOverlayChange
* @param {Object} e An event object.
*/
_bgOverlayChange: function(e)
{
var type = this.elements.bgOverlayType.val(),
color = this.elements.bgOverlayColor.val(),
rgb, alpha, value;
if ( 'color' === type ) {
if ( color === '' ) {
this.elements.node.removeClass('fl-row-bg-overlay');
this.elements.node.removeClass('fl-col-bg-overlay');
this.updateCSSRule(this.classes.content + '::after', 'background-color', 'transparent');
} else {
value = this.hexOrRgb( this.elements.bgOverlayColor.val() );
this.delay(100, $.proxy(function(){
this._bgOverlayAddClasses();
this.updateCSSRule( this.classes.content + '::after', 'background-color', value );
}, this));
}
this.updateCSSRule(this.classes.content + '::after', 'background-image', 'none');
} else if ( 'gradient' === type ) {
this._bgOverlayAddClasses();
this.updateCSSRule(this.classes.content + '::after', 'background-color', 'transparent');
this.elements.bgOverlayGradient.trigger( 'change' );
} else {
this.elements.node.removeClass('fl-row-bg-overlay');
this.elements.node.removeClass('fl-col-bg-overlay');
this.updateCSSRule(this.classes.content + '::after', 'background-color', 'transparent');
this.updateCSSRule(this.classes.content + '::after', 'background-image', 'none');
}
},
/**
* Adds the necessary classes for background overlays.
*
* @since 2.2
* @access private
* @method _bgOverlayAddClasses
*/
_bgOverlayAddClasses: function() {
if ( this.elements.node.hasClass( 'fl-col' ) ) {
this.elements.node.addClass( 'fl-col-bg-overlay' );
} else {
this.elements.node.addClass( 'fl-row-bg-overlay' );
}
},
/**
* Fires when a background overlay color is cleared.
*
* @since 1.3.3
* @access private
* @method _bgOverlayClear
* @param {Object} e An event object.
*/
_bgOverlayClear: function(e)
{
this.elements.bgOverlayColor.prev('.fl-color-picker-clear').trigger('click');
this.elements.bgOverlayType.val( 'color' ).trigger( 'change' );
},
/* Node Class Name Settings
----------------------------------------------------------*/
/**
* Initializes node classname previews.
*
* @since 1.3.3
* @access private
* @method _initNodeClassName
*/
_initNodeClassName: function()
{
// Elements
$.extend(this.elements, {
className : $(this.classes.settings + ' input[name=class]', window.parent.document)
});
// Events
this.elements.className.on('keyup', $.proxy(this._classNameChange, this));
this._lastClassName = this.elements.className.val();
},
/**
* Fires when the classname of a node changes.
*
* @since 1.3.3
* @access private
* @method _classNameChange
* @param {Object} e An event object.
*/
_classNameChange: function(e)
{
var className = this.elements.className.val();
if(this._lastClassName !== null) {
this.elements.node.removeClass(this._lastClassName);
}
this.elements.node.addClass(className);
this._lastClassName = className;
},
/* Node Spacing Settings
----------------------------------------------------------*/
/**
* Initializes node responsive dimension previews for things
* like margins, padding and borders.
*
* @since 1.9
* @access private
* @method _initNodeDimensions
*/
_initNodeDimensions: function( property )
{
var elements = {},
dimensions = [ 'Top', 'Bottom', 'Left', 'Right' ],
devices = [ '', 'Large', 'Medium', 'Responsive' ],
settingsClass = this.classes.settings,
elementKey = '',
inputName = '',
i = null,
k = null;
for ( i = 0; i < dimensions.length; i++ ) {
for ( k = 0; k < devices.length; k++ ) {
elementKey = property + dimensions[ i ] + devices[ k ];
inputName = property + '_' + dimensions[ i ].toLowerCase();
if ( '' != devices[ k ] ) {
inputName += '_' + devices[ k ].toLowerCase();
}
elements[ elementKey ] = $( settingsClass + ' input[name=' + inputName + ']', window.parent.document );
}
}
$.extend( this.elements, elements );
},
/* Row Settings
----------------------------------------------------------*/
/**
* Initializes a row preview.
*
* @since 1.3.3
* @access private
* @method _initRow
*/
_initRow: function()
{
// Elements
$.extend(this.elements, {
width : $(this.classes.settings + ' select[name=width]', window.parent.document),
contentWidth : $(this.classes.settings + ' select[name=content_width]', window.parent.document),
maxContentWidth : $(this.classes.settings + ' input[name=max_content_width]', window.parent.document),
maxContentWidthLarge : $(this.classes.settings + ' input[name=max_content_width_large]', window.parent.document),
maxContentWidthMedium : $(this.classes.settings + ' input[name=max_content_width_medium]', window.parent.document),
maxContentWidthResponsive : $(this.classes.settings + ' input[name=max_content_width_responsive]', window.parent.document),
maxContentWidthUnit : $(this.classes.settings + ' select[name=max_content_width_unit]', window.parent.document),
maxContentWidthLargeUnit : $(this.classes.settings + ' select[name=max_content_width_large_unit]', window.parent.document),
maxContentWidthMediumUnit : $(this.classes.settings + ' select[name=max_content_width_medium_unit]', window.parent.document),
maxContentWidthResponsiveUnit : $(this.classes.settings + ' select[name=max_content_width_responsive_unit]', window.parent.document),
height : $(this.classes.settings + ' select[name=full_height]', window.parent.document),
minHeight : $(this.classes.settings + ' input[name=min_height]', window.parent.document),
align : $(this.classes.settings + ' select[name=content_alignment]', window.parent.document),
aspectRatio : $(this.classes.settings + ' input[name=aspect_ratio]', window.parent.document)
});
// Events
this.elements.width.on( 'change', $.proxy(this._rowWidthChange, this));
this.elements.contentWidth.on( 'change', $.proxy(this._rowContentWidthChange, this));
this.elements.maxContentWidth.on( 'input', $.proxy(this._rowMaxContentWidthChange, this));
this.elements.maxContentWidthLarge.on( 'input', $.proxy(this._rowMaxContentWidthChange, this));
this.elements.maxContentWidthMedium.on( 'input', $.proxy(this._rowMaxContentWidthChange, this));
this.elements.maxContentWidthResponsive.on( 'input', $.proxy(this._rowMaxContentWidthChange, this));
this.elements.maxContentWidthUnit.on( 'change', $.proxy(this._rowMaxContentWidthChange, this));
this.elements.maxContentWidthLargeUnit.on( 'change', $.proxy(this._rowMaxContentWidthChange, this));
this.elements.maxContentWidthMediumUnit.on( 'change', $.proxy(this._rowMaxContentWidthChange, this));
this.elements.maxContentWidthResponsiveUnit.on( 'change', $.proxy(this._rowMaxContentWidthChange, this));
this.elements.height.on( 'change', $.proxy(this._rowHeightChange, this));
this.elements.align.on( 'change', $.proxy(this._rowHeightChange, this));
this.elements.aspectRatio.on( 'input', $.proxy(this._rowInitContentAlignment, this));
// Common Elements
this._initNodeTextColor();
this._initNodeBg();
this._initNodeClassName();
this._initNodeDimensions( 'border' );
this._initNodeDimensions( 'margin' );
this._initNodeDimensions( 'padding' );
this._rowInitContentAlignment();
},
/**
* Fires when the width field of a row changes.
*
* @since 1.3.3
* @access private
* @method _rowWidthChange
* @param {Object} e An event object.
*/
_rowWidthChange: function(e)
{
var settings = FLBuilderConfig.global,
row = this.elements.node,
content = this.elements.content.find('.fl-row-content'),
maxWidth = this.elements.maxContentWidth.val(),
maxWidthUnit = this.elements.maxContentWidthUnit.val();
row.css( 'max-width', 'none' );
content.css( 'max-width', 'none' );
if(this.elements.width.val() == 'full') {
row.removeClass('fl-row-fixed-width');
row.addClass('fl-row-full-width');
}
else {
row.removeClass('fl-row-full-width');
row.addClass('fl-row-fixed-width');
}
this._rowMaxContentWidthChange();
},
/**
* Fires when the content width field of a row changes.
*
* @since 1.3.3
* @access private
* @method _rowContentWidthChange
* @param {Object} e An event object.
*/
_rowContentWidthChange: function(e)
{
var settings = FLBuilderConfig.global,
row = this.elements.node,
content = this.elements.content.find('.fl-row-content'),
maxWidth = this.elements.maxContentWidth.val(),
maxWidthUnit = this.elements.maxContentWidthUnit.val();
row.css( 'max-width', 'none' );
content.css( 'max-width', 'none' );
if(this.elements.contentWidth.val() == 'full') {
content.removeClass('fl-row-fixed-width');
content.addClass('fl-row-full-width');
}
else {
content.removeClass('fl-row-full-width');
content.addClass('fl-row-fixed-width');
this._rowMaxContentWidthChange();
}
},
/**
* Fires when the content width field of a row changes.
*
* @since 1.3.3
* @access private
* @method _rowContentWidthChange
* @param {Object} e An event object.
*/
_rowMaxContentWidthChange: function(e)
{
var mode = FLBuilderResponsiveEditing._mode,
settings = FLBuilderConfig.global,
selector = '',
width = '';
if ( 'fixed' == this.elements.width.val() ) {
selector = '.fl-node-' + this.nodeId + '.fl-row-fixed-width, .fl-node-' + this.nodeId + ' .fl-row-fixed-width';
} else {
selector = '.fl-node-' + this.nodeId + ' .fl-row-content';
}
if ( 'default' === mode ) {
if ( '' !== this.elements.maxContentWidth.val() ) {
width = this.elements.maxContentWidth.val() + this.elements.maxContentWidthUnit.val();
} else if ( '' !== settings.row_width ) {
width = settings.row_width + settings.row_width_unit;
}
this.updateCSSRule( selector, 'max-width', width, false );
} else if ( 'large' === mode ) {
if ( '' !== this.elements.maxContentWidthLarge.val() ) {
width = this.elements.maxContentWidthLarge.val() + this.elements.maxContentWidthLargeUnit.val();
} else if ( '' !== settings.row_width_medium ) {
width = settings.row_width_medium + settings.row_width_medium_unit;
}
this.updateCSSRule( selector, 'max-width', width, true );
} else if ( 'medium' === mode ) {
if ( '' !== this.elements.maxContentWidthMedium.val() ) {
width = this.elements.maxContentWidthMedium.val() + this.elements.maxContentWidthMediumUnit.val();
} else if ( '' !== settings.row_width_medium ) {
width = settings.row_width_medium + settings.row_width_medium_unit;
}
this.updateCSSRule( selector, 'max-width', width, true );
} else if ( 'responsive' === mode ) {
if ( '' !== this.elements.maxContentWidthResponsive.val() ) {
width = this.elements.maxContentWidthResponsive.val() + this.elements.maxContentWidthResponsiveUnit.val();
} else if ( '' !== settings.row_width_responsive ) {
width = settings.row_width_responsive + settings.row_width_responsive_unit;
}
this.updateCSSRule( selector, 'max-width', width, true );
}
},
/**
* Fires when the height field of a row changes.
*
* @since 1.6.3
* @access private
* @method _rowHeightChange
* @param {Object} e An event object.
*/
_rowHeightChange: function(e)
{
var row = this.elements.node;
row.removeClass('fl-row-align-top');
row.removeClass('fl-row-align-center');
row.removeClass('fl-row-align-bottom');
row.removeClass('fl-row-default-height');
row.removeClass('fl-row-full-height');
row.removeClass('fl-row-custom-height');
if(this.elements.height.val() == 'full') {
row.addClass('fl-row-full-height');
row.addClass('fl-row-align-' + this.elements.align.val());
this.elements.minHeight.val( '' ).trigger( 'input' );
} else if(this.elements.height.val() == 'custom') {
row.addClass('fl-row-custom-height');
row.addClass('fl-row-align-' + this.elements.align.val());
this.elements.minHeight.trigger( 'input' );
} else {
row.addClass('fl-row-default-height');
row.addClass('fl-row-align-' + this.elements.align.val());
this.elements.minHeight.val( '' ).trigger( 'input' );
}
this._rowInitContentAlignment();
},
/**
* Show/hide Content Alignment field when Row Setting Form renders.
*
* @since 2.8.2
* @access private
* @method _rowInitContentAlignment
* @param {Object} e An event object.
*/
_rowInitContentAlignment: function(e)
{
if ( this.elements.height.length ) {
const height = this.elements.height.val().trim();
const form = $( '.fl-builder-settings:visible' );
const alignFieldRow = form.find( '#fl-field-content_alignment' );
const aspectValue = form.find( '#fl-field-aspect_ratio input' ).val();
if ( height === 'default' && ! aspectValue ) {
alignFieldRow.hide();
} else {
alignFieldRow.show();
}
}
},
/* Columns Settings
----------------------------------------------------------*/
/**
* Initializes a column preview.
*
* @since 1.3.3
* @access private
* @method _initRow
*/
_initColumn: function()
{
// Elements
$.extend(this.elements, {
size : $(this.classes.settings + ' input[name=size]', window.parent.document),
sizeLarge : $(this.classes.settings + ' input[name=size_large]', window.parent.document),
sizeMedium : $(this.classes.settings + ' input[name=size_medium]', window.parent.document),
sizeResponsive : $(this.classes.settings + ' input[name=size_responsive]', window.parent.document),
columnHeight : $(this.classes.settings + ' select[name=equal_height]', window.parent.document),
columnAlign : $(this.classes.settings + ' select[name=content_alignment]', window.parent.document),
responsiveOrder : $(this.classes.settings + ' select[name=responsive_order]', window.parent.document)
});
// Events
this.elements.size.on( 'input', $.proxy( this._colSizeChange, this ) );
this.elements.sizeLarge.on( 'input', $.proxy( this._colSizeChange, this ) );
this.elements.sizeMedium.on( 'input', $.proxy( this._colSizeChange, this ) );
this.elements.sizeResponsive.on( 'input', $.proxy( this._colSizeChange, this ) );
this.elements.columnHeight.on( 'change', $.proxy( this._colHeightChange, this ) );
this.elements.columnAlign.on( 'change', $.proxy( this._colHeightChange, this ) );
this.elements.responsiveOrder.on( 'change', $.proxy( this._colResponsiveOrder, this ) );
// Common Elements
this._initNodeTextColor();
this._initNodeBg();
this._initNodeClassName();
this._initNodeDimensions( 'border' );
this._initNodeDimensions( 'margin' );
this._initNodeDimensions( 'padding' );
},
/**
* Fires when the size field of a column changes.
*
* @since 1.3.3
* @access private
* @method _colSizeChange
*/
_colSizeChange: function( e )
{
var input = $( e.target ),
minWidth = 8,
maxWidth = 100 - minWidth,
size = parseFloat( input.val() ),
group = this.elements.node.closest( '.fl-col-group' ),
prev = this.elements.node.prev('.fl-col'),
next = this.elements.node.next('.fl-col'),
sibling = next.length === 0 ? prev : next,
siblings = this.elements.node.siblings('.fl-col'),
siblingsWidth = 0,
mode = FLBuilderResponsiveEditing._mode;
// Don't resize if we only have one column.
if(siblings.length === 0) {
return;
}
// Find the fallback size if we don't have a number.
if ( isNaN( size ) ) {
if ( 'large' === mode ) {
size = this.elements.size.val();
} else if ( 'medium' === mode ) {
if ( this.elements.sizeLarge.val() ) {
size = this.elements.sizeLarge.val();
} else {
size = this.elements.size.val();
}
} else if ( 'responsive' === mode ) {
if ( this.elements.sizeMedium.val() ) {
size = this.elements.sizeMedium.val();
} else if ( this.elements.sizeLarge.val() ) {
size = this.elements.sizeLarge.val();
} else {
size = 'auto';
}
}
if ( 'auto' !== size && isNaN( size ) ) {
size = minWidth;
}
}
// Default mode logic to keep columns from stacking because of resize.
if ( 'default' === mode ) {
// Adjust sizes based on other columns.
siblings.each(function() {
if($(this).data('node') == sibling.data('node')) {
return;
}
maxWidth -= parseFloat($(this)[0].style.width);
siblingsWidth += parseFloat($(this)[0].style.width);
});
// Make sure the new width isn't too small.
if(size < minWidth) {
size = minWidth;
}
// Make sure the new width isn't too big.
if(size > maxWidth) {
size = maxWidth;
}
// Update the width.
this.elements.node.css('width', size + '%');
sibling.css('width', (100 - siblingsWidth - size) + '%');
} else {
// Don't allow resizing past 100%.
if ( size > 100 ) {
size = 100;
input.val( 100 );
}
// Update the width for responsive sizes.
this.updateCSSRule( this.classes.node, {
'max-width': ( 'auto' === size ? 100 : size ) + '% !important',
'width': ( 'auto' === size ? size : size + '%' ) + ' !important',
}, undefined, true );
// Float the column only if we have a responsive size.
if ( 'responsive' === mode ) {
if ( input.val() ) {
this.updateCSSRule( this.classes.node, 'float', ( FLBuilderConfig.isRtl ? 'right' : 'left' ), true );
this.updateCSSRule( this.classes.node, 'clear', 'none', true );
} else {
this.updateCSSRule( this.classes.node, 'float', 'none', true );
this.updateCSSRule( this.classes.node, 'clear', 'both', true );
}
if ( input.val() || this._colsHaveCustomResponsiveWidth( siblings ) ) {
group.addClass( 'fl-col-group-custom-width' );
} else {
group.removeClass( 'fl-col-group-custom-width' );
}
}
}
},
/**
* Checks to see if any columns in a group have
* custom responsive widths.
*
* @since 2.2
* @access private
* @method _colsHaveCustomResponsiveWidth
* @return {Boolean}
*/
_colsHaveCustomResponsiveWidth: function( cols )
{
var settings = FLBuilderSettingsConfig.nodes,
hasWidth = false;
cols.each( function() {
var id = $( this ).data( 'node' );
if ( settings[ id ] && settings[ id ].size_responsive ) {
hasWidth = true;
}
} );
return hasWidth;
},
/**
* Fires when the equal height field of a column changes.
*
* @since 1.6.3
* @access private
* @method _colHeightChange
*/
_colHeightChange: function()
{
var parent = this.elements.node.parent('.fl-col-group');
parent.removeClass('fl-col-group-align-top');
parent.removeClass('fl-col-group-align-center');
parent.removeClass('fl-col-group-align-bottom');
if(this.elements.columnHeight.val() == 'yes') {
parent.addClass('fl-col-group-equal-height');
parent.addClass('fl-col-group-align-' + this.elements.columnAlign.val());
}
else {
parent.removeClass('fl-col-group-equal-height');
}
},
/**
* Fires when the responsive order field of a column changes.
*
* @since 1.8
* @access private
* @method _colResponsiveOrder
*/
_colResponsiveOrder: function()
{
var parent = this.elements.node.parent('.fl-col-group'),
enabledOn = this.elements.responsiveOrder.val().split(',');
if ( enabledOn.includes( 'medium' ) ) {
parent.addClass('fl-col-group-medium-reversed');
} else {
parent.removeClass('fl-col-group-medium-reversed');
}
if ( enabledOn.includes( 'mobile' ) ) {
parent.addClass('fl-col-group-responsive-reversed');
} else {
parent.removeClass('fl-col-group-responsive-reversed');
}
},
/* Module Settings
----------------------------------------------------------*/
/**
* Initializes a module preview.
*
* @since 1.3.3
* @access private
* @method _initModule
*/
_initModule: function()
{
this._initNodeClassName();
this._initNodeDimensions( 'margin' );
},
/* Default Field Previews
----------------------------------------------------------*/
/**
* Initializes the default preview logic for each
* field in a settings form.
*
* @since 1.3.3
* @access private
* @method _initDefaultFieldPreviews
* @param {Object} fields
*/
_initDefaultFieldPreviews: function( fields )
{
var fields = ! FLBuilder.isUndefined(fields) ? fields : this.elements.settings.find('.fl-field'),
field = null,
fieldType = null,
preview = null,
i = 0;
if ( FLBuilderConfig.safemode ) {
return false;
}
this._initDefaultComponentPreviews( fields );
for( ; i < fields.length; i++) {
field = fields.eq(i);
fieldType = field.data( 'type' );
preview = field.data('preview');
if(preview.type == 'refresh') {
this._initFieldRefreshPreview(field);
}
if(preview.type == 'text') {
this._initFieldTextPreview(field);
}
if(preview.type == 'css') {
this._initFieldCSSPreview(field);
}
if(preview.type == 'widget') {
this._initFieldWidgetPreview(field);
}
if(preview.type == 'font') {
this._initFieldFontPreview(field);
}
if(preview.type == 'attribute') {
this._initFieldAttributePreview(field);
}
if(preview.type == 'animation') {
this._initFieldAnimationPreview(preview, field);
}
if(preview.type == 'callback') {
this._initFieldCallbackPreview( preview, field, fieldType, fields );
}
this._initFieldUnitSelect(field);
}
},
/**
* Initializes the default preview logic specific to components.
* This mainly handles previews that are set to none because those
* type of previews are typically done via settings.js in a module.
* That isn't loaded for component settings since they are made up
* of potentially multiple nodes, so we refresh instead.
*
* @since 2.10
*/
_initDefaultComponentPreviews: function( fields ) {
const isComponent = 'dynamic_node_form' === this.elements.settings.attr( 'data-form-id' );
if ( ! isComponent ) {
return;
}
for ( let i = 0; i < fields.length; i++ ) {
const field = fields.eq( i );
const preview = field.data( 'preview' );
if ( 'none' === preview.type ) {
this._initFieldRefreshPreview(field);
}
}
},
/**
* Reinitializes the preview logic for deferred fields
* to setup events again if they have re-rendered.
*
* @since 2.9
*/
_reinitDeferredFieldPreviews: function() {
const fields = this.elements.settings.find( '.fl-field' );
for ( let i = 0; i < fields.length; i++ ) {
const field = $( fields[ i ] );
const type = field.data('type' );
const canDefer = FL.Builder.settingsForms.canDeferField( { type } );
if ( canDefer ) {
this._initDefaultFieldPreviews( field )
}
}
},
/**
* Setup callback type previews
*
* @since 2.2
* @access private
* @method _initFieldCallbackPreview
* @param {Object} preview - the preview args from the field configuration
* @param {Object} field - reference to the .fl-field DOM element
* @return void
*/
_initFieldCallbackPreview: function ( preview, field, fieldType, fields ) {
var callback,
callback_name = preview['callback'],
form = $( '.fl-builder-settings:visible', window.parent.document ),
nodeID = form.data('node'),
node = $( FLBuilder._contentClass + ' .fl-node-' + nodeID, this.layoutDoc );
if ( 'undefined' !== typeof FLBuilderPreviewCallbacks[callback_name] ) {
callback = FLBuilderPreviewCallbacks[callback_name];
} else if ( 'undefined' !== typeof window[callback_name] ) {
callback = window[callback_name];
} else if ( 'undefined' !== typeof window.parent[callback_name] ) {
callback = window.parent[callback_name];
}
if ( 'function' === typeof callback ) {
var args = {
field: field,
fields: fields,
type: fieldType,
preview: preview,
form: form,
nodeID: nodeID,
node: node,
};
// Grab input references
switch( fieldType ) {
case 'align':
case 'button-group':
case 'text':
case 'multiple-photos':
case 'video':
case 'icon':
case 'ordering':
args.input = field.find('input:not(.fl-preview-ignore)');
args.getValue = function() {
return args.input.val();
}
break;
case 'color':
args.input = field.find('input.fl-color-picker-value');
args.getValues = function() {
var value = args.input.val(),
values = {
value: value,
formattedValue: FLBuilderPreview.formatColor( value ),
};
}
break;
case 'textarea':
case 'code':
args.textarea = field.find('textarea');
args.getValue = function() {
return args.textarea.val();
}
break;
case 'select':
case 'photo-sizes':
case 'post-type':
args.select = field.find('select');
args.getValue = function() {
return args.select.val();
}
break;
case 'photo':
args.input = field.find('input[type=hidden]:not(.fl-preview-ignore)');
args.sizeSelect = field.find('select');
args.getValues = function() {
return {
value: args.input.val(),
size: args.sizeSelect.val(),
};
}
break;
case 'unit':
args.input = field.find('input[type=number]');
args.unitSelect = field.find( '.fl-field-unit-select' );
args.getValues = function() {
var inputVal = args.input.val(),
unitVal = args.unitSelect.val(),
values = {
value: inputVal,
unit: unitVal,
formattedValue: inputVal + unitVal
};
return values;
}
break;
case 'dimension':
args.inputs = field.find('input[type=number]');
args.unitSelect = field.find( '.fl-field-unit-select' );
args.getValues = function() {
var values = {
inputs: [],
props: {},
unit: args.unitSelect.val(),
};
args.inputs.each( function( i, input ) {
var input = $( input ),
val = input.val(),
prop = input.data('unit');
values.inputs.push( val );
values.props[prop] = val;
} );
return values;
}
break;
case 'animation':
args.input = field.find('input:not(.fl-preview-ignore)');
args.select = field.find('select');
args.getValues = function() {
return {
delay: args.input.val(),
style: args.select.val(),
};
}
break;
case 'link':
args.input = field.find('.fl-link-field-input-wrap input:not(.fl-preview-ignore)');
args.targetInput = field.find('input[name$=_target]');
args.noFollowInput = field.find('input[name$=_nofollow]');
args.getValues = function() {
return {
url: args.input.val(),
target: args.targetInput.val(),
noFollow: args.noFollowInput.val(),
}
}
break;
case 'shadow':
args.colorInput = field.find('input.fl-color-picker-value');
args.inputs = field.find('input[type=number]');
args.getValues = function() {
var values = {
color: args.colorInput.val(),
x: args.inputs[0].val(),
y: args.inputs[1].val(),
blur: args.inputs[2].val(),
spread: args.inputs[3].val(),
}
}
break;
case 'gradient':
// for event setup
args.inputs = field.find('input:not(.fl-preview-ignore)');
args.select = field.find('select');
// callback helpers
args.gradientInputs = {};
args.gradientInputs.type = field.find('select[name$="[type]"]');
args.gradientInputs.angle = field.find('input[name$="[angle]"]');
args.gradientInputs.position = field.find('select[name$="[position]"]');
args.gradientInputs.stops = [];
field.find('.fl-gradient-picker-colors .fl-gradient-picker-color-row').each( function( i, row ) {
row = $(row);
args.gradientInputs.stops.push({
color: row.find('.fl-gradient-picker-color input'),
stop: row.find('.fl-gradient-picker-stop input'),
});
});
args.getValues = function() {
var values = {
type: args.gradientInputs.type.val(),
angle: args.gradientInputs.angle.val(),
position: args.gradientInputs.position.val(),
stops: [],
};
for( var i in args.gradientInputs.stops ) {
var stop = args.gradientInputs.stops[i];
values.stops[i] = {
color: stop.color.val(),
stop: stop.stop.val(),
}
}
return values;
}
break;
case 'shape-transform':
args.inputs = field.find('input:not(.fl-preview-ignore)');
args.getValues = function() {
return {
scaleXSign: args.inputs.eq(0).val(),
scaleYSign: args.inputs.eq(1).val(),
skewX: args.inputs.eq(2).val(),
skewY: args.inputs.eq(3).val(),
scaleX: args.inputs.eq(4).val(),
rotate: args.inputs.eq(5).val(),
scaleY: args.inputs.eq(6).val(), /* hidden field */
}
}
break;
default:
args.input = field.find('input:not(.fl-preview-ignore)');
args.getValue = function() {
return args.input.val();
}
}
// Grab reference to responsive toggle
var toggle = field.find( '.fl-field-responsive-toggle');
args.responsiveToggle = toggle.length ? toggle : false;
callback = callback.bind( this, args );
// Loop over gathered inputs and setup event listeners
var props = {
input: 'change keyup input',
inputs: 'change keyup input',
targetInput: 'change keyup input',
noFollowInput: 'change keyup input',
colorInput: 'change input',
textarea: 'change keyup input',
select: 'change',
sizeSelect: 'change',
unitSelect: 'change',
};
for( var i in props ) {
if ( 'undefined' !== typeof args[i] ) {
args[i].on( props[i], callback );
}
}
}
},
/* Refresh Preview
----------------------------------------------------------*/
/**
* Initializes the refresh preview for a field.
*
* @since 1.3.3
* @access private
* @method _initFieldRefreshPreview
* @param {Object} field The field to preview.
*/
_initFieldRefreshPreview: function(field)
{
var fieldType = field.data('type'),
preview = field.data('preview'),
callback = $.proxy(this.delayPreview, this);
switch(fieldType) {
case 'align':
field.find( 'input:not(.fl-preview-ignore)' ).on( 'change', callback );
break;
case 'text':
field.find('input[type=text]:not(.fl-preview-ignore)').on('keyup', callback);
break;
case 'textarea':
field.find('textarea:not(.fl-preview-ignore)').on('keyup', callback);
break;
case 'select':
field.find('select:not(.fl-preview-ignore)').on('change', callback);
break;
case 'color':
field.find('.fl-color-picker-value').on('change', callback);
break;
case 'photo':
field.find('select:not(.fl-preview-ignore)').on('change', callback);
break;
case 'multiple-photos':
field.find('input:not(.fl-preview-ignore)').on('change', callback);
break;
case 'photo-sizes':
field.find('select:not(.fl-preview-ignore)').on('change', callback);
break;
case 'video':
field.find('input:not(.fl-preview-ignore)').on('change', callback);
break;
case 'multiple-audios':
field.find('input:not(.fl-preview-ignore)').on('change', callback);
break;
case 'icon':
field.find('input:not(.fl-preview-ignore)').on('change', callback);
break;
case 'form':
field.on( 'change', 'input:not(.fl-preview-ignore)', callback);
break;
case 'editor':
this._addTextEditorCallback(field, preview);
break;
case 'code':
field.find('textarea:not(.fl-preview-ignore)').on('change', callback);
break;
case 'post-type':
field.find('select:not(.fl-preview-ignore)').on('change', callback);
break;
case 'suggest':
field.find('.as-values').on('change', callback);
field.find('select:not(.fl-preview-ignore)').on('change', callback);
break;
case 'unit':
case 'dimension':
field.find('input[type=number]:not(.fl-preview-ignore)').on('input', callback);
break;
case 'ordering':
field.find('input[type=hidden]:not(.fl-preview-ignore)').on('change', callback);
break;
default:
field.on('change', callback);
}
},
/* Text Preview
----------------------------------------------------------*/
/**
* Initializes a text preview for a field.
*
* @since 1.3.3
* @access private
* @method _initFieldTextPreview
* @param {Object} field The field to preview.
*/
_initFieldTextPreview: function(field)
{
var fieldType = field.data('type'),
preview = field.data('preview'),
callback = $.proxy(this._previewText, this, preview);
switch(fieldType) {
case 'text':
field.find('input[type=text]:not(.fl-preview-ignore)').on('input', callback);
break;
case 'unit':
field.find('input[type=number]:not(.fl-preview-ignore)').on('input', callback);
break;
case 'textarea':
field.find('textarea:not(.fl-preview-ignore)').on('input', callback);
break;
case 'code':
field.find('textarea:not(.fl-preview-ignore)').on('change', callback);
break;
case 'editor':
this._addTextEditorCallback(field, preview);
break;
}
},
/**
* Runs a real time preview for text fields.
*
* @since 1.3.3
* @access private
* @method _previewText
* @param {Object} preview A preview object.
* @param {Object} e An event object.
*/
_previewText: function(preview, e)
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
element = $( selector, this.layoutDoc ),
setting = $(e.target).attr( 'name' ),
text = $('<div>' + $(e.target).val() + '</div>');
text.find('script').remove();
if ( element.length > 0 ) {
element.html( text.html() );
} else {
this.delayPreview( e );
}
},
/**
* Runs a real time preview for text editor fields.
*
* @since 1.3.3
* @access private
* @method _previewText
* @param {Object} preview A preview object.
* @param {String} id The ID of the text editor.
* @param {Object} e An event object.
*/
_previewTextEditor: function(preview, id, e)
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
element = $( selector, this.layoutDoc ),
editor = typeof window.parent.tinyMCE != 'undefined' ? window.parent.tinyMCE.get(id) : null,
textarea = $('#' + id, window.parent.document),
setting = textarea.closest( '.fl-editor-field' ).attr( 'data-name' ),
text = '';
if(editor && textarea.css('display') == 'none') {
text = $('<div>' + editor.getContent() + '</div>');
} else if ( textarea.length ) {
if ( 'undefined' == typeof switchEditors || 'undefined' == typeof switchEditors.wpautop ) {
text = $('<div>' + textarea.val() + '</div>');
} else {
text = $('<div>' + switchEditors.wpautop( textarea.val() ) + '</div>');
}
} else {
return;
}
text.find('script').remove();
if ( element.length > 0 ) {
element.html( text.html() );
} else {
this.delayPreview( e );
}
},
/**
* Callback for text editor previews.
*
* @since 1.3.3
* @access private
* @method _previewText
* @param {Object} field A field object.
* @param {Object} preview A preview object.
*/
_addTextEditorCallback: function(field, preview)
{
var id = field.find('textarea.wp-editor-area').attr('id'),
callback = null;
if(preview.type == 'refresh') {
callback = $.proxy(this.delayPreview, this);
}
else if(preview.type == 'text') {
callback = $.proxy(this._previewTextEditor, this, preview, id);
}
else {
return;
}
$('#' + id, window.parent.document).on('keyup', callback);
if(typeof window.parent.tinyMCE != 'undefined') {
editor = window.parent.tinyMCE.get(id);
editor.on('change', callback);
editor.on('keyup', callback);
}
},
/* Font Field Preview
----------------------------------------------------------*/
/**
* Initializes a font preview for a field.
*
* @since 1.3.3
* @access private
* @method _initFieldFontPreview
* @param {Object} field The field to preview.
*/
_initFieldFontPreview: function(field)
{
var fieldType = field.data('type'),
preview = field.data('preview');
// store field id
preview.id = field.attr( 'id' );
var callback = $.proxy(this._previewFont, this, preview);
if( fieldType == 'font' ){
field.find('.fl-font-field').on('change', 'select', callback);
}
},
/**
* Gets the selected font and weight, and make the necessary updates for live preview.
*
* @since 1.6.3
* @access private
* @see _getPreviewSelector
* @see _buildFontStylesheet
* @see updateCSSRule
*
* @method _previewFont
* @param {Object} preview An object with data about the current field and css selector.
* @param {[type]} e The current field.
*/
_previewFont: function( preview, e ){
var parent = $( e.delegateTarget ),
font = parent.find( '.fl-font-field-font' ),
selected = $( font ).find( ':selected' ),
fontGroup = selected.parent().attr( 'label' ),
weight = parent.find( '.fl-font-field-weight' ),
uniqueID = preview.id + '-' + this.nodeId,
selector = this._getPreviewSelector( this.classes.node, preview.selector ),
important = preview.important ? ' !important' : '',
val = ''
// If the selected font is a Google Font, build the font stylesheet
if( fontGroup == 'Google' || fontGroup == 'Recently Used' ){
this._buildFontStylesheet( uniqueID, font.val(), weight.val() );
}
val = font.val();
// Some google fonts that end with numbers need to be wrapped in quotes.
var checkNum = new RegExp('[0-9]');
if( checkNum.test( font.val() ) ){
val = '"' + font.val() + '"';
}
this.updateCSSRule( selector, 'font-family', 'Default' === font.val() ? '' : val + important );
this.updateCSSRule( selector, 'font-weight', 'default' === weight.val() ? '' : weight.val() + important );
},
/**
* Gets all fonts store insite FLBuilderPreview._fontsList and renders the respective
* link tag with Google Fonts.
*
* @since 1.6.3
* @access private
*
* @method _buildFontStylesheet
* @param {String} id The field unique ID.
* @param {String} font The selected font.
* @param {String} weight The selected weight.
*/
_buildFontStylesheet: function( id, font, weight ){
var url = FLBuilderConfig.googleFontsUrl,
href = '',
fontObj = {},
fontArray = {};
// build the font family / weight object
fontObj[ font ] = [ weight ];
// adds to the list of fonts for this font setting
FLBuilderPreview._fontsList[ id ] = fontObj;
// iterate over the keys of the FLBuilderPreview._fontsList object
Object.keys( FLBuilderPreview._fontsList ).forEach( function( fieldFont ) {
var field = FLBuilderPreview._fontsList[ fieldFont ];
// iterate over the font / weight object
Object.keys( field ).forEach( function( key ) {
// get the weights of this font
var weights = field[ key ];
fontArray[ key ] = fontArray[ key ] || [];
// remove duplicates from the values array
weights = weights.filter( function( weight ) {
return fontArray[ key ].indexOf( weight ) < 0;
});
fontArray[ key ] = fontArray[ key ].concat( weights );
});
});
$.each( fontArray, function( font, weight ){
if ( 'Molle' === font ) {
href += font + ':i|';
} else {
/**
* If default is set for weight, then we have to load all the weights so Google
* can use the intended default.
* This wrecked my head.
*/
if ( weight[0] == 'default' ) {
weight = [FLBuilderFontFamilies.google[font].join('|')]
}
href += font + ':' + weight.join() + '|';
}
} );
// remove last character and replace spaces with plus signs
href = url + href.slice( 0, -1 ).replace( ' ', '+' );
if( $( '#fl-builder-google-fonts-preview', this.layoutDoc ).length < 1 ){
$( 'head', this.layoutDoc ).append(
$( '<link>' )
.attr( 'id', 'fl-builder-google-fonts-preview' )
.attr( 'type', 'text/css' )
.attr( 'rel', 'stylesheet' )
.attr( 'href', href )
);
} else{
$( '#fl-builder-google-fonts-preview', this.layoutDoc ).attr( 'href', href );
}
},
/* CSS Preview
----------------------------------------------------------*/
/**
* Initializes CSS previews for a node.
*
* @since 1.3.3
* @since 1.6.1 Reworked to accept a preview.rules array.
* @access private
* @method _initFieldCSSPreview
* @param {Object} field A field object.
*/
_initFieldCSSPreview: function( field )
{
var preview = field.data( 'preview' ),
i = null;
if ( 'undefined' != typeof preview.rules ) {
for ( i in preview.rules ) {
this._initFieldCSSPreviewCallback( field, preview.rules[ i ] );
}
}
else {
this._initFieldCSSPreviewCallback( field, preview );
}
},
/**
* Initializes CSS preview callbacks for a field.
*
* @since 1.6.1
* @access private
* @method _initFieldCSSPreviewCallback
* @param {Object} field A field object.
* @param {Object} preview The preview data object.
*/
_initFieldCSSPreviewCallback: function( field, preview )
{
switch ( field.data( 'type' ) ) {
case 'align':
case 'justify':
case 'grid-auto-flow':
field.find( 'input:not(.fl-preview-ignore)' ).on( 'change', $.proxy( this._previewCSS, this, preview, field ) );
break;
case 'object-fit':
// Position Sub-field
field.find( 'input:not(.fl-preview-ignore)' ).on( 'input', $.proxy( this._previewCSS, this, preview, field ) );
// Fit Button Group
const fit = field.find( 'input[type="hidden"]:not(.fl-preview-ignore)' )
fit.on( 'change', $.proxy( this._previewCSS, this, preview, field ) );
fit.on( 'change', $.proxy( this._previewFieldObjectFit, this, preview, field ) );
break;
case 'border':
field.find( 'select:not(.fl-preview-ignore)' ).on( 'change', $.proxy( this._previewBorderCSS, this, preview, field ) );
field.find( 'input[type=number]:not(.fl-preview-ignore)' ).on( 'input', $.proxy( this._previewBorderCSS, this, preview, field ) );
field.find( 'input[type=hidden]:not(.fl-preview-ignore)' ).on( 'change', $.proxy( this._previewBorderCSS, this, preview, field ) );
break;
case 'color':
field.find( '.fl-color-picker-value' ).on( 'change', $.proxy( this._previewColorCSS, this, preview, field ) );
break;
case 'dimension':
field.find( 'input[type=number]:not(.fl-preview-ignore)' ).on( 'input', $.proxy( this._previewDimensionCSS, this, preview, field ) );
break;
case 'gradient':
field.find( 'select:not(.fl-preview-ignore)' ).on( 'change', $.proxy( this._previewGradientCSS, this, preview, field ) );
field.find( '.fl-gradient-picker-angle' ).on( 'input', $.proxy( this._previewGradientCSS, this, preview, field ) );
field.find( '.fl-color-picker-value' ).on( 'change', $.proxy( this._previewGradientCSS, this, preview, field ) );
field.find( '.fl-gradient-picker-stop' ).on( 'input', $.proxy( this._previewGradientCSS, this, preview, field ) );
break;
case 'photo':
field.find( 'select:not(.fl-preview-ignore)' ).on( 'change', $.proxy( this._previewCSS, this, preview, field ) );
break;
case 'select':
case 'aspect-ratio':
case 'placement':
field.find( 'select:not(.fl-preview-ignore)' ).on( 'change', $.proxy( this._previewCSS, this, preview, field ) );
break;
case 'shadow':
field.find( 'input:not(.fl-preview-ignore)' ).on( 'input', $.proxy( this._previewShadowCSS, this, preview, field ) );
field.find( '.fl-color-picker-value' ).on( 'change', $.proxy( this._previewShadowCSS, this, preview, field ) );
break;
case 'text':
field.find( 'input[type=text]:not(.fl-preview-ignore)' ).on( 'keyup input change', $.proxy( this._previewCSS, this, preview, field ) );
break;
case 'hidden':
field.find( 'input[type=hidden]:not(.fl-preview-ignore)' ).on( 'change', $.proxy( this._previewCSS, this, preview, field ) );
break;
case 'typography':
field.find( 'select:not(.fl-preview-ignore)' ).on( 'change', $.proxy( this._previewTypographyCSS, this, preview, field ) );
field.find( 'input[type=number]:not(.fl-preview-ignore)' ).on( 'input', $.proxy( this._previewTypographyCSS, this, preview, field ) );
field.find( 'input[type=hidden]:not(.fl-preview-ignore)' ).on( 'change', $.proxy( this._previewTypographyCSS, this, preview, field ) );
break;
case 'unit':
field.find( 'input[type=number]:not(.fl-preview-ignore)' ).on( 'input', $.proxy( this._previewCSS, this, preview, field ) );
break;
case 'button-group':
case 'grid-tracklist':
case 'grid-area':
field.find( 'input[type=hidden]:not(.fl-preview-ignore)').on( 'change', $.proxy( this._previewCSS, this, preview, field ) );
break;
case 'flex':
case 'size':
field.find( 'input[type=number]:not(.fl-preview-ignore)' ).on( 'input', $.proxy( this._previewCSS, this, preview, field ) );
field.find( 'input[type=hidden]:not(.fl-preview-ignore)').on( 'change', $.proxy( this._previewCSS, this, preview, field ) );
break;
case 'background':
const bg = field.find( 'input[type=hidden]:not(.fl-preview-ignore)').on( 'change', $.proxy( this._previewCSS, this, preview, field ) );
}
},
/**
* Updates the CSS rule for a preview.
*
* @since 1.3.3
* @access private
* @method _previewCSS
* @param {Object} preview A preview object.
* @param {Object} field A preview field element.
* @param {Object} e An event object.
*/
_previewCSS: function( preview, field, e )
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
property = preview.property,
unit = this._getPreviewCSSUnit( preview, field, e ),
input = $( e.target ),
subVals = preview.substitute_values, /* substitute the css value for a staticly-defined alternative */
rawVal = input.val(),
value = subVals && Object.keys( subVals ).includes( rawVal ) ? subVals[rawVal] : rawVal,
responsive = input.closest( '.fl-field-responsive-setting' ).length ? true : false,
important = preview.important && '' !== value ? ' !important' : '';
if ( property.indexOf( 'image' ) > -1 && value ) {
value = 'url(' + value + ')';
} else if ( '%' === unit && 'opacity' === property ) {
value = parseInt( value ) / 100;
} else if ( '' !== value && ! isNaN(value) ) {
value += unit;
}
if ( preview.format_value ) {
value = wp.i18n.sprintf( preview.format_value, value )
}
/**
* Support for sub_value key when previewing compound fields.
*/
if ( undefined !== preview.sub_value ) {
const name = input.attr('name')
if ( 'string' === typeof preview.sub_value && ! name.endsWith( `[${preview.sub_value}]` ) ) {
return
} else if ( 'object' === typeof preview.sub_value && ! name.includes(`[${preview.sub_value.setting_name}]`) ) {
return
}
}
/**
* Support for enabled property
* Meant to closely match FLBuilderCSS::is_rule_enabled()
*/
if ( 'boolean' === typeof preview.enabled ) {
return preview.enabled
} else if ( preview.enabled && 'object' === typeof preview.enabled ) {
const form = $( '.fl-builder-settings:visible', window.parent.document )
const settings = FLBuilder._getSettings( form )
// Test each setting in the object
for( let name in preview.enabled ) {
const test = preview.enabled[name]
if ( 'object' === typeof test ) {
if ( 'nearest_value' in test ) {
const mode = FLBuilderResponsiveEditing._mode
const inherited = this.getInheritedSettingValue( name, mode, settings )
if ( 'string' === typeof test.nearest_value && test.nearest_value !== inherited ) {
return
} else if ( Array.isArray( test.nearest_value ) && ! test.nearest_value.includes( inherited ) ) {
return
}
}
} else if ( Array.isArray( preview.enabled[name] ) ) {
if ( ! preview.enabled[name].some( v => v === settings[name] ) ) {
return
}
} else if ( preview.enabled[name] !== settings[name] ) {
return
}
}
}
if ( Array.isArray( property ) ) {
for( let i in property ) {
this.updateCSSRule( selector, property[i], value + important, responsive );
}
} else {
this.updateCSSRule( selector, property, value + important, responsive );
}
},
/**
* Find the nearest inherited value for a particular setting from a given breakpoint.
*/
getInheritedSettingValue: function( settingBaseName, currentBreakpoint, settings ) {
const isDefault = '' === currentBreakpoint || 'default' === currentBreakpoint
let name = isDefault ? settingBaseName : `${settingBaseName}_${currentBreakpoint}`
if ( undefined !== settings[name] && '' !== settings[name] ) {
return settings[name]
}
// Meant to be in reverse (most-to-least specific)
const current = 'default' === currentBreakpoint ? '' : currentBreakpoint
let breakpoints = [ 'responsive', 'medium', 'large', '' ]
let i = breakpoints.indexOf( current )
if ( -1 === i ) {
return null
}
breakpoints = breakpoints.slice(i)
for( let i in breakpoints ) {
const breakpoint = breakpoints[i]
name = '' === breakpoint ? settingBaseName : `${settingBaseName}_${breakpoint}`
if ( undefined !== settings[name] && '' !== settings[name] ) {
return settings[name]
}
}
return null
},
/* Border Field CSS Preview
----------------------------------------------------------*/
/**
* Updates the CSS rule for a border preview.
*
* @since 2.2
* @access private
* @method _previewBorderCSS
* @param {Object} preview A preview object.
* @param {Object} field A field object.
* @param {Object} e An event object.
*/
_previewBorderCSS: function( preview, field, e )
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
target = $( e.target ),
field = target.closest( '.fl-field' ),
wrap = target.closest( '.fl-compound-field-setting' ),
property = wrap.data( 'property' ),
value = target.val(),
unit = wrap.find( '.fl-field-unit-select' ),
responsive = target.closest( '.fl-field-responsive-setting' ).length ? true : false,
important = preview.important && '' !== value ? ' !important' : '';
preview.property = property;
if ( 'border-color' === property ) {
this._previewColorCSS( preview, field, e );
} else if ( 'border-width' === property || 'border-radius' === property ) {
this._previewDimensionCSS( preview, field, e );
} else if ( 'box-shadow' === property ) {
this._previewShadowCSS( preview, wrap, e );
} else {
if ( 'border-style' === property ) {
field.find( '.fl-border-field-width input:visible' ).trigger( 'input' );
}
this.updateCSSRule( selector, property, value + important, responsive );
}
},
/* Color Field CSS Preview
----------------------------------------------------------*/
/**
* Updates the CSS rule for a color preview.
*
* @since 1.3.3
* @access private
* @method _previewColorCSS
* @param {Object} preview A preview object.
* @param {Object} field A field object.
* @param {Object} e An event object.
*/
_previewColorCSS: function(preview, field, e)
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
input = $(e.target),
value = input.val(),
responsive = input.closest( '.fl-field-responsive-setting' ).length ? true : false,
important = preview.important && '' !== value ? ' !important' : '';
// Add # to hex values if needed
if ( ! CSS.supports( 'color', value ) && CSS.supports( 'color', '#' + value ) ) {
value = '#' + value;
}
this.updateCSSRule( selector, preview.property, value + important, responsive );
},
/* Dimension Field CSS Preview
----------------------------------------------------------*/
/**
* Updates the CSS rule for a dimension field preview.
*
* @since 2.0.7
* @access private
* @method _previewDimensionCSS
* @param {Object} preview A preview object.
* @param {Object} field A preview field element.
* @param {Object} e An event object.
*/
_previewDimensionCSS: function( preview, field, e )
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
property = preview.property,
key = field.attr( 'id' ).replace( 'fl-field-', '' ),
dimension = $( e.target ).data( 'unit' ),
value = this._getDimensionValue( preview, field, dimension, e ),
responsive = field.find( '.fl-field-responsive-setting' ).length ? true : false,
important = preview.important && '' !== value ? ' !important' : '';
if ( 'border-radius' === property ) {
property = 'border-' + dimension.replace( '_', '-' ) + '-radius';
} else if ( 'border-width' === property ) {
property = 'border-' + dimension + '-width';
} else if ( 'gap' === property ) {
property = dimension + '-' + property;
} else {
property = property + '-' + dimension;
}
this.updateCSSRule( selector, property, value + important, responsive );
if ( 'margin' === key || 'padding' === key || 'border' === key ) {
if ( this.elements.node.find('.fl-bg-slideshow').length ) {
FLBuilder._resizeLayout();
}
}
},
/**
* Get a preview dimension value for a property.
*
* @since 2.2
* @access private
* @param {Object} preview A preview object.
* @param {Object} field A preview field element.
* @param {String} dimension The dimension key.
* @param {Object} e An event object.
* @return {String}
*/
_getDimensionValue: function( preview, field, dimension, e )
{
var input = $( e.target ),
value = input.val(),
unit = '';
value = value.toLowerCase().replace( /[^a-z0-9%.\-]/g, '' );
if ( null !== value && '' !== value && ! isNaN( value ) ) {
unit = this._getPreviewCSSUnit( preview, field, e );
value = parseFloat( value ) + ( unit ? unit : 'px' );
} else if ( input.attr( 'placeholder' ) ) {
unit = this._getPreviewCSSUnit( preview, field, e );
value = parseFloat( input.attr( 'placeholder' ) ) + ( unit ? unit : 'px' );
}
return value;
},
/**
* Get the value's unit for a CSS preview.
*
* @since 2.2
* @access private
* @param {Object} preview A preview object.
* @param {Object} field A preview field element.
* @param {Object} e An event object.
* @return {String}
*/
_getPreviewCSSUnit: function( preview, field, e )
{
var input = $( e.target ),
mode = FLBuilderResponsiveEditing._mode,
compound = input.closest( '.fl-compound-field-setting' ).length ? true : false,
responsive = input.closest( '.fl-field-responsive-setting' ).length ? true : false,
select = null;
if ( compound ) {
select = input.closest( '.fl-compound-field-setting' ).find( '.fl-field-unit-select' );
} else if ( responsive ) {
select = input.closest( '.fl-field-responsive-setting' ).find( '.fl-field-unit-select' );
} else {
select = field.find( '.fl-field-unit-select' );
}
if ( select && select.length ) {
if ( 'SELECT' === select.prop( 'tagName' ) ) {
return select.val();
} else {
return select.text();
}
} else if ( preview.unit ) {
return preview.unit;
}
return '';
},
/**
* Initializes the custom unit select for a field.
*
* @since 2.2
* @access private
* @method _initFieldUnitSelect
* @param {Object} field
*/
_initFieldUnitSelect: function(field)
{
field.find( '.fl-field-unit-select' ).on( 'change', function() {
var select = $( this ),
responsive = select.closest( '.fl-field-responsive-setting' ),
field = select.closest( '.fl-field' );
if ( responsive.length ) {
responsive.find( 'input' ).trigger( 'input' );
} else {
field.find( 'input' ).trigger( 'input' );
}
} );
},
/* Gradient Field CSS Preview
----------------------------------------------------------*/
/**
* Updates the CSS rule for a gradient preview.
*
* @since 2.2
* @access private
* @method _previewGradientCSS
* @param {Object} preview A preview object.
* @param {Object} field A field object.
* @param {Object} e An event object.
*/
_previewGradientCSS: function( preview, field, e )
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
type = field.find( '.fl-gradient-picker-type-select' ).val(),
angle = field.find( '.fl-gradient-picker-angle' ).val(),
position = field.find( '.fl-gradient-picker-position' ).val(),
colors = field.find( '.fl-color-picker-value' ),
stops = field.find( '.fl-gradient-picker-stop input' ),
values = [],
value = '',
important = '';
colors.each( function( i ) {
var color = $( this ).val(),
stop = stops.eq( i ).val();
if ( '' === color ) {
color = 'rgba(255,255,255,0)';
}
if ( color.indexOf( 'rgb' ) < 0 ) {
color = '#' + color;
}
if ( isNaN( stop ) ) {
stop = 0;
}
values.push( color + ' ' + stop + '%' );
} );
values = values.join( ', ' );
if ( 'linear' === type ) {
if ( isNaN( angle ) ) {
angle = 0;
}
value = 'linear-gradient(' + angle + 'deg, ' + values + ')';
} else {
value = 'radial-gradient(at ' + position + ', ' + values + ')';
}
important = preview.important && '' !== value ? ' !important' : '';
this.updateCSSRule( selector, preview.property, value + important );
},
/* Shadow Field CSS Preview
----------------------------------------------------------*/
/**
* Updates the CSS rule for a shadow preview.
*
* @since 2.2
* @access private
* @method _previewShadowCSS
* @param {Object} preview A preview object.
* @param {Object} field A field object.
* @param {Object} e An event object.
*/
_previewShadowCSS: function( preview, field, e )
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
color = field.find( '.fl-shadow-field-color input' ).val(),
horizontal = field.find( '.fl-shadow-field-horizontal input' ).val(),
vertical = field.find( '.fl-shadow-field-vertical input' ).val(),
blur = field.find( '.fl-shadow-field-blur input' ).val(),
spread = field.find( '.fl-shadow-field-spread input' ).val(),
hasSpread = field.find( '.fl-shadow-field-spread input' ).length ? true : false,
responsive = $( e.target ).closest( '.fl-field-responsive-setting' ).length ? true : false,
value = '',
important = '';
if ( '' !== color ) {
if ( '' === horizontal ) {
horizontal = 0;
}
if ( '' === vertical ) {
vertical = 0;
}
if ( '' === blur ) {
blur = 0;
}
if ( '' === spread ) {
spread = 0;
}
color = FLBuilderPreview.formatColor( color )
value = horizontal + 'px ';
value += vertical + 'px ';
value += blur + 'px ';
if ( hasSpread ) {
value += spread + 'px ';
}
value += color;
value += important;
}
important = preview.important && '' !== value ? ' !important' : '';
this.updateCSSRule( selector, preview.property, value, responsive );
},
/* Typography Field CSS Preview
----------------------------------------------------------*/
/**
* Updates the CSS rule for a typography preview.
*
* @since 2.2
* @access private
* @method _previewTypographyCSS
* @param {Object} preview A preview object.
* @param {Object} field A field object.
* @param {Object} e An event object.
*/
_previewTypographyCSS: function( preview, field, e )
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
target = $( e.target ),
field = target.closest( '.fl-field' ),
wrap = target.closest( '.fl-compound-field-setting' ),
property = wrap.data( 'property' ),
value = target.val(),
unit = wrap.find( '.fl-field-unit-select' ),
responsive = target.closest( '.fl-field-responsive-setting' ).length ? true : false,
important = preview.important && '' !== value ? ' !important' : '';
if ( 'font-family' === property ) {
preview.id = field.attr( 'id' );
this._previewFont( preview, { delegateTarget: wrap } );
} else if ( 'text-shadow' === property ) {
preview.property = 'text-shadow';
this._previewShadowCSS( preview, wrap, e );
} else {
if ( unit.length && '' !== value ) {
if ( 'vw' === unit.val() ) {
// calc(14px + 5vw);
value = 'calc(' + FLBuilderConfig.global.responsive_base_fontsize + 'px + ' + value + 'vw)'
} else {
value += 'SELECT' === unit.prop( 'tagName' ) ? unit.val() : 'px';
}
}
this.updateCSSRule( selector, property, value + important, responsive );
}
},
/* Widget Preview
----------------------------------------------------------*/
/**
* Initializes the attribute preview for a field.
*
* @since 2.2
* @access private
* @method _initFieldAttributePreview
* @param {Object} field The field to preview.
*/
_initFieldAttributePreview: function(field)
{
var preview = field.data('preview'),
attrName = preview.attribute,
input = field.find('input'),
value = field.val(),
formatValue = window[preview.format_callback];
var fullSelector = this._getPreviewSelector( this.classes.node, preview.selector ),
element = $( fullSelector, this.layoutDoc );
var callback = this._previewAttribute.bind( this, input, element, attrName, formatValue );
input.on('change', callback );
input.on('keyup', callback );
input.on('input', callback );
},
/**
* Runs a real time preview for attribute fields.
*
* @since 2.2
* @access private
* @method _previewAttribute
* @param {Object} input A ref to the input control.
* @param {Object} element A ref to the selected element within the node.
* @param String attrName The name of the attribute to be changed.
*/
_previewAttribute: function( input, element, attrName, formatValue ) {
var value = input.val();
if ( 'function' === typeof formatValue ) {
value = formatValue( value );
}
for (i = 0; i < element.length; i++) {
element[i].setAttribute( attrName, value );
}
},
/**
* Initializes the preview for a WordPress widget.
*
* @since 1.3.3
* @access private
* @method _initFieldWidgetPreview
* @param {Object} field A field object.
*/
_initFieldWidgetPreview: function(field)
{
var callback = $.proxy(this.delayPreview, this);
field.find('input').on('keyup', callback);
field.find('input[type=checkbox]').on('click', callback);
field.find('textarea').on('keyup', callback);
field.find('select').on('change', callback);
},
/* Animation Field Preview
----------------------------------------------------------*/
/**
* Initializes animation previews.
*
* @since 2.2
* @access private
* @method _initFieldAnimationPreview
*/
_initFieldAnimationPreview: function( preview, field )
{
field.find( '.fl-animation-field-style select' ).on( 'change', $.proxy( this._previewAnimationField, this, preview, field ) );
field.find( '.fl-animation-field-duration input' ).on( 'input', $.proxy( this._previewAnimationField, this, preview, field ) );
},
/**
* Previews an animation field.
*
* @since 2.2
* @access private
* @method _previewAnimationField
*/
_previewAnimationField: function( preview, field, e )
{
var selector = this._getPreviewSelector( this.classes.node, preview.selector ),
element = $( selector, this.layoutDoc ),
animation = field.find( '.fl-animation-field-style select' ),
duration = field.find( '.fl-animation-field-duration input' ),
options = animation[0].options;
element.removeClass( 'fl-animated' );
element.removeClass( 'fl-animation' );
element.css( 'animation-duration', '' );
for ( var i = 0; i < options.length; i++ ) {
element.removeClass( 'fl-' + options[i].value );
}
if ( '' !== animation.val() ) {
element.addClass( 'fl-animation' );
element.addClass( 'fl-' + animation.val() );
element.data( 'animation-delay', 0 );
element.data( 'animation-duration', duration.val() );
}
FLBuilderLayoutModules._doModuleAnimation.apply( element );
},
_previewFieldObjectFit: function( preview, field, e ) {
const selector = this._getPreviewSelector( this.classes.node, preview.selector ),
element = $( selector, this.layoutDoc ).closest('.fl-module').get(0),
className = 'fl-fill-container',
fit = field.find( '.fl-button-group-field input' ).val();
if ( '' === fit && element.classList.contains( className ) ) {
element.classList.remove( className )
}
if ( '' !== fit && ! element.classList.contains( className ) ) {
element.classList.add( className )
}
},
/**
* Returns a formatted selector string for a preview.
*
* @since 1.6.1
* @access private
* @method _getPreviewSelector
* @param {String} selector A CSS selector string.
* @return {String}
*/
_getPreviewSelector: function( prefix, selector )
{
return FLBuilderPreview.getFormattedSelector.call( this, prefix, selector );
},
/**
* Converts words in a string to upper case.
*
* @since 2.2
* @method toUpperCaseWords
* @param {String} string
* @return {String}
*/
toUpperCaseWords: function( string ) {
return string.charAt( 0 ).toUpperCase() + string.slice( 1 );
},
};
/**
* Format a color value for use with CSS
* @since 2.2
* @method formatColor
* @param {String} value
* @return {String}
*/
FLBuilderPreview.formatColor = function( value ) {
// Check if hex needs hash prepended
if ( ! CSS.supports( 'color', value ) && CSS.supports( 'color', '#' + value ) ) {
value = '#' + value;
}
return value;
};
FLBuilderPreviewCallbacks = {
/**
* Setup the shape when selected
*/
previewShape: function( args, e ) {
var position = args.preview.position,
prefix = args.preview.prefix,
form = args.form,
yOrientationInputName = prefix + 'transform[][scaleYSign]';
yOrientation = form.find('input[name="' + yOrientationInputName + '"]');
if ( 'bottom' === position ) {
yOrientation.val('invert');
} else {
yOrientation.val('');
}
yOrientation.trigger('change');
// Cause refresh
this.delayPreview();
},
/**
* Preview the layer's width, height and Y offset
*/
previewShapeLayerSize: function( args, e ) {
var values = args.getValues(),
unitValue = values.unit,
width = values.props.width,
height = values.props.height,
top = values.props.top,
/* static data from field config */
prefix = args.preview.prefix,
position = args.preview.position,
layerSelector = this._getPreviewSelector( this.classes.node, '.fl-builder-' + position + '-edge-layer' ),
shapeSelector = layerSelector + ' > *',
/* the align field */
align = args.form.find('[name="' + prefix + 'align"]'),
alignValue = align.val(),
alignParts = alignValue.split(' '),
yAlign = alignParts[0],
xAlign = alignParts[1],
/* calculated props */
shapeField = args.form.find('[name="' + prefix + 'shape"]'),
shapeValue = shapeField.val(),
shapePreset = FLBuilderConfig.presets.shape[shapeValue]
shapeProps = {};
// Defaults
shapeProps.width = '100%';
shapeProps.left = 'auto';
shapeProps.right = 'auto';
shapeProps.height = 'auto';
shapeProps.top = 'auto';
shapeProps.bottom = 'auto';
// Width
if ( width ) {
shapeProps.width = width + unitValue;
var offset = ( width / 2 ) + unitValue;
switch( xAlign ) {
case 'left':
shapeProps.left = '0';
shapeProps.right = 'auto';
break;
case 'right':
shapeProps.left = 'auto';
shapeProps.right = '0';
break;
case 'center':
shapeProps.left = 'calc( 50% - ' + offset + ')';
shapeProps.right = 'auto';
break;
}
}
this.updateCSSRule( shapeSelector, 'width', shapeProps.width );
this.updateCSSRule( shapeSelector, 'left', shapeProps.left );
this.updateCSSRule( shapeSelector, 'right', shapeProps.right );
// Height
// We need a height for vertical centering to work, but it doesn't have to be explicit.
var heightOffset;
if ( height ) {
heightOffset = ( height / 2 ) + unitValue;
} else if ( width ) {
var viewBoxHeight = shapePreset.data.viewBox.width,
impliedHeight = ( width / viewBoxHeight ) * 100;
heightOffset = ( impliedHeight / 2 ) + unitValue ;
} else {
heightOffset = ''
}
if ( height ) {
shapeProps.height = height + unitValue;
}
switch( yAlign ) {
case 'top':
shapeProps.top = '0';
shapeProps.bottom = 'auto';
break;
case 'bottom':
shapeProps.top = 'auto';
shapeProps.bottom = '0';
break;
case 'center':
shapeProps.top = 'calc( 50% - ' + heightOffset + ')';
shapeProps.bottom = 'auto';
break;
}
this.updateCSSRule( shapeSelector, 'height', shapeProps.height + ' !important' );
this.updateCSSRule( shapeSelector, 'top', shapeProps.top );
this.updateCSSRule( shapeSelector, 'bottom', shapeProps.bottom );
// Y offset
if ( '' === top ) {
this.updateCSSRule( layerSelector, position, '0' );
} else {
this.updateCSSRule( layerSelector, position, top + unitValue );
}
},
previewShapeAlign: function( args, e ) {
// Let width and height preview do the work.
var prefix = args.preview.prefix,
widthField = args.form.find('[name="' + prefix + 'size_width"]');
widthField.trigger('input');
},
/**
* Process the fill style when toggled
*
* @param {Object} args - a collection of helper references setup on field init
* @param Event e - the event passed by the event listener
* @return void
*/
previewShapeFillStyle: function( args, e ) {
var value = args.input.val(),
preview = args.preview,
prefix = args.preview.prefix,
linearGradientId = 'fl-row-' + args.nodeID + '-' + prefix + '-linear-gradient',
radialGradientId = 'fl-row-' + args.nodeID + '-' + prefix + '-radial-gradient',
patternId = 'fl-row-' + args.nodeID + '-' + prefix + '-pattern',
form = args.form;
if ( 'undefined' !== typeof value ) {
var selector = this._getPreviewSelector( this.classes.node, preview.selector );
switch( value ) {
case 'color':
var colorValue = form.find('[name=' + prefix + 'fill_color]').val();
this.updateCSSRule( selector, 'fill', FLBuilderPreview.formatColor( colorValue ) );
break;
case 'gradient':
var gradientField = form.find('#fl-field-' + prefix + 'fill_gradient'),
gradientType = gradientField.find('select[name$="[type]"]').val();
var gradientId = 'radial' === gradientType ? radialGradientId : linearGradientId ;
this.updateCSSRule( selector, 'fill', 'url(#' + gradientId + ')' );
break;
case 'pattern':
var fill = 'url(#' + patternId + ')';
this.updateCSSRule( selector, 'fill', fill );
}
}
},
/**
* Process the gradient control values
*
* @param {Object} args - a collection of helper references setup on field init
* @param Event e - the event passed by the event listener
* @return void
*/
previewShapeGradientFill: function( args, e ) {
var values = args.getValues(),
node = args.node,
preview = args.preview,
layerSelector = '.fl-builder-' + preview.position + '-edge-layer',
gradientDef = node.find( layerSelector + ' ' + values.type + 'Gradient' ),
fill = 'url(#' + gradientDef.attr('id') + ')',
shapeSelector = this._getPreviewSelector( this.classes.node, layerSelector + ' .fl-shape' );
this.updateCSSRule( shapeSelector, 'fill', fill );
// Set stops
var stopEls = gradientDef.find('stop');
for( var i in values.stops ) {
var stopVal = values.stops[i],
stop = stopEls.eq(i),
color = stopVal.color,
offset = stopVal.stop,
opacity = 1;
if ( color.indexOf( 'rgba' ) === 0 ) {
var rawValues = color.substring( color.indexOf('(') + 1, color.lastIndexOf(')') ).split( /,\s*/ );
opacity = rawValues.pop();
color = 'rgb(' + rawValues.join(',') + ')';
}
stop.attr('stop-color', FLBuilderPreview.formatColor( color ) );
stop.attr('stop-opacity', opacity );
stop.attr('offset', offset + '%' );
}
// Set Angle
if ( 'linear' === values.type && 'undefined' !== typeof gradientDef[0] ) {
gradientDef[0].setAttribute( 'gradientTransform', 'rotate(' + values.angle + ' .5 .5 )' );
}
// Set Position
if ( 'radial' === values.type ) {
// Split string by space
parts = values.position.split(' ');
var x = parts[0],
y = parts[1],
cx,
cy,
r;
switch( x ) {
case 'top':
case 'left':
cx = 0;
break;
case 'center':
cx = .5;
break;
case 'bottom':
case 'right':
cx = 1;
break;
}
switch( y ) {
case 'top':
case 'left':
cy = 0;
break;
case 'center':
cy = .5;
break;
case 'bottom':
case 'right':
cy = 1;
break;
}
r = .5;
if ( cx !== .5 || cy !== .5 ) r = 1;
gradientDef.attr( 'cx', cx );
gradientDef.attr( 'cy', cy );
gradientDef.attr( 'r', r );
}
},
/**
* Process the transform control values
*
* @param {Object} args - a collection of helper references setup on field init
* @param Event e - the event passed by the event listener
* @return void
*/
previewShapeTransform: function ( args, e ) {
var form = args.form,
preview = args.preview,
prefix = preview.prefix,
layerSelector = this._getPreviewSelector( this.classes.node, preview.selector ),
shapeSelector = layerSelector + ' > *',
values = args.getValues(),
shapeTransforms = [];
Object.keys( values ).map( function( prop ) {
var value = values[prop];
var unit = '',
sign = '';
switch( prop ) {
case 'scaleXSign':
case 'scaleYSign':
return;
case 'scaleX':
case 'scaleY':
if ( !value || '' === value || 0 === value ) value = '1';
sign = 'scaleX' === prop ? values['scaleXSign'] : values['scaleYSign'] ; // Positive or negative?
if ( 'invert' === sign ) {
value = -Math.abs( value );
} else {
value = Math.abs( value );
}
shapeTransforms.push( prop + '(' + value + ')' ); // scale has no unit
break;
case 'translateX':
case 'translateY':
if ( value ) {
unit = 'px';
shapeTransforms.push( prop + '(' + value + unit + ')' );
}
break;
case 'skewX':
case 'skewY':
if ( value ) {
unit = 'deg';
shapeTransforms.push( prop + '(' + value + unit + ')' );
}
break;
case 'rotate':
unit = 'deg';
if ( value !== '' && value !== '0' ) {
shapeTransforms.push( 'rotate(' + value + unit + ')' );
}
break;
}
} );
this.updateCSSRule( shapeSelector, 'transform', shapeTransforms.join(' ') );
},
}
})(jQuery);