File: /home/nassaugo/www/wp-content/plugins/wpforms/assets/js/admin-builder-conditional-logic-core.js
/* global wpf, wpforms_builder */
'use strict';
var WPFormsConditionals = window.WPFormsConditionals || ( function( document, window, $ ) {
/**
* Helper methods.
*
* @since 1.6.0.2
*/
var helpers = {
/**
* Splits an array to chunks of n elements.
*
* @since 1.6.0.2
*
* @param {Array} array Array to split.
* @param {number} n Number of elements in each chunks.
*
* @returns {Array} Array.
*/
arraySplitIntoChunks: function( array, n ) {
if ( ! array.length ) {
return [];
}
return [ array.slice( 0, n ) ]
.concat( helpers.arraySplitIntoChunks( array.slice( n ), n ) );
},
};
/**
* Conditional rules updating methods.
*
* @since 1.6.0.2
*/
var updater = {
/**
* All form fields.
*
* @since 1.6.0.2
*/
allFields: {},
/**
* Conditional rule rows.
*
* @since 1.6.0.2
*/
$ruleRows: {},
/**
* Form fields supporting conditional logic.
*
* @since 1.6.0.2
*/
conditionalFields: {},
/**
* Form fields changed in the process of updating the conditional logic.
*
* @since 1.6.0.2
*/
changedConditionalFields: [],
/**
* HTML template containing a list of <option> elements representing available conditional fields.
*
* @since 1.6.0.2
*/
fieldsListTemplate: '',
/**
* HTML templates containing a list of <option> elements representing values of every conditional field.
*
* @since 1.6.0.2
*/
fieldValuesListTemplates: {},
/**
* Cache all form fields.
*
* @since 1.6.0.2
*
* @param {Array} allFields List of all fields.
*/
cacheAllFields: function( allFields ) {
updater.allFields = allFields;
},
/**
* Cache all rule rows.
*
* @since 1.6.0.2
*
* @param {jQuery} $rows Collection of all conditional rule rows.
*/
cacheRuleRows: function( $rows ) {
updater.$ruleRows = $rows || $( '.wpforms-conditional-row' );
},
/**
* Cache allowed form fields supporting conditional logic.
*
* @since 1.6.0.2
*/
setConditionalFields: function() {
updater.conditionalFields = updater.removeUnsupportedFields();
},
/**
* Remove field types that are not allowed and whitelisted.
*
* @since 1.6.0.2
*
* @returns {Array} Filtered list of fields.
*/
removeUnsupportedFields: function() {
var allowed = wpforms_builder.cl_fields_supported,
fields = $.extend( {}, updater.allFields ),
key;
for ( key in fields ) {
if ( $.inArray( fields[key].type, allowed ) === -1 ) {
delete fields[key];
} else if ( typeof fields[key].dynamic_choices !== 'undefined' && fields[key].dynamic_choices !== '' ) {
delete fields[key];
}
}
return fields;
},
/**
* Setup all HTML templates.
*
* @since 1.6.0.2
*/
setTemplates: function() {
updater.setFieldsListTemplate();
// Reset cached field values templates before processing.
updater.fieldValuesListTemplates = {};
},
/**
* Return an HTML template for a select with all the fields names.
*
* Doing a jQuery-DOM and copying the underlying HTML makes rendering
* twice as fast.
*
* @since 1.6.0.2
*/
setFieldsListTemplate: function() {
var key,
label;
var $fieldsListTemplate = $( '<select>' )
.append( $( '<option>', { value: '', text: wpforms_builder.select_field } ) );
for ( key in wpf.orders.fields ) {
var field_id = wpf.orders.fields[ key ];
if ( ! updater.conditionalFields[ field_id ] ) {
continue;
}
if ( typeof updater.conditionalFields[ field_id ].label !== 'undefined' && updater.conditionalFields[ field_id ].label.toString().trim() !== '' ) {
label = wpf.sanitizeHTML( updater.conditionalFields[ field_id ].label.toString().trim() );
} else {
label = wpforms_builder.field + ' #' + updater.conditionalFields[ field_id ].id;
}
$fieldsListTemplate.append( $( '<option>', {
value: updater.conditionalFields[ field_id ].id,
text : label,
id : 'option-' + field_id,
} ) );
}
updater.fieldsListTemplate = $fieldsListTemplate.html();
},
/**
* Return an HTML with a list of options from a given field.
*
* @since 1.6.0.2
*
* @param {Array} fields Array of fields.
* @param {number} fieldSelected ID of selected field.
*
* @returns {string} HTML template.
*/
getFieldValuesListTemplate: function( fields, fieldSelected ) {
// Return cached template if possible.
if ( updater.fieldValuesListTemplates[fieldSelected] ) {
return updater.fieldValuesListTemplates[fieldSelected];
}
var key;
var items = wpf.orders.choices['field_' + fieldSelected];
var $select = $( '<select>' );
for ( key in items ) {
var choiceKey = items[key],
label = typeof fields[ fieldSelected ].choices[ choiceKey ] !== 'undefined' && fields[ fieldSelected ].choices[ choiceKey ].label.toString().trim() !== '' ?
wpf.sanitizeHTML( fields[ fieldSelected ].choices[ choiceKey ].label.toString().trim() ) :
wpforms_builder.choice_empty_label_tpl.replace( '{number}', choiceKey );
$select.append( $( '<option>', {value: choiceKey, text: label, id: 'choice-' + choiceKey} ) );
}
// Cache the template for future use and return the HTML.
return updater.fieldValuesListTemplates[fieldSelected] = $select.html();
},
/**
* Form fields supporting conditional logic.
*
* @since 1.6.0.2
*/
updateConditionalRuleRows: function() {
// Clear changed conditional fields cache before processing.
updater.changedConditionalFields = [];
var rowsToProcess = updater.$ruleRows.length;
/**
* Split all the rows in sets of at most 20 elements.
*
* The set of 20 rows would then be processed as soon as possible but without blocking
* the main thread (thanks to setTimeout).
*
* When all the groups are processed the function finalize is called.
*
* @since 1.6.0.2
*/
helpers.arraySplitIntoChunks( updater.$ruleRows, 20 ).map( function( elements ) {
setTimeout( function() {
for ( var i = 0; i < elements.length; ++i ) {
updater.updateConditionalRuleRow( elements[i] );
--rowsToProcess;
}
if ( 0 === rowsToProcess ) {
updater.finalizeConditionalRuleRowsUpdate();
}
}, 0 );
} );
},
/**
* Redraw the conditional rule in the builder.
*
* @since 1.6.0.2
*
* @param {object} row Element container.
*/
updateConditionalRuleRow: function( row ) {
var $row = $( row ),
fieldID = $row.attr( 'data-field-id' ),
$fields = $row.find( '.wpforms-conditional-field' ),
fieldSelected = $fields.val(),
$value = $row.find( '.wpforms-conditional-value' );
// Clone template
$fields[0].innerHTML = updater.fieldsListTemplate;
// Remove the current item
$fields.find( '#option-' + fieldID ).remove();
if ( ! fieldSelected ) {
// Remove all ids properties.
// Querying the DOM by ID is much faster. It is safe to remove the IDs now.
$fields.find( 'option' ).removeAttr( 'id' );
return;
}
// Check if previous selected field exists in the new options added.
if ( $fields.find( '#option-' + fieldSelected ).length ) {
updater.restorePreviousRuleRowSelection( $row, $fields, fieldSelected, $value );
} else {
updater.removeRuleRow( $row );
}
// Remove all ids properties.
// Querying the DOM by ID is much faster. It is safe to remove the IDs now.
$fields.find( 'option' ).removeAttr( 'id' );
$value.find( 'option' ).removeAttr( 'id' );
},
/**
* Finalize the updates.
*
* @since 1.6.0.2
*/
finalizeConditionalRuleRowsUpdate: function() {
// If conditional rules have been altered due to form updates then
// we alert the user.
if ( ! updater.changedConditionalFields.length ) {
return;
}
// Remove dupes
var changedUnique = updater.changedConditionalFields.reduce( function( a, b ) {
if ( a.indexOf( b ) < 0 ) {
a.push( b );
}
return a;
}, [] );
// Build and trigger alert
var alert = wpforms_builder.conditionals_change,
key;
for ( key in changedUnique ) {
alert += updater.getChangedFieldNameForAlert( changedUnique[key], updater.allFields );
}
$.alert( {
title: wpforms_builder.heads_up,
content: alert,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Restore the rule row selection before conditional rules update.
*
* @since 1.6.0.2
*
* @param {object} $row Row container.
* @param {object} $fields Field object.
* @param {string} fieldSelected Field selected value.
* @param {object} $value Field Value.
*/
restorePreviousRuleRowSelection: function( $row, $fields, fieldSelected, $value ) {
var valueSelected = '';
// Exists, so restore previous selected value
$fields.find( '#option-' + fieldSelected ).prop( 'selected', true );
if ( ! $value.length || ! $value.is( 'select' ) ) {
return;
}
// Since the field exist and was selected, now we must proceed
// to updating the field values. Luckily, we only have to do
// this for fields that leverage a select element.
// Grab the currently selected value to restore later
valueSelected = $value.val();
$value[0].innerHTML = updater.getFieldValuesListTemplate( updater.conditionalFields, fieldSelected );
// Check if previous selected value exists in the new options added
if ( $value.find( '#choice-' + valueSelected ).length ) {
$value.find( '#choice-' + valueSelected ).prop( 'selected', true );
} else {
// Old value does not exist in the new options, likely
// deleted. Add the field ID to the charged variable,
// which will let the user know the fields conditional
// logic has been altered.
if ( valueSelected.length > 0 ) {
updater.changedConditionalFields.push( $row.closest( '.wpforms-conditional-group' ).data( 'reference' ) );
}
}
},
/**
* Check if previous selected field exists in the new options added.
*
* @since 1.6.0.2
*
* @param {object} $row Row container.
*/
removeRuleRow: function( $row ) {
// Old field does not exist in the new options, likely deleted.
// Add the field ID to the charged variable, which will let
// the user know the fields conditional logic has been altered.
updater.changedConditionalFields.push( $row.closest( '.wpforms-conditional-group' ).data( 'reference' ) );
// Since previously selected field no longer exists, this
// means this rule is now invalid. So the rule gets
// deleted as long as it isn't the only rule remaining.
var $group = $row.closest( '.wpforms-conditional-group' );
if ( $group.find( 'table >tbody >tr' ).length === 1 ) {
var $groups = $row.closest( '.wpforms-conditional-groups' );
if ( $groups.find( '.wpforms-conditional-group' ).length > 1 ) {
$group.remove();
} else {
$row.find( '.wpforms-conditional-value' ).remove();
$row.find( '.value' ).append( '<select>' );
}
} else {
$row.remove();
}
},
/**
* Return HTML with error Message.
*
* @since 1.6.0.2
*
* @param {mixed} field Field ID or field name.
* @returns {string} HTML message.
*/
getChangedFieldNameForAlert: function( field ) {
if ( ! wpf.isNumber( field ) ) {
// Panel
return '<br>' + field;
}
// Field
if ( ( ( updater.allFields[field] || {} ).label || '' ).length ) {
return '<br/>' + wpf.sanitizeString( updater.allFields[field].label ) + ' (' + wpforms_builder.field + ' #' + field + ')';
} else {
return '<br>' + wpforms_builder.field + ' #' + field;
}
},
};
var app = {
/**
* Start the engine.
*
* @since 1.0.0
*/
init: function() {
// Document ready
$( WPFormsConditionals.ready );
},
/**
* Document ready.
*
* @since 1.0.0
*/
ready: function() {
WPFormsConditionals.bindUIActions();
},
/**
* Element bindings.
*
* @since 1.0.0
*/
bindUIActions: function() {
var $builder = $( '#wpforms-builder' );
// Conditional support toggle.
$builder.on( 'change', '.wpforms-conditionals-enable-toggle input[type=checkbox]', function( e ) {
WPFormsConditionals.conditionalToggle( this, e );
} );
// Conditional process field select.
$builder.on( 'change', '.wpforms-conditional-field', function( e ) {
WPFormsConditionals.conditionalField( this, e );
} );
// Conditional process operator select.
$builder.on( 'change', '.wpforms-conditional-operator', function( e ) {
WPFormsConditionals.conditionalOperator( this, e );
} );
// Conditional add new rule.
$builder.on( 'click', '.wpforms-conditional-rule-add', function( e ) {
WPFormsConditionals.conditionalRuleAdd( this, e );
} );
// Conditional delete rule.
$builder.on( 'click', '.wpforms-conditional-rule-delete', function( e ) {
WPFormsConditionals.conditionalRuleDelete( this, e );
} );
// Conditional add new group.
$builder.on( 'click', '.wpforms-conditional-groups-add', function( e ) {
WPFormsConditionals.conditionalGroupAdd( this, e );
} );
// Conditional logic update/refresh.
$( document ).on( 'wpformsFieldUpdate', WPFormsConditionals.conditionalUpdateOptions );
},
/**
* Update/refresh the conditional logic fields and associated options.
*
* @since 1.0.0
*/
conditionalUpdateOptions: function( e, allFields, $rows ) {
if ( wpf.empty( allFields ) ) {
return;
}
updater.cacheAllFields( allFields );
updater.cacheRuleRows( $rows );
updater.setConditionalFields();
updater.setTemplates();
updater.updateConditionalRuleRows();
},
/**
* Toggle conditional support.
*
* @since 1.0.0
*/
conditionalToggle: function( el, e ) {
e.preventDefault();
var $this = $( el ),
$block = $this.parent().parent(),
logicBlock = wp.template( 'wpforms-conditional-block' ),
data = {
fieldID : $this.parent().data( 'field-id' ),
fieldName : $this.data( 'name' ),
actions : $this.data( 'actions' ),
actionDesc : $this.data( 'action-desc' ),
};
if ( $this.is( ':checked' ) ) {
// Add conditional logic rules.
$block.append( logicBlock( data ) );
// Update fields in the added rule.
WPFormsConditionals.conditionalUpdateOptions( false, wpf.getFields( false, true ), $block.find( '.wpforms-conditional-row' ) );
} else {
// Remove conditional logic rules.
$.confirm( {
title: false,
content: wpforms_builder.conditionals_disable,
backgroundDismiss: false,
closeIcon: false,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action: function() {
// Prompt
$block.find( '.wpforms-conditional-groups' ).remove();
},
},
cancel: {
text: wpforms_builder.cancel,
action: function() {
$this.prop( 'checked', true );
},
},
},
} );
}
},
/**
* Process conditional field.
*
* @since 1.0.0
*/
conditionalField: function( el, e ) {
e.preventDefault();
var $this = $( el ),
$rule = $this.parent().parent(),
$operator = $rule.find( '.wpforms-conditional-operator' ),
operator = $operator.find( 'option:selected' ).val(),
data = WPFormsConditionals.conditionalData( $this ),
name = data.inputName + '[' + data.groupID + '][' + data.ruleID + '][value]',
$element;
if ( ! data.field ) {
// Placeholder has been selected.
$element = $( '<select>' );
} else if (
data.field.type === 'select' ||
data.field.type === 'radio' ||
data.field.type === 'checkbox' ||
data.field.type === 'payment-multiple' ||
data.field.type === 'payment-checkbox' ||
data.field.type === 'payment-select'
) {
// Selector type fields use select elements.
$element = $( '<select>' ).attr( { name: name, class: 'wpforms-conditional-value' } ); // jshint ignore:line
$element.append( $( '<option>', { value: '', text : wpforms_builder.select_choice } ) );
if ( data.field.choices ) {
for ( var key in wpf.orders.choices[ 'field_' + data.field.id ] ) {
var choiceKey = wpf.orders.choices[ 'field_' + data.field.id ][ key ],
label = typeof data.field.choices[ choiceKey ].label !== 'undefined' && data.field.choices[ choiceKey ].label.toString().trim() !== '' ?
wpf.sanitizeHTML( data.field.choices[ choiceKey ].label.toString().trim() ) :
wpforms_builder.choice_empty_label_tpl.replace( '{number}', choiceKey );
$element.append( $( '<option>', { value: choiceKey, text: wpf.sanitizeHTML( label ) } ) );
}
}
$operator.find( "option:not([value='=='],[value='!='],[value='e'],[value='!e'])" ).prop( 'disabled', true ).prop( 'selected', false ); // jshint ignore:line
} else {
// Text type fields (everything else) use text inputs.
// Determine input type.
var inputType = 'text';
if ( 'rating' === data.field.type || 'net_promoter_score' === data.field.type || 'number-slider' === data.field.type ) {
inputType = 'number';
}
$element = $( '<input>' ).attr( { type: inputType, name: name, class: 'wpforms-conditional-value' } ); // jshint ignore:line
$operator.find( 'option' ).prop( 'disabled', false );
}
if ( operator === 'e' || operator === '!e' ) {
// Empty/not empty doesn't use input, so we disable it.
$element.prop( 'disabled', true );
}
$rule.find( '.value' ).empty().append( $element );
},
/**
* Process conditional field.
*
* @since 1.2.0
*/
conditionalOperator: function( el, e ) {
e.preventDefault();
var $this = $( el ),
$rule = $this.parent().parent(),
$value = $rule.find( '.wpforms-conditional-value' ),
operator = $this.find( 'option:selected' ).val();
if ( operator === 'e' || operator === '!e' ) {
$value.prop( 'disabled', true );
if ( $value.is( 'select' ) ) {
$value.find( 'option:selected' ).prop( 'selected', false );
} else {
$value.val( '' );
}
} else {
$value.prop( 'disabled', false );
}
},
/**
* Add new conditional rule.
*
* @since 1.0.0
*/
conditionalRuleAdd: function( el, e ) {
e.preventDefault();
var $this = $( el ),
$group = $this.closest( '.wpforms-conditional-group' ),
$rule = $group.find( 'tr' ).last(),
$newRule = $rule.clone(),
$field = $newRule.find( '.wpforms-conditional-field' ),
$operator = $newRule.find( '.wpforms-conditional-operator' ),
data = WPFormsConditionals.conditionalData( $field ),
ruleID = Number( data.ruleID ) + 1,
name = data.inputName + '[' + data.groupID + '][' + ruleID + ']';
$newRule.find( 'option:selected' ).prop( 'selected', false );
$newRule.find( '.value' ).empty().append( $( '<select>' ) );
$field.attr( 'name', name + '[field]' ).attr( 'data-ruleid', ruleID );
$operator.attr( 'name', name + '[operator]' );
$rule.after( $newRule );
},
/**
* Delete conditional rule. If the only rule in group then group will
* also be removed.
*
* @since 1.0.0
*/
conditionalRuleDelete: function( el, e ) {
e.preventDefault();
var $this = $( el ),
$group = $this.closest( '.wpforms-conditional-group' ),
$rows = $group.find( 'table >tbody >tr' );
if ( $rows && $rows.length === 1 ) {
var $groups = $this.closest( '.wpforms-conditional-groups' );
if ( $groups.find( '.wpforms-conditional-group' ).length > 1 ) {
$group.remove();
} else {
return;
}
} else {
$this.parent().parent().remove();
}
},
/**
* Add new conditional group.
*
* @since 1.0.0
*/
conditionalGroupAdd: function( el, e ) {
e.preventDefault();
var $this = $( el ),
$groupLast = $this.parent().find( '.wpforms-conditional-group' ).last(),
$newGroup = $groupLast.clone();
$newGroup.find( 'tr' ).not( ':first' ).remove();
var $field = $newGroup.find( '.wpforms-conditional-field' ),
$operator = $newGroup.find( '.wpforms-conditional-operator' ),
data = WPFormsConditionals.conditionalData( $field ),
groupID = Number( data.groupID ) + 1,
ruleID = 0,
name = data.inputName + '[' + groupID + '][' + ruleID + ']';
$newGroup.find( 'option:selected' ).prop( 'selected', false );
$newGroup.find( '.value' ).empty().append( $( '<select>' ) );
$field.attr( 'name', name + '[field]' ).attr( 'data-ruleid', ruleID ).attr( 'data-groupid', groupID );
$operator.attr( 'name', name + '[operator]' );
$this.before( $newGroup );
},
//--------------------------------------------------------------------//
// Helper functions
//--------------------------------------------------------------------//
/**
* Return various data for the conditional field.
*
* @since 1.0.0
*/
conditionalData: function( el ) {
var $this = $( el );
var data = {
fields : wpf.getFields( false, true ),
inputBase : $this.closest( '.wpforms-conditional-row' ).attr( 'data-input-name' ),
fieldID : $this.closest( '.wpforms-conditional-row' ).attr( 'data-field-id' ),
ruleID : $this.attr( 'data-ruleid' ),
groupID : $this.attr( 'data-groupid' ),
selectedID : $this.find( ':selected' ).val(),
};
data.inputName = data.inputBase + '[conditionals]';
if ( data.selectedID.length ) {
data.field = data.fields[ data.selectedID ];
} else {
data.field = false;
}
return data;
},
};
return app;
}( document, window, jQuery ) );
WPFormsConditionals.init();