' ).parent().html();
newSettingsBlock = newSettingsBlock.replace( /\[conditionals\]\[(\d+)\]\[(\d+)\]/g, '[conditionals][0][0]' );
$firstSettingsBlock.before( newSettingsBlock );
var $addedSettingBlock = $firstSettingsBlock.prev();
// Reset the confirmation type to the 1st one.
if ( blockType === 'confirmation' ) {
app.prepareChoicesJSField( $addedSettingBlock, nextID );
app.confirmationFieldsToggle( $( '.wpforms-panel-field-confirmations-type' ).first() );
}
// Init the WP Editor.
if ( typeof tinymce !== 'undefined' && typeof wp.editor !== 'undefined' && blockType === 'confirmation' ) {
wp.editor.initialize( 'wpforms-panel-field-confirmations-message-' + nextID, s.tinymceDefaults );
}
// Init tooltips for new section.
wpf.initTooltips();
$builder.trigger( 'wpformsSettingsBlockAdded', [ $addedSettingBlock ] );
$el.attr( 'data-next-id', nextID + 1 );
}
},
},
cancel: {
text: wpforms_builder.cancel,
},
},
} );
// We need to process this event here, because we need a confirmation
// modal object defined, so we can intrude into it.
// Pressing Enter will click the Ok button.
$builder.on( 'keypress', '#settings-block-name', function( e ) {
if ( e.keyCode === 13 ) {
$( modal.buttons.confirm.el ).trigger( 'click' );
}
} );
},
/**
* Reset the 'Select Page' field to it's initial state then
* re-initialize ChoicesJS on it.
*
* @since 1.7.9
*
* @param {jQuery} $addedSettingBlock Newly added Settings Block jQuery object.
* @param {number} addedSettingBlockID Number ID used when `$addedSettingBlock` was created.
*/
prepareChoicesJSField: function( $addedSettingBlock, addedSettingBlockID ) {
const $addedConfirmationWrap = $addedSettingBlock.find( `#wpforms-panel-field-confirmations-${addedSettingBlockID}-page-wrap` );
if ( $addedConfirmationWrap.length <= 0 ) {
return;
}
const $confirmationSelectPageField = $addedConfirmationWrap.find( `#wpforms-panel-field-confirmations-${addedSettingBlockID}-page` );
if ( $confirmationSelectPageField.length <= 0 && ! $confirmationSelectPageField.hasClass( 'choicesjs-select' ) ) {
return;
}
const $choicesWrapper = $addedConfirmationWrap.find( '.choices' );
if ( $choicesWrapper.length <= 0 ) {
return;
}
// Remove ChoicesJS-related attr.
const $selectPageField = $confirmationSelectPageField.first();
$selectPageField.removeAttr( 'data-choice' );
$selectPageField.removeAttr( 'hidden' );
$selectPageField.removeClass( 'choices__input' );
// Move the select page field to it's initial location in the DOM.
$( $selectPageField ).appendTo( $addedConfirmationWrap.first() );
// Remove the `.choices` wrapper.
$choicesWrapper.first().remove();
// Re-init ChoicesJS.
app.dropdownField.events.choicesInit( $selectPageField );
},
/**
* Show settings block editing interface.
*
* @since 1.4.8
*/
settingsBlockNameEditingShow: function( $el ) {
var headerHolder = $el.parents( '.wpforms-builder-settings-block-header' ),
nameHolder = headerHolder.find( '.wpforms-builder-settings-block-name' );
nameHolder
.addClass( 'editing' )
.hide();
// Make the editing interface active and in focus
headerHolder.find( '.wpforms-builder-settings-block-name-edit' ).addClass( 'active' );
wpf.focusCaretToEnd( headerHolder.find( 'input' ) );
},
/**
* Update settings block name and hide editing interface.
*
* @since 1.4.8
*/
settingsBlockNameEditingHide: function( $el ) {
var headerHolder = $el.parents( '.wpforms-builder-settings-block-header' ),
nameHolder = headerHolder.find( '.wpforms-builder-settings-block-name' ),
editHolder = headerHolder.find( '.wpforms-builder-settings-block-name-edit' ),
currentName = editHolder.find( 'input' ).val().trim(),
blockType = $el.closest( '.wpforms-builder-settings-block' ).data( 'block-type' );
// Provide a default value for empty settings block name.
if ( ! currentName.length ) {
currentName = wpforms_builder[blockType + '_def_name'];
}
// This is done for sanitizing.
editHolder.find( 'input' ).val( currentName );
nameHolder.text( currentName );
// Editing should be hidden, displaying - active.
nameHolder
.removeClass( 'editing' )
.show();
editHolder.removeClass( 'active' );
},
/**
* Clone the Notification block with all of its content and events.
* Put the newly created clone above the target.
*
* @since 1.6.5
* @since 1.7.7 Registered `wpformsSettingsBlockCloned` trigger.
*
* @param {object} $el Clone icon DOM element.
*/
settingsBlockPanelClone: function( $el ) {
var $panel = $el.closest( '.wpforms-panel-content-section' ),
$addNewSettingButton = $panel.find( '.wpforms-builder-settings-block-add' ),
$settingsBlock = $el.closest( '.wpforms-builder-settings-block' ),
$settingBlockContent = $settingsBlock.find( '.wpforms-builder-settings-block-content' ),
settingsBlockId = parseInt( $addNewSettingButton.attr( 'data-next-id' ), 10 ),
settingsBlockType = $settingsBlock.data( 'block-type' ),
settingsBlockName = $settingsBlock.find( '.wpforms-builder-settings-block-name' ).text().trim() + wpforms_builder[ settingsBlockType + '_clone' ],
isVisibleContent = $settingBlockContent.is( ':hidden' );
// Restore tooltips before cloning.
wpf.restoreTooltips( $settingsBlock );
var $clone = $settingsBlock.clone( false, true );
// Save open/close state while cloning.
app.settingsBlockUpdateState( isVisibleContent, settingsBlockId, settingsBlockType );
// Change the cloned setting block ID and name.
$clone.data( 'block-id', settingsBlockId );
$clone.find( '.wpforms-builder-settings-block-header span' ).text( settingsBlockName );
$clone.find( '.wpforms-builder-settings-block-header input' ).val( settingsBlockName );
$clone.removeClass( 'wpforms-builder-settings-block-default' );
// Change the Next Settings block ID for "Add new" button.
$addNewSettingButton.attr( 'data-next-id', settingsBlockId + 1 );
// Change the name attribute.
$clone.find( 'input, textarea, select' ).each( function() {
var $this = $( this );
if ( $this.attr( 'name' ) ) {
$this.attr( 'name', $this.attr( 'name' ).replace( /\[(\d+)\]/, '[' + settingsBlockId + ']' ) );
}
if ( $this.data( 'name' ) ) {
$this.data( 'name', $this.data( 'name' ).replace( /\[(\d+)\]/, '[' + settingsBlockId + ']' ) );
}
if ( $this.attr( 'class' ) ) {
$this.attr( 'class', $this.attr( 'class' ).replace( /-(\d+)/, '-' + settingsBlockId ) );
}
if ( $this.attr( 'data-radio-group' ) ) {
$this.attr( 'data-radio-group', $this.attr( 'data-radio-group' ).replace( /(\d+)-/, settingsBlockId + '-' ) );
}
} );
// Change IDs/data-attributes in DOM elements.
$clone.find( '*' ).each( function() {
var $this = $( this );
if ( $this.attr( 'id' ) ) {
$this.attr( 'id', $this.attr( 'id' ).replace( /-(\d+)/, '-' + settingsBlockId ) );
}
if ( $this.attr( 'for' ) ) {
$this.attr( 'for', $this.attr( 'for' ).replace( /-(\d+)-/, '-' + settingsBlockId + '-' ) );
}
if ( $this.data( 'input-name' ) ) {
$this.data( 'input-name', $this.data( 'input-name' ).replace( /\[(\d+)\]/, '[' + settingsBlockId + ']' ) );
}
} );
// Transfer selected values to copied elements since jQuery doesn't clone the current selected state.
$settingsBlock.find( 'select' ).each( function() {
var baseSelectName = $( this ).attr( 'name' ),
clonedSelectName = $( this ).attr( 'name' ).replace( /\[(\d+)\]/, '[' + settingsBlockId + ']' );
$clone.find( 'select[name="' + clonedSelectName + '"]' ).val( $( this ).attr( 'name', baseSelectName ).val() );
} );
// Insert before the target settings block.
$clone
.css( 'display', 'none' )
.insertBefore( $settingsBlock )
.show( 'fast', function() {
// Init tooltips for new section.
wpf.initTooltips();
} );
$builder.trigger( 'wpformsSettingsBlockCloned', [ $clone, $settingsBlock.data( 'block-id' ) ] );
},
/**
* Show or hide settings block panel content.
*
* @since 1.4.8
*
* @param {object} $el Toggle icon DOM element.
*/
settingsBlockPanelToggle: function( $el ) {
var $settingsBlock = $el.closest( '.wpforms-builder-settings-block' ),
settingsBlockId = $settingsBlock.data( 'block-id' ),
settingsBlockType = $settingsBlock.data( 'block-type' ),
$content = $settingsBlock.find( '.wpforms-builder-settings-block-content' ),
isVisible = $content.is( ':visible' );
$content.stop().slideToggle( {
duration: 400,
start: function() {
// Send early to save fast.
// It's animation start, so we should save the state for animation end (reversed).
app.settingsBlockUpdateState( isVisible, settingsBlockId, settingsBlockType );
},
always: function() {
if ( $content.is( ':visible' ) ) {
$el.html( '
' );
} else {
$el.html( '
' );
}
},
} );
},
/**
* Delete settings block.
*
* @since 1.4.8
* @since 1.6.1.2 Registered `wpformsSettingsBlockDeleted` trigger.
*
* @param {jQuery} $el Delete button element.
*/
settingsBlockDelete: function( $el ) {
var $contentSection = $el.closest( '.wpforms-panel-content-section' ),
$currentBlock = $el.closest( '.wpforms-builder-settings-block' ),
blockType = $currentBlock.data( 'block-type' );
// Skip if only one block persist.
// This condition should not execute in normal circumstances.
if ( $contentSection.find( '.wpforms-builder-settings-block' ).length < 2 ) {
return;
}
$.confirm( {
title: false,
content: wpforms_builder[ blockType + '_delete' ],
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action: function() {
var settingsBlockId = $currentBlock.data( 'block-id' ),
settingsBlockType = $currentBlock.data( 'block-type' );
/* eslint-disable camelcase */
$.post( wpforms_builder.ajax_url, {
action : 'wpforms_builder_settings_block_state_remove',
nonce : wpforms_builder.nonce,
block_id : settingsBlockId,
block_type: settingsBlockType,
form_id : s.formID,
} );
/* eslint-enable */
$currentBlock.remove();
$builder.trigger( 'wpformsSettingsBlockDeleted', [ blockType, settingsBlockId ] );
},
},
cancel: {
text: wpforms_builder.cancel,
},
},
} );
},
/**
* Change open/close state for setting block.
*
* @since 1.6.5
*
* @param {boolean} isVisible State status.
* @param {number} settingsBlockId Block ID.
* @param {string} settingsBlockType Block type.
*
*/
settingsBlockUpdateState: function( isVisible, settingsBlockId, settingsBlockType ) {
$.post( wpforms_builder.ajax_url, {
action: 'wpforms_builder_settings_block_state_save',
state: isVisible ? 'closed' : 'opened',
form_id: s.formID,
block_id: settingsBlockId,
block_type: settingsBlockType,
nonce: wpforms_builder.nonce,
} );
},
//--------------------------------------------------------------------//
// Revisions Panel
//--------------------------------------------------------------------//
/**
* Element bindings for Revisions panel.
*
* @since 1.7.3
*/
bindUIActionsRevisions: function() {
// Update revisions panel when it becomes active.
$builder.on( 'wpformsPanelSwitched', function( event, panel ) {
if ( panel !== 'revisions' ) {
return;
}
app.updateRevisionsList();
app.updateRevisionPreview();
} );
// Update revisions list when the form was saved with revisions panel being active.
$builder.on( 'wpformsSaved', function( event ) {
if ( wpf.getQueryString( 'view' ) !== 'revisions' ) {
return;
}
app.updateRevisionsList();
} );
},
/**
* Fetch and update a list of form revisions.
*
* @since 1.7.3
*/
updateRevisionsList: function() {
var $revisionsList = $( '#wpforms-panel-revisions .wpforms-revisions-content' ),
$revisionsButtonBadge = $( '.wpforms-panel-revisions-button .badge-exclamation' );
// Revisions badge exists, send a request and remove the badge on successful response.
if ( $revisionsButtonBadge.length ) {
$.post( wpforms_builder.ajax_url, {
action: 'wpforms_mark_panel_viewed',
form_id: s.formID, // eslint-disable-line camelcase
nonce: wpforms_builder.nonce,
} )
.done( function( response ) {
response.success ? $revisionsButtonBadge.remove() : wpf.debug( response );
} )
.fail( function( xhr, textStatus, e ) {
wpf.debug( xhr.responseText || textStatus || '' );
} );
}
// Revisions are disabled, no need to fetch a list of revisions.
if ( ! $builder.hasClass( 'wpforms-revisions-enabled' ) ) {
return;
}
// Dim the list, send a request and replace the list on successful response.
$revisionsList.fadeTo( 250, 0.25, function() {
$.post( wpforms_builder.ajax_url, {
action: 'wpforms_get_form_revisions',
form_id: s.formID, // eslint-disable-line camelcase
revision_id: wpf.getQueryString( 'revision_id' ), // eslint-disable-line camelcase
nonce: wpforms_builder.nonce,
} )
.done( function( response ) {
response.success ? $revisionsList.replaceWith( response.data.html ) : wpf.debug( response );
} )
.fail( function( xhr, textStatus, e ) {
wpf.debug( xhr.responseText || textStatus || '' );
// Un-dim the list to reset the UI.
$revisionsList.fadeTo( 250, 1 );
} );
} );
},
/**
* Clone form preview from Fields panel.
*
* @since 1.7.3
*/
updateRevisionPreview: function() {
// Clone preview DOM from Fields panel.
var $preview = elements.$formPreview.clone();
// Clean up the cloned preview, remove unnecessary elements, set states etc.
$preview
.find( '.wpforms-field-duplicate, .wpforms-field-delete, .wpforms-field-helper, .wpforms-debug' )
.remove()
.end();
$preview
.find( '.wpforms-field-wrap' )
.removeClass( 'ui-sortable' )
.addClass( 'ui-sortable-disabled' );
$preview
.find( '.wpforms-field' )
.removeClass( 'ui-sortable-handle ui-draggable ui-draggable-handle active' )
.removeAttr( 'id data-field-id data-field-type' )
.removeData();
$preview
.find( '.wpforms-field-submit-button' )
.prop( 'disabled', true );
// Put the cleaned up clone into Preview panel.
if ( elements.$revisionPreview.hasClass( 'has-preview' ) ) {
elements
.$revisionPreview
.find( '.wpforms-preview-wrap' )
.replaceWith( $preview );
} else {
elements
.$revisionPreview
.append( $preview )
.addClass( 'has-preview' );
}
},
/**
* Inform the user about making this version the default if revision is currently loaded, and it was modified.
*
* @since 1.7.3
*/
confirmSaveRevision: function() {
$.confirm( {
title: wpforms_builder.heads_up,
content: wpforms_builder.revision_update_confirm,
icon: 'fa fa-exclamation-circle',
type: 'orange',
closeIcon: false,
buttons: {
confirm: {
text: wpforms_builder.save,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action: function() {
// Put the Form Builder into "saving state".
$builder.addClass( 'wpforms-revision-is-saving' );
// Save the revision as current version and reload the Form Builder.
WPFormsBuilder.formSave( false ).done( app.revisionSavedReload );
},
},
cancel: {
text: wpforms_builder.cancel,
action: function() {
WPFormsBuilder.setCloseConfirmation( true );
},
},
},
} );
},
/**
* When a modified revision was saved as current version, reload the Form Builder with the current tab active.
*
* @since 1.7.3
*/
revisionSavedReload: function() {
wpf.updateQueryString( 'view', wpf.getQueryString( 'view' ) );
wpf.removeQueryParam( 'revision_id' );
window.location.reload();
},
//--------------------------------------------------------------------//
// Save and Exit
//--------------------------------------------------------------------//
/**
* Element bindings for Embed and Save/Exit items.
*
* @since 1.0.0
* @since 1.5.8 Added trigger on `wpformsSaved` event to remove a `newform` URL-parameter.
*/
bindUIActionsSaveExit: function() {
// Embed form.
$builder.on( 'click', '#wpforms-embed', function( e ) {
e.preventDefault();
if ( $( this ).hasClass( 'wpforms-disabled' ) ) {
return;
}
WPFormsFormEmbedWizard.openPopup();
} );
// Save form.
$builder.on( 'click', '#wpforms-save', function( e ) {
e.preventDefault();
app.formSave( false );
} );
// Exit builder.
$builder.on( 'click', '#wpforms-exit', function( e ) {
e.preventDefault();
app.formExit();
} );
// After form save.
$builder.on( 'wpformsSaved', function( e, data ) {
/**
* Remove `newform` parameter, if it's in URL, otherwise we can to get a "race condition".
* E.g. form settings will be updated before some provider connection is loaded.
*/
wpf.removeQueryParam( 'newform' );
} );
},
// eslint-disable-next-line jsdoc/require-returns
/**
* Save form.
*
* @since 1.0.0
* @since 1.7.5 Added `wpformsBeforeSave` trigger.
*
* @param {boolean} redirect Whether to redirect after save.
*/
formSave: function( redirect ) {
var $saveBtn = elements.$saveButton,
$icon = $saveBtn.find( 'i.fa-check' ),
$spinner = $saveBtn.find( 'i.wpforms-loading-spinner' ),
$label = $saveBtn.find( 'span' ),
text = $label.text();
// Saving a revision directly is not allowed. We need to notify the user that it will overwrite the current version.
if ( $builder.hasClass( 'wpforms-is-revision' ) && ! $builder.hasClass( 'wpforms-revision-is-saving' ) ) {
app.confirmSaveRevision();
return;
}
if ( typeof tinyMCE !== 'undefined' ) {
tinyMCE.triggerSave();
}
var event = WPFormsUtils.triggerEvent( $builder, 'wpformsBeforeSave' );
// Allow callbacks on `wpformsBeforeSave` to cancel form submission by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return;
}
$label.text( wpforms_builder.saving );
$saveBtn.prop( 'disabled', true );
$icon.addClass( 'wpforms-hidden' );
$spinner.removeClass( 'wpforms-hidden' );
var data = {
action: 'wpforms_save_form',
data : JSON.stringify( $( '#wpforms-builder-form' ).serializeArray() ),
id : s.formID,
nonce : wpforms_builder.nonce,
};
return $.post( wpforms_builder.ajax_url, data, function( response ) {
if ( response.success ) {
wpf.savedState = wpf.getFormState( '#wpforms-builder-form' );
wpf.initialSave = false;
$builder.trigger( 'wpformsSaved', response.data );
if ( true === redirect && app.isBuilderInPopup() ) {
app.builderInPopupClose( 'saved' );
return;
}
if ( true === redirect ) {
window.location.href = wpforms_builder.exit_url;
}
} else {
wpf.debug( response );
app.formSaveError( response.data );
}
} ).fail( function( xhr, textStatus, e ) {
wpf.debug( xhr );
app.formSaveError();
} ).always( function() {
$label.text( text );
$saveBtn.prop( 'disabled', false );
$spinner.addClass( 'wpforms-hidden' );
$icon.removeClass( 'wpforms-hidden' );
} );
},
/**
* Form save error.
*
* @since 1.6.3
*
* @param {string} error Error message.
*/
formSaveError: function( error ) {
// Default error message.
if ( wpf.empty( error ) ) {
error = wpforms_builder.error_save_form;
}
// Display error in modal window.
$.confirm( {
title: wpforms_builder.heads_up,
content: '
' + error + '
' + wpforms_builder.error_contact_support + '
',
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Exit form builder.
*
* @since 1.0.0
*/
formExit: function() {
if ( app.isBuilderInPopup() && app.formIsSaved() ) {
app.builderInPopupClose( 'saved' );
return;
}
if ( app.formIsSaved() ) {
window.location.href = wpforms_builder.exit_url;
} else {
$.confirm( {
title: false,
content: wpforms_builder.exit_confirm,
icon: 'fa fa-exclamation-circle',
type: 'orange',
closeIcon: true,
buttons: {
confirm: {
text: wpforms_builder.save_exit,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action: function() {
app.formSave( true );
},
},
cancel: {
text: wpforms_builder.exit,
action: function() {
closeConfirmation = false;
if ( app.isBuilderInPopup() ) {
app.builderInPopupClose( 'canceled' );
return;
}
window.location.href = wpforms_builder.exit_url;
},
},
},
} );
}
},
/**
* Close confirmation setter.
*
* @since 1.6.2
*
* @param {boolean} confirm Close confirmation flag value.
*/
setCloseConfirmation: function( confirm ) {
closeConfirmation = ! ! confirm;
},
/**
* Check current form state.
*
* @since 1.0.0
*/
formIsSaved: function() {
if ( wpf.savedState == wpf.getFormState( '#wpforms-builder-form' ) ) {
return true;
} else {
return false;
}
},
/**
* Check if the builder opened in the popup (iframe).
*
* @since 1.6.2
*
* @returns {boolean} True if builder opened in the popup.
*/
isBuilderInPopup: function() {
return window.self !== window.parent && window.self.frameElement.id === 'wpforms-builder-iframe';
},
/**
* Close popup with the form builder.
*
* @since 1.6.2
*
* @param {string} action Performed action: saved or canceled.
*/
builderInPopupClose: function( action ) {
const $popup = window.parent.jQuery( '.wpforms-builder-popup' );
const $title = $( '.wpforms-center-form-name' ).text();
$popup.find( '#wpforms-builder-iframe' ).attr( 'src', 'about:blank' );
$popup.fadeOut();
$popup.trigger( 'wpformsBuilderInPopupClose', [ action, s.formID, $title ] );
},
//--------------------------------------------------------------------//
// General / global
//--------------------------------------------------------------------//
/**
* Element bindings for general and global items
*
* @since 1.2.0
*/
bindUIActionsGeneral: function() {
// Toggle Smart Tags
$builder.on( 'click', '.toggle-smart-tag-display', app.smartTagToggle );
$builder.on( 'click', '.smart-tags-list-display a', app.smartTagInsert );
// Toggle unfoldable group of fields
$builder.on( 'click', '.wpforms-panel-fields-group.unfoldable .wpforms-panel-fields-group-title', app.toggleUnfoldableGroup );
// Hide field preview helper box.
$builder.on( 'click', '.wpforms-field-helper-hide ', app.hideFieldHelper );
// Field map table, update key source
$builder.on( 'input', '.wpforms-field-map-table .key-source', function() {
var value = $( this ).val(),
$dest = $( this ).parent().parent().find( '.key-destination' ),
name = $dest.data( 'name' );
if ( value ) {
$dest.attr( 'name', name.replace( '{source}', value.replace( /[^0-9a-zA-Z_-]/gi, '' ) ) );
}
} );
// Field map table, delete row
$builder.on( 'click', '.wpforms-field-map-table .remove', function( e ) {
e.preventDefault();
app.fieldMapTableDeleteRow( e, $( this ) );
} );
// Field map table, Add row
$builder.on( 'click', '.wpforms-field-map-table .add', function( e ) {
e.preventDefault();
app.fieldMapTableAddRow( e, $( this ) );
} );
// Global select field mapping
$( document ).on( 'wpformsFieldUpdate', app.fieldMapSelect );
// Restrict user money input fields
$builder.on( 'input', '.wpforms-money-input', function( event ) {
var $this = $( this ),
amount = $this.val(),
start = $this[ 0 ].selectionStart,
end = $this[ 0 ].selectionEnd;
$this.val( amount.replace( /[^0-9.,]/g, '' ) );
$this[ 0 ].setSelectionRange( start, end );
} );
// Format user money input fields
$builder.on( 'focusout', '.wpforms-money-input', function( event ) {
var $this = $( this ),
amount = $this.val();
if ( ! amount ) {
return amount;
}
var sanitized = wpf.amountSanitize( amount ),
formatted = wpf.amountFormat( sanitized );
$this.val( formatted );
} );
// Show/hide a group of options.
$builder.on( 'change', '.wpforms-panel-field-toggle', function() {
var $input = $( this );
if ( $input.prop( 'disabled' ) ) {
return;
}
$input.prop( 'disabled', true );
app.toggleOptionsGroup( $input );
} );
// Don't allow users to enable payments if storing entries has
// been disabled in the General settings.
$builder.on( 'change', app.getPaymentsTogglesSelector(), function( event ) {
var $this = $( this ),
gateway = $this.attr( 'id' ).replace( /wpforms-panel-field-|-enable|_one_time|_recurring/gi, '' ),
$notificationWrap = $( '.wpforms-panel-content-section-notifications [id*="-' + gateway + '-wrap"]' ),
gatewayEnabled = $this.prop( 'checked' ) || $( '#wpforms-panel-field-' + gateway + '-enable_one_time' ).prop( 'checked' ) || $( '#wpforms-panel-field-' + gateway + '-enable_recurring' ).prop( 'checked' );
if ( gatewayEnabled ) {
var disabled = $( '#wpforms-panel-field-settings-disable_entries' ).prop( 'checked' );
if ( disabled ) {
$.confirm( {
title: wpforms_builder.heads_up,
content: wpforms_builder.payments_entries_off,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
$this.prop( 'checked', false );
} else {
$notificationWrap.removeClass( 'wpforms-hidden' );
}
} else {
$notificationWrap.addClass( 'wpforms-hidden' );
$notificationWrap.find( 'input[id*="-' + gateway + '"]' ).prop( 'checked', false );
}
} );
// Don't allow users to disable entries if payments has been enabled.
$builder.on( 'change', '#wpforms-panel-field-settings-disable_entries', function( event ) {
var $this = $( this );
if ( $this.prop( 'checked' ) ) {
if ( app.isPaymentsEnabled() ) {
$.confirm( {
title: wpforms_builder.heads_up,
content: wpforms_builder.payments_on_entries_off,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
$this.prop( 'checked', false );
} else {
$.alert( {
title: wpforms_builder.heads_up,
content: wpforms_builder.disable_entries,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
}
}
} );
// Upload or add an image.
$builder.on( 'click', '.wpforms-image-upload-add', function( event ) {
event.preventDefault();
var $this = $( this ),
$container = $this.parent(),
mediaModal;
mediaModal = wp.media.frames.wpforms_media_frame = wp.media( {
className: 'media-frame wpforms-media-frame',
frame: 'select',
multiple: false,
title: wpforms_builder.upload_image_title,
library: {
type: 'image',
},
button: {
text: wpforms_builder.upload_image_button,
},
} );
mediaModal.on( 'select', function() {
var mediaAttachment = mediaModal.state().get( 'selection' ).first().toJSON();
$container.find( '.source' ).val( mediaAttachment.url );
$container.find( '.preview' ).empty();
$container.find( '.preview' ).prepend( '
' );
if ( $this.data( 'after-upload' ) === 'hide' ) {
$this.hide();
}
$builder.trigger( 'wpformsImageUploadAdd', [ $this, $container ] );
} );
// Now that everything has been set, let's open up the frame.
mediaModal.open();
} );
// Remove and uploaded image.
$builder.on( 'click', '.wpforms-image-upload-remove', function( event ) {
event.preventDefault();
var $container = $( this ).parent().parent();
$container.find( '.preview' ).empty();
$container.find( '.wpforms-image-upload-add' ).show();
$container.find( '.source' ).val( '' );
$builder.trigger( 'wpformsImageUploadRemove', [ $( this ), $container ] );
} );
// Validate email smart tags in Notifications fields.
$builder.on( 'blur', '.wpforms-notification .wpforms-panel-field-text input', function() {
app.validateEmailSmartTags( $( this ) );
} );
$builder.on( 'blur', '.wpforms-notification .wpforms-panel-field-textarea textarea', function() {
app.validateEmailSmartTags( $( this ) );
} );
// Validate From Email in Notification settings.
$builder.on( 'focusout', '.wpforms-notification .wpforms-panel-field.js-wpforms-from-email-validation input', app.validateFromEmail );
$builder.on( 'wpformsPanelSectionSwitch', app.notificationsPanelSectionSwitch );
// Mobile notice primary button / close icon click.
$builder.on( 'click', '#wpforms-builder-mobile-notice .wpforms-fullscreen-notice-button-primary, #wpforms-builder-mobile-notice .close', function() {
window.location.href = wpforms_builder.exit_url;
} );
// Mobile notice secondary button click.
$builder.on( 'click', '#wpforms-builder-mobile-notice .wpforms-fullscreen-notice-button-secondary', function() {
window.location.href = wpf.updateQueryString( 'force_desktop_view', 1, window.location.href );
} );
// License Alert close button click.
$( '#wpforms-builder-license-alert .close' ).on( 'click', function() {
window.location.href = wpforms_builder.exit_url;
} );
// License Alert dismiss button click.
$( '#wpforms-builder-license-alert .dismiss' ).on( 'click', function( event ) {
event.preventDefault();
$( '#wpforms-builder-license-alert' ).remove();
wpCookies.set( 'wpforms-builder-license-alert', 'true', 3600 );
} );
// Don't allow the Akismet setting to be enabled if the Akismet plugin isn't available.
$builder.on( 'change', '#wpforms-panel-field-settings-akismet.wpforms-akismet-disabled', function( event ) {
const $this = $( this ),
akismetStatus = $this.data( 'akismet-status' );
if ( $this.prop( 'checked' ) ) {
$.alert( {
title: wpforms_builder.heads_up,
content: wpforms_builder[akismetStatus],
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
onClose: function() {
$this.prop( 'checked', false );
},
} );
}
} );
},
/**
* Notification section switch event handler.
*
* @since 1.8.2.3
*
* @param {object} e Event object.
* @param {string} panel Panel name.
*/
notificationsPanelSectionSwitch: function( e, panel ) {
if ( panel !== 'notifications' ) {
return;
}
$( '.wpforms-notification .wpforms-panel-field.js-wpforms-from-email-validation input' ).trigger( 'focusout' );
},
/**
* Check if one of the payment addons payments enabled.
*
* @since 1.7.5
*
* @returns {boolean} True if one of the payment addons payment enabled.
*/
isPaymentsEnabled: function() {
var paymentEnabled = false;
$( app.getPaymentsTogglesSelector() ).each( function() {
if ( $( this ).prop( 'checked' ) ) {
paymentEnabled = true;
return false;
}
} );
return paymentEnabled;
},
/**
* Get Payments toggles selector.
*
* @since 1.7.5
*
* @returns {string} List of selectors.
*/
getPaymentsTogglesSelector: function() {
return `.wpforms-panel-content-section-payment-toggle-one-time input,
.wpforms-panel-content-section-payment-toggle-recurring input,
#wpforms-panel-field-stripe-enable,
#wpforms-panel-field-paypal_standard-enable,
#wpforms-panel-field-authorize_net-enable,
#wpforms-panel-field-square-enable`;
},
/**
* Toggle an options group.
*
* @since 1.6.3
*
* @param {object} $input Toggled field.
*/
toggleOptionsGroup: function( $input ) {
var name = $input.attr( 'name' ),
type = $input.attr( 'type' ),
value = '',
$body = $( '.wpforms-panel-field-toggle-body[data-toggle="' + name + '"]' ),
enableInput = function() {
$input.prop( 'disabled', false );
};
if ( $body.length === 0 ) {
enableInput();
return;
}
if ( 'checkbox' === type || 'radio' === type ) {
value = $input.prop( 'checked' ) ? $input.val() : '0';
} else {
value = $input.val();
}
$body.each( function() {
var $this = $( this );
$this.attr( 'data-toggle-value' ).toString() === value.toString() ?
$this.slideDown( '', enableInput ) :
$this.slideUp( '', enableInput );
} );
},
/**
* Toggle all option groups.
*
* @since 1.6.3
*
* @param {jQuery} $context Context container jQuery object.
*/
toggleAllOptionGroups: function( $context ) {
$context = $context || $builder || $( '#wpforms-builder' ) || $( 'body' );
if ( ! $context ) {
return;
}
// Show a toggled bodies.
$context.find( '.wpforms-panel-field-toggle' ).each( function() {
var $input = $( this );
$input.prop( 'disabled', true );
app.toggleOptionsGroup( $input );
} );
},
/**
* Toggle unfoldable group of fields.
*
* @since 1.6.8
*
* @param {object} e Event object.
*/
toggleUnfoldableGroup: function( e ) {
e.preventDefault();
var $title = $( e.target ),
$group = $title.closest( '.wpforms-panel-fields-group' ),
$inner = $group.find( '.wpforms-panel-fields-group-inner' ),
cookieName = 'wpforms_fields_group_' + $group.data( 'group' );
if ( $group.hasClass( 'opened' ) ) {
wpCookies.remove( cookieName );
$inner.stop().slideUp( 150, function() {
$group.removeClass( 'opened' );
} );
} else {
wpCookies.set( cookieName, 'true', 2592000 ); // 1 month.
$group.addClass( 'opened' );
$inner.stop().slideDown( 150 );
}
},
/**
* Hide field preview helper box.
*
* @since 1.7.1
*
* @param {object} e Event object.
*/
hideFieldHelper: function( e ) {
e.preventDefault();
e.stopPropagation();
var $helpers = $( '.wpforms-field-helper' ),
cookieName = 'wpforms_field_helper_hide';
wpCookies.set( cookieName, 'true', 30 * 24 * 60 * 60 ); // 1 month.
$helpers.hide();
},
/**
* Smart Tag toggling.
*
* @since 1.0.1
* @since 1.6.9 Simplify method.
*
* @param {Event} e Event.
*/
smartTagToggle: function( e ) {
e.preventDefault();
// Prevent ajax to validate the default email queued on focusout event.
elements.$focusOutTarget = null;
var $this = $( this ),
$wrapper = $this.closest( '.wpforms-panel-field,.wpforms-field-option-row' );
if ( $wrapper.hasClass( 'smart-tags-toggling' ) ) {
return;
}
$wrapper.addClass( 'smart-tags-toggling' );
if ( $this.hasClass( 'smart-tag-showing' ) ) {
app.removeSmartTagsList( $this );
return;
}
app.insertSmartTagsList( $this );
},
/**
* Remove Smart Tag list.
*
* @since 1.6.9
*
* @param {jQuery} $el Toggle element.
*/
removeSmartTagsList: function( $el ) {
var $wrapper = $el.closest( '.wpforms-panel-field,.wpforms-field-option-row' ),
$list = $wrapper.find( '.smart-tags-list-display' );
$el.find( 'span' ).text( wpforms_builder.smart_tags_show );
$list.slideUp( '', function() {
$list.remove();
$el.removeClass( 'smart-tag-showing' );
$wrapper.removeClass( 'smart-tags-toggling' );
} );
},
/**
* Insert Smart Tag list.
*
* @since 1.6.9
*
* @param {jQuery} $el Toggle element.
*/
insertSmartTagsList: function( $el ) {
var $wrapper = $el.closest( '.wpforms-panel-field,.wpforms-field-option-row' ),
$label = $el.closest( 'label' ),
insideLabel = true,
smartTagList;
if ( ! $label.length ) {
$label = $wrapper.find( 'label' );
insideLabel = false;
}
smartTagList = app.getSmartTagsList( $el, $label.attr( 'for' ).indexOf( 'wpforms-field-option-' ) !== -1 );
insideLabel ?
$label.after( smartTagList ) :
$el.after( smartTagList );
$el.find( 'span' ).text( wpforms_builder.smart_tags_hide );
$wrapper.find( '.smart-tags-list-display' ).slideDown( '', function() {
$el.addClass( 'smart-tag-showing' );
$wrapper.removeClass( 'smart-tags-toggling' );
} );
},
/**
* Get Smart Tag list markup.
*
* @since 1.6.9
*
* @param {jQuery} $el Toggle element.
* @param {boolean} isFieldOption Is a field option.
*
* @returns {string} Smart Tags list markup.
*/
getSmartTagsList: function( $el, isFieldOption ) {
var smartTagList;
smartTagList = '
';
return smartTagList;
},
/**
* Get Smart Tag fields elements markup.
*
* @since 1.6.9
*
* @param {jQuery} $el Toggle element.
*
* @returns {string} Smart Tags list elements markup.
*/
getSmartTagsListFieldsElements: function( $el ) {
var type = $el.data( 'type' ),
fields = app.getSmartTagsFields( $el ),
smartTagListElements = '';
if ( ! [ 'fields', 'all' ].includes( type ) ) {
return '';
}
if ( ! fields ) {
return '
' + wpforms_builder.fields_unavailable + '';
}
smartTagListElements += '
' + wpforms_builder.fields_available + '';
for ( var fieldKey in wpf.orders.fields ) {
var fieldId = wpf.orders.fields[ fieldKey ];
if ( ! fields[ fieldId ] ) {
continue;
}
smartTagListElements += app.getSmartTagsListFieldsElement( fields[ fieldId ] );
}
return smartTagListElements;
},
/**
* Get fields that possible to create smart tag.
*
* @since 1.6.9
*
* @param {jQuery} $el Toggle element.
*
* @returns {Array} Fields for smart tags.
*/
getSmartTagsFields: function( $el ) {
var allowed = $el.data( 'fields' );
return allowed && allowed.length ? wpf.getFields( allowed.split( ',' ), true ) : wpf.getFields( false, true );
},
/**
* Get field markup for the Smart Tags list.
*
* @since 1.6.9
*
* @param {object} field A field.
*
* @returns {string} Smart Tags field markup.
*/
getSmartTagsListFieldsElement: function( field ) {
var label = field.label ?
wpf.encodeHTMLEntities( wpf.sanitizeHTML( field.label ) ) :
wpforms_builder.field + ' #' + field.id;
return '
' + label + '';
},
/**
* Get Smart Tag other elements markup.
*
* @since 1.6.9
*
* @param {jQuery} $el Toggle element.
* @param {boolean} isFieldOption Is a field option.
*
* @returns {string} Smart Tags list elements markup.
*/
getSmartTagsListOtherElements: function( $el, isFieldOption ) {
var type = $el.data( 'type' ),
smartTagListElements;
if ( type !== 'other' && type !== 'all' ) {
return '';
}
smartTagListElements = '
' + wpforms_builder.other + '';
for ( var smartTagKey in wpforms_builder.smart_tags ) {
if ( isFieldOption && wpforms_builder.smart_tags_disabled_for_fields.indexOf( smartTagKey ) > -1 ) {
continue;
}
smartTagListElements += '
' + wpforms_builder.smart_tags[ smartTagKey ] + '';
}
return smartTagListElements;
},
/**
* Smart Tag insert.
*
* @since 1.0.1
* @since 1.6.9 TinyMCE compatibility.
*
* @param {Event} e Event.
*/
smartTagInsert: function( e ) {
e.preventDefault();
var $this = $( this ),
$list = $this.closest( '.smart-tags-list-display' ),
$wrapper = $list.closest( '.wpforms-panel-field,.wpforms-field-option-row' ),
$toggle = $wrapper.find( '.toggle-smart-tag-display' ),
$input = $wrapper.find( 'input[type=text], textarea' ),
meta = $this.data( 'meta' ),
type = $this.data( 'type' ),
smartTag = type === 'field' ? '{field_id="' + meta + '"}' : '{' + meta + '}',
editor;
if ( typeof tinyMCE !== 'undefined' ) {
editor = tinyMCE.get( $input.prop( 'id' ) );
if ( editor && ! editor.hasFocus() ) {
editor.focus( true );
}
}
if ( editor && ! editor.isHidden() ) {
editor.insertContent( smartTag );
} else {
smartTag = ' ' + smartTag + ' ';
$input.insertAtCaret( smartTag );
// Remove redundant spaces after wrapping smartTag into spaces.
$input.val( $input.val().trim().replace( ' ', ' ' ) );
$input.trigger( 'focus' ).trigger( 'input' );
}
// remove list, all done!
$list.slideUp( '', function() {
$list.remove();
} );
$toggle.find( 'span' ).text( wpforms_builder.smart_tags_show );
$wrapper.find( '.toggle-smart-tag-display' ).removeClass( 'smart-tag-showing' );
},
/**
* Field map table - Delete row.
*
* @since 1.2.0
* @since 1.6.1.2 Registered `wpformsFieldMapTableDeletedRow` trigger.
*/
fieldMapTableDeleteRow: function( e, el ) {
var $this = $( el ),
$row = $this.closest( 'tr' ),
$table = $this.closest( 'table' ),
$block = $row.closest( '.wpforms-builder-settings-block' ),
total = $table.find( 'tr' ).length;
if ( total > '1' ) {
$row.remove();
$builder.trigger( 'wpformsFieldMapTableDeletedRow', [ $block ] );
}
},
/**
* Field map table - Add row.
*
* @since 1.2.0
* @since 1.6.1.2 Registered `wpformsFieldMapTableAddedRow` trigger.
*/
fieldMapTableAddRow: function( e, el ) {
var $this = $( el ),
$row = $this.closest( 'tr' ),
$block = $row.closest( '.wpforms-builder-settings-block' ),
choice = $row.clone().insertAfter( $row );
choice.find( 'input' ).val( '' );
choice.find( 'select :selected' ).prop( 'selected', false );
choice.find( '.key-destination' ).attr( 'name', '' );
$builder.trigger( 'wpformsFieldMapTableAddedRow', [ $block, choice ] );
},
/**
* Update field mapped select items on form updates.
*
* @since 1.2.0
* @since 1.6.1.2 Registered `wpformsFieldSelectMapped` trigger.
*/
fieldMapSelect: function( e, fields ) {
$( '.wpforms-field-map-select' ).each( function( index, el ) {
var $this = $( this ),
selected = $this.find( 'option:selected' ).val(),
allowedFields = $this.data( 'field-map-allowed' ),
placeholder = $this.data( 'field-map-placeholder' );
// Check if custom placeholder was provided.
if ( typeof placeholder === 'undefined' || ! placeholder ) {
placeholder = wpforms_builder.select_field;
}
// Reset select and add a placeholder option.
$this.empty().append( $( '