/**
* USOF Field: Design Options
* TODO: Make code optimizations to improve performance!
*/
! function( $, _undefined ) {
// Private variables that are used only in the context of this function, it is necessary to optimize the code
var _window = window,
_document = document;
// Check for is set availability objects
_window.$ush = _window.$ush || {};
if ( $ush.isUndefined( _window.$usof ) ) {
return;
}
$usof.field[ 'design_options' ] = {
init: function() {
var self = this;
// Variables
self._lastSelectedScreen = 'default';
self.defaultGroupValues = {}; // default param values by groups
self.defaultValues = {}; // default inline values
self.groupParams = {};
// Elements
self.$container = $( '.usof-design-options', self.$row );
self.$input = $( 'textarea.usof_design_value', self.$container );
self.$import = $( '.usof-design-options-import', self.$container );
self.$btnCopyToClipboard = $( 'button.copy_to_clipboard', self.$import );
self.$errorMessage = $( '.usof-design-options-import-novalid', self.$import );
// Get responsive states
self.states = $ush.toArray( self.$container[0].onclick() || ['default'] );
self.screensList = self.states.slice( 1 );
self.$container.removeAttr( 'onclick' );
// The value is a string otherwise it will be an object
self.hasStringValue = (
self.isLiveBuilder()
|| self.isWPBakeryParamValue()
);
if ( self.isWPBakeryParamValue() ) {
self.$container
.closest( '.edit_form_line' )
.addClass( 'usof-not-live' );
}
/**
* @var {{}} Bindable events.
*/
self._events = {
changeValue: $ush.debounce( self._changeValue.bind( self ), 1 ),
copyValueToClipboard: self._copyValueToClipboard.bind( self ),
pasteValueFromClipboard: self._pasteValueFromClipboard.bind( self ),
resetValues: self._resetValues.bind( self ),
selectResponsiveState: self._selectResponsiveState.bind( self ),
toggleAccordion: self._toggleAccordion.bind( self ),
toggleResponsive: self._toggleResponsive.bind( self ),
switchRelatedFields: self.switchRelatedFields.bind( self ),
changedRelatedField: $ush.debounce( self.changedRelatedField.bind( self ), 1 ),
};
/**
* Check for changes in the param group
* Note: The code is moved to a separate function since `debounced` must be initialized before calling
*
* @type debounced
* TODO: There was a replacement for `$ush.debounce()` and we need to test the functionality well
*/
self.__checkChangeValues = $ush.debounce( self.checkChangeValues.bind( self ), 1 );
// Creates copy settings for different screen sizes
if ( self.screensList.length ) {
$( '[data-responsive-state-content="default"]', self.$container ).each( function( _, content ) {
var $content = $( content );
self.screensList.map( function( screen ) {
var $cloneContent = $content
.clone()
.attr( 'data-responsive-state-content', screen )
.addClass( 'hidden' );
$content
.after( $cloneContent );
} );
} );
}
// Create screens group
self.states.map( function( screen ) {
self.groupParams[ screen ] = new $usof.GroupParams(
$( '[data-responsive-state-content="' + screen + '"]', self.$container )
);
} );
$.each( self.groupParams, function( screen, groupParams ) {
// Group start params
$.each( groupParams.fields, function( fieldName, field ) {
var $group = field.$row.closest( '[data-accordion-content]' ),
value = field.getValue();
if ( $group.length ) {
var groupKey = $group.data( 'accordion-content' );
// Save groups
if ( ! self.defaultGroupValues.hasOwnProperty( groupKey ) ) {
self.defaultGroupValues[ groupKey ] = {};
}
if ( ! self.defaultGroupValues[ groupKey ].hasOwnProperty( screen ) ) {
self.defaultGroupValues[ groupKey ][ screen ] = {};
}
self.defaultGroupValues[ groupKey ][ screen ][ fieldName ] = value;
// Save default value
if ( ! self.defaultValues.hasOwnProperty( screen ) ) {
self.defaultValues[ screen ] = {};
}
self.defaultValues[ screen ][ fieldName ] = value;
$group.data( 'responsive-state', screen )
field.screen = screen;
}
} );
// Init related fields and events
$.each( groupParams.fields, function( _, usofField ) {
var $row = usofField.$row;
if ( $row.is( '[onclick]' ) ) {
usofField.$$relatedFields = [ usofField ];
usofField._data = $row[0].onclick() || {};
if ( usofField._data.relations && usofField.screen ) {
$.each( self.groupParams[ usofField.screen ].fields, ( name, itemField ) => {
if ( $.inArray( name, usofField._data.relations || [] ) !== -1 ) {
usofField.$$relatedFields.push( itemField );
itemField.$$relatedFields = usofField.$$relatedFields;
}
} );
}
$row.removeAttr( 'onclick' );
if ( $row.hasClass( 'fullwidth-direction-field' ) ) {
$row.find( '.usof-form-row-title' ).append( '' );
} else {
$row.append( '' );
}
$row.on( 'click', 'i.fas', self._events.switchRelatedFields );
}
usofField.trigger( 'beforeShow' );
usofField.on( 'change', self._events.changeValue );
} );
} );
// Init params for shortcodes
var pid = $ush.timeout( function() {
if ( ! self.inited ) {
self.setValue( self.$input.val() );
self.checkChangeValues();
}
self.$btnCopyToClipboard.prop( 'disabled', ! self.getValue() );
$ush.clearTimeout( pid );
}, 1 );
// Show / Hide states panel
$( '.usof-responsive-buttons', self.$container )
.toggleClass( 'hidden', ! self.screensList.length );
// Events
self.$container
.on( 'click', '[data-accordion-id]', self._events.toggleAccordion )
.on( 'click', '.usof-design-options-reset', self._events.resetValues )
.on( 'click', '.usof-design-options-responsive', self._events.toggleResponsive )
.on( 'click', '[data-responsive-state]', self._events.selectResponsiveState )
.on( 'click', 'button.copy_to_clipboard', self._events.copyValueToClipboard )
.on( 'click', 'button.paste_from_clipboard', self._events.pasteValueFromClipboard );
},
/**
* Copy value to clipboard.
*
* @event handler
*/
_copyValueToClipboard: function() {
var self = this,
value = self.$input.val();
if ( value ) {
$ush.copyTextToClipboard( value );
$ush.storage( 'usof' ).set( 'design_options', value );
}
},
/**
* Paste value from clipboard.
*
* @event handler
*/
_pasteValueFromClipboard: function() {
var self = this,
isValid = false,
value = $ush.toString( $ush.storage( 'usof' ).get( 'design_options' ) ).trim();
if ( ! value ) {
return;
}
try {
value = $ush.toPlainObject( value );
for ( var i in self.states ) {
var state = self.states[ i ];
if ( state && value[ state ] ) {
isValid = true;
break;
}
}
} catch ( err ) {
}
if ( isValid ) {
// Reset values to default
$.each( self.groupParams, function( screen, groupParams ) {
groupParams.setValues( self.defaultValues[ screen ] || {}, true );
} );
self.setValue( value );
}
self.$errorMessage.toggleClass( 'hidden', isValid );
},
/**
* Switch bindings for related fields
*
* @param {Event} e The Event interface represents an event which takes place in the DOM.
*/
switchRelatedFields: function( e ) {
let self = this,
$button = $( e.currentTarget ),
isDisabled = $button.hasClass( 'fa-unlink' ),
mainField = $( e.delegateTarget ).data( 'usofField' ),
mainValue = mainField.getValue();
$button
.toggleClass( 'fa-unlink', ! isDisabled )
.toggleClass( 'fa-link', isDisabled );
mainField.$$relatedFields.map( ( relatedField ) => {
// Disable related fields in full width
if ( mainField.$row.hasClass( 'fullwidth-direction-field' ) ) {
if ( relatedField !== mainField ) {
if ( isDisabled ) {
relatedField.$text[0].setAttribute( 'disabled', 'disabled' );
} else {
relatedField.$text[0].removeAttribute( 'disabled' );
}
}
}
if ( isDisabled ) {
if ( relatedField !== mainField ) {
relatedField.setValue( mainValue );
}
relatedField.on( 'change', self._events.changedRelatedField );
} else {
relatedField.off( 'change', self._events.changedRelatedField );
}
} );
},
/**
* Sync values in all related fields.
*
* @event handler
* @param {$usof.field} relatedField The related field.
*/
changedRelatedField: function( relatedField ) {
let value = relatedField.getValue();
relatedField.$$relatedFields.map( ( itemField ) => {
if ( itemField !== relatedField ) {
itemField.setValue( value, /* quiet */true );
}
} );
},
/**
* Collects params into a string when change any param.
*
* @param {$usof.field} usofField
*/
_changeValue: function( usofField ) {
var self = this,
result = {}, // result of design options
enabledResponsives = [], // params with active responsive settings
valuesChanged = {}; // params whose values are set
// Define params that have responsive settings enabled and values set
$( '[data-accordion-id].responsive', self.$container ).each( function( _, node ) {
var accordionId = $( node ).data( 'accordionId' ),
props = self.defaultGroupValues[ accordionId ].default || {};
for ( var k in props ) {
enabledResponsives.push( k );
valuesChanged[ k ] = [];
$.each( self.groupParams, function( _, groupParams ) {
valuesChanged[ k ].push( groupParams.getValue( k ) );
} );
}
} );
// Get values for result
$.each( self.groupParams, function( screen, groupParams ) {
$.each( groupParams.getValues(), function( param, value ) {
var defaultValue = self.defaultValues[ screen ][ param ],
isValueChanged = false;
// If responsive screens are enabled, then check the value setting on any screen
if ( enabledResponsives.indexOf( param ) > -1 ) {
for ( var i in valuesChanged[ param ] ) {
isValueChanged = valuesChanged[ param ][ i ] !== defaultValue;
if ( isValueChanged ) {
break;
}
}
}
// If the value is set, then add to the result
if ( isValueChanged || value !== defaultValue ) {
if ( ! $.isPlainObject( result[ screen ] ) ) {
result[ screen ] = {};
}
if ( param === 'background-image' && /http/.test( value ) ) {
value = 'url(' + value + ')';
}
result[ screen ][ param ] = value;
}
} );
} );
// Due to the nature of WPBakery Page Builder, we convert
// special characters standard escape function
result = $ush.toString( result );
if ( result === /* $ush.toString( {} ) > */'%7B%7D' ) {
result = '';
}
self.$input.val( result );
// Only when the result changes, then fire the change event.
if ( ! self._lastResult || self._lastResult !== result ) {
self._lastResult = result;
self.trigger( 'change', result );
}
self.__checkChangeValues();
self.$btnCopyToClipboard.prop( 'disabled', ! result ); // enable or disable button
self.trigger( 'changeDesignField', usofField ); // send an event to Live Builder
},
/**
* Resets all group settings to default.
*
* @event handler
* @param {Event} e The Event interface represents an event which takes place in the DOM.
*/
_resetValues: function( e ) {
e.stopPropagation();
var self = this,
$target = $( e.currentTarget ),
$groupHeader = $target.closest( '[data-accordion-id]' ),
groupName = $groupHeader.data( 'accordion-id' );
// Hide responsive options
if ( $groupHeader.hasClass( 'responsive' ) ) {
self._events.toggleResponsive( e );
}
if ( self.defaultGroupValues.hasOwnProperty( groupName ) ) {
$.each( self.defaultGroupValues[ groupName ], function( screen, defaultValues ) {
var groupParams = self.groupParams[ screen ];
/**
* Note: Setting the default values is done by combining from the
* current ones because of the way usof works.
*/
groupParams.setValues( $.extend( groupParams.getValues(), defaultValues ) );
// Disable fields link
$.each( defaultValues, function( groupParams, name ) {
var fields = groupParams.fields;
if (
fields.hasOwnProperty( name )
&& fields[ name ].hasOwnProperty( '_data' )
&& fields[ name ]._data.hasOwnProperty( 'relations' )
) {
var $link = $( 'i.fas', groupParams.$fields[ name ] );
if ( $link.length && $link.hasClass( 'fa-link' ) ) {
$link.trigger( 'click' );
}
}
}.bind( self, groupParams ) );
} );
}
var pid = $ush.timeout( function() {
$groupHeader.removeClass( 'changed' );
$ush.clearTimeout( pid );
}, 1000 * 0.5 );
},
/**
* Accordion Switch.
*
* @event handler
* @param {Event} e The Event interface represents an event which takes place in the DOM.
*/
_toggleAccordion: function( e ) {
var $target = $( e.currentTarget ),
$content = $( '[data-accordion-content="' + $target.data( 'accordion-id' ) + '"]' );
if ( $target.hasClass( 'active' ) ) {
$target.removeClass( 'active' );
$content.removeClass( 'active' );
} else {
$target.siblings().removeClass( 'active' );
$content.siblings().removeClass( 'active' );
$target.addClass( 'active' );
$content.addClass( 'active' );
}
},
/**
* ON/OFF Responsive options.
*
* @event handler
* @param {Event} e The Event interface represents an event which takes place in the DOM.
*/
_toggleResponsive: function( e ) {
e.preventDefault();
e.stopPropagation();
var self = this,
$target = $( e.currentTarget ),
$header = $target.closest( '[data-accordion-id]' ),
groupKey = $header.data( 'accordion-id' ),
isEnabled = $header.hasClass( 'responsive' ),
// Determine the first responsive settings or not
isFirstResponsive = ! isEnabled
? ! $( '.usof-design-options-header.responsive:first', self.$container ).length
: false;
$header.toggleClass( 'responsive', ! isEnabled );
if ( ! isEnabled ) {
// If the first setting will send a change event
if ( isFirstResponsive ) {
self.trigger( 'syncResponsiveState', self._lastSelectedScreen );
}
self.switchResponsiveState( self._lastSelectedScreen );
} else {
self.switchResponsiveState( 'default', /* hidden */true );
}
if ( self.defaultGroupValues.hasOwnProperty( groupKey ) ) {
self.screensList.map( function( screen ) {
// Reset values for a group whose responsive support is enabled
var values = $.extend( {}, self.defaultGroupValues[ groupKey ][ screen ] || {} );
if ( ! isEnabled ) {
// Set default values for current screen
$.each( values, function( prop ) {
if ( self.groupParams[ 'default' ].fields.hasOwnProperty( prop ) ) {
values[ prop ] = self.groupParams[ 'default' ].fields[ prop ].getValue();
}
} );
}
if (
self.groupParams.hasOwnProperty( screen )
&& self.groupParams[ screen ] instanceof $usof.GroupParams
) {
// Get current values to support already set values
values = $.extend( self.groupParams[ screen ].getValues(), values );
self.groupParams[ screen ].setValues( values, /* quiet mode */ true );
}
// Checking and duplicating wiretap related fields
if ( ! isEnabled && self.groupParams.hasOwnProperty( screen ) ) {
$.each( self.groupParams[ 'default' ].fields, function( _, field ) {
if ( field.hasOwnProperty( 'watchValue' ) ) {
$( '.fas', self.groupParams[ screen ].fields[ field.name ].$row )
.trigger( 'click', field.watchValue );
}
} );
}
} );
}
},
/**
* Handler for selecting a responsive state on click of a button.
*
* @event handler
* @param {Event} e The Event interface represents an event which takes place in the DOM.
*/
_selectResponsiveState: function( e ) {
var self = this,
screen = $( e.currentTarget ).data( 'responsive-state' );
self.switchResponsiveState( screen );
self.trigger( 'syncResponsiveState', screen );
self._lastSelectedScreen = screen;
},
/**
* This is the install handler `screen` of builder.
*
* @event handler
* @param {Event} _
* @param {String} screen The screen name 'mobiles', 'tablets', 'laptops' or 'default'.
*/
_usbSyncResponsiveState: function( _, screen ) {
var self = this;
self._lastSelectedScreen = screen || 'default';
self.switchResponsiveState( self._lastSelectedScreen );
},
/**
* Switch screens.
*
* @param {String} screen
* @param {Boolean} hidden
*/
switchResponsiveState: function( screen, hidden ) {
var self = this;
if ( ! screen ) {
return;
}
// Switch between hidden/shown targets
var hasResponsiveClass = ! hidden
? '.responsive'
: ':not(.responsive)'
// Get active responsive blocks
var $target = $( '[data-accordion-id]' + hasResponsiveClass, self.$container )
.next( '[data-accordion-content]' )
.find( '[data-responsive-state="'+ screen +'"]' );
// Remove active class from siblings
$target
.siblings()
.removeClass( 'active' );
// Show the required content by screen
$target
.addClass( 'active' )
.closest( '.usof-design-options-content' )
.find( '> [data-responsive-state-content]' )
.addClass( 'hidden' )
.filter( '[data-responsive-state-content="' + screen + '"]' )
.removeClass( 'hidden' );
},
/**
* Check for changes in the param group.
*/
checkChangeValues: function() {
var self = this,
currentGroupValues = {};
$.each( self.groupParams, function( screen, groupParams ) {
$.each( groupParams.fields, function( _, field ) {
var groupName = field.$row
.closest( '[data-accordion-content]' )
.data( 'accordion-content' );
if ( ! currentGroupValues.hasOwnProperty( groupName ) ) {
currentGroupValues[ groupName ] = {};
}
if ( ! currentGroupValues[ groupName ].hasOwnProperty( screen ) ) {
currentGroupValues[ groupName ][ screen ] = {};
}
currentGroupValues[ groupName ][ screen ][ field.name ] = field.getValue();
} );
} );
$.each( self.defaultGroupValues, function( groupName, screensList ) {
var change = false;
$.each( screensList, function( screen, values ) {
if ( ! currentGroupValues.hasOwnProperty( groupName ) || ! currentGroupValues[ groupName ].hasOwnProperty( screen ) ) {
return;
}
change = ( change || JSON.stringify( values ) !== JSON.stringify( currentGroupValues[ groupName ][ screen ] ) );
} );
$( '[data-accordion-id=' + groupName + ']', self.$container )
.toggleClass( 'changed', change );
} );
},
/**
* Get the value.
*
* @return {String} Returns design options as a string or object.
*/
getValue: function() {
var self = this,
value = self.$input.val().trim();
if ( ! self.hasStringValue && value && typeof value === 'string' ) {
value = $ush.toPlainObject( value );
}
return value;
},
/**
* Set the value.
*
* @param {String} value
* @param {Boolean} quiet The quiet
*/
setValue: function( value, quiet ) {
var self = this;
// Get saved param values
var savedValues = {};
if ( typeof value === 'string' ) {
savedValues = $ush.toPlainObject( value );
} else if ( $.isPlainObject( value ) ) {
savedValues = value;
}
var pid = $ush.timeout( function() {
// Set values and check link
$.each( self.groupParams, function( screen, groupParams ) {
// Reset values
if ( ! self.hasStringValue ) {
groupParams.setValues( self.defaultValues[ screen ] || {}, true );
}
var values = savedValues[ screen ] || {};
propName = 'background-image';
// Image URL support
if ( values.hasOwnProperty( propName ) && /url\(/.test( values[ propName ] || '' ) ) {
values[ propName ] = values[ propName ]
.replace( /\s?url\("?(.*?)"?\)/gi, '$1' );
}
// Border style support.
for ( var k in values ) {
if ( ! /border-(\w+)-style/.test( k ) ) {
continue;
}
values[ 'border-style' ] = values[ k ];
delete values[ k ];
}
// Set values
groupParams.setValues( values, true );
// Check relations link
$.each( groupParams.fields, function( _, field ) {
if ( field.hasOwnProperty( '_data' ) && field._data.hasOwnProperty( 'relations' ) ) {
var $row = field.$row,
value = $ush.toString( field.getValue() ).trim(),
isLink = [];
// Matching all related params, and if necessary enable communication.
( field._data.relations || [] ).map( function( name ) {
if ( value && self.groupParams[ field.screen ].fields.hasOwnProperty( name ) ) {
isLink.push( value === $ush.toString( self.groupParams[ field.screen ].fields[ name ].getValue() ).trim() );
}
} );
if ( isLink.length ) {
isLink = isLink.filter( function( v ) {
return v == true
} );
if ( isLink.length === 3 ) {
var pid = $ush.timeout( function() {
$( 'i.fas', $row ).trigger( 'click' );
$ush.clearTimeout( pid );
}, 1 );
}
}
}
} );
} );
// Check options for screens
var responsiveGroups = {};
self.screensList.map( function( screen ) {
var values = savedValues[ screen ] || {};
$.each( self.defaultGroupValues, function( groupKey, screensList ) {
var isEnabled = false;
$.each( screensList[ screen ], function( prop ) {
if ( ! responsiveGroups[ groupKey ] ) {
responsiveGroups[ groupKey ] = values.hasOwnProperty( prop );
}
} );
} );
} );
$.each( responsiveGroups, function( groupKey, isEnabled ) {
$( '[data-accordion-id="' + groupKey + '"]', self.$container )
.toggleClass( 'responsive', isEnabled );
} );
self.checkChangeValues(); // check for changes in the param group
self.switchResponsiveState( 'default', /* hidden */true ); // default tab selection
}, 1 );
// Set value
if ( value ) {
value = self.hasStringValue ? value : $ush.toString( value );
}
self.$input.val( $ush.toString( value ) );
if ( ! quiet ) {
self.trigger( 'change', [ value ] );
}
// Hide all sections of the accordion
if ( ! self.isWPBakeryParamValue() ) {
$( '> div', self.$container ).removeClass( 'active' );
}
},
/**
* Force value for WPBakery.
*/
forceWPBValue: function() {
var self = this;
if ( self.hasStringValue ) {
self.setValue( self.getValue() );
}
}
};
}( jQuery );