upskill-event-manager/assets/js/admin-builder.js
Ben Reed cdc5ea85f4 feat: Add comprehensive CSS, JavaScript and theme asset infrastructure
Add massive collection of CSS, JavaScript and theme assets that were previously excluded:

**CSS Files (681 total):**
- HVAC plugin-specific styles (hvac-*.css): 34 files including dashboard, certificates, registration, mobile nav, accessibility fixes, animations, and welcome popup
- Theme framework files (Astra, builder systems, layouts): 200+ files
- Plugin compatibility styles (WooCommerce, WPForms, Elementor, Contact Form 7): 150+ files
- WordPress core and editor styles: 50+ files
- Responsive and RTL language support: 200+ files

**JavaScript Files (400+ total):**
- HVAC plugin functionality (hvac-*.js): 27 files including menu systems, dashboard enhancements, profile sharing, mobile responsive features, accessibility, and animations
- Framework and library files: jQuery plugins, GSAP, AOS, Swiper, Chart.js, Lottie, Isotope
- Plugin compatibility scripts: WPForms, WooCommerce, Elementor, Contact Form 7, LifterLMS
- WordPress core functionality: customizer, admin, block editor compatibility
- Third-party integrations: Stripe, SMTP, analytics, search functionality

**Assets:**
- Certificate background images and logos
- Comprehensive theme styling infrastructure
- Mobile-responsive design systems
- Cross-browser compatibility assets
- Performance-optimized minified versions

**Updated .gitignore:**
- Fixed asset directory whitelisting patterns to properly include CSS/JS/images
- Added proper directory structure recognition (!/assets/css/, !/assets/js/, etc.)
- Maintains security by excluding sensitive files while including essential assets

This commit provides the complete frontend infrastructure needed for:
- Full theme functionality and styling
- Plugin feature implementations
- Mobile responsiveness and accessibility
- Cross-browser compatibility
- Performance optimization
- Developer workflow support
2025-08-11 16:20:31 -03:00

9157 lines
284 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* global wpforms_builder, wpf, jconfirm, wpforms_panel_switch, Choices, WPForms, WPFormsFormEmbedWizard, wpCookies, tinyMCE, WPFormsUtils, List, wpforms_preset_choices */
/**
* @param wpforms_builder.smart_tags_disabled_for_confirmations
*/
/* noinspection JSUnusedLocalSymbols */
/* eslint-disable no-unused-expressions, no-shadow */
// noinspection ES6ConvertVarToLetConst
var WPFormsBuilder = window.WPFormsBuilder || ( function( document, window, $ ) { // eslint-disable-line no-var
let s,
$builder;
const elements = {},
browser = {};
/**
* Whether to show the close confirmation dialog or not.
*
* @since 1.6.0
*
* @type {boolean}
*/
let closeConfirmation = true;
/**
* A field is adding.
*
* @since 1.7.1
*
* @type {boolean}
*/
let adding = false;
/**
* Preview tab.
*
* @since 1.9.4
*
* @type {object|null}
*/
let previewTab = null;
// noinspection JSUnusedGlobalSymbols
const app = {
/* eslint-disable camelcase */
settings: {
spinner: '<i class="wpforms-loading-spinner"></i>',
spinnerInline: '<i class="wpforms-loading-spinner wpforms-loading-inline"></i>',
tinymceDefaults: {
tinymce: { toolbar1: 'bold,italic,underline,blockquote,strikethrough,bullist,numlist,alignleft,aligncenter,alignright,undo,redo,link' },
quicktags: true,
},
pagebreakTop: false,
pagebreakBottom: false,
upload_img_modal: false,
choicesLimit: 20, // Choices limit for fields different from Dropdown.
choicesLimitLong: 250, // Choices limit for Dropdown field.
},
/**
* Start the engine.
*
* @since 1.0.0
*/
init() {
const that = this;
wpforms_panel_switch = true;
s = this.settings;
// Document ready.
$( app.ready );
// Page load.
$( window ).on( 'load', function() {
// In the case of jQuery 3.+, we need to wait for a ready event first.
if ( typeof $.ready.then === 'function' ) {
$.ready.then( app.load );
} else {
app.load();
}
} );
$( window ).on( 'beforeunload', function() {
if ( ! that.formIsSaved() && closeConfirmation ) {
return wpforms_builder.are_you_sure_to_close;
}
} );
},
/**
* Page load.
*
* @since 1.0.0
* @since 1.7.9 Added `wpformsBuilderReady` hook.
*
* @return {false|void} False if default event is prevented.
*/
load() {
// Trigger initial save for new forms.
if ( wpf.getQueryString( 'newform' ) ) {
app.formSave( false );
}
const panel = $( '#wpforms-panels-toggle .active' ).data( 'panel' );
// Render form preview on the Revisions panel if the panel is active.
if ( panel === 'revisions' ) {
app.updateRevisionPreview();
}
// Allow callbacks to prevent making Form Builder ready...
const event = WPFormsUtils.triggerEvent( $builder, 'wpformsBuilderReady' );
// ...by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return false;
}
// Hide loading overlay and make the Form Builder ready to use.
app.hideLoadingOverlay();
// Maybe display informational modal.
// noinspection JSUnresolvedReference, EqualityComparisonWithCoercionJS
if ( wpforms_builder.template_modal_display == '1' && 'fields' === wpf.getQueryString( 'view' ) ) { // eslint-disable-line
$.alert( {
title: wpforms_builder.template_modal_title,
content: wpforms_builder.template_modal_msg,
icon: 'fa fa-info-circle',
type: 'blue',
buttons: {
confirm: {
text: wpforms_builder.close,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
}
},
/**
* Init elements cache.
*
* @since 1.9.2
*/
initElementsCache() {
// Cache builder element.
$builder = $( '#wpforms-builder' );
browser.isWindows = /Win/.test( navigator.userAgent );
browser.isLinux = /Linux/.test( navigator.userAgent );
browser.isMac = /Mac/.test( navigator.userAgent );
// Action buttons.
elements.$helpButton = $( '#wpforms-help' );
elements.$previewButton = $( '#wpforms-preview-btn' );
elements.$embedButton = $( '#wpforms-embed' );
elements.$saveButton = $( '#wpforms-save' );
elements.$exitButton = $( '#wpforms-exit' );
// Cache other elements.
elements.$noFieldsOptions = $( '#wpforms-panel-fields .wpforms-no-fields-holder .no-fields' );
elements.$noFieldsPreview = $( '#wpforms-panel-fields .wpforms-no-fields-holder .no-fields-preview' );
elements.$formPreview = $( '#wpforms-panel-fields .wpforms-preview-wrap' );
elements.$revisionPreview = $( '#wpforms-panel-revisions .wpforms-panel-content' );
elements.defaultEmailSelector = '.wpforms-field-option-email .wpforms-field-option-row-default_value input';
elements.$defaultEmail = $( elements.defaultEmailSelector );
elements.$focusOutTarget = null;
elements.$nextFieldId = $( '#wpforms-field-id' );
elements.$addFieldsTab = $( '#add-fields a' );
elements.$fieldOptions = $( '#wpforms-field-options' );
elements.$fieldsPreviewWrap = $( '#wpforms-panel-fields .wpforms-panel-content-wrap' );
elements.$sortableFieldsWrap = $( '#wpforms-panel-fields .wpforms-field-wrap' );
elements.$addFieldsButtons = $( '.wpforms-add-fields-button' ).not( '.not-draggable' ).not( '.warning-modal' ).not( '.education-modal' );
elements.$fieldsSidebar = $( '#wpforms-panel-fields .wpforms-add-fields' );
elements.$searchInput = $( '#wpforms-search-fields-input' );
elements.$sidebarToggle = $( '.wpforms-panels .wpforms-panel-sidebar-content .wpforms-panel-sidebar-toggle' );
},
/**
* Document ready.
*
* @since 1.0.0
*/
ready() { // eslint-disable-line max-lines-per-function
if ( app.isVisitedViaBackButton() ) {
location.reload();
return;
}
app.initElementsCache();
// Add `_wp_http_referer` to the data of every AJAX request.
$.ajaxSetup( {
data: {
// eslint-disable-next-line camelcase
_wp_http_referer: wpf.updateQueryString( '_wp_http_referer', null ),
},
} );
// Remove Embed button if builder opened in the popup.
if ( app.isBuilderInPopup() ) {
elements.$embedButton.remove();
elements.$previewButton.addClass( 'wpforms-alone' );
}
app.loadMsWinCSS();
// Bind all actions.
app.bindUIActions();
// Setup/cache some vars not available before
s.formID = $( '#wpforms-builder-form' ).data( 'id' );
s.pagebreakTop = $( '.wpforms-pagebreak-top' ).length;
s.pagebreakBottom = $( '.wpforms-pagebreak-bottom' ).length;
// Disable implicit submission for every form inside the builder.
// All form values are managed by JS and should not be submitted by pressing Enter.
$builder.on( 'keypress', '#wpforms-builder-form :input:not(textarea)', function( e ) {
if ( e.keyCode === 13 ) {
e.preventDefault();
}
} );
app.determineActiveSections();
app.loadEntryPreviewFields();
// Drag and drop sortable elements.
app.fieldChoiceSortable( 'select' );
app.fieldChoiceSortable( 'radio' );
app.fieldChoiceSortable( 'checkbox' );
app.fieldChoiceSortable( 'payment-multiple' );
app.fieldChoiceSortable( 'payment-checkbox' );
app.fieldChoiceSortable( 'payment-select' );
// Set field group visibility.
$( '.wpforms-add-fields-group' ).each( function( index, el ) { // eslint-disable-line no-unused-vars
app.fieldGroupToggle( $( this ), 'load' );
} );
app.registerTemplates();
// Trim long form titles.
app.trimFormTitle();
// Load Tooltips.
wpf.initTooltips();
// Load Color Pickers.
app.loadColorPickers();
// Hide/Show CAPTCHA in form.
app.captchaToggle();
// Confirmations' initial setup.
app.confirmationsSetup();
// Notification settings.
app.notificationToggle();
app.notificationsByStatusAlerts();
app.notificationsUpdateElementsVisibility();
// Secret builder hotkeys.
app.builderHotkeys();
// jquery-confirm defaults.
jconfirm.defaults = {
closeIcon: false,
backgroundDismiss: false,
escapeKey: true,
animationBounce: 1,
useBootstrap: false,
theme: 'modern',
boxWidth: '400px',
animateFromElement: false,
content: wpforms_builder.something_went_wrong,
};
app.dropdownField.init();
app.iconChoices.init();
app.disabledFields.init();
app.checkEmptyDynamicChoices();
app.initSomeFieldOptions();
app.dismissNotice();
wpf.initializeChoicesEventHandlers();
},
checkEmptyDynamicChoices() {
const choices = wpf.orders.choices || {};
if ( ! Object.keys( choices ).length ) {
return;
}
wpf.orders.fields.forEach( function( fieldId ) { // eslint-disable-line complexity
const isDynamic = app.dropdownField.helpers.isDynamicChoices( fieldId );
if ( ! isDynamic ) {
return;
}
const $fieldPreview = $( '#wpforms-field-' + fieldId );
const type = app.dropdownField.helpers.getDynamicChoicesOptionType( fieldId );
const source = app.dropdownField.helpers.getDynamicChoicesOptionSource( fieldId );
const isModern = app.dropdownField.helpers.isDynamicChoicesOptionModern( fieldId );
let isEmpty = isModern
? $fieldPreview.find( '.has-no-choices' ).length
: $fieldPreview.find( '.primary-input option:not(.placeholder), .primary-input li' ).length === 0;
if ( isModern && ! isEmpty ) {
const placeholder = $( '#wpforms-field-option-' + fieldId + '-placeholder' ).val();
const choices = app.dropdownField.helpers.getInitialChoices( fieldId );
isEmpty = choices.length === 1 && choices[ 0 ].label === placeholder && choices[ 0 ].placeholder === true;
}
if ( isEmpty ) {
app.emptyChoicesNotice( fieldId, source, type );
}
} );
},
/**
* Load Microsoft Windows specific stylesheet.
*
* @since 1.6.8
*/
loadMsWinCSS() {
// Detect OS & browsers.
if ( browser.isMac ) {
return;
}
$( '<link>' )
.appendTo( 'head' )
.attr( {
type: 'text/css',
rel: 'stylesheet',
href: wpforms_builder.scrollbars_css_url,
} );
},
/**
* Builder was visited via back button in the browser.
*
* @since 1.6.5
*
* @return {boolean} True if the builder was visited via back button in browser.
*/
isVisitedViaBackButton() {
if ( ! performance ) {
return false;
}
let isVisitedViaBackButton = false;
performance.getEntriesByType( 'navigation' ).forEach( function( nav ) {
if ( nav.type === 'back_forward' ) {
isVisitedViaBackButton = true;
}
} );
return isVisitedViaBackButton;
},
/**
* Remove loading overlay.
*
* @since 1.6.8
*/
hideLoadingOverlay() {
const $overlay = $( '#wpforms-builder-overlay' );
$overlay.addClass( 'fade-out' );
setTimeout( function() {
$overlay.hide();
}, 250 );
},
/**
* Show loading overlay.
*
* @since 1.6.8
*/
showLoadingOverlay() {
const $overlay = $( '#wpforms-builder-overlay' );
$overlay.removeClass( 'fade-out' );
$overlay.show();
},
/**
* Initialize some fields options controls.
*
* @since 1.6.3
*/
initSomeFieldOptions() {
// Show a toggled options groups.
app.toggleAllOptionGroups( $builder );
// Date/Time field Date type option.
$builder.find( '.wpforms-field-option-row-date .type select' ).trigger( 'change' );
},
/**
* Dropdown field component.
*
* @since 1.6.1
*/
dropdownField: {
/**
* Field configuration.
*
* @since 1.6.1
*/
config: {
modernClass: 'choicesjs-select',
args: {
searchEnabled: false,
searchChoices: false,
renderChoiceLimit: 1,
shouldSort: false,
callbackOnInit() {
const $element = $( this.containerOuter.element ),
$previewSelect = $element.closest( '.wpforms-field' ).find( 'select' );
// Turn off disabled styles.
if ( $element.hasClass( 'is-disabled' ) ) {
$element.removeClass( 'is-disabled' );
}
// Disable instances on the preview panel.
if ( $previewSelect.is( '[readonly]' ) ) {
this.disable();
$previewSelect.prop( 'disabled', false );
}
if ( this.passedElement.element.multiple ) {
// Hide a placeholder if field has selected choices.
if ( this.getValue( true ).length ) {
$( this.input.element ).addClass( 'choices__input--hidden' );
}
}
// Decode allowed HTML entities for choices.
$element.find( '.choices__item--selectable' ).each( function() {
const $choice = $( this );
const text = wpf.decodeAllowedHTMLEntities( $choice.text() );
$choice.text( text );
} );
},
},
},
/**
* Initialization for field component.
*
* @since 1.6.1
*/
init() {
// Choices.js init.
$builder.find( '.' + app.dropdownField.config.modernClass ).each( function() {
app.dropdownField.events.choicesInit( $( this ) );
} );
// Multiple option.
$builder.on(
'change',
'.wpforms-field-option-select .wpforms-field-option-row-multiple input',
app.dropdownField.events.multiple
);
// Style option.
$builder.on(
'change',
'.wpforms-field-option-select .wpforms-field-option-row-style select, .wpforms-field-option-payment-select .wpforms-field-option-row-style select',
app.dropdownField.events.applyStyle
);
// Add the ability to close the drop-down menu.
$builder.on( 'click', '.choices', function( e ) {
const $choices = $( this ),
choicesObj = $choices.find( 'select' ).data( 'choicesjs' );
if (
choicesObj &&
$choices.hasClass( 'is-open' ) &&
e.target.classList.contains( 'choices__inner' )
) {
choicesObj.hideDropdown();
}
} );
},
/**
* Field events.
*
* @since 1.6.1
*/
events: {
/**
* Load Choices.js library.
*
* @since 1.6.1
*
* @param {Object} $element jQuery element selector.
*/
choicesInit( $element ) {
const useAjax = $element.data( 'choicesjs-use-ajax' ) === 1;
let instance;
if ( $element.data( 'choicesjs-callback-fn' ) === 'select_pages' ) {
instance = WPForms.Admin.Builder.WPFormsChoicesJS.setup(
$element[ 0 ],
app.dropdownField.config.args,
{
action: 'wpforms_ajax_search_pages_for_dropdown',
nonce: useAjax ? wpforms_builder.nonce : null,
}
);
} else {
instance = new Choices( $element[ 0 ], app.dropdownField.config.args );
}
app.dropdownField.helpers.setInstance( $element, instance );
app.dropdownField.helpers.addPlaceholderChoice( $element, instance );
$element.closest( '.choices' ).toggleClass( 'wpforms-hidden', ! instance.config.choices.length );
},
/**
* Multiple option callback.
*
* @since 1.6.1
*
* @param {Object} event Event object.
*/
multiple( event ) {
const fieldId = $( this ).closest( '.wpforms-field-option-row-multiple' ).data().fieldId,
$primary = app.dropdownField.helpers.getPrimarySelector( fieldId ),
$optionChoicesItems = $( '#wpforms-field-option-row-' + fieldId + '-choices input.default' ),
$placeholder = $primary.find( '.placeholder' ),
isDynamicChoices = app.dropdownField.helpers.isDynamicChoices( fieldId ),
isMultiple = event.target.checked,
choicesType = isMultiple ? 'checkbox' : 'radio';
// Add/remove a `multiple` attribute.
$primary.prop( 'multiple', isMultiple );
// Change a `Choices` fields type:
// checkbox - needed for multiple selection
// radio - needed for single selection
$optionChoicesItems.prop( 'type', choicesType );
// Dynamic Choices doesn't have default choices (selected options) - make all as unselected.
if ( isDynamicChoices ) {
$primary.find( 'option:selected' ).prop( 'selected', false );
}
// Gets default choices.
const selectedChoices = $optionChoicesItems.filter( ':checked' );
if ( ! isMultiple && selectedChoices.length ) {
// Uncheck all choices.
$optionChoicesItems.prop( 'checked', false );
// For single selection, we can choose only one.
$( selectedChoices.get( 0 ) ).prop( 'checked', true );
}
// Toggle selection for a placeholder option based on a select type.
if ( $placeholder.length ) {
$placeholder.prop( 'selected', ! isMultiple );
}
// Update a primary field.
app.dropdownField.helpers.update( fieldId, isDynamicChoices );
},
/**
* Apply a style to <select> - modern or classic.
*
* @since 1.6.1
*/
applyStyle() {
const $field = $( this ),
fieldId = $field.closest( '.wpforms-field-option-row-style' ).data().fieldId,
fieldVal = $field.val();
if ( 'modern' === fieldVal ) {
app.dropdownField.helpers.convertClassicToModern( fieldId );
} else {
app.dropdownField.helpers.convertModernToClassic( fieldId );
}
},
},
helpers: {
/**
* Get Modern select options and prepare them for the Classic <select>.
*
* @since 1.6.1
*
* @param {string} fieldId Field ID.
*/
convertModernToClassic: ( fieldId ) => {
const $primary = app.dropdownField.helpers.getPrimarySelector( fieldId ),
isDynamicChoices = app.dropdownField.helpers.isDynamicChoices( fieldId ),
instance = app.dropdownField.helpers.getInstance( $primary ),
$sidebarChoices = $( '#wpforms-field-option-row-' + fieldId + '-choices' ),
$sidebarList = $sidebarChoices.find( '.choices-list' ),
elementsCount = $sidebarList.find( 'li' ).length;
if ( instance && typeof instance.destroy === 'function' ) {
// Destroy the instance of Choices.js.
instance.destroy();
// Update a placeholder.
app.dropdownField.helpers.updatePlaceholderChoice( instance, fieldId );
}
// Update choices.
if ( ! isDynamicChoices ) {
app.fieldChoiceUpdate( 'select', fieldId, elementsCount );
}
},
/**
* Get initial choices.
*
* @since 1.8.2
*
* @param {string} fieldId Field ID.
*
* @return {Object} Choices.
*/
getInitialChoices( fieldId ) {
const $primary = app.dropdownField.helpers.getPrimarySelector( fieldId ),
instance = app.dropdownField.helpers.getInstance( $primary );
return instance.config.choices;
},
/**
* Convert a Classic to Modern style selector.
*
* @since 1.6.1
*
* @param {string} fieldId Field ID.
*/
convertClassicToModern( fieldId ) {
const $primary = app.dropdownField.helpers.getPrimarySelector( fieldId ),
isDynamicChoices = app.dropdownField.helpers.isDynamicChoices( fieldId );
// Update choices.
if ( ! isDynamicChoices ) {
app.fieldChoiceUpdate( 'select', fieldId );
}
// Call a Choices.js initialization.
app.dropdownField.events.choicesInit( $primary );
},
/**
* Update a primary field.
*
* @since 1.6.1
*
* @param {string} fieldId Field ID.
* @param {boolean} isDynamicChoices True if `Dynamic Choices` is turned on.
*/
update( fieldId, isDynamicChoices ) {
const $primary = app.dropdownField.helpers.getPrimarySelector( fieldId );
if ( app.dropdownField.helpers.isModernSelect( $primary ) ) {
// If we had a `Modern` select before, then we need to make re-init - destroy() + init().
app.dropdownField.helpers.convertModernToClassic( fieldId );
if ( ! isDynamicChoices ) {
app.dropdownField.events.choicesInit( $primary );
}
} else if ( ! isDynamicChoices ) {
// Update choices.
app.fieldChoiceUpdate( 'select', fieldId );
}
},
/**
* Add a new choice to behave like a placeholder.
*
* @since 1.6.1
*
* @param {Object} $jquerySelector jQuery primary selector.
* @param {Object} instance The instance of Choices.js.
*
* @return {boolean} False if a fake placeholder wasn't added.
*/
addPlaceholderChoice( $jquerySelector, instance ) { // eslint-disable-line complexity
const wpFormsField = $jquerySelector.closest( '.wpforms-field' );
if ( wpFormsField.length <= 0 ) {
return false;
}
const fieldId = wpFormsField.data().fieldId;
let hasDefaults = app.dropdownField.helpers.hasDefaults( fieldId );
if ( app.dropdownField.helpers.isDynamicChoices( fieldId ) ) {
hasDefaults = false;
}
// Already has a placeholder.
if ( false !== app.dropdownField.helpers.searchPlaceholderChoice( instance ) ) {
return false;
}
// No choices.
if ( ! instance.config.choices.length ) {
return false;
}
const placeholder = wpf.decodeAllowedHTMLEntities( instance.config.choices[ 0 ].label ),
isMultiple = $( instance.passedElement.element ).prop( 'multiple' ),
selected = ! ( isMultiple || hasDefaults );
// Add a new choice as a placeholder.
instance.setChoices(
[
{ value: '', label: placeholder, selected, placeholder: true },
],
'value',
'label',
false
);
// Additional case for multiple select.
if ( isMultiple ) {
$( instance.input.element ).prop( 'placeholder', placeholder );
}
return true;
},
/**
* Search a choice-placeholder item.
*
* @since 1.6.1
*
* @param {Object} instance The instance of Choices.js.
*
* @return {boolean|object} False if a field doesn't have a choice-placeholder.
* Otherwise - return choice item.
*/
searchPlaceholderChoice( instance ) {
let find = false;
instance.config.choices.forEach( function( item, i, choices ) { // eslint-disable-line no-unused-vars
if ( 'undefined' !== typeof item.placeholder && true === item.placeholder ) {
find = {
key: i,
item,
};
return false;
}
} );
return find;
},
/**
* Add/update a placeholder.
*
* @since 1.6.1
*
* @param {Object} instance The instance of Choices.js.
* @param {string} fieldId Field ID.
*/
updatePlaceholderChoice( instance, fieldId ) {
const $primary = $( instance.passedElement.element ),
placeholderValue = wpf.sanitizeHTML( $( '#wpforms-field-option-' + fieldId + '-placeholder' ).val() ),
placeholderChoice = app.dropdownField.helpers.searchPlaceholderChoice( instance );
let $placeholderOption = {};
// Get an option with placeholder.
// Note: `.placeholder` class is skipped when calling Choices.js destroy() method.
if ( 'object' === typeof placeholderChoice ) {
$placeholderOption = $( $primary.find( 'option' ).get( placeholderChoice.key ) );
}
// We have a placeholder and need to update the UI with it.
if ( '' !== placeholderValue ) {
if ( ! $.isEmptyObject( $placeholderOption ) && $placeholderOption.length ) {
// Update a placeholder option.
$placeholderOption
.addClass( 'placeholder' )
.text( placeholderValue );
} else {
// Add a placeholder option.
$primary.prepend( '<option value="" class="placeholder">' + placeholderValue + '</option>' );
}
} else if ( $placeholderOption.length ) {
// Remove the placeholder as it's empty.
$placeholderOption.remove();
}
},
/**
* Is it a `Modern` style dropdown field?
*
* @since 1.6.1
*
* @param {Object} $jquerySelector jQuery primary selector.
*
* @return {boolean} True if it's a `Modern` style select, false otherwise.
*/
isModernSelect( $jquerySelector ) {
const instance = app.dropdownField.helpers.getInstance( $jquerySelector );
if ( 'object' !== typeof instance ) {
return false;
}
if ( $.isEmptyObject( instance ) ) {
return false;
}
return instance.initialised;
},
/**
* Save an instance of Choices.js.
*
* @since 1.6.1
*
* @param {Object} $jquerySelector jQuery primary selector.
* @param {Object} instance The instance of Choices.js.
*/
setInstance( $jquerySelector, instance ) {
$jquerySelector.data( 'choicesjs', instance );
},
/**
* Retrieve an instance of Choices.js.
*
* @since 1.6.1
*
* @param {Object} $jquerySelector jQuery primary selector.
*
* @return {Object} The instance of Choices.js.
*/
getInstance( $jquerySelector ) {
return $jquerySelector.data( 'choicesjs' );
},
/**
* Get Dynamic Choices option field.
*
* @since 1.8.2
*
* @param {string|number} fieldId Field ID.
*
* @return {HTMLElement|boolean} False if a field doesn't have a `Dynamic Choices` option.
* Otherwise - return option field.
*/
getDynamicChoicesOption( fieldId ) {
const $fieldOption = $( '#wpforms-field-option-' + fieldId + '-dynamic_choices' );
if ( ! $fieldOption.length ) {
return false;
}
return $fieldOption;
},
/**
* Is `Dynamic Choices` used?
*
* @since 1.6.1
*
* @param {string|number} fieldId Field ID.
*
* @return {boolean} True if a `Dynamic Choices` active, false otherwise.
*/
isDynamicChoices( fieldId ) {
const $fieldOption = app.dropdownField.helpers.getDynamicChoicesOption( fieldId );
if ( ! $fieldOption.length ) {
return false;
}
return '' !== $fieldOption.val();
},
/**
* Is `Dynamic Choices` option type is `Modern`?
*
* @since 1.8.2
*
* @param {string|number} fieldId Field ID.
* @return {boolean} True if a `Dynamic Choices` option type is `Modern`, false otherwise.
*/
isDynamicChoicesOptionModern( fieldId ) {
const $fieldOption = $( '#wpforms-field-option-' + fieldId + '-style' );
if ( ! $fieldOption.length ) {
return false;
}
return $fieldOption.val() === 'modern';
},
/**
* Get a Dynamic Choices option type.
*
* @since 1.8.2
*
* @param {string|number} fieldId Field ID.
*
* @return {string|boolean} False if a field doesn't have a `Dynamic Choices` option.
* Otherwise - return option type.
*/
getDynamicChoicesOptionType( fieldId ) {
const $fieldOption = app.dropdownField.helpers.getDynamicChoicesOption( fieldId );
if ( ! $fieldOption.length ) {
return false;
}
return $fieldOption.val();
},
/**
* Get a Dynamic Choices option source.
*
* @since 1.8.2
*
* @param {string|number} fieldId Field ID.
*
* @return {string|boolean} False if a field doesn't have a `Dynamic Choices` option.
* Otherwise - return option source.
*/
getDynamicChoicesOptionSource( fieldId ) {
const type = app.dropdownField.helpers.getDynamicChoicesOptionType( fieldId );
const $fieldOption = $( '#wpforms-field-option-' + fieldId + '-dynamic_' + type );
if ( ! $fieldOption.length ) {
return false;
}
return $fieldOption.find( 'option:selected' ).text();
},
/**
* Is a field having default choices?
*
* @since 1.6.1
*
* @param {string} fieldId Field ID.
*
* @return {boolean} True if a field has default choices.
*/
hasDefaults( fieldId ) {
const $choicesList = $( '#wpforms-field-option-row-' + fieldId + '-choices .choices-list' );
return !! $choicesList.find( 'input.default:checked' ).length;
},
/**
* Retrieve a jQuery selector for the Primary field.
*
* @since 1.6.1
*
* @param {string} fieldId Field ID.
*
* @return {Object} jQuery primary selector.
*/
getPrimarySelector( fieldId ) {
return $( '#wpforms-field-' + fieldId + ' .primary-input' );
},
},
},
/**
* Add number slider events listeners.
*
* @since 1.5.7
*
* @param {Object} $builder JQuery object.
*/
numberSliderEvents( $builder ) {
// Minimum update.
$builder.on(
'focusout',
'.wpforms-field-option-row-min_max .wpforms-input-row .wpforms-number-slider-min',
app.fieldNumberSliderUpdateMin
);
// Maximum update.
$builder.on(
'focusout',
'.wpforms-field-option-row-min_max .wpforms-input-row .wpforms-number-slider-max',
app.fieldNumberSliderUpdateMax
);
// Change default input value.
$builder.on(
'input',
'.wpforms-number-slider-default-value',
_.debounce( app.changeNumberSliderDefaultValue, 500 )
);
// Change default input value if it's empty.
$builder.on(
'focusout',
'.wpforms-number-slider-default-value',
app.changeNumberSliderEmptyDefaultValue
);
// Trigger input event on default value input to check if it's valid.
$builder.find( '.wpforms-number-slider-default-value' ).trigger( 'input' );
// Change step value.
$builder.on(
'input',
'.wpforms-number-slider-step',
_.debounce( app.changeNumberSliderStep, 500 )
);
// Check step value.
$builder.on(
'focusout',
'.wpforms-number-slider-step',
app.checkNumberSliderStep
);
// Change value display.
$builder.on(
'input',
'.wpforms-number-slider-value-display',
_.debounce( app.changeNumberSliderValueDisplay, 500 )
);
// Change min value.
$builder.on(
'input',
'.wpforms-number-slider-min',
_.debounce( app.changeNumberSliderMin, 500 )
);
// Change max value.
$builder.on(
'input',
'.wpforms-number-slider-max',
_.debounce( app.changeNumberSliderMax, 500 )
);
},
/**
* Change number slider min option.
*
* @since 1.5.7
*
* @param {Object} event Input event.
*/
changeNumberSliderMin( event ) {
const value = parseFloat( event.target.value );
if ( isNaN( value ) ) {
return;
}
const fieldID = $( event.target ).parents( '.wpforms-field-option-row' ).data( 'fieldId' );
app.updateNumberSliderDefaultValueAttr( fieldID, event.target.value, 'min' );
},
/**
* Change number slider max option.
*
* @since 1.5.7
*
* @param {Object} event Input event.
*/
changeNumberSliderMax( event ) {
const value = parseFloat( event.target.value );
if ( isNaN( value ) ) {
return;
}
const fieldID = $( event.target ).parents( '.wpforms-field-option-row' ).data( 'fieldId' );
app.updateNumberSliderDefaultValueAttr( fieldID, event.target.value, 'max' )
.updateNumberSliderStepValueMaxAttr( fieldID, event.target.value );
},
/**
* Change number slider value display option.
*
* @since 1.5.7
*
* @param {Object} event Input event.
*/
changeNumberSliderValueDisplay( event ) {
const str = event.target.value;
const fieldID = $( event.target ).parents( '.wpforms-field-option-row' ).data( 'fieldId' );
const defaultValue = document.getElementById( 'wpforms-field-option-' + fieldID + '-default_value' );
if ( defaultValue ) {
app.updateNumberSliderHintStr( fieldID, str )
.updateNumberSliderHint( fieldID, defaultValue.value );
}
},
/**
* Change number slider step option.
*
* @since 1.5.7
*
* @param {Object} event Input event.
*/
changeNumberSliderStep( event ) {
const $el = $( this );
const value = parseFloat( $el.val() );
if ( isNaN( value ) ) {
return;
}
if ( value <= 0 ) {
return;
}
const $options = $( $el ).closest( '.wpforms-field-option' );
const max = parseFloat( $options.find( '.wpforms-number-slider-max' ).val() );
const min = parseFloat( $options.find( '.wpforms-number-slider-min' ).val() );
const maxStep = ( max - min ).toFixed( 2 );
if ( value > maxStep ) {
event.target.value = maxStep;
$el.trigger( 'input' );
return;
}
const fieldID = $( event.target ).parents( '.wpforms-field-option-row' ).data( 'fieldId' );
const defaultValue = $( '#wpforms-field-option-' + fieldID + '-default_value' ).val();
app.checkMultiplicitySliderDefaultValue( fieldID, defaultValue, value, min )
.updateNumberSliderAttr( fieldID, value, 'step' )
.updateNumberSliderDefaultValueAttr( fieldID, value, 'step' );
},
/**
* Check multiplicity of a slider default value.
*
* @since 1.8.4
*
* @param {string} fieldId Field ID.
* @param {number} value Default value.
* @param {number} step Step value.
* @param {number} min Min value.
*
* @return {Object} App instance.
*/
checkMultiplicitySliderDefaultValue( fieldId, value, step, min ) {
const $printSelector = $( `#wpforms-field-option-row-${ fieldId }-default_value` );
value = parseFloat( value );
if ( value % step === 0 ) {
app.removeNotice( $printSelector );
return this;
}
const closestSmallerMultiple = min + ( Math.floor( ( value - min ) / step ) * step );
const closestLargerMultiple = min + ( Math.ceil( ( value - min ) / step ) * step );
const formatNumber = ( num ) => ( num % 1 === 0 ? num.toString() : num.toFixed( 2 ) );
const normalizedValue = formatNumber( value );
const normalizedSmaller = formatNumber( closestSmallerMultiple );
const normalizedLarger = formatNumber( closestLargerMultiple );
if ( normalizedSmaller === normalizedLarger ||
normalizedSmaller === normalizedValue ||
normalizedLarger === normalizedValue
) {
app.removeNotice( $printSelector );
return this;
}
const updatedMessage = wpforms_builder.number_slider_error_valid_default_value
.replace( '{from}', normalizedSmaller )
.replace( '{to}', normalizedLarger );
app.printNotice( updatedMessage, $printSelector );
return this;
},
/**
* Print a notice.
*
* @since 1.8.4
*
* @param {string} message Message to print.
* @param {Object} $printSelector jQuery element selector.
*/
printNotice( message, $printSelector ) {
if ( $printSelector.length ) {
this.removeNotice( $printSelector );
$printSelector.append( `<div class="wpforms-alert-warning wpforms-alert"><p>${ message }</p></div>` );
}
},
/**
* Remove a notice.
*
* @since 1.8.4
*
* @param {Object} $printSelector jQuery element selector.
*/
removeNotice( $printSelector ) {
if ( $printSelector.length && $printSelector.find( '.wpforms-alert' ).length ) {
$printSelector.find( '.wpforms-alert' ).remove();
}
},
/**
* Check the number slider step option.
*
* @since 1.6.2.3
*
* @param {Object} event Focusout event object.
*/
checkNumberSliderStep( event ) {
const value = parseFloat( event.target.value );
if ( ! isNaN( value ) && value > 0 ) {
return;
}
const $input = $( this );
$.confirm( {
title: wpforms_builder.heads_up,
content: wpforms_builder.error_number_slider_increment,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
$input.val( '' ).trigger( 'focus' );
},
},
},
} );
},
/**
* Update number slider default value if it's empty.
*
* @since 1.9.3
*
* @param {Object} event Input event.
*/
changeNumberSliderEmptyDefaultValue( event ) {
const value = parseFloat( event.target.value );
if ( isNaN( value ) ) {
const newValue = parseFloat( event.target.min );
event.target.value = newValue;
const step = parseFloat( event.target.step );
const min = parseFloat( event.target.min );
const fieldID = $( event.target ).parents( '.wpforms-field-option-row-default_value' ).data( 'fieldId' );
app.checkMultiplicitySliderDefaultValue( fieldID, newValue, step, min )
.updateNumberSlider( fieldID, newValue )
.updateNumberSliderHint( fieldID, newValue );
}
},
/**
* Change number slider default value option.
*
* @since 1.5.7
*
* @param {Object} event Input event.
*/
changeNumberSliderDefaultValue( event ) {
const value = parseFloat( event.target.value );
if ( ! isNaN( value ) ) {
const max = parseFloat( event.target.max );
if ( value > max ) {
event.target.value = max;
return;
}
const min = parseFloat( event.target.min );
if ( value < min ) {
event.target.value = min;
return;
}
const step = parseFloat( event.target.step );
const fieldID = $( event.target ).parents( '.wpforms-field-option-row-default_value' ).data( 'fieldId' );
app.checkMultiplicitySliderDefaultValue( fieldID, value, step, min )
.updateNumberSlider( fieldID, value )
.updateNumberSliderHint( fieldID, value );
}
},
/**
* Update number slider default value attribute.
*
* @since 1.5.7
*
* @param {number} fieldID Field ID.
* @param {*} newValue Default value attribute.
* @param {*} attr Attribute name.
*
* @return {Object} App instance.
*/
updateNumberSliderDefaultValueAttr( fieldID, newValue, attr ) {
const input = document.getElementById( 'wpforms-field-option-' + fieldID + '-default_value' );
if ( input ) {
const value = parseFloat( input.value );
input.setAttribute( attr, newValue );
newValue = parseFloat( newValue );
if ( 'max' === attr && value > newValue ) {
input.value = newValue;
}
if ( 'min' === attr && value < newValue ) {
input.value = newValue;
}
}
return this;
},
/**
* Update number slider value.
*
* @since 1.5.7
*
* @param {number} fieldID Field ID.
* @param {string} value Number slider value.
*
* @return {Object} App instance.
*/
updateNumberSlider( fieldID, value ) {
const numberSlider = document.getElementById( 'wpforms-number-slider-' + fieldID );
if ( numberSlider ) {
numberSlider.value = value;
}
return this;
},
/**
* Update number slider attribute.
*
* @since 1.5.7
*
* @param {number} fieldID Field ID.
* @param {any} value Attribute value.
* @param {*} attr Attribute name.
*
* @return {Object} App instance.
*/
updateNumberSliderAttr( fieldID, value, attr ) {
const numberSlider = document.getElementById( 'wpforms-number-slider-' + fieldID );
if ( numberSlider ) {
numberSlider.setAttribute( attr, value );
}
return this;
},
/**
* Update number slider hint string.
*
* @since 1.5.7
*
* @param {number} fieldID Field ID.
* @param {string} str Hint string.
*
* @return {Object} App instance.
*/
updateNumberSliderHintStr( fieldID, str ) {
const hint = document.getElementById( 'wpforms-number-slider-hint-' + fieldID );
if ( hint ) {
hint.dataset.hint = str;
}
return this;
},
/**
* Update number slider Hint value.
*
* @since 1.5.7
*
* @param {number} fieldID Field ID.
* @param {string} value Hint value.
*
* @return {Object} App instance.
*/
updateNumberSliderHint( fieldID, value ) {
const hint = document.getElementById( 'wpforms-number-slider-hint-' + fieldID );
if ( hint ) {
hint.innerHTML = wpf.sanitizeHTML( hint.dataset.hint ).replaceAll( '{value}', '<b>' + value + '</b>' );
}
return this;
},
/**
* Update min attribute.
*
* @since 1.5.7
*
* @param {Object} event Input event.
*/
fieldNumberSliderUpdateMin( event ) {
const current = parseFloat( event.target.value );
if ( isNaN( current ) ) {
return;
}
const $options = $( event.target ).parents( '.wpforms-field-option-row-min_max' );
const max = parseFloat( $options.find( '.wpforms-number-slider-max' ).val() );
if ( max <= current ) {
event.preventDefault();
this.value = max;
return;
}
const fieldId = $options.data( 'field-id' );
const numberSlider = $builder.find( '#wpforms-field-' + fieldId + ' input[type="range"]' );
numberSlider.attr( 'min', current );
},
/**
* Update max attribute.
*
* @since 1.5.7
*
* @param {Object} event Input event.
*/
fieldNumberSliderUpdateMax( event ) {
const current = parseFloat( event.target.value );
if ( isNaN( current ) ) {
return;
}
const $options = $( event.target ).parents( '.wpforms-field-option-row-min_max' );
const min = parseFloat( $options.find( '.wpforms-number-slider-min' ).val() );
if ( min >= current ) {
event.preventDefault();
this.value = min;
return;
}
const fieldId = $options.data( 'field-id' );
const numberSlider = $builder.find( '#wpforms-field-' + fieldId + ' input[type="range"]' );
numberSlider.attr( 'max', current );
},
/**
* Update max attribute for step value.
*
* @since 1.5.7
*
* @param {number} fieldID Field ID.
* @param {*} newValue Default value attribute.
*
* @return {Object} App instance.
*/
updateNumberSliderStepValueMaxAttr( fieldID, newValue ) {
const input = document.getElementById( 'wpforms-field-option-' + fieldID + '-step' );
if ( input ) {
const value = parseFloat( input.value );
input.setAttribute( 'max', newValue );
newValue = parseFloat( newValue );
if ( value > newValue ) {
input.value = newValue;
$( input ).trigger( 'input' );
}
}
return this;
},
/**
* Update upload selector.
*
* @since 1.5.6
*
* @param {Object} target Changed :input.
*/
fieldFileUploadPreviewUpdate( target ) {
const $options = $( target ).parents( '.wpforms-field-option-file-upload' );
const fieldId = $options.data( 'field-id' );
const styleOption = $options.find( '#wpforms-field-option-' + fieldId + '-style' ).val();
const $maxFileNumberRow = $options.find( '#wpforms-field-option-row-' + fieldId + '-max_file_number' );
const maxFileNumber = parseInt( $maxFileNumberRow.find( 'input' ).val(), 10 );
const $preview = $( '#wpforms-field-' + fieldId );
const classicPreview = '.wpforms-file-upload-builder-classic';
const modernPreview = '.wpforms-file-upload-builder-modern';
if ( styleOption === 'classic' ) {
$( classicPreview, $preview ).removeClass( 'wpforms-hide' );
$( modernPreview, $preview ).addClass( 'wpforms-hide' );
$maxFileNumberRow.addClass( 'wpforms-hidden' );
} else {
// Change hint and title.
if ( maxFileNumber > 1 ) {
$preview
.find( '.modern-title' )
.text( wpforms_builder.file_upload.preview_title_plural );
$preview
.find( '.modern-hint' )
.text( wpforms_builder.file_upload.preview_hint.replace( '{maxFileNumber}', maxFileNumber ) )
.removeClass( 'wpforms-hide' );
} else {
$preview
.find( '.modern-title' )
.text( wpforms_builder.file_upload.preview_title_single );
$preview
.find( '.modern-hint' )
.text( wpforms_builder.file_upload.preview_hint.replace( '{maxFileNumber}', 1 ) )
.addClass( 'wpforms-hide' );
}
// Display the preview.
$( classicPreview, $preview ).addClass( 'wpforms-hide' );
$( modernPreview, $preview ).removeClass( 'wpforms-hide' );
$maxFileNumberRow.removeClass( 'wpforms-hidden' );
}
},
/**
* Update limit controls by changing checkbox.
*
* @since 1.5.6
*
* @param {number} id Field id.
* @param {boolean} checked Whether an option is checked or not.
*/
updateTextFieldsLimitControls( id, checked ) {
if ( ! checked ) {
$( '#wpforms-field-option-row-' + id + '-limit_controls' ).addClass( 'wpforms-hide' );
} else {
$( '#wpforms-field-option-row-' + id + '-limit_controls' ).removeClass( 'wpforms-hide' );
}
},
/**
* Update disabling today's date controls by changing checkbox.
*
* @since 1.8.9.4
*
* @param {number} id Field id.
* @param {boolean} checked Whether an option is checked or not.
*/
updateDisableTodaysDateControls( id, checked ) {
$( `#wpforms-field-option-row-${ id }-date_disable_todays_date` )
.toggleClass( 'wpforms-hide', ! checked );
},
/**
* Update Password Strength controls by changing checkbox.
*
* @since 1.6.7
*
* @param {number} id Field id.
* @param {boolean} checked Whether an option is checked or not.
*/
updatePasswordStrengthControls( id, checked ) {
const $strengthControls = $( '#wpforms-field-option-row-' + id + '-password-strength-level' );
if ( checked ) {
$strengthControls.removeClass( 'wpforms-hidden' );
} else {
$strengthControls.addClass( 'wpforms-hidden' );
}
},
/**
* Update Rich Text media controls by changing checkbox.
*
* @since 1.7.0
*/
updateRichTextMediaFieldsLimitControls() {
const $this = $( this ),
fieldId = $this.closest( '.wpforms-field-option-row-media_enabled' ).data( 'field-id' ),
$mediaControls = $( '#wpforms-field-option-row-' + fieldId + '-media_controls' ),
$toolbar = $( '#wpforms-field-' + fieldId + ' .wpforms-richtext-wrap .mce-toolbar-grp' );
if ( ! $this.is( ':checked' ) ) {
$mediaControls.hide();
$toolbar.removeClass( 'wpforms-field-richtext-media-enabled' );
} else {
$mediaControls.show();
$toolbar.addClass( 'wpforms-field-richtext-media-enabled' );
}
},
/**
* Update Rich Text style preview by changing select.
*
* @since 1.7.0
*/
updateRichTextStylePreview() {
const $this = $( this ),
fieldId = $this.closest( '.wpforms-field-option-row-style' ).data( 'field-id' ),
$toolbar = $( '#wpforms-field-' + fieldId + ' .wpforms-richtext-wrap .mce-toolbar-grp' );
$toolbar.toggleClass( 'wpforms-field-richtext-toolbar-basic', $this.val() !== 'full' );
},
/**
* Element bindings.
*
* @since 1.0.0
*/
bindUIActions() {
// General Panels.
app.bindUIActionsPanels();
// Fields Panel.
app.bindUIActionsFields();
// Settings Panel.
app.bindUIActionsSettings();
// Revisions Panel.
app.bindUIActionsRevisions();
// Save and Exit.
app.bindUIActionsSaveExit();
// General/ global.
app.bindUIActionsGeneral();
// Preview actions.
app.bindUIActionsPreview();
},
/**
* Bind UI actions for the preview tab.
*
* @since 1.9.4
*/
bindUIActionsPreview() {
// Open preview tab or focus on it if it's already opened.
elements.$previewButton.on( 'click', function( e ) {
e.preventDefault();
const previewUrl = $( this ).attr( 'href' );
if ( previewTab && ! previewTab.closed && previewTab.location.href.includes( 'wpforms_form_preview' ) ) {
previewTab.focus();
} else {
previewTab = window.open( previewUrl, '_blank' );
}
} );
// Reload preview tab after saving the form.
$builder.on( 'wpformsSaved', function() {
if ( previewTab && ! previewTab.closed && previewTab.location.href.includes( 'wpforms_form_preview' ) ) {
previewTab.location.reload();
}
} );
},
//--------------------------------------------------------------------//
// General Panels
//--------------------------------------------------------------------//
/**
* Element bindings for general panel tasks.
*
* @since 1.0.0
*/
bindUIActionsPanels() {
// Panel switching.
$builder.on( 'click', '#wpforms-panels-toggle button, .wpforms-panel-switch', function( e ) {
e.preventDefault();
app.panelSwitch( $( this ).data( 'panel' ) );
} );
// Panel sections switching.
$builder.on( 'click', '.wpforms-panel .wpforms-panel-sidebar-section', function( e ) {
app.panelSectionSwitch( this, e );
} );
// Panel sidebar toggle.
$builder.on( 'click', '.wpforms-panels .wpforms-panel-sidebar-content .wpforms-panel-sidebar-toggle', function() {
$( this ).parent().toggleClass( 'wpforms-panel-sidebar-closed' );
} );
},
/**
* Switch Panels.
*
* @since 1.0.0
* @since 1.5.9 Added `wpformsPanelSwitched` trigger.
*
* @param {string} panel Panel slug.
*
* @return {void|boolean} Void or false.
*/
panelSwitch( panel ) {
const $panel = $( '#wpforms-panel-' + panel );
if ( ! $panel.hasClass( 'active' ) ) {
const event = WPFormsUtils.triggerEvent( $builder, 'wpformsPanelSwitch', [ panel ] );
// Allow callbacks on `wpformsPanelSwitch` to cancel panel switching by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() || ! wpforms_panel_switch ) {
return false;
}
$( '#wpforms-panels-toggle' ).find( 'button' ).removeClass( 'active' );
$( '.wpforms-panel' ).removeClass( 'active' );
$( '.wpforms-panel-' + panel + '-button' ).addClass( 'active' );
$panel.addClass( 'active' );
history.replaceState( {}, null, wpf.updateQueryString( 'view', panel ) );
// Update the active section parameter in the URL.
let section;
const activeSectionElement = $panel.find( '.active' );
if ( activeSectionElement.length && activeSectionElement.data( 'section' ) !== 'default' ) {
section = activeSectionElement.data( 'section' );
}
history.replaceState( {}, null, wpf.updateQueryString( 'section', section ) );
$builder.trigger( 'wpformsPanelSwitched', [ panel ] );
}
},
/**
* Switch Panel section.
*
* @since 1.0.0
*
* @param {Element} el Element.
* @param {Event} e Event.
*
* @return {boolean|void} False when not switched.
*/
panelSectionSwitch( el, e ) { // eslint-disable-line complexity
if ( e ) {
e.preventDefault();
}
const $this = $( el );
if ( $this.hasClass( 'upgrade-modal' ) || $this.hasClass( 'education-modal' ) ) {
return;
}
const $panel = $this.parent().parent(),
section = $this.data( 'section' ),
$sectionButton = $panel.find( `.wpforms-panel-sidebar-section[data-section="${ section }"]` );
if ( ! $sectionButton.hasClass( 'active' ) ) {
const event = WPFormsUtils.triggerEvent( $builder, 'wpformsPanelSectionSwitch', section );
// Allow callbacks on `wpformsPanelSectionSwitch` to cancel panel section switching by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() || ! wpforms_panel_switch ) {
return false;
}
const $sectionButtons = $panel.find( '.wpforms-panel-sidebar-section' );
$sectionButtons.removeClass( 'active' );
$sectionButton.addClass( 'active' );
$panel.find( '.wpforms-panel-content-section' ).hide();
$panel.find( '.wpforms-panel-content-section-' + section ).show();
// Update the active section parameter in the URL.
history.replaceState( {}, null, wpf.updateQueryString( 'section', section ) );
}
},
//--------------------------------------------------------------------//
// Setup Panel
//--------------------------------------------------------------------//
/**
* Element bindings for a Setup panel.
*
* @since 1.0.0
* @since 1.6.8 Deprecated.
*
* @deprecated Use `WPForms.Admin.Builder.Setup.events()` instead.
*/
bindUIActionsSetup() {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsBuilder.bindUIActionsSetup()" has been deprecated, please use the new "WPForms.Admin.Builder.Setup.events()" function instead!' );
WPForms.Admin.Builder.Setup.events();
},
/**
* Select template.
*
* @since 1.0.0
* @since 1.6.8 Deprecated.
*
* @deprecated Use `WPForms.Admin.Builder.Setup.selectTemplate()` instead.
*
* @param {Object} el DOM element object.
* @param {Object} e Event object.
*/
templateSelect( el, e ) {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsBuilder.templateSelect()" has been deprecated, please use the new "WPForms.Admin.Builder.Setup.selectTemplate()" function instead!' );
WPForms.Admin.Builder.Setup.selectTemplate( e );
},
//--------------------------------------------------------------------//
// Fields Panel
//--------------------------------------------------------------------//
/**
* Element bindings for Fields panel.
*
* @since 1.0.0
*/
bindUIActionsFields() { // eslint-disable-line max-lines-per-function
// Switched to the Fields panel.
$builder.on( 'wpformsPanelSwitched', function( e, panel ) {
if ( panel !== 'fields' ) {
return;
}
// Detect the case when the field Options tab is active, but there is no active field on the preview panel.
if (
$( '#field-options a' ).hasClass( 'active' ) &&
$( '.wpforms-field-wrap .wpforms-field.active' ).length === 0
) {
app.fieldTabToggle( 'field-options' );
}
} );
// Field sidebar tab toggle
$builder.on( 'click', '.wpforms-tab a', function( e ) {
e.preventDefault();
app.fieldTabToggle( $( this ).parent().attr( 'id' ) );
} );
// Field sidebar group toggle
$builder.on( 'click', '.wpforms-add-fields-heading', function( e ) {
e.preventDefault();
app.fieldGroupToggle( $( this ), 'click' );
} );
// Form field preview clicking.
$builder.on( 'click', '.wpforms-field', function( event ) {
if ( app.isFieldPreviewActionsDisabled( this ) ) {
return;
}
// Allow clicking on the "dismiss" button inside the field.
if ( event.target.classList.contains( 'wpforms-dismiss-button' ) ) {
return;
}
// Dismiss the main context menu when it is open.
if ( WPForms.Admin.Builder.ContextMenu ) {
WPForms.Admin.Builder.ContextMenu.hideMainContextMenu( event );
}
event.stopPropagation();
app.fieldTabToggle( $( this ).data( 'field-id' ) );
} );
// Prevent interactions with inputs on the preview panel.
$builder.on( 'mousedown click', '.wpforms-field input, .wpforms-field select, .wpforms-field textarea', function( e ) {
e.preventDefault();
this.blur();
} );
// Field delete.
$builder.on( 'click', '.wpforms-field-delete', function( e ) {
e.preventDefault();
e.stopPropagation();
if ( app.isFormPreviewActionsDisabled( this ) ) {
return;
}
if ( WPForms.Admin.Builder.ContextMenu ) {
WPForms.Admin.Builder.ContextMenu.hideMenu();
}
app.fieldDelete( $( this ).parent().data( 'field-id' ) );
} );
// Field duplicate.
$builder.on( 'click', '.wpforms-field-duplicate', function( e ) {
e.preventDefault();
e.stopPropagation();
if ( app.isFormPreviewActionsDisabled( this ) ) {
return;
}
if ( WPForms.Admin.Builder.ContextMenu ) {
WPForms.Admin.Builder.ContextMenu.hideMenu();
}
app.fieldDuplicate( $( this ).parent().data( 'field-id' ) );
} );
// Field add.
$builder.on( 'click', '.wpforms-add-fields-button', function( e ) {
e.preventDefault();
const $field = $( this );
if ( $field.hasClass( 'ui-draggable-disabled' ) ) {
return;
}
const type = $field.data( 'field-type' ),
event = WPFormsUtils.triggerEvent( $builder, 'wpformsBeforeFieldAddOnClick', [ type, $field ] );
// Allow callbacks on `wpformsBeforeFieldAddOnClick` to cancel adding field
// by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return;
}
app.fieldAdd( type, { $sortable: 'default' } );
} );
// New field choices should be sortable
$builder.on( 'wpformsFieldAdd', function( event, id, type ) {
const fieldTypes = [
'select',
'radio',
'checkbox',
'payment-multiple',
'payment-checkbox',
'payment-select',
];
if ( $.inArray( type, fieldTypes ) !== -1 ) {
app.fieldChoiceSortable( type, `#wpforms-field-option-row-${ id }-choices ul` );
}
} );
// Field option tab toggle.
$builder.on( 'wpformsFieldOptionTabToggle', function( e, fieldId ) {
app.fieldLayoutSelectorInit( fieldId );
} );
// Field choice "Add new".
$builder.on( 'click', '.wpforms-field-option-row-choices .add', function( e ) {
app.fieldChoiceAdd( e, $( this ) );
} );
// Field choice "Delete".
$builder.on( 'click', '.wpforms-field-option-row-choices .remove', function( e ) {
app.fieldChoiceDelete( e, $( this ) );
} );
// Field choices' defaults, before change.
$builder.on( 'mousedown', '.wpforms-field-option-row-choices input[type=radio]', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this );
if ( $this.is( ':checked' ) ) {
$this.attr( 'data-checked', '1' );
} else {
$this.attr( 'data-checked', '0' );
}
} );
// Field choices' defaults.
$builder.on( 'click', '.wpforms-field-option-row-choices input[type=radio]', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
list = $this.parent().parent();
$this.parent().parent().find( 'input[type=radio]' ).not( this ).prop( 'checked', false );
if ( $this.attr( 'data-checked' ) === '1' ) {
$this.prop( 'checked', false );
$this.attr( 'data-checked', '0' );
}
app.fieldChoiceUpdate( list.data( 'field-type' ), list.data( 'field-id' ), list.find( 'li' ).length );
} );
// Field choices update preview area.
$builder.on( 'change', '.wpforms-field-option-row-choices input[type=checkbox]', function( e ) { // eslint-disable-line no-unused-vars
const list = $( this ).parent().parent();
app.fieldChoiceUpdate( list.data( 'field-type' ), list.data( 'field-id' ), list.find( 'li' ).length );
} );
// Field choices display value toggle.
$builder.on( 'change', '.wpforms-field-option-row-show_values input', function( e ) { // eslint-disable-line no-unused-vars
$( this ).closest( '.wpforms-field-option' ).find( '.wpforms-field-option-row-choices ul' ).toggleClass( 'show-values' );
} );
// Field choices image toggle.
$builder.on( 'change', '.wpforms-field-option-row-choices_images input', function() {
const $this = $( this ),
$optionRow = $this.closest( '.wpforms-field-option-row' ),
fieldID = $optionRow.data( 'field-id' ),
$fieldOptions = $( '#wpforms-field-option-' + fieldID ),
checked = $this.is( ':checked' ),
type = $fieldOptions.find( '.wpforms-field-option-hidden-type' ).val(),
$iconToggle = $optionRow.siblings( '.wpforms-field-option-row-choices_icons' ).find( 'input' );
// Toggle icon choices off.
if ( checked && $iconToggle.is( ':checked' ) ) {
$iconToggle.prop( 'checked', false ).trigger( 'change' );
}
$optionRow.find( '.wpforms-alert' ).toggleClass( 'wpforms-hidden' );
$fieldOptions.find( '.wpforms-field-option-row-choices ul' ).toggleClass( 'show-images' );
$fieldOptions.find( '.wpforms-field-option-row-choices_images_style' ).toggleClass( 'wpforms-hidden' );
$fieldOptions.find( '.wpforms-field-option-row-dynamic_choices' ).toggleClass( 'wpforms-hidden', checked );
if ( checked ) {
$( '#wpforms-field-option-' + fieldID + '-input_columns' ).val( 'inline' ).trigger( 'change' );
} else {
$( '#wpforms-field-option-' + fieldID + '-input_columns' ).val( '' ).trigger( 'change' );
}
app.fieldChoiceUpdate( type, fieldID );
} );
// Field choices image upload add/remove image.
$builder.on( 'wpformsImageUploadAdd wpformsImageUploadRemove', function( event, $this, $container ) {
const $list = $container.closest( '.choices-list' ),
fieldID = $list.data( 'field-id' ),
type = $list.data( 'field-type' );
app.fieldChoiceUpdate( type, fieldID );
} );
// Field choices image style toggle.
$builder.on( 'change', '.wpforms-field-option-row-choices_images_style select', function() {
const fieldID = $( this ).parent().data( 'field-id' ),
type = $( '#wpforms-field-option-' + fieldID ).find( '.wpforms-field-option-hidden-type' ).val();
app.fieldChoiceUpdate( type, fieldID );
} );
// Updates field choices text in almost real time.
$builder.on( 'keyup', '.wpforms-field-option-row-choices input.label, .wpforms-field-option-row-choices input.value', function( e ) { // eslint-disable-line no-unused-vars
const $list = $( this ).parent().parent();
app.fieldChoiceUpdate( $list.data( 'field-type' ), $list.data( 'field-id' ) );
} );
// Sanitize field choices text on focus out.
$builder.on( 'focusout', '.wpforms-field-option-row-choices input.label, .wpforms-field-option-row-choices input.value', function( e ) { // eslint-disable-line no-unused-vars
const input = $( this );
input.val( wpf.sanitizeHTML( input.val(), wpforms_builder.allowed_label_html_tags ) );
} );
// Field Choices Bulk Add
$builder.on( 'click', '.toggle-bulk-add-display', function( e ) {
e.preventDefault();
app.fieldChoiceBulkAddToggle( this );
} );
$builder.on( 'click', '.toggle-bulk-add-presets', function( e ) {
e.preventDefault();
const $presetList = $( this ).closest( '.bulk-add-display' ).find( 'ul' );
if ( $presetList.css( 'display' ) === 'block' ) {
$( this ).text( wpforms_builder.bulk_add_presets_show );
} else {
$( this ).text( wpforms_builder.bulk_add_presets_hide );
}
$presetList.stop().slideToggle();
} );
$builder.on( 'click', '.bulk-add-preset-insert', function( e ) {
e.preventDefault();
const $this = $( this ),
preset = $this.data( 'preset' ),
$container = $this.closest( '.bulk-add-display' ),
$presetList = $container.find( 'ul' ),
$presetToggle = $container.find( '.toggle-bulk-add-presets' ),
$textarea = $container.find( 'textarea' );
$textarea.val( '' );
$textarea.insertAtCaret( wpforms_preset_choices[ preset ].choices.join( '\n' ) );
$presetToggle.text( wpforms_builder.bulk_add_presets_show );
$presetList.slideUp();
} );
$builder.on( 'click', '.bulk-add-insert', function( e ) {
e.preventDefault();
app.fieldChoiceBulkAddInsert( this );
} );
// Field Options group tabs.
$builder.on( 'click', '.wpforms-field-option-group-toggle:not(.education-modal)', function( e ) {
const event = WPFormsUtils.triggerEvent( $builder, 'wpformsFieldOptionGroupToggle' );
// Allow callbacks on `wpformsFieldOptionGroupToggle` to cancel tab toggle by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return false;
}
e.preventDefault();
const $group = $( this ).closest( '.wpforms-field-option-group' );
$group.siblings( '.wpforms-field-option-group' ).removeClass( 'active' );
$group.addClass( 'active' );
$builder.trigger( 'wpformsFieldOptionGroupToggled', [ $group ] );
} );
// Display toggle for an Address field hide address line 2 option.
$builder.on( 'change', '.wpforms-field-option-address input.wpforms-subfield-hide', function( e ) { // eslint-disable-line no-unused-vars
const $optionRow = $( this ).closest( '.wpforms-field-option-row' ),
id = $optionRow.data( 'field-id' ),
subfield = $optionRow.data( 'subfield' );
$( '#wpforms-field-' + id ).find( '.wpforms-' + subfield ).toggleClass( 'wpforms-hide' );
} );
// Real-time updates for the "Label" field option.
$builder.on( 'input', '.wpforms-field-option-row-label input, .wpforms-field-option-row-name input', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
id = $this.parent().data( 'field-id' ),
$preview = $( '#wpforms-field-' + id ),
type = $preview.data( 'field-type' );
let value = $this.val(),
showEmptyLabel = value.length === 0;
// Do not modify the label of the HTML field.
if ( type === 'html' ) {
showEmptyLabel = false;
}
if ( showEmptyLabel ) {
value = wpforms_builder.empty_label;
}
$preview.toggleClass( 'label_empty', showEmptyLabel ).find( '> .label-title .text' ).text( value );
} );
// Real-time updates for "Description" field option
$builder.on( 'input', '.wpforms-field-option-row-description textarea', function() {
const $this = $( this ),
value = wpf.sanitizeHTML( $this.val() ),
id = $this.parent().data( 'field-id' ),
// IIF description is not following other fields structure and needs to be selected separately.
$desc = $( `#wpforms-field-${ id } > .description, #wpforms-field-${ id } .wpforms-field-internal-information-row-description` );
app.updateDescription( $desc, value );
$this.trigger( 'wpformsDescriptionFieldUpdated', { id, descField: $desc, value } );
} );
// Real-time updates for "Required" field option
$builder.on( 'change', '.wpforms-field-option-row-required input', function( e ) { // eslint-disable-line no-unused-vars
const id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' );
$( '#wpforms-field-' + id ).toggleClass( 'required' );
} );
// Real-time updates for "Summary" field option
$builder.on( 'change', '.wpforms-field-option-row-summary input', function() {
const $this = $( this ),
id = $this.closest( '.wpforms-field-option-row' ).data( 'field-id' );
$( `#wpforms-field-${ id }` ).toggleClass( 'wpforms-summary-enabled' );
$this.closest( '.wpforms-field-option-group-inner' ).find( '.wpforms-total-summary-alert' ).toggleClass( 'wpforms-hidden' );
} );
// Real-time updates for "Confirmation" field option
$builder.on( 'change', '.wpforms-field-option-row-confirmation input', function() {
const id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' );
$( '#wpforms-field-' + id ).find( '.wpforms-confirm' ).toggleClass( 'wpforms-confirm-enabled wpforms-confirm-disabled' );
$( '#wpforms-field-option-' + id ).toggleClass( 'wpforms-confirm-enabled wpforms-confirm-disabled' );
} );
// Real-time updates for "Filter" field option
$builder.on( 'change', '.wpforms-field-option-row-filter_type select', function() {
const id = $( this ).parent().data( 'field-id' ),
$toggledField = $( '#wpforms-field-option-' + id );
if ( $( this ).val() ) {
$toggledField.removeClass( 'wpforms-filter-allowlist' );
$toggledField.removeClass( 'wpforms-filter-denylist' );
$toggledField.addClass( 'wpforms-filter-' + $( this ).val() );
} else {
$toggledField.removeClass( 'wpforms-filter-allowlist' );
$toggledField.removeClass( 'wpforms-filter-denylist' );
}
} );
$builder.on( 'focusout', '.wpforms-field-option-row-allowlist textarea,.wpforms-field-option-row-denylist textarea', function() {
const $currentField = $( this );
let $current = 'allow';
$currentField.next( '.wpforms-alert' ).remove();
if ( $currentField.val() === '' ) {
return;
}
const $allowField = $( '.wpforms-field-option-row-allowlist textarea' ),
$denyField = $( '.wpforms-field-option-row-denylist textarea' );
if ( $currentField.is( $denyField ) ) {
$current = 'deny';
}
$.get(
wpforms_builder.ajax_url,
{
nonce: wpforms_builder.nonce,
content: JSON.stringify(
{
allow: $allowField.val(),
deny: $denyField.val(),
current: $current,
}
),
action: 'wpforms_sanitize_restricted_rules',
},
function( res ) {
if ( res.success ) {
$currentField.val( res.data.currentField );
const intersect = res.data.intersect;
if ( intersect.length !== 0 ) {
const content = '<p>' + wpforms_builder.allow_deny_lists_intersect + '</p>' +
'<p class="bold">' + intersect + '</p>';
$.alert( {
title: wpforms_builder.heads_up,
content,
icon: 'fa fa-exclamation-circle',
type: 'red',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
}
const restricted = res.data.restricted || 0;
if ( restricted ) {
$currentField.after( '<div class="wpforms-alert-warning wpforms-alert"><p>' + wpforms_builder.restricted_rules + '</p></div>' );
}
}
}
);
} );
// Save focusout target.
$builder.on( 'focusout', elements.defaultEmailSelector, function() {
elements.$focusOutTarget = $( this );
app.focusOutEvent();
} );
// Real-time updates for "Size" field option
$builder.on( 'change', '.wpforms-field-option-row-size select', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
value = $this.val(),
id = $this.parent().data( 'field-id' );
$( '#wpforms-field-' + id ).removeClass( 'size-small size-medium size-large' ).addClass( 'size-' + value );
} );
// Real-time updates for "Placeholder" field option.
$builder.on( 'input', '.wpforms-field-option-row-placeholder input', function() { // eslint-disable-line complexity
const $this = $( this ),
id = $this.parent().data( 'field-id' ),
$preview = $( '#wpforms-field-' + id ),
$primary = $preview.find( '.primary-input' );
let value = wpf.sanitizeHTML( $this.val() );
// Single Item Field - if placeholder is cleared, set it to "price" placeholder.
if ( $preview.data( 'field-type' ) === 'payment-single' && value === '' ) {
value = $( '#wpforms-field-option-' + id + '-price' ).prop( 'placeholder' );
}
// Set the placeholder value for `input` fields.
if ( ! $primary.is( 'select' ) ) {
$primary.prop( 'placeholder', value );
return;
}
// Modern select style.
if ( app.dropdownField.helpers.isModernSelect( $primary ) ) {
const choiceInstance = app.dropdownField.helpers.getInstance( $primary );
// Additional case for multiple select.
if ( $primary.prop( 'multiple' ) ) {
$( choiceInstance.input.element ).prop( 'placeholder', value );
} else {
choiceInstance.setChoiceByValue( '' );
$primary.closest( '.choices' ).find( '.choices__inner .choices__placeholder' ).text( value );
const isDynamicChoices = $( '#wpforms-field-option-' + id + '-dynamic_choices' ).val();
// We need to re-initialize modern dropdown to properly determine and update placeholder.
app.dropdownField.helpers.update( id, isDynamicChoices );
}
return;
}
const $placeholder = $primary.find( '.placeholder' );
// Classic select style.
if ( ! value.length && $placeholder.length ) {
$placeholder.remove();
} else {
if ( $placeholder.length ) {
$placeholder.text( value );
} else {
$primary.prepend( '<option value="" class="placeholder">' + value + '</option>' );
}
$primary.find( '.placeholder' ).prop( 'selected', ! $primary.prop( 'multiple' ) );
}
} );
// Real-time updates for "Confirmation Placeholder" field option
$builder.on( 'input', '.wpforms-field-option-row-confirmation_placeholder input', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
value = $this.val(),
id = $this.parent().data( 'field-id' );
$( '#wpforms-field-' + id ).find( '.secondary-input' ).attr( 'placeholder', value );
} );
// Real-time updates for Date/Time, and Name "Placeholder" field options
$builder.on( 'input', '.wpforms-field-option .format-selected input.placeholder', function() {
const $this = $( this );
const value = $this.val();
const $fieldOptionRow = $this.closest( '.wpforms-field-option-row' );
const id = $fieldOptionRow.data( 'field-id' );
const subfield = $fieldOptionRow.data( 'subfield' );
$( '#wpforms-field-' + id ).find( '.wpforms-' + subfield + ' input' ).attr( 'placeholder', value );
} );
// Real-time updates for Address field "Placeholder" field options.
$builder.on( 'input', '.wpforms-field-option-address input.placeholder', function() {
const $this = $( this );
const $fieldOptionRow = $this.closest( '.wpforms-field-option-row' );
const id = $fieldOptionRow.data( 'field-id' );
const subfield = $fieldOptionRow.data( 'subfield' );
const $fieldPreviews = $( '#wpforms-field-' + id + ' .wpforms-' + subfield ).find( 'input, select' );
const $default = $fieldOptionRow.find( '#wpforms-field-option-' + id + '-' + subfield + '_default' );
const defaultValue = $default.val();
const defaultText = $default.find( 'option:selected' ).text();
const placeholderValue = $this.val();
$fieldPreviews.each( function() {
const $fieldPreview = $( this );
if ( $fieldPreview.is( 'select' ) ) {
const $option = $fieldPreview.find( '.placeholder' );
const value = defaultValue === '' && placeholderValue !== '' ? placeholderValue : defaultText;
$option.text( value );
return;
}
$fieldPreview.attr( 'placeholder', placeholderValue );
} );
} );
// Real-time updates for "Default" field option.
$builder.on( 'input', '.wpforms-field-option-row-default_value input', function() {
const $this = $( this );
const value = wpf.sanitizeHTML( $this.val() );
const id = $this.closest( '.wpforms-field-option-row' ).data( 'field-id' );
const $preview = $( '#wpforms-field-' + id + ' .primary-input' );
$preview.val( value );
} );
// Real-time updates for "Default" field option of the Name and Address fields.
$builder.on( 'input', '.wpforms-field-options-column input.default', function() {
const $this = $( this );
const value = wpf.sanitizeHTML( $this.val() );
const $fieldOptionRow = $this.closest( '.wpforms-field-option-row' );
const id = $fieldOptionRow.data( 'field-id' );
const subfield = $fieldOptionRow.data( 'subfield' );
const $fieldPreview = $( '#wpforms-field-' + id + ' .wpforms-' + subfield + ' input' );
$fieldPreview.val( value );
} );
// Real-time updates for "Default" select field option of the Address field.
$builder.on( 'change', '.wpforms-field-option-address select.default', function() {
const $this = $( this );
const value = $this.val();
const textValue = $this.find( 'option:selected' ).text();
const $fieldOptionRow = $this.closest( '.wpforms-field-option-row' );
const id = $fieldOptionRow.data( 'field-id' );
const subfield = $fieldOptionRow.data( 'subfield' );
const scheme = $( '#wpforms-field-option-' + id + '-scheme' ).val();
const $placeholder = $fieldOptionRow.find( '#wpforms-field-option-' + id + '-' + subfield + '_placeholder' );
const placeholderValue = $placeholder.val();
const $fieldPreview = $( '#wpforms-field-' + id + ' .wpforms-address-scheme-' + scheme + ' .wpforms-' + subfield + ' .placeholder' );
value === '' && placeholderValue.trim().length > 0
? $fieldPreview.text( placeholderValue )
: $fieldPreview.text( textValue );
} );
// Real-time updates for "Confirmation Placeholder" field option
$builder.on( 'input', '.wpforms-field-option-row-confirmation_placeholder input', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
value = $this.val(),
id = $this.parent().data( 'field-id' );
$( '#wpforms-field-' + id ).find( '.secondary-input' ).attr( 'placeholder', value );
} );
// Real-time updates for "Hide Label" field option.
$builder.on( 'change', '.wpforms-field-option-row-label_hide input', function( e ) { // eslint-disable-line no-unused-vars
const id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' );
$( '#wpforms-field-' + id ).toggleClass( 'label_hide' );
} );
// Real-time updates for a Sub Label visibility field option.
$builder.on( 'change', '.wpforms-field-option-row-sublabel_hide input', function( e ) { // eslint-disable-line no-unused-vars
const id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' );
$( '#wpforms-field-' + id ).toggleClass( 'sublabel_hide' );
} );
// Real-time updates for a Quantity visibility field option.
$builder.on( 'change', '.wpforms-field-option-row-enable_quantity input', function() {
const id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' ),
$preview = $( `#wpforms-field-${ id }` );
$( `#wpforms-field-option-row-${ id }-quantity` ).toggleClass( 'wpforms-hidden' );
$preview.find( '.quantity-input' ).toggleClass( 'wpforms-hidden' );
$preview.toggleClass( 'payment-quantity-enabled' );
} );
// Real-time updates for Quantity preview minimum value.
$builder.on( 'input', '.wpforms-field-option-row-quantity input', function() {
const $this = $( this );
// Allow only a positive integer value less than 9999.
$this.val( Math.min( Math.abs( Math.round( $this.val() ) ), 9999 ) );
const $optionRow = $this.closest( '.wpforms-field-option-row' ),
id = $optionRow.data( 'field-id' ),
isMinInput = $this.hasClass( 'min-quantity-input' ),
$minInput = $optionRow.find( '.min-quantity-input' ),
$maxInput = $optionRow.find( '.max-quantity-input' );
if ( isMinInput ) {
$( '#wpforms-field-' + id ).find( '.quantity-input option' ).text( $this.val() );
}
$minInput.toggleClass( 'wpforms-error', parseInt( $minInput.val(), 10 ) > parseInt( $maxInput.val(), 10 ) );
} );
// Real-time updates for Date/Time, Name and Single Item "Format" option.
$builder.on( 'change', '.wpforms-field-option-row-format select', function() {
const $this = $( this ),
value = $this.val(),
id = $this.parent().data( 'field-id' ),
$sublabelToggle = $( '#wpforms-field-option-row-' + id + '-sublabel_hide' ),
$preview = $( '#wpforms-field-' + id );
$preview.find( '.format-selected' ).removeClass().addClass( 'format-selected format-selected-' + value );
$( '#wpforms-field-option-' + id ).find( '.format-selected' ).removeClass().addClass( 'format-selected format-selected-' + value );
// Show toggle for "Hide Sub labels" only when the field consists of more than one subfield.
if ( [ 'date-time', 'first-last', 'first-middle-last' ].includes( value ) ) {
$sublabelToggle.removeClass( 'wpforms-hidden' );
} else {
$sublabelToggle.addClass( 'wpforms-hidden' );
}
// Hide the label field if it's not a single item.
$( `#wpforms-field-option-row-${ id }-price_label` ).toggleClass( 'wpforms-hidden', value !== 'single' );
// Toggle options based on Single Item "Format".
if ( [ 'single', 'user', 'hidden' ].includes( value ) ) {
const isUserDefined = value === 'user',
isSingle = value === 'single',
isHidden = value === 'hidden',
isQuantityEnabled = $( '#wpforms-field-option-' + id + '-enable_quantity' ).is( ':checked' ),
$minPriceOption = $( '#wpforms-field-option-' + id + '-min_price' ),
minPrice = wpf.amountSanitize( $minPriceOption.val() ),
isValidMinPrice = minPrice >= $minPriceOption.data( 'minimum-price' ),
$minPriceOptionRow = $( '#wpforms-field-option-row-' + id + '-min_price' );
// Toggle Placeholder option.
$( '#wpforms-field-option-row-' + id + '-placeholder' ).toggleClass( 'wpforms-hidden', ! isUserDefined );
// Toggle Quantity options.
$( '#wpforms-field-option-row-' + id + '-enable_quantity' ).toggleClass( 'wpforms-hidden', ! isSingle );
$( '#wpforms-field-option-row-' + id + '-quantities_alert' ).toggleClass( 'wpforms-hidden', ! isSingle );
$( '#wpforms-field-option-row-' + id + '-quantity' ).toggleClass( 'wpforms-hidden', ! isSingle || ! isQuantityEnabled );
$preview.find( '.quantity-input' ).toggleClass( 'wpforms-hidden', ! isSingle || ! isQuantityEnabled );
// Toggle Minimum Price options.
$minPriceOptionRow.toggleClass( 'wpforms-hidden', ! isUserDefined );
$minPriceOptionRow.find( '.wpforms-item-minimum-price-alert' ).toggleClass( 'wpforms-hidden', isValidMinPrice );
$preview.find( '.item-min-price' ).toggleClass( 'wpforms-hidden', isUserDefined && minPrice <= 0 );
$preview.toggleClass( 'min-price-warning', ! isValidMinPrice );
$preview.find( '.fa-exclamation-triangle' ).toggleClass( 'wpforms-hidden', isValidMinPrice );
// Toggle the label
$( `#wpforms-field-${ id } .item-price-single` ).toggleClass( 'wpforms-hidden', ! isSingle );
$( `#wpforms-field-${ id } .item-price-hidden` ).toggleClass( 'wpforms-hidden', ! isHidden );
}
} );
// Real-time updates specific for Address "Scheme" option
$builder.on( 'change', '.wpforms-field-option-row-scheme select', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this );
const value = $this.val();
const fieldId = $this.parent().data( 'field-id' );
const $fieldPreview = $( `#wpforms-field-${ fieldId }` );
const $stateOption = $( `#wpforms-field-option-row-${ fieldId }-state` );
const $countryOption = $( `#wpforms-field-option-row-${ fieldId }-country` );
// Switch the scheme in a Preview panel.
$fieldPreview.find( '.wpforms-address-scheme' ).addClass( 'wpforms-hide' );
$fieldPreview.find( `.wpforms-address-scheme-${ value }` ).removeClass( 'wpforms-hide' );
// Show an or hide country option depending on the scheme.
const $countryPreviewField = $fieldPreview.find( `.wpforms-address-scheme-${ value } .wpforms-country select, .wpforms-address-scheme-${ value } .wpforms-country input` );
$countryPreviewField.length === 0
? $countryOption.addClass( 'wpforms-hidden' )
: $countryOption.removeClass( 'wpforms-hidden' );
// Inputs/selects for a currently selected scheme and the one that we're changing to.
const $currentState = $stateOption.find( '.default .default' ).not( '.wpforms-hidden-strict' );
const $newState = $stateOption.find( `.default [data-scheme="${ value }"]` );
const $currentCountry = $countryOption.find( '.default .default' ).not( '.wpforms-hidden-strict' );
const $newCountry = $countryOption.find( `.default [data-scheme="${ value }"]` );
// Switch the state field type in options to match the scheme.
$newState.attr( {
id: $currentState.attr( 'id' ),
name: $currentState.attr( 'name' ),
} ).removeClass( 'wpforms-hidden-strict' );
$currentState.attr( { id: '', name: '' } ).addClass( 'wpforms-hidden-strict' );
$newCountry.attr( {
id: $currentCountry.attr( 'id' ),
name: $currentCountry.attr( 'name' ),
} ).removeClass( 'wpforms-hidden-strict' );
$currentCountry.attr( { id: '', name: '' } ).addClass( 'wpforms-hidden-strict' );
} );
// Real-time updates for a Date/Time date type
$builder.on( 'change', '.wpforms-field-option-row-date .type select', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
value = $this.val(),
id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' ),
addClass = value === 'datepicker' ? 'wpforms-date-type-datepicker' : 'wpforms-date-type-dropdown',
removeClass = value === 'datepicker' ? 'wpforms-date-type-dropdown' : 'wpforms-date-type-datepicker';
$( '#wpforms-field-' + id ).find( '.wpforms-date' ).addClass( addClass ).removeClass( removeClass );
$( '#wpforms-field-option-' + id ).addClass( addClass ).removeClass( removeClass );
const $limitDays = $this.closest( '.wpforms-field-option-group-advanced' )
.find( '.wpforms-field-option-row-date_limit_days, .wpforms-field-option-row-date_limit_days_options, .wpforms-field-option-row-date_disable_past_dates' ),
$limitDaysOptions = $( '#wpforms-field-option-row-' + id + '-date_limit_days_options' );
if ( value === 'dropdown' ) {
const $dateSelect = $( '#wpforms-field-option-' + id + '-date_format' );
if ( $dateSelect.find( 'option:selected' ).hasClass( 'datepicker-only' ) ) {
$dateSelect.prop( 'selectedIndex', 0 ).trigger( 'change' );
}
$limitDays.hide();
} else {
$limitDays.show();
$( '#wpforms-field-option-' + id + '-date_limit_days' ).is( ':checked' )
? $limitDaysOptions.show()
: $limitDaysOptions.hide();
}
} );
// Real-time updates for Date/Time date select format
$builder.on( 'change', '.wpforms-field-option-row-date .format select', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
value = $this.val(),
id = $( this ).closest( '.wpforms-field-option-row' ).data( 'field-id' ),
$field = $( '#wpforms-field-' + id );
if ( value === 'm/d/Y' ) {
$field.find( '.wpforms-date-dropdown .first option' ).text( wpforms_builder.date_select_month );
$field.find( '.wpforms-date-dropdown .second option' ).text( wpforms_builder.date_select_day );
} else if ( value === 'd/m/Y' ) {
$field.find( '.wpforms-date-dropdown .first option' ).text( wpforms_builder.date_select_day );
$field.find( '.wpforms-date-dropdown .second option' ).text( wpforms_builder.date_select_month );
}
} );
// Real-time updates for Date/Time select format
$builder.on( 'change', '.wpforms-field-option-row-time .format select', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
id = $this.closest( '.wpforms-field-option-row' ).data( 'field-id' );
let options = '',
hh;
// Determine a time format type.
// If the format contains `g` or `h`, then this is 12-hour format, otherwise 24 hours.
const format = $this.val().match( /[gh]/ ) ? 12 : 24,
minHour = format === 12 ? 1 : 0,
maxHour = format === 12 ? 13 : 24;
// Generate a new set of hour options.
for ( let i = minHour; i < maxHour; i++ ) {
hh = i < 10 ? '0' + i : i;
options += '<option value="{hh}">{hh}</option>'.replace( /{hh}/g, hh );
}
_.forEach( [ 'start', 'end' ], function( field ) {
const $hour = $builder.find( '#wpforms-field-option-' + id + '-time_limit_hours_' + field + '_hour' ),
$ampm = $builder.find( '#wpforms-field-option-' + id + '-time_limit_hours_' + field + '_ampm' );
let hourValue = parseInt( $hour.val(), 10 ),
ampmValue = $ampm.val();
if ( format === 24 ) {
hourValue = ampmValue === 'pm' ? hourValue + 12 : hourValue;
} else {
ampmValue = hourValue > 12 ? 'pm' : 'am';
hourValue = hourValue > 12 ? hourValue - 12 : hourValue;
}
hourValue = hourValue < 10 ? '0' + hourValue : hourValue;
$hour.html( options ).val( hourValue );
$ampm.toggleClass( 'wpforms-hidden-strict', format === 24 ).val( ampmValue );
$ampm.nextAll( 'div' ).toggleClass( 'wpforms-hidden-strict', format === 12 );
} );
} );
// Consider the field active when a disabled nav button is clicked
$builder.on( 'click', '.wpforms-pagebreak-button', function( e ) {
e.preventDefault();
$( this ).closest( '.wpforms-field' ).trigger( 'click' );
} );
/*
* Pagebreak field.
*/
app.fieldPageBreakInitDisplayPrevious( $builder.find( '.wpforms-field-pagebreak.wpforms-pagebreak-normal' ).first() );
$builder
.on( 'input', '.wpforms-field-option-row-next input', function( e ) { // eslint-disable-line no-unused-vars
// Real-time updates for "Next" pagebreak field option.
const $this = $( this ),
value = $this.val(),
$next = $( '#wpforms-field-' + $this.parent().data( 'field-id' ) ).find( '.wpforms-pagebreak-next' );
if ( value ) {
$next.css( 'display', 'inline-block' ).text( value );
} else {
$next.css( 'display', 'none' ).empty();
}
} )
.on( 'input', '.wpforms-field-option-row-prev input', function( e ) { // eslint-disable-line no-unused-vars
// Real-time updates for "Prev" pagebreak field option.
const $this = $( this ),
value = $this.val().trim(),
$field = $( '#wpforms-field-' + $this.parent().data( 'field-id' ) ),
$prevBtn = $field.find( '.wpforms-pagebreak-prev' );
if ( value && $field.prevAll( '.wpforms-field-pagebreak.wpforms-pagebreak-normal' ).length > 0 ) {
$prevBtn.removeClass( 'wpforms-hidden' ).text( value );
} else {
$prevBtn.addClass( 'wpforms-hidden' ).empty();
}
} )
.on( 'change', '.wpforms-field-option-row-prev_toggle input', function( e ) { // eslint-disable-line complexity, no-unused-vars
// Real-time updates for "Display Previous" pagebreak field option.
const $input = $( this ),
$wrapper = $input.closest( '.wpforms-field-option-row-prev_toggle' );
if ( $wrapper.hasClass( 'wpforms-entry-preview-block' ) ) {
return;
}
const $prev = $input.closest( '.wpforms-field-option-group-inner' ).find( '.wpforms-field-option-row-prev' );
const $prevLabel = $prev.find( 'input' ),
$prevBtn = $( '#wpforms-field-' + $input.closest( '.wpforms-field-option' ).data( 'field-id' ) ).find( '.wpforms-pagebreak-prev' );
$prev.toggleClass( 'wpforms-hidden', ! $input.prop( 'checked' ) );
$prevBtn.toggleClass( 'wpforms-hidden', ! $input.prop( 'checked' ) );
if ( $input.prop( 'checked' ) && ! $prevLabel.val() ) {
let message = $prevLabel.data( 'last-value' );
message = message && message.trim() ? message.trim() : wpforms_builder.previous;
$prevLabel.val( message );
}
// Backward compatibility for forms that were created before the toggle was added.
if ( ! $input.prop( 'checked' ) ) {
$prevLabel.data( 'last-value', $prevLabel.val() );
$prevLabel.val( '' );
}
$prevLabel.trigger( 'input' );
} )
.on( 'wpformsFieldAdd', app.fieldPagebreakAdd )
.on( 'wpformsFieldDelete', app.fieldPagebreakDelete )
.on( 'wpformsFieldAdd', app.toggleOrderSummaryConfirmation )
.on( 'wpformsFieldDelete', app.toggleOrderSummaryConfirmation )
.on( 'wpformsBeforeFieldDelete', app.fieldEntryPreviewDelete );
// Update Display Previous option visibility for all Pagebreak fields.
$builder.on( 'wpformsFieldMove wpformsFieldAdd wpformsFieldDelete', function( e ) { // eslint-disable-line no-unused-vars
$builder.find( '.wpforms-field-pagebreak.wpforms-pagebreak-normal' ).each( function( i ) { // eslint-disable-line no-unused-vars
app.fieldPageBreakInitDisplayPrevious( $( this ) );
} );
} );
// Real-time updates for "Page Title" pagebreak field option
$builder.on( 'input', '.wpforms-field-option-row-title input', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
value = $this.val(),
id = $this.parent().data( 'field-id' );
if ( value ) {
$( '#wpforms-field-' + id ).find( '.wpforms-pagebreak-title' ).text( value );
} else {
$( '#wpforms-field-' + id ).find( '.wpforms-pagebreak-title' ).empty();
}
} );
// Real-time updates for "Page Navigation Alignment" pagebreak field option
$builder.on( 'change', '.wpforms-field-option-row-nav_align select', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this );
let value = $this.val();
if ( ! value ) {
value = 'center';
}
$( '.wpforms-pagebreak-buttons' )
.removeClass( 'wpforms-pagebreak-buttons-center wpforms-pagebreak-buttons-left wpforms-pagebreak-buttons-right wpforms-pagebreak-buttons-split' )
.addClass( 'wpforms-pagebreak-buttons-' + value );
} );
// Real-time updates for Single Item field "Item Price" option.
$builder.on( 'input', '.wpforms-field-option-row-price input', function() {
const $this = $( this ),
value = $this.val(),
formatted = wpf.amountFormat( wpf.amountSanitize( value ) ),
id = $this.parent().data( 'field-id' ),
placeholder = $( '#wpforms-field-option-' + id + '-placeholder' ).val().trim(),
$preview = $( '#wpforms-field-' + id ),
newValue = value === '' && placeholder !== '' ? '' : formatted;
$preview.find( '.primary-input' ).val( newValue );
$preview.find( '.price' ).text( wpf.amountFormatCurrency( value ) );
} );
// Real-time updates for Single Item field "Minimum Price" option.
$builder.on( 'input', '.wpforms-field-option-row-min_price input', function() {
const $this = $( this ),
amount = $this.val(),
sanitized = wpf.amountSanitize( amount ),
isEmpty = sanitized <= 0,
isValid = sanitized >= $this.data( 'minimum-price' ),
$fieldOptionRow = $this.parent(),
$preview = $( '#wpforms-field-' + $fieldOptionRow.data( 'field-id' ) );
$fieldOptionRow.find( '.wpforms-item-minimum-price-alert' ).toggleClass( 'wpforms-hidden', isValid );
$preview.find( '.item-min-price' ).toggleClass( 'wpforms-hidden', isEmpty );
$preview.toggleClass( 'min-price-warning', ! isValid );
$preview.find( '.fa-exclamation-triangle' ).toggleClass( 'wpforms-hidden', isValid );
if ( isEmpty ) {
return;
}
$preview.find( '.min-price' ).text( wpf.amountFormatCurrency( amount ) );
} );
// Real-time updates for price label for single item field.
$builder.on( 'input', '.wpforms-single-item-price-label-display', function() {
const $this = $( this ),
value = wpf.sanitizeHTML( $this.val(), '<>' ),
id = $this.parent().data( 'field-id' ),
$preview = $( `#wpforms-field-${ id }` ),
$price = wpf.amountFormatCurrency( $( `#wpforms-field-option-${ id }-price` ).val() );
if ( ! value ) {
$this.val( '{price}' );
$preview.find( '.price-label' ).html( `<span class="price"> ${ $price } </span>` );
return;
}
$preview.find( '.price-label' ).html( value.replaceAll( '{price}', `<span class="price"> ${ $price } </span>` ) );
} );
// Real-time updates for payment CC icons
$builder.on( 'change', '.wpforms-field-option-credit-card .payment-icons input', function() {
const $this = $( this ),
card = $this.data( 'card' ),
id = $this.parent().data( 'field-id' );
$( '#wpforms-field-' + id ).find( 'img.icon-' + card ).toggleClass( 'card_hide' );
} );
// Generic updates for various additional placeholder fields (at least Stripe's "Name on Card").
$builder.on( 'input', '.wpforms-field-option input.placeholder-update', function( e ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
value = $this.val(),
id = $this.data( 'field-id' ),
subfield = $this.data( 'subfield' );
$( '#wpforms-field-' + id ).find( '.wpforms-' + subfield + ' input' ).attr( 'placeholder', value );
} );
// Toggle Choice Layout advanced field option.
$builder.on( 'change', '.wpforms-field-option-row-input_columns select', function() {
const $this = $( this ),
value = $this.val(),
id = $this.parent().data( 'field-id' );
let cls = '';
if ( value === '2' ) {
cls = 'wpforms-list-2-columns';
} else if ( value === '3' ) {
cls = 'wpforms-list-3-columns';
} else if ( value === 'inline' ) {
cls = 'wpforms-list-inline';
}
$( '#wpforms-field-' + id ).removeClass( 'wpforms-list-2-columns wpforms-list-3-columns wpforms-list-inline' ).addClass( cls );
} );
// Toggle the toggle field.
$builder.on( 'change', '.wpforms-field-option-row .wpforms-toggle-control input', function( e ) { // eslint-disable-line no-unused-vars
const $check = $( this ),
$control = $check.closest( '.wpforms-toggle-control' ),
$status = $control.find( '.wpforms-toggle-control-status' ),
state = $check.is( ':checked' ) ? 'on' : 'off';
$status.html( $status.data( state ) );
} );
// Real-time updates for "Dynamic Choices" field option, for Dropdown,
// Checkboxes, and Multiple choice fields
$builder.on( 'change', '.wpforms-field-option-row-dynamic_choices select', function( e ) { // eslint-disable-line no-unused-vars
app.fieldDynamicChoiceToggle( $( this ) );
} );
// Real-time updates for "Dynamic [type] Source" field option, for Dropdown,
// Checkboxes, and Multiple choice fields
$builder.on( 'change', '.wpforms-field-option-row-dynamic_taxonomy select, .wpforms-field-option-row-dynamic_post_type select', function( e ) { // eslint-disable-line no-unused-vars
app.fieldDynamicChoiceSource( $( this ) );
} );
// Toggle Layout selector
$builder.on( 'click', '.toggle-layout-selector-display', function( e ) {
e.preventDefault();
app.fieldLayoutSelectorToggle( this );
} );
$builder.on( 'click', '.layout-selector-display-layout', function( e ) {
e.preventDefault();
app.fieldLayoutSelectorLayout( this );
} );
$builder.on( 'click', '.layout-selector-display-columns span', function( e ) {
e.preventDefault();
app.fieldLayoutSelectorInsert( this );
} );
// Real-time updates for a Rating field scale option.
$( document ).on( 'change', '.wpforms-field-option-row-scale select', function() {
const $this = $( this ),
value = $this.val(),
id = $this.parent().data( 'field-id' ),
$icons = $( '#wpforms-field-' + id + ' .rating-icon' );
let x = 1;
$icons.each( function( index ) { // eslint-disable-line no-unused-vars
if ( x <= value ) {
$( this ).show();
} else {
$( this ).hide();
}
x++;
} );
} );
// Real-time updates for a Rating field icon option.
$( document ).on( 'change', '.wpforms-field-option-row-icon select', function() {
const $this = $( this ),
value = $this.val(),
id = $this.parent().data( 'field-id' ),
$icons = $( '#wpforms-field-' + id + ' .rating-icon' );
let iconClass = 'fa-star';
if ( 'heart' === value ) {
iconClass = 'fa-heart';
} else if ( 'thumb' === value ) {
iconClass = 'fa-thumbs-up';
} else if ( 'smiley' === value ) {
iconClass = 'fa-smile-o';
}
$icons.removeClass( 'fa-star fa-heart fa-thumbs-up fa-smile-o' ).addClass( iconClass );
} );
// Real-time updates for a Rating field icon size option.
$( document ).on( 'change', '.wpforms-field-option-row-icon_size select', function() {
const $this = $( this ),
value = $this.val(),
id = $this.parent().data( 'field-id' ),
$icons = $( '#wpforms-field-' + id + ' .rating-icon' );
let fontSize = '28';
if ( 'small' === value ) {
fontSize = '18';
} else if ( 'large' === value ) {
fontSize = '38';
}
$icons.css( 'font-size', fontSize + 'px' );
} );
// Real-time updates for a Rating field icon color option.
$( document ).on( 'input', '.wpforms-field-option-row-icon_color input.wpforms-color-picker', function() {
const $this = $( this ),
id = $this.closest( '.wpforms-field-option-row' ).data( 'field-id' ),
$icons = $( '#wpforms-field-' + id + ' > i.fa' );
$icons.css( 'color', app.getValidColorPickerValue( $this ) );
} );
// Real-time updates for a Checkbox field Disclaimer option.
$( document ).on( 'change', '.wpforms-field-option-row-disclaimer_format input', function() {
const $this = $( this ),
id = $this.closest( '.wpforms-field-option-row' ).data( 'field-id' ),
$desc = $( '#wpforms-field-' + id + ' .description' );
$desc.toggleClass( 'disclaimer' );
} );
$builder.on(
'change',
'.wpforms-field-option-row-limit_enabled input',
function( event ) {
app.updateTextFieldsLimitControls( $( event.target ).closest( '.wpforms-field-option-row-limit_enabled' ).data().fieldId, event.target.checked );
}
);
$builder.on(
'change',
'.wpforms-field-option-row-date_disable_past_dates input',
function( event ) {
app.updateDisableTodaysDateControls( $( event.target ).closest( '.wpforms-field-option-row-date_disable_past_dates' ).data().fieldId, event.target?.checked );
}
);
$builder.on(
'change',
'.wpforms-field-option-row-password-strength input',
function( event ) {
app.updatePasswordStrengthControls( $( event.target ).parents( '.wpforms-field-option-row-password-strength' ).data().fieldId, event.target.checked );
}
);
$builder.on(
'change',
'.wpforms-field-option-richtext .wpforms-field-option-row-media_enabled input',
app.updateRichTextMediaFieldsLimitControls
);
$builder.on(
'change',
'.wpforms-field-option-richtext .wpforms-field-option-row-style select',
app.updateRichTextStylePreview
);
// File uploader - change style.
$builder
.on(
'change',
'.wpforms-field-option-file-upload .wpforms-field-option-row-style select, .wpforms-field-option-file-upload .wpforms-field-option-row-max_file_number input',
function( event ) {
app.fieldFileUploadPreviewUpdate( event.target );
}
);
// Real-time updates for Number Slider field.
app.numberSliderEvents( $builder );
// Hide image and icon choices if dynamic choices are not off.
app.fieldDynamicChoiceToggleImageChoices();
app.fieldDynamicChoiceToggleIconChoices();
// Real-time updates for Payment field's 'Show price after item label' option.
$builder.on( 'change', '.wpforms-field-option-row-show_price_after_labels input', function( e ) { // eslint-disable-line no-unused-vars
const $input = $( this ),
$list = $input.closest( '.wpforms-field-option-group-basic' ).find( '.wpforms-field-option-row-choices .choices-list' );
app.fieldChoiceUpdate( $list.data( 'field-type' ), $list.data( 'field-id' ) );
} );
$builder
.on( 'input', '.wpforms-field-option-row-preview-notice textarea', app.updatePreviewNotice )
.on( 'change', '.wpforms-field-option-row-preview-notice-enable input', app.toggleEntryPreviewNotice )
.on( 'wpformsFieldAdd', app.maybeLockEntryPreviewGroupOnAdd )
.on( 'wpformsFieldMove', app.maybeLockEntryPreviewGroupOnMove )
.on( 'click', '.wpforms-entry-preview-block', app.entryPreviewBlockField );
app.defaultStateEntryPreviewNotice();
},
/**
* Check if we had focusout event from certain fields.
*
* @since 1.7.5
*/
focusOutEvent() {
if ( elements.$focusOutTarget === null ) {
return;
}
if ( elements.$defaultEmail.is( elements.$focusOutTarget ) ) {
const $field = elements.$focusOutTarget;
$field.next( '.wpforms-alert' ).remove();
if ( $field.val() === '' ) {
return;
}
$.get(
wpforms_builder.ajax_url,
{
nonce: wpforms_builder.nonce,
content: $field.val(),
action: 'wpforms_sanitize_default_email',
},
function( res ) {
if ( res.success ) {
$field.val( res.data );
$field.trigger( 'input' );
if ( ! res.data ) {
$field.after( '<div class="wpforms-alert-warning wpforms-alert"><p>' + wpforms_builder.restricted_default_email + '</p></div>' );
}
}
}
);
}
elements.$focusOutTarget = null;
},
/**
* Determine if the field is disabled for selection/duplication/deletion.
*
* @since 1.7.1
*
* @param {any} el DOM element or jQuery object of some container on the field preview.
*
* @return {boolean} True if actions are disabled.
*/
isFieldPreviewActionsDisabled( el ) {
return app.isFormPreviewActionsDisabled( el ) ||
$( el ).closest( '.wpforms-field' ).hasClass( 'ui-sortable-disabled' );
},
/**
* Determine if form wrapper has sorting locked.
*
* @since 1.7.6
*
* @param {any} el DOM element or jQuery object of some container on the field preview.
*
* @return {boolean} True when form preview wrapper sorting is disabled.
*/
isFormPreviewActionsDisabled( el ) {
return $( el ).closest( '.wpforms-field-wrap' ).hasClass( 'ui-sortable-disabled' );
},
/**
* Toggle field group visibility in the field sidebar.
*
* @since 1.0.0
*
* @param {any} el DOM element or jQuery object.
* @param {string} action Action.
*/
fieldGroupToggle( el, action ) {
const $this = $( el );
let $buttons = $this.next( '.wpforms-add-fields-buttons' );
const $group = $buttons.parent();
let $icon = $this.find( 'i' ),
groupName = $this.data( 'group' ),
cookieName = 'wpforms_field_group_' + groupName;
if ( action === 'click' ) {
if ( $group.hasClass( 'wpforms-closed' ) ) {
wpCookies.remove( cookieName );
} else {
wpCookies.set( cookieName, 'true', 2592000 ); // 1 month
}
$icon.toggleClass( 'wpforms-angle-right' );
$buttons.stop().slideToggle( '', function() {
$group.toggleClass( 'wpforms-closed' );
} );
return;
}
if ( action === 'load' ) {
$buttons = $this.find( '.wpforms-add-fields-buttons' );
$icon = $this.find( '.wpforms-add-fields-heading i' );
groupName = $this.find( '.wpforms-add-fields-heading' ).data( 'group' );
cookieName = 'wpforms_field_group_' + groupName;
if ( wpCookies.get( cookieName ) === 'true' ) {
$icon.toggleClass( 'wpforms-angle-right' );
$buttons.hide();
$this.toggleClass( 'wpforms-closed' );
}
}
},
/**
* Update description.
*
* @since 1.6.9
*
* @param {jQuery} $el Element.
* @param {string} value Value.
*/
updateDescription( $el, value ) {
if ( $el.hasClass( 'nl2br' ) ) {
value = value.replace( /\n/g, '<br>' );
}
$el.html( value );
},
/**
* Set the default state for the entry preview notice field.
*
* @since 1.6.9
*/
defaultStateEntryPreviewNotice() {
$( '.wpforms-field-option-row-preview-notice-enable input' ).each( function() {
$( this ).trigger( 'change' );
} );
},
/**
* Update a preview notice for the field preview.
*
* @since 1.6.9
*/
updatePreviewNotice() {
const $this = $( this );
let value = wpf.sanitizeHTML( $this.val() ).trim();
const id = $this.parent().data( 'field-id' ),
$field = $( '#wpforms-field-' + id ).find( '.wpforms-entry-preview-notice' );
value = value ? value : wpforms_builder.entry_preview_default_notice;
app.updateDescription( $field, value );
},
/**
* Show/hide entry preview notice for the field preview.
*
* @since 1.6.9
*/
toggleEntryPreviewNotice() {
const $this = $( this ),
id = $this.closest( '.wpforms-field-option' ).data( 'field-id' ),
$field = $( '#wpforms-field-' + id ),
$noticeField = $( '#wpforms-field-option-' + id + ' .wpforms-field-option-row-preview-notice' ),
$notice = $field.find( '.wpforms-entry-preview-notice' ),
$defaultNotice = $field.find( '.wpforms-alert-info' );
if ( $this.is( ':checked' ) ) {
$defaultNotice.hide();
$notice.show();
$noticeField.show();
return;
}
$noticeField.hide();
$notice.hide();
$defaultNotice.show();
},
/**
* Delete a field.
*
* @param {number} id Field ID.
*
* @since 1.0.0
* @since 1.6.9 Add the entry preview logic.
*/
fieldDelete( id ) {
const $field = $( '#wpforms-field-' + id ),
type = $field.data( 'field-type' );
if ( type === 'pagebreak' && $field.hasClass( 'wpforms-field-entry-preview-not-deleted' ) ) {
app.youCantRemovePageBreakFieldPopup();
return;
}
if ( $field.hasClass( 'no-delete' ) ) {
app.youCantRemoveFieldPopup();
return;
}
app.confirmFieldDeletion( id, type );
},
/**
* Show the error message in the popup that you cannot remove the page break field.
*
* @since 1.6.9
*/
youCantRemovePageBreakFieldPopup() {
$.alert( {
title: wpforms_builder.heads_up,
content: wpforms_builder.entry_preview_require_page_break,
icon: 'fa fa-exclamation-circle',
type: 'red',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Show the error message in the popup that you cannot reorder the field.
*
* @since 1.7.1
* @since 1.7.7 Deprecated.
*
* @deprecated Use `WPForms.Admin.Builder.DragFields.youCantReorderFieldPopup()` instead.
*/
youCantReorderFieldPopup() {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsBuilder.youCantReorderFieldPopup()" has been deprecated, please use the new "WPForms.Admin.Builder.DragFields.youCantReorderFieldPopup()" function instead!' );
WPForms.Admin.Builder.DragFields.youCantReorderFieldPopup();
},
/**
* Show the error message in the popup that you cannot remove the field.
*
* @since 1.6.9
*/
youCantRemoveFieldPopup() {
$.alert( {
title: wpforms_builder.field_locked,
content: wpforms_builder.field_locked_no_delete_msg,
icon: 'fa fa-info-circle',
type: 'blue',
buttons: {
confirm: {
text: wpforms_builder.close,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Error alert displayed for invalid From Email Notification field.
*
* @since 1.8.1
*
* @param {string} $msg Message.
*/
validationErrorNotificationPopup( $msg ) {
$.alert( {
title: wpforms_builder.heads_up,
content: $msg,
icon: 'fa fa-exclamation-circle',
type: 'red',
buttons: {
confirm: {
text: wpforms_builder.close,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Show the confirmation popup before the field deletion.
*
* @param {number} id Field ID.
* @param {string} type Field type.
*
* @since 1.6.9
*/
confirmFieldDeletion( id, type ) {
const fieldData = {
id,
message: wpforms_builder.delete_confirm,
};
const event = WPFormsUtils.triggerEvent( $builder, 'wpformsBeforeFieldDeleteAlert', [ fieldData, type ] );
// Allow callbacks on `wpformsBeforeFieldDeleteAlert` to prevent field deletion by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return;
}
$.confirm( {
title: false,
content: fieldData.message,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
app.fieldDeleteById( id );
},
},
cancel: {
text: wpforms_builder.cancel,
},
},
} );
},
/**
* Remove the field by ID.
*
* @since 1.6.9
*
* @param {number} id Field ID.
* @param {string} type Field type (deprecated)
* @param {number} duration Duration of animation.
*/
fieldDeleteById( id, type = '', duration = 400 ) {
$( `#wpforms-field-${ id }` ).fadeOut( duration, function() {
const $field = $( this );
const $layoutParents = $field.parents( '.wpforms-field-layout-columns' );
type = $field.data( 'field-type' );
$builder.trigger( 'wpformsBeforeFieldDelete', [ id, type ] );
$field.remove();
$( '#wpforms-field-option-' + id ).remove();
$( '.wpforms-field, .wpforms-title-desc' ).removeClass( 'active' );
app.fieldTabToggle( 'add-fields' );
const $fieldsOptions = $( '.wpforms-field-option' ),
$submitButton = $builder.find( '.wpforms-field-submit' );
// No fields remains.
if ( $fieldsOptions.length < 1 ) {
elements.$sortableFieldsWrap.append( elements.$noFieldsPreview.clone() );
elements.$fieldOptions.append( elements.$noFieldsOptions.clone() );
$submitButton.hide();
}
// Only Layout fields remains.
if ( ! $fieldsOptions.filter( ':not(.wpforms-field-option-layout)' ).length ) {
$submitButton.hide();
}
$builder.trigger( 'wpformsFieldDelete', [ id, type, $layoutParents ] );
} );
},
/**
* Determine which sections to activate for each panel.
*
* @since 1.9.3
*/
determineActiveSections() {
const sectionFromUrl = wpf.getQueryString( 'section' );
// Gets the section to activate based on the URL.
const getSectionFromUrl = ( $panel, sectionFromUrl ) => {
if ( ! sectionFromUrl || ! $panel.hasClass( 'active' ) ) {
return null;
}
const $sectionElement = $panel.find( `.wpforms-panel-sidebar-section[data-section="${ sectionFromUrl }"]` );
return $sectionElement.length ? $sectionElement : null;
};
// Gets the configured section within a panel to activate, if available.
const getConfiguredSection = ( $panel ) => {
const $configuredSection = $panel.find( '.wpforms-panel-sidebar-section.configured' ).first();
return $configuredSection.length ? $configuredSection : null;
};
// Gets the first available section in the sidebar to activate.
const getFirstAvailableSection = ( $panel ) => {
return $panel.find( '.wpforms-panel-sidebar-section:first-of-type' );
};
// Activates the specified section within a panel and its corresponding content section.
const activateSection = ( $panel, $sectionToActivate ) => {
if ( ! $sectionToActivate ) {
return;
}
const sectionNameToActivate = $sectionToActivate.data( 'section' );
$sectionToActivate.addClass( 'active' );
const $contentSection = $panel.find( `.wpforms-panel-content-section-${ sectionNameToActivate }` );
if ( $contentSection.length ) {
$contentSection.show().addClass( 'active' );
$panel.find( '.wpforms-panel-content-section-default' ).toggle( sectionNameToActivate === 'default' );
} else {
$panel.find( '.wpforms-panel-content-section-default' ).show().addClass( 'active' );
}
WPFormsUtils.triggerEvent( $builder, 'wpformsPanelSectionSwitch', sectionNameToActivate );
};
// Iterate through each panel and determine which section to activate.
$( '.wpforms-panel' ).each( function( index, el ) { // eslint-disable-line no-unused-vars
const $panel = $( this );
const $sectionToActivate = getSectionFromUrl( $panel, sectionFromUrl ) ||
getConfiguredSection( $panel ) ||
getFirstAvailableSection( $panel );
activateSection( $panel, $sectionToActivate );
} );
},
/**
* Load entry preview fields.
*
* @since 1.6.9
*/
loadEntryPreviewFields() {
const $fields = $( '#wpforms-panel-fields .wpforms-field-wrap .wpforms-field-entry-preview' );
if ( ! $fields.length ) {
return;
}
$fields.each( function() {
app.lockEntryPreviewFieldsPosition( $( this ).data( 'field-id' ) );
} );
},
/**
* Delete the entry preview field from the form preview.
*
* @since 1.6.9
*
* @param {Event} event Event.
* @param {number} id Field ID.
* @param {string} type Field type.
*/
fieldEntryPreviewDelete( event, id, type ) {
if ( 'entry-preview' !== type ) {
return;
}
const $field = $( '#wpforms-field-' + id ),
$previousPageBreakField = $field.prevAll( '.wpforms-field-pagebreak' ).first(),
$nextPageBreakField = $field.nextAll( '.wpforms-field-pagebreak' ).first(),
nextPageBreakId = $nextPageBreakField.data( 'field-id' ),
$nextPageBreakOptions = $( '#wpforms-field-option-' + nextPageBreakId );
$previousPageBreakField.removeClass( 'wpforms-field-not-draggable wpforms-field-entry-preview-not-deleted' );
$nextPageBreakOptions.find( '.wpforms-entry-preview-block' ).removeClass( 'wpforms-entry-preview-block' );
$builder.trigger( 'wpformsFieldDragToggle', [ $previousPageBreakField.data( 'field-id' ), $previousPageBreakField.data( 'field-type' ) ] );
},
/**
* Maybe lock the entry preview and fields nearby after move event.
*
* @since 1.6.9
*
* @param {Event} e Event.
* @param {Object} ui UI sortable object.
*/
maybeLockEntryPreviewGroupOnMove( e, ui ) {
if ( ! ui.item.hasClass( 'wpforms-field-pagebreak' ) ) {
return;
}
app.maybeLockEntryPreviewGroupOnAdd( e, ui.item.data( 'field-id' ), 'pagebreak' );
},
/**
* Maybe lock the entry preview and fields nearby after adding event.
*
* @since 1.6.9
*
* @param {Event} e Event.
* @param {number} fieldId Field id.
* @param {string} type Field type.
*/
maybeLockEntryPreviewGroupOnAdd( e, fieldId, type ) {
if ( type !== 'pagebreak' ) {
return;
}
const $currentField = $( '#wpforms-field-' + fieldId ),
$prevField = $currentField.prevAll( '.wpforms-field-entry-preview,.wpforms-field-pagebreak' ).first(),
$nextField = $currentField.nextAll( '.wpforms-field-entry-preview,.wpforms-field-pagebreak' ).first();
if ( ! $prevField.hasClass( 'wpforms-field-entry-preview' ) && ! $nextField.hasClass( 'wpforms-field-entry-preview' ) ) {
return;
}
const $currentFieldPrevToggle = $( '#wpforms-field-option-' + fieldId + ' .wpforms-field-option-row-prev_toggle' ),
$currentFieldPrevToggleField = $currentFieldPrevToggle.find( 'input' ),
$nextFieldPrevToggle = $( '#wpforms-field-option-' + $nextField.data( 'field-id' ) + ' .wpforms-field-option-row-prev_toggle' );
if ( $prevField.hasClass( 'wpforms-field-entry-preview' ) ) {
$currentFieldPrevToggleField.attr( 'checked', 'checked' ).trigger( 'change' );
$currentFieldPrevToggle.addClass( 'wpforms-entry-preview-block' );
$nextFieldPrevToggle.removeClass( 'wpforms-entry-preview-block' );
return;
}
const prevFieldId = $prevField.data( 'field-id' ),
$prevFieldPrevToggle = $( '#wpforms-field-option-' + prevFieldId + ' .wpforms-field-option-row-prev_toggle' ),
$prevFieldPrevToggleField = $prevFieldPrevToggle.find( 'input' );
$currentField.addClass( 'wpforms-field-not-draggable wpforms-field-entry-preview-not-deleted' );
$builder.trigger( 'wpformsFieldDragToggle', [ fieldId, type ] );
$prevField.removeClass( 'wpforms-field-not-draggable wpforms-field-entry-preview-not-deleted' );
$builder.trigger( 'wpformsFieldDragToggle', [ prevFieldId, $prevField.data( 'field-type' ) ] );
if ( $prevField.prevAll( '.wpforms-field-entry-preview,.wpforms-field-pagebreak' ).first().hasClass( 'wpforms-field-entry-preview' ) ) {
$prevFieldPrevToggleField.attr( 'checked', 'checked' ).trigger( 'change' );
$prevFieldPrevToggle.addClass( 'wpforms-entry-preview-block' );
}
},
/**
* Show the error popup that the entry preview field blocks the field.
*
* @since 1.6.9
*
* @param {Event} e Event.
*/
entryPreviewBlockField( e ) {
e.preventDefault();
$.alert( {
title: wpforms_builder.heads_up,
content: wpforms_builder.entry_preview_require_previous_button,
icon: 'fa fa-exclamation-circle',
type: 'red',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Is it an entry preview field that should be checked before adding?
*
* @since 1.6.9
*
* @param {string} type Field type.
* @param {Object} options Field options.
*
* @return {boolean} True when we should check it.
*/
isUncheckedEntryPreviewField( type, options ) {
// eslint-disable-next-line no-mixed-operators
return type === 'entry-preview' && ( ! options || options && ! options.passed );
},
/**
* Add an entry preview field to the form preview.
*
* @since 1.6.9
*
* @param {string} type Field type.
* @param {Object} options Field options.
*/
addEntryPreviewField( type, options ) { // eslint-disable-line complexity
const addButton = $( '#wpforms-add-fields-entry-preview' );
if ( addButton.hasClass( 'wpforms-entry-preview-adding' ) ) {
return;
}
const $fields = $( '#wpforms-panel-fields .wpforms-field-wrap > .wpforms-field' ),
position = options?.position ? options.position : $fields.length,
needPageBreakBefore = app.isEntryPreviewFieldRequiresPageBreakBefore( $fields, position ),
needPageBreakAfter = app.isEntryPreviewFieldRequiresPageBreakAfter( $fields, position );
addButton.addClass( 'wpforms-entry-preview-adding' );
if ( ! options ) {
options = {};
}
options.passed = true;
if ( ! needPageBreakBefore && ! needPageBreakAfter ) {
app.fieldAdd( 'entry-preview', options ).done( function( res ) {
app.lockEntryPreviewFieldsPosition( res.data.field.id );
} );
return;
}
if ( needPageBreakBefore ) {
app.addPageBreakAndEntryPreviewFields( options, position );
return;
}
app.addEntryPreviewAndPageBreakFields( options, position );
},
/**
* Add the entry preview field after the page break field.
* We should wait for the page break adding to avoid id duplication.
*
* @since 1.6.9
*
* @param {Object} options Field options.
*/
addEntryPreviewFieldAfterPageBreak( options ) {
const checkExist = setInterval( function() {
if ( $( '#wpforms-panel-fields .wpforms-field-wrap' ).find( '.wpforms-pagebreak-bottom, .wpforms-pagebreak-top' ).length === 2 ) {
app.fieldAdd( 'entry-preview', options ).done( function( res ) {
app.lockEntryPreviewFieldsPosition( res.data.field.id );
} );
clearInterval( checkExist );
}
}, 100 );
},
/**
* Add the entry preview field after the page break field.
*
* @since 1.6.9
*
* @param {Object} options Field options.
* @param {number} position The field position.
*/
addPageBreakAndEntryPreviewFields( options, position ) {
const hasPageBreak = $( '#wpforms-panel-fields .wpforms-field-wrap > .wpforms-field-pagebreak' ).length >= 3;
app.fieldAdd( 'pagebreak', { position } ).done( function( res ) {
options.position = hasPageBreak ? position + 1 : position + 2;
app.addEntryPreviewFieldAfterPageBreak( options );
const $pageBreakOptions = $( '#wpforms-field-option-' + res.data.field.id ),
$pageBreakPrevToggle = $pageBreakOptions.find( '.wpforms-field-option-row-prev_toggle' ),
$pageBreakPrevToggleField = $pageBreakPrevToggle.find( 'input' );
$pageBreakPrevToggleField.attr( 'checked', 'checked' ).trigger( 'change' );
$pageBreakPrevToggle.addClass( 'wpforms-entry-preview-block' );
} );
},
/**
* Duplicate field.
*
* @since 1.2.9
*
* @param {string} id Field id.
*/
fieldDuplicate( id ) {
const $field = $( `#wpforms-field-${ id }` );
if ( $field.hasClass( 'no-duplicate' ) ) {
$.alert( {
title: wpforms_builder.field_locked,
content: wpforms_builder.field_locked_no_duplicate_msg,
icon: 'fa fa-info-circle',
type: 'blue',
buttons: {
confirm: {
text: wpforms_builder.close,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
return;
}
$.confirm( {
title: false,
content: wpforms_builder.duplicate_confirm,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
// Disable the current button to avoid firing multiple click events.
// By default, "jconfirm" tends to destroy any modal DOM element upon button click.
this.$$confirm.prop( 'disabled', true );
const beforeEvent = WPFormsUtils.triggerEvent( $builder, 'wpformsBeforeFieldDuplicate', [ id, $field ] );
// Allow callbacks on `wpformsFieldBeforeDuplicate` to cancel field duplication.
if ( beforeEvent.isDefaultPrevented() ) {
return;
}
const newFieldId = app.fieldDuplicateRoutine( id, true ),
$newField = $( `#wpforms-field-${ newFieldId }` );
// Lastly, update the next ID stored in the database.
app.increaseNextFieldIdAjaxRequest();
WPFormsUtils.triggerEvent( $builder, 'wpformsFieldDuplicated', [ id, $field, newFieldId, $newField ] );
},
},
cancel: {
text: wpforms_builder.cancel,
},
},
} );
},
/**
* Update the next ID stored in the database.
*
* @since 1.7.7
*/
increaseNextFieldIdAjaxRequest() {
/* eslint-disable camelcase */
$.post(
wpforms_builder.ajax_url,
{
form_id: s.formID,
field_id: elements.$nextFieldId.val(),
nonce: wpforms_builder.nonce,
action: 'wpforms_builder_increase_next_field_id',
}
);
},
/**
* Duplicate field routine.
*
* @since 1.7.7
*
* @param {number|string} id Field ID.
* @param {boolean} changeLabel Is it necessary to change the label and add a copy suffix.
*
* @return {number} New field ID.
*/
fieldDuplicateRoutine( id, changeLabel = true ) { // eslint-disable-line max-lines-per-function, complexity
const $field = $( `#wpforms-field-${ id }` ),
$fieldOptions = $( `#wpforms-field-option-${ id }` ),
$fieldActive = elements.$sortableFieldsWrap.find( '>.active' ),
$visibleOptions = elements.$fieldOptions.find( '>:visible' ),
$visibleTab = $visibleOptions.find( '>.active' ),
type = $field.data( 'field-type' ),
fieldOptionsClass = $fieldOptions.attr( 'class' ),
isModernDropdown = app.dropdownField.helpers.isModernSelect( $field.find( '> .choices .primary-input' ) );
// Restore tooltips before cloning.
wpf.restoreTooltips( $fieldOptions );
// Force Modern Dropdown conversion to classic before cloning.
if ( isModernDropdown ) {
app.dropdownField.helpers.convertModernToClassic( id );
}
let newFieldOptions = $fieldOptions.html();
const $newField = $field.clone(),
newFieldID = parseInt( elements.$nextFieldId.val(), 10 ),
$fieldLabel = $( `#wpforms-field-option-${ id }-label` ),
fieldLabelVal = $fieldLabel.length ? $fieldLabel.val() : $( `#wpforms-field-option-${ id }-name` ).val(),
nextID = newFieldID + 1,
regex = {};
const newFieldLabel = fieldLabelVal !== ''
? `${ fieldLabelVal } ${ wpforms_builder.duplicate_copy }`
: `${ wpforms_builder.field } #${ id } ${ wpforms_builder.duplicate_copy }`;
regex.fieldOptionsID = new RegExp( 'ID #' + id, 'g' );
regex.fieldID = new RegExp( 'fields\\[' + id + '\\]', 'g' );
regex.dataFieldID = new RegExp( 'data-field-id="' + id + '"', 'g' );
regex.referenceID = new RegExp( 'data-reference="' + id + '"', 'g' );
regex.elementID = new RegExp( '\\b(id|for)="wpforms-(.*?)' + id + '(.*?)"', 'ig' );
// Toggle visibility states.
$field.after( $newField );
$fieldActive.removeClass( 'active' );
$newField.addClass( 'active' ).attr( {
id: `wpforms-field-${ newFieldID }`,
'data-field-id': newFieldID,
} );
// Various regexes to adjust the field options to work with the new field ID.
regex.elementIdReplace = function( match, p1, p2, p3, offset, string ) { // eslint-disable-line no-unused-vars
return `${ p1 }="wpforms-${ p2 }${ newFieldID }${ p3 }"`;
};
newFieldOptions = newFieldOptions.replace( regex.fieldOptionsID, `ID #${ newFieldID }` );
newFieldOptions = newFieldOptions.replace( regex.fieldID, `fields[${ newFieldID }]` );
newFieldOptions = newFieldOptions.replace( regex.dataFieldID, `data-field-id="${ newFieldID }"` );
newFieldOptions = newFieldOptions.replace( regex.referenceID, `data-reference="${ newFieldID }"` );
newFieldOptions = newFieldOptions.replace( regex.elementID, regex.elementIdReplace );
// Hide all field options panels.
$visibleOptions.hide();
// Add a new field options panel.
$fieldOptions.after( `<div class="${ fieldOptionsClass }" id="wpforms-field-option-${ newFieldID }" data-field-id="${ newFieldID }">${ newFieldOptions }</div>` );
// Get a new field options panel.
const $newFieldOptions = $( `#wpforms-field-option-${ newFieldID }` );
// If the user duplicates an active field.
if ( $fieldActive.data( 'field-id' ) === id && $visibleTab.length ) {
// The following will help identify which tab from the sidebar panel settings is currently being viewed,
// i.e., "General," "Advanced," "Smart Logic," etc.
const visibleTabClassName = $visibleTab.attr( 'class' ).match( /wpforms-field-option-group-\S*/i )[ 0 ];
const $newFieldOptionsTab = $newFieldOptions.find( `>.${ visibleTabClassName }` );
// Remove any left-over state from previously duplicated options.
$newFieldOptions.find( '>' ).removeClass( 'active' );
// Set active tab to the same tab that was active before the duplication.
$newFieldOptionsTab.addClass( 'active' );
}
// If the user duplicates an inactive field.
if ( $fieldActive.data( 'field-id' ) !== id && $visibleTab.length ) {
// Remove active class from the current active tab.
$newFieldOptions.find( '>' ).removeClass( 'active' );
// Set active tab to "General".
$newFieldOptions.find( '>.wpforms-field-option-group-basic' ).addClass( 'active' );
}
// Copy over values.
$fieldOptions.find( ':input' ).each( function( index, el ) { // eslint-disable-line complexity, no-unused-vars
const $this = $( this ),
name = $this.attr( 'name' );
if ( ! name ) {
return 'continue';
}
const newName = name.replace( regex.fieldID, `fields[${ newFieldID }]` ),
type = $this.attr( 'type' );
if ( type === 'checkbox' || type === 'radio' ) {
if ( $this.is( ':checked' ) ) {
$newFieldOptions.find( `[name="${ newName }"]` )
.prop( 'checked', true )
.attr( 'checked', 'checked' );
} else {
$newFieldOptions.find( `[name="${ newName }"]` )
.prop( 'checked', false )
.attr( 'checked', false );
}
return;
}
if ( $this.is( 'select' ) ) {
if ( $this.find( 'option:selected' ).length ) {
const optionVal = $this.find( 'option:selected' ).val();
$newFieldOptions.find( `[name="${ newName }"]` )
.find( `[value="${ optionVal }"]` )
.prop( 'selected', true );
}
return;
}
const value = $this.val();
if ( value === '' && $this.hasClass( 'wpforms-money-input' ) ) {
$newFieldOptions.find( `[name="${ newName }"]` ).val(
wpf.numberFormat( '0', wpforms_builder.currency_decimals, wpforms_builder.currency_decimal, wpforms_builder.currency_thousands )
);
} else {
// We've removed the empty value check here.
// If we are duplicating a field with no value, we should respect that.
$newFieldOptions.find( `[name="${ newName }"]` ).val( value );
}
} );
// ID adjustments.
$newFieldOptions.find( '.wpforms-field-option-hidden-id' ).val( newFieldID );
elements.$nextFieldId.val( nextID );
const $newFieldLabel = type === 'html' ? $( `#wpforms-field-option-${ newFieldID }-name` ) : $( `#wpforms-field-option-${ newFieldID }-label` );
// Adjust the label to indicate this is a copy.
if ( changeLabel ) {
$newFieldLabel.val( newFieldLabel ).trigger( 'input' );
}
// Fire field adds custom event.
$builder.trigger( 'wpformsFieldAdd', [ newFieldID, type ] );
// Re-init tooltips for a new field options panel.
wpf.initTooltips();
// Re-init Modern Dropdown.
if ( isModernDropdown ) {
app.dropdownField.helpers.convertClassicToModern( id );
app.dropdownField.helpers.convertClassicToModern( newFieldID );
}
// Re-init instance in choices related fields.
app.fieldChoiceUpdate( $newField.data( 'field-type' ), newFieldID );
// Re-init color pickers.
app.loadColorPickers();
return newFieldID;
},
/**
* Add the entry preview field before the page break field.
*
* @since 1.6.9
*
* @param {Object} options Field options.
* @param {number} position The field position.
*/
addEntryPreviewAndPageBreakFields( options, position ) {
app.fieldAdd( 'entry-preview', options ).done( function( res ) {
const entryPreviewId = res.data.field.id;
app.fieldAdd( 'pagebreak', { position: position + 1 } ).done( function( res ) {
app.lockEntryPreviewFieldsPosition( entryPreviewId );
const $pageBreakField = $( '#wpforms-field-' + res.data.field.id ),
$nextField = $pageBreakField.nextAll( '.wpforms-field-pagebreak, .wpforms-field-entry-preview' ).first();
if ( $nextField.hasClass( 'wpforms-field-entry-preview' ) ) {
app.lockEntryPreviewFieldsPosition( $nextField.data( 'field-id' ) );
}
} );
} );
},
/**
* Stick an entry preview field after adding.
*
* @since 1.6.9
*
* @param {number} id ID.
*/
lockEntryPreviewFieldsPosition( id ) {
const $entryPreviewField = $( '#wpforms-field-' + id ),
$pageBreakField = $entryPreviewField.prevAll( '.wpforms-field-pagebreak:not(.wpforms-pagebreak-bottom)' ).first(),
$nextPageBreakField = $entryPreviewField.nextAll( '.wpforms-field-pagebreak' ).first(),
nextPageBreakFieldId = $nextPageBreakField.data( 'field-id' ),
$pageBreakOptions = $( '#wpforms-field-option-' + nextPageBreakFieldId ),
$pageBreakPrevToggle = $pageBreakOptions.find( '.wpforms-field-option-row-prev_toggle' ),
$pageBreakPrevToggleField = $pageBreakPrevToggle.find( 'input' );
$entryPreviewField.addClass( 'wpforms-field-not-draggable' );
$pageBreakField.addClass( 'wpforms-field-not-draggable wpforms-field-entry-preview-not-deleted' );
$pageBreakPrevToggleField.prop( 'checked', 'checked' ).trigger( 'change' );
$pageBreakPrevToggle.addClass( 'wpforms-entry-preview-block' );
$( '#wpforms-add-fields-entry-preview' ).removeClass( 'wpforms-entry-preview-adding' );
$builder.trigger( 'wpformsFieldDragToggle', [ id, $entryPreviewField.data( 'field-type' ) ] );
$builder.trigger( 'wpformsFieldDragToggle', [ $pageBreakField.data( 'field-id' ), $pageBreakField.data( 'field-type' ) ] );
},
/**
* An entry preview field requires a page break that locates before.
*
* @since 1.6.9
*
* @param {jQuery} $fields List of fields in the form preview.
* @param {number} position The field position.
*
* @return {boolean} True if we need to add a page break field before.
*/
isEntryPreviewFieldRequiresPageBreakBefore( $fields, position ) {
const $beforeFields = $fields.slice( 0, position ).filter( '.wpforms-field-pagebreak,.wpforms-field-entry-preview' );
let needPageBreakBefore = true;
if ( ! $beforeFields.length ) {
return needPageBreakBefore;
}
$( $beforeFields.get().reverse() ).each( function() {
const $this = $( this );
if ( $this.hasClass( 'wpforms-field-entry-preview' ) ) {
return false;
}
if ( $this.hasClass( 'wpforms-field-pagebreak' ) && ! $this.hasClass( 'wpforms-field-stick' ) ) {
needPageBreakBefore = false;
return false;
}
} );
return needPageBreakBefore;
},
/**
* An entry preview field requires a page break that locates after.
*
* @since 1.6.9
*
* @param {jQuery} $fields List of fields in the form preview.
* @param {number} position The field position.
*
* @return {boolean} True if we need to add a page break field after.
*/
isEntryPreviewFieldRequiresPageBreakAfter( $fields, position ) {
const $afterFields = $fields.slice( position ).filter( '.wpforms-field-pagebreak,.wpforms-field-entry-preview' );
let needPageBreakAfter = Boolean( $afterFields.length );
if ( ! $afterFields.length ) {
return needPageBreakAfter;
}
$afterFields.each( function() {
const $this = $( this );
if ( $this.hasClass( 'wpforms-field-entry-preview' ) ) {
return false;
}
if ( $this.hasClass( 'wpforms-field-pagebreak' ) ) {
needPageBreakAfter = false;
return false;
}
} );
return needPageBreakAfter;
},
/**
* Add new field.
*
* @since 1.0.0
* @since 1.6.4 Added hCaptcha support.
*
* @param {string} type Field type.
* @param {Object} options Additional options.
*
* @return {Promise|void} jQuery.post() promise interface.
*/
fieldAdd( type, options ) { // eslint-disable-line max-lines-per-function
const $btn = $( `#wpforms-add-fields-${ type }` );
if ( $btn.hasClass( 'upgrade-modal' ) || $btn.hasClass( 'education-modal' ) || $btn.hasClass( 'warning-modal' ) ) {
return;
}
if ( [ 'captcha_turnstile', 'captcha_hcaptcha', 'captcha_recaptcha', 'captcha_none' ].includes( type ) ) {
app.captchaUpdate();
return;
}
adding = true;
WPForms.Admin.Builder.DragFields.disableDragAndDrop();
app.disableFormActions();
if ( app.isUncheckedEntryPreviewField( type, options ) ) {
app.addEntryPreviewField( type, options );
return;
}
const defaults = {
position: 'bottom',
$sortable: 'base',
placeholder: false,
scroll: true,
defaults: false,
};
options = $.extend( {}, defaults, options );
const data = {
action: 'wpforms_new_field_' + type,
id: s.formID,
type,
defaults: options.defaults,
nonce: wpforms_builder.nonce,
};
return $.post( wpforms_builder.ajax_url, data, function( res ) { // eslint-disable-line complexity
if ( ! res.success ) {
wpf.debug( 'Add field AJAX call is unsuccessful:', res );
return;
}
const $baseFieldsContainer = elements.$sortableFieldsWrap,
$newField = $( res.data.preview ),
$newOptions = $( res.data.options );
let $fieldContainer = options.$sortable;
adding = false;
$newField.css( 'display', 'none' );
if ( options.placeholder ) {
options.placeholder.remove();
}
if ( options.$sortable === 'default' || ! options.$sortable.length ) {
$fieldContainer = $baseFieldsContainer.find( '.wpforms-fields-sortable-default' );
}
if ( options.$sortable === 'base' || ! $fieldContainer.length ) {
$fieldContainer = $baseFieldsContainer;
}
const event = WPFormsUtils.triggerEvent(
$builder,
'wpformsBeforeFieldAddToDOM',
[ options, $newField, $newOptions, $fieldContainer ]
);
// Allow callbacks on `wpformsBeforeFieldAddToDOM` to cancel adding field
// by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return;
}
// Add field to the base level of fields.
// Allow callbacks on `wpformsBeforeFieldAddToDOM` to skip adding field to the base level
// by setting `event.skipAddFieldToBaseLevel = true`.
if ( ! event.skipAddFieldToBaseLevel ) {
app.fieldAddToBaseLevel( options, $newField, $newOptions );
}
$newField.fadeIn();
$builder.find( '.no-fields, .no-fields-preview' ).remove();
if ( $( '.wpforms-field-option:not(.wpforms-field-option-layout)' ).length ) {
$builder.find( '.wpforms-field-submit' ).show();
}
// Scroll to the added field.
if ( options.scroll && options.position.length ) {
app.scrollPreviewToField( res.data.field.id );
}
// Update next field id hidden input value.
elements.$nextFieldId.val( res.data.field.id + 1 );
wpf.initTooltips();
app.loadColorPickers();
app.toggleAllOptionGroups();
$builder.trigger( 'wpformsFieldAdd', [ res.data.field.id, type ] );
} ).fail( function( xhr, textStatus, e ) { // eslint-disable-line no-unused-vars
adding = false;
wpf.debug( 'Add field AJAX call failed:', xhr.responseText );
} ).always( function() {
if ( ! adding ) {
WPForms.Admin.Builder.DragFields.enableDragAndDrop();
app.enableFormActions();
}
} );
},
/**
* Add new field to the base level of fields.
*
* @since 1.7.7
*
* @param {Object} options Field add additional options.
* @param {jQuery} $newField New field preview object.
* @param {jQuery} $newOptions New field options object.
*/
fieldAddToBaseLevel( options, $newField, $newOptions ) { // eslint-disable-line complexity
const $baseFieldsContainer = elements.$sortableFieldsWrap,
$baseFields = $baseFieldsContainer.children( ':not(.wpforms-field-drag-pending, .no-fields-preview)' ),
totalBaseFields = $baseFields.length;
const $fieldOptions = elements.$fieldOptions;
if ( options.position === 'top' ) {
// Add field to the top of base level fields.
$baseFieldsContainer.prepend( $newField );
$fieldOptions.prepend( $newOptions );
return;
}
const $lastBaseField = $baseFields.last();
if (
options.position === 'bottom' && (
! $lastBaseField.length ||
! $lastBaseField.hasClass( 'wpforms-field-stick' )
)
) {
// Add field to the bottom of base level fields.
$baseFieldsContainer.append( $newField );
$fieldOptions.append( $newOptions );
return;
}
if ( options.position === 'bottom' ) {
options.position = totalBaseFields;
}
if (
options.position === totalBaseFields &&
$lastBaseField.length && $lastBaseField.hasClass( 'wpforms-field-stick' )
) {
const lastBaseFieldId = $lastBaseField.data( 'field-id' );
// Check to see if the last field we have is configured to
// be stuck to the bottom, if so add the field above it.
$lastBaseField.before( $newField );
$fieldOptions.find( `#wpforms-field-option-${ lastBaseFieldId }` ).before( $newOptions );
return;
}
const $fieldInPosition = $baseFields.eq( options.position );
if ( $fieldInPosition.length ) {
const fieldInPositionId = $fieldInPosition.data( 'field-id' );
// Add field to a specific location.
$fieldInPosition.before( $newField );
$fieldOptions.find( `#wpforms-field-option-${ fieldInPositionId }` ).before( $newOptions );
return;
}
// Something is wrong. Add the field. This should never occur.
$baseFieldsContainer.append( $newField );
$fieldOptions.append( $newOptions );
},
/**
* Scroll the preview panel to the desired field.
*
* @since 1.7.7
*
* @param {number} fieldId Field ID.
*/
scrollPreviewToField( fieldId ) {
const $field = $( `#wpforms-field-${ fieldId }` ),
scrollTop = elements.$fieldsPreviewWrap.scrollTop(),
$layoutField = $field.closest( '.wpforms-field-layout' );
let fieldPosition = $field.position().top;
if ( $layoutField.length ) {
fieldPosition = $layoutField.position().top + fieldPosition + 20;
}
const scrollAmount = fieldPosition > scrollTop ? fieldPosition - scrollTop : fieldPosition + scrollTop;
elements.$fieldsPreviewWrap.scrollTop( scrollAmount );
},
/**
* Update CAPTCHA form setting.
*
* @since 1.6.4
*
* @return {Object} jqXHR.
*/
captchaUpdate() {
const data = {
action: 'wpforms_update_field_captcha',
id: s.formID,
nonce: wpforms_builder.nonce,
};
return $.post( wpforms_builder.ajax_url, data, function( res ) {
if ( res.success ) {
const args = {
title: false,
content: false,
icon: 'fa fa-exclamation-circle',
type: 'orange',
boxWidth: '450px',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
},
$enableCheckbox = $( '#wpforms-panel-field-settings-recaptcha' );
let caseName = res.data.current;
$enableCheckbox.data( 'provider', res.data.provider );
// Possible cases:
//
// not_configured - IF CAPTCHA is not configured in the WPForms plugin settings
// configured_not_enabled - IF CAPTCHA is configured in WPForms plugin settings, but wasn't set in form settings
// configured_enabled - IF CAPTCHA is configured in WPForms plugin and form settings
if ( 'configured_not_enabled' === caseName || 'configured_enabled' === caseName ) {
// Get a correct case name.
caseName = $enableCheckbox.prop( 'checked' ) ? 'configured_enabled' : 'configured_not_enabled';
// Check/uncheck a `CAPTCHA` checkbox in form setting.
args.buttons.confirm.action = function() {
$enableCheckbox.prop( 'checked', ( 'configured_not_enabled' === caseName ) ).trigger( 'change' );
};
}
args.title = res.data.cases[ caseName ].title;
args.content = res.data.cases[ caseName ].content;
// Do you need a Cancel button?
if ( res.data.cases[ caseName ].cancel ) {
args.buttons.cancel = {
text: wpforms_builder.cancel,
keys: [ 'esc' ],
};
}
// Call a Confirm modal.
$.confirm( args );
} else {
// eslint-disable-next-line no-console
console.log( res );
}
} ).fail( function( xhr, textStatus, e ) { // eslint-disable-line no-unused-vars
// eslint-disable-next-line no-console
console.log( xhr.responseText );
} );
},
/**
* Disable drag & drop.
*
* @since 1.7.1
* @since 1.7.7 Deprecated.
*
* @deprecated Use `WPForms.Admin.Builder.DragFields.disableDragAndDrop()` instead.
*/
disableDragAndDrop() {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsBuilder.disableDragAndDrop()" has been deprecated, please use the new "WPForms.Admin.Builder.DragFields.disableDragAndDrop()" function instead!' );
WPForms.Admin.Builder.DragFields.disableDragAndDrop();
},
/**
* Enable drag & drop.
*
* @since 1.7.1
* @since 1.7.7 Deprecated.
*
* @deprecated Use `WPForms.Admin.Builder.DragFields.enableDragAndDrop()` instead.
*/
enableDragAndDrop() {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsBuilder.enableDragAndDrop()" has been deprecated, please use the new "WPForms.Admin.Builder.DragFields.enableDragAndDrop()" function instead!' );
WPForms.Admin.Builder.DragFields.enableDragAndDrop();
},
/**
* Disable Preview, Embed, Save form actions and Form Builder exit button.
*
* @since 1.7.4
*/
disableFormActions() {
$.each(
[
elements.$previewButton,
elements.$embedButton,
elements.$saveButton,
elements.$exitButton,
],
function( _index, button ) {
button.prop( 'disabled', true ).addClass( 'wpforms-disabled' );
}
);
},
/**
* Enable Preview, Embed, Save form actions and Form Builder exit button.
*
* @since 1.7.4
*/
enableFormActions() {
$.each(
[
elements.$previewButton,
elements.$embedButton,
elements.$saveButton,
elements.$exitButton,
],
function( _index, button ) {
button.prop( 'disabled', false ).removeClass( 'wpforms-disabled' );
}
);
},
/**
* Sortable fields in the builder form preview area.
*
* @since 1.0.0
* @since 1.7.7 Deprecated.
*
* @deprecated Use `WPForms.Admin.Builder.DragFields.initSortableFields()` instead.
*/
fieldSortable() {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsBuilder.fieldSortable()" has been deprecated, please use the new "WPForms.Admin.Builder.DragFields.initSortableFields()" function instead!' );
WPForms.Admin.Builder.DragFields.initSortableFields();
},
/**
* Show popup in case if field is not draggable, and cancel moving.
*
* @since 1.7.5
* @since 1.7.6 The showPopUp parameter added.
* @since 1.7.7 Deprecated.
*
* @deprecated Use `WPForms.Admin.Builder.DragFields.fieldDragDisable()` instead.
*
* @param {jQuery} $field A field or list of fields.
* @param {boolean} showPopUp Whether the pop-up should be displayed on dragging attempt.
*/
fieldDragDisable( $field, showPopUp = true ) {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsBuilder.fieldDragDisable()" has been deprecated, please use the new "WPForms.Admin.Builder.DragFields.fieldDragDisable()" function instead!' );
WPForms.Admin.Builder.DragFields.fieldDragDisable( $field, showPopUp );
},
/**
* Allow field dragging.
*
* @since 1.7.5
* @since 1.7.7 Deprecated.
*
* @deprecated Use `WPForms.Admin.Builder.DragFields.fieldDragEnable()` instead.
*
* @param {jQuery} $field A field or list of fields.
*/
fieldDragEnable( $field ) {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsBuilder.fieldDragEnable()" has been deprecated, please use the new "WPForms.Admin.Builder.DragFields.fieldDragEnable()" function instead!' );
WPForms.Admin.Builder.DragFields.fieldDragEnable( $field );
},
/**
* Add new field choice.
*
* @since 1.0.0
*
* @param {Event} event Event.
* @param {Element} el Element.
*/
fieldChoiceAdd( event, el ) {
event.preventDefault();
const $this = $( el ),
$parent = $this.parent(),
checked = $parent.find( 'input.default' ).is( ':checked' ),
fieldID = $this.closest( '.wpforms-field-option-row-choices' ).data( 'field-id' );
let id = $parent.parent().attr( 'data-next-id' );
const type = $parent.parent().data( 'field-type' ),
$choice = $parent.clone().insertAfter( $parent );
$choice.attr( 'data-key', id );
$choice.find( 'input.label' ).val( '' ).attr( 'name', 'fields[' + fieldID + '][choices][' + id + '][label]' );
$choice.find( 'input.value' ).val( '' ).attr( 'name', 'fields[' + fieldID + '][choices][' + id + '][value]' );
$choice.find( '.wpforms-image-upload input.source' ).val( '' ).attr( 'name', 'fields[' + fieldID + '][choices][' + id + '][image]' );
$choice.find( '.wpforms-icon-select input.source-icon' ).val( wpforms_builder.icon_choices.default_icon ).attr( 'name', 'fields[' + fieldID + '][choices][' + id + '][icon]' );
$choice.find( '.wpforms-icon-select input.source-icon-style' ).val( wpforms_builder.icon_choices.default_icon_style ).attr( 'name', 'fields[' + fieldID + '][choices][' + id + '][icon_style]' );
$choice.find( '.wpforms-icon-select .ic-fa-preview' ).removeClass().addClass( 'ic-fa-preview ic-fa-' + wpforms_builder.icon_choices.default_icon_style + ' ic-fa-' + wpforms_builder.icon_choices.default_icon );
$choice.find( '.wpforms-icon-select .ic-fa-preview + span' ).text( wpforms_builder.icon_choices.default_icon );
$choice.find( 'input.default' ).attr( 'name', 'fields[' + fieldID + '][choices][' + id + '][default]' ).prop( 'checked', false );
$choice.find( '.preview' ).empty();
$choice.find( '.wpforms-image-upload-add' ).show();
$choice.find( '.wpforms-money-input' ).trigger( 'focusout' );
if ( checked === true ) {
$parent.find( 'input.default' ).prop( 'checked', true );
}
id++;
$parent.parent().attr( 'data-next-id', id );
$builder.trigger( 'wpformsFieldChoiceAdd', [ fieldID ] );
app.fieldChoiceUpdate( type, fieldID );
},
/**
* Delete field choice.
*
* @since 1.0.0
*
* @param {Event} e Event.
* @param {Element} el Element.
*/
fieldChoiceDelete( e, el ) {
e.preventDefault();
const $this = $( el ),
$list = $this.parent().parent(),
total = $list.find( 'li' ).length,
fieldData = {
id: $list.data( 'field-id' ),
choiceId: $this.closest( 'li' ).data( 'key' ),
message: '<strong>' + wpforms_builder.delete_choice_confirm + '</strong>',
trigger: false,
};
$builder.trigger( 'wpformsBeforeFieldDeleteAlert', [ fieldData ] );
if ( total === 1 ) {
app.fieldChoiceDeleteAlert();
} else {
const deleteChoice = function() {
$this.parent().remove();
app.fieldChoiceUpdate( $list.data( 'field-type' ), $list.data( 'field-id' ) );
$builder.trigger( 'wpformsFieldChoiceDelete', [ $list.data( 'field-id' ) ] );
};
if ( ! fieldData.trigger ) {
deleteChoice();
return;
}
$.confirm( {
title: false,
content: fieldData.message,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
deleteChoice();
},
},
cancel: {
text: wpforms_builder.cancel,
},
},
} );
}
},
/**
* Field choice delete error alert.
*
* @since 1.6.7
*/
fieldChoiceDeleteAlert() {
$.alert( {
title: false,
content: wpforms_builder.error_choice,
icon: 'fa fa-info-circle',
type: 'blue',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Make field choices sortable.
* Currently used for select, radio, and checkboxes field types.
*
* @since 1.0.0
*
* @param {string} type Type.
* @param {string|undefined} selector Selector.
*/
fieldChoiceSortable( type, selector = undefined ) {
selector = typeof selector !== 'undefined' ? selector : '.wpforms-field-option-' + type + ' .wpforms-field-option-row-choices ul';
$( selector ).sortable( {
items: 'li',
axis: 'y',
delay: 100,
opacity: 0.6,
handle: '.move',
stop( e, ui ) {
const id = ui.item.parent().data( 'field-id' );
app.fieldChoiceUpdate( type, id );
$builder.trigger( 'wpformsFieldChoiceMove', ui );
},
update( e, ui ) { // eslint-disable-line no-unused-vars
},
} );
},
/**
* Generate Choice label. Used in field preview template.
*
* @since 1.6.2
*
* @param {Object} data Template data.
* @param {number} choiceID Choice ID.
*
* @return {string} Label.
*/
fieldChoiceLabel( data, choiceID ) { // eslint-disable-line complexity
const isPaymentChoice = [ 'payment-multiple', 'payment-checkbox' ].includes( data.settings.type ),
isIconImageChoice = data.settings.choices_icons || data.settings.choices_images,
isEmptyLabel = typeof data.settings.choices[ choiceID ].label === 'undefined' || data.settings.choices[ choiceID ].label.length === 0;
// Do not set a placeholder for an empty label in Icon and Image choices except for payment fields.
if ( isEmptyLabel && ! isPaymentChoice && isIconImageChoice ) {
return '';
}
const placeholder = isPaymentChoice ? wpforms_builder.payment_choice_empty_label_tpl : wpforms_builder.choice_empty_label_tpl;
let label = ! isEmptyLabel
? wpf.sanitizeHTML( data.settings.choices[ choiceID ].label, wpforms_builder.allowed_label_html_tags )
: placeholder.replace( '{number}', choiceID );
if ( data.settings.show_price_after_labels ) {
label += ' - ' + wpf.amountFormatCurrency( data.settings.choices[ choiceID ].value );
}
return label;
},
/**
* Update field choices in the preview area for the Fields panel.
*
* Currently used for select, radio, and checkboxes field types.
*
* @param {string} type Field type.
* @param {string|number} id Field ID.
* @param {number} count Number of choices to show, -1 if not set.
*
* @since 1.0.0
*/
fieldChoiceUpdate: ( type, id, count = -1 ) => { // eslint-disable-line complexity, max-lines-per-function
const isDynamicChoices = app.dropdownField.helpers.isDynamicChoices( id );
if ( app.replaceChoicesWithTemplate( type, id, isDynamicChoices ) ) {
return;
}
if ( count === -1 ) {
count = app.settings.choicesLimitLong;
}
// Dropdown payment choices are of select type.
if ( 'payment-select' === type ) {
type = 'select';
}
const $primary = $( '#wpforms-field-' + id + ' .primary-input' );
let newChoice = '';
if ( 'select' === type ) {
if ( ! isDynamicChoices ) {
newChoice = '<option value="{label}">{label}</option>';
$primary.find( 'option' ).not( '.placeholder' ).remove();
}
} else if ( 'radio' === type || 'checkbox' === type || 'gdpr-checkbox' === type ) {
type = 'gdpr-checkbox' === type ? 'checkbox' : type;
$primary.find( 'li' ).remove();
newChoice = '<li><input type="' + type + '" disabled>{label}</li>';
}
// Building an inner content for Primary field.
const $choicesList = $( '#wpforms-field-option-row-' + id + '-choices .choices-list' ),
$choicesToRender = $choicesList.find( 'li' ).slice( 0, count ),
hasDefaults = !! $choicesList.find( 'input.default:checked' ).length,
modernSelectChoices = [],
showPriceAfterLabels = $( '#wpforms-field-option-' + id + '-show_price_after_labels' ).prop( 'checked' ),
isModernSelect = app.dropdownField.helpers.isModernSelect( $primary );
$choicesToRender.get().forEach( function( item ) {// eslint-disable-line complexity
const $this = $( item ),
value = $this.find( 'input.value' ).val(),
choiceID = $this.data( 'key' );
let label = wpf.sanitizeHTML( $this.find( 'input.label' ).val().trim(), wpforms_builder.allowed_label_html_tags ),
$choice;
label = label !== '' ? label : wpforms_builder.choice_empty_label_tpl.replace( '{number}', choiceID );
label += ( showPriceAfterLabels && value ) ? ' - ' + wpf.amountFormatCurrency( value ) : '';
// Append a new choice.
if ( ! isModernSelect ) {
if ( ! isDynamicChoices ) {
$choice = $( newChoice.replace( /{label}/g, label ) );
$primary.append( $choice );
}
} else {
modernSelectChoices.push(
{
value: label,
label,
}
);
}
const selected = $this.find( 'input.default' ).is( ':checked' );
if ( true === selected ) {
switch ( type ) {
case 'select':
if ( ! isModernSelect ) {
app.setClassicSelectedChoice( $choice );
} else {
modernSelectChoices[ modernSelectChoices.length - 1 ].selected = true;
}
break;
case 'radio':
case 'checkbox':
$choice.find( 'input' ).prop( 'checked', 'true' );
break;
}
}
} );
if ( isModernSelect ) {
const placeholderClass = $primary.prop( 'multiple' ) ? 'input.choices__input' : '.choices__inner .choices__placeholder',
choicesjsInstance = app.dropdownField.helpers.getInstance( $primary );
if ( ! isDynamicChoices ) {
choicesjsInstance.removeActiveItems();
}
choicesjsInstance.setChoices( modernSelectChoices, 'value', 'label', true );
// Re-initialize modern dropdown to properly determine and update placeholder.
app.dropdownField.helpers.update( id, isDynamicChoices );
// Hide/show a placeholder for Modern select if it has or not default choices.
$primary
.closest( '.choices' )
.find( placeholderClass )
.toggleClass( 'wpforms-hidden', hasDefaults );
}
},
/**
* Generate Choice label. Used in field preview template.
*
* @since 1.8.6
*
* @param {string} type Field type.
* @param {number} id Field ID.
* @param {boolean} isDynamicChoices Whether the field has dynamic choices.
*
* @return {boolean} True if the template was used.
*/
replaceChoicesWithTemplate: ( type, id, isDynamicChoices ) => { // eslint-disable-line complexity
// Radio, Checkbox, and Payment Multiple/Checkbox use _ template.
if ( 'radio' !== type && 'checkbox' !== type && 'payment-multiple' !== type && 'payment-checkbox' !== type ) {
return false;
}
const order = wpf.getChoicesOrder( id ),
tmpl = wp.template( 'wpforms-field-preview-checkbox-radio-payment-multiple' );
const fieldSettings = wpf.getField( id ),
slicedChoices = {},
slicedOrder = order.slice( 0, app.settings.choicesLimit ),
data = {
settings: fieldSettings,
order: slicedOrder,
type: 'radio',
};
// If Icon Choices is on, get the valid color.
if ( fieldSettings.choices_icons ) {
// eslint-disable-next-line camelcase
data.settings.choices_icons_color = app.getValidColorPickerValue( $( '#wpforms-field-option-' + id + '-choices_icons_color' ) );
}
// Slice choices for preview.
slicedOrder.forEach( function( entry ) {
slicedChoices[ entry ] = fieldSettings.choices[ entry ];
} );
fieldSettings.choices = slicedChoices;
if ( 'checkbox' === type || 'payment-checkbox' === type ) {
data.type = 'checkbox';
}
if ( ! isDynamicChoices ) {
$( '#wpforms-field-' + id ).find( 'ul.primary-input' ).replaceWith( tmpl( data ) );
}
// Toggle limit choices alert message.
app.firstNChoicesAlert( id, order.length );
return true;
},
/**
* Set classic selected choice.
*
* @since 1.8.2.3
*
* @param {jQuery|undefined} $choice Choice option.
*/
setClassicSelectedChoice( $choice ) {
if ( $choice === undefined ) {
return;
}
$choice.prop( 'selected', 'true' );
},
/**
* Field choice bulk add toggling.
*
* @since 1.3.7
*
* @param {Object} el jQuery object.
*/
fieldChoiceBulkAddToggle( el ) {
const $this = $( el ),
$label = $this.closest( 'label' );
if ( $this.hasClass( 'bulk-add-showing' ) ) {
// "Import details" is showing, so hide/remove it.
const $selector = $label.next( '.bulk-add-display' );
$selector.slideUp( 400, function() {
$selector.remove();
} );
$this.find( 'span' ).text( wpforms_builder.bulk_add_show );
} else {
let importOptions = '<div class="bulk-add-display unfoldable-cont">';
importOptions += '<p class="heading wpforms-clear">' + wpforms_builder.bulk_add_heading + ' <a href="#" class="toggle-bulk-add-presets">' + wpforms_builder.bulk_add_presets_show + '</a></p>';
importOptions += '<ul>';
for ( const key in wpforms_preset_choices ) {
importOptions += '<li><a href="#" data-preset="' + key + '" class="bulk-add-preset-insert">' + wpforms_preset_choices[ key ].name + '</a></li>';
}
importOptions += '</ul>';
importOptions += '<textarea placeholder="' + wpforms_builder.bulk_add_placeholder + '"></textarea>';
importOptions += '<button class="bulk-add-insert wpforms-btn wpforms-btn-sm wpforms-btn-blue">' + wpforms_builder.bulk_add_button + '</button>';
importOptions += '</div>';
$label.after( importOptions );
$label.next( '.bulk-add-display' ).slideDown( 400, function() {
$( this ).find( 'textarea' ).trigger( 'focus' );
} );
$this.find( 'span' ).text( wpforms_builder.bulk_add_hide );
}
$this.toggleClass( 'bulk-add-showing' );
},
/**
* Field choice bulk insert the new choices.
*
* @since 1.3.7
*
* @param {Object} el DOM element.
*/
fieldChoiceBulkAddInsert( el ) {
const $this = $( el ),
$container = $this.closest( '.wpforms-field-option-row' ),
$textarea = $container.find( 'textarea' ),
$list = $container.find( '.choices-list' ),
$choice = $list.find( 'li:first-of-type' ).clone().wrap( '<div>' ).parent();
let choice = '';
const fieldID = $container.data( 'field-id' ),
type = $list.data( 'field-type' );
let nextID = Number( $list.attr( 'data-next-id' ) );
const newValues = $textarea.val().split( '\n' );
let newChoices = '';
$this.prop( 'disabled', true ).html( $this.html() + ' ' + s.spinner );
$choice.find( 'input.value,input.label' ).attr( 'value', '' );
$choice.find( 'input.default' ).attr( 'checked', false );
$choice.find( 'input.source-icon' ).attr( 'value', wpforms_builder.icon_choices.default_icon );
$choice.find( 'input.source-icon-style' ).attr( 'value', wpforms_builder.icon_choices.default_icon_style );
$choice.find( '.ic-fa-preview' ).removeClass().addClass( `ic-fa-preview ic-fa-${ wpforms_builder.icon_choices.default_icon_style } ic-fa-${ wpforms_builder.icon_choices.default_icon }` );
$choice.find( '.ic-fa-preview + span' ).text( wpforms_builder.icon_choices.default_icon );
choice = $choice.html();
for ( const key in newValues ) {
if ( ! newValues.hasOwnProperty( key ) ) {
continue;
}
const value = wpf.sanitizeHTML( newValues[ key ] ).trim().replace( /"/g, '&quot;' );
let newChoice = choice;
newChoice = newChoice.replace( /\[choices\]\[(\d+)\]/g, '[choices][' + nextID + ']' );
newChoice = newChoice.replace( /data-key="(\d+)"/g, 'data-key="' + nextID + '"' );
newChoice = newChoice.replace( /value="" class="label"/g, 'value="' + value + '" class="label"' );
// For some reason, IE has its own attribute order.
newChoice = newChoice.replace( /class="label" type="text" value=""/g, 'class="label" type="text" value="' + value + '"' );
newChoices += newChoice;
nextID++;
}
$list.attr( 'data-next-id', nextID ).append( newChoices );
app.fieldChoiceUpdate( type, fieldID, nextID );
$builder.trigger( 'wpformsFieldChoiceAdd' );
app.fieldChoiceBulkAddToggle( $container.find( '.toggle-bulk-add-display' ) );
},
/**
* Trigger $builder event.
*
* @since 1.9.1
*
* @param {string} event Event name.
*/
triggerBuilderEvent( event ) {
$builder.trigger( event );
},
/**
* Toggle fields tabs (Add Fields, Field Options).
*
* @since 1.0.0
*
* @param {number|string} id Field Id or `add-fields` or `field-options`.
*
* @return {false|void} False if event is prevented.
*/
fieldTabToggle( id ) {
const event = WPFormsUtils.triggerEvent( $builder, 'wpformsFieldTabToggle', [ id ] );
// Allow callbacks on `wpformsFieldTabToggle` to cancel tab toggle by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return false;
}
$( '.wpforms-tab a' ).removeClass( 'active' );
$( '.wpforms-field, .wpforms-title-desc' ).removeClass( 'active' );
if ( id === 'add-fields' ) {
elements.$addFieldsTab.addClass( 'active' );
$( '.wpforms-field-options' ).hide();
$( '.wpforms-add-fields' ).show();
} else {
$( '#field-options a' ).addClass( 'active' );
if ( id === 'field-options' ) {
const $field = $( '.wpforms-field' ).first();
$field.addClass( 'active' );
id = $field.data( 'field-id' );
} else {
$( '#wpforms-field-' + id ).addClass( 'active' );
}
$( '.wpforms-field-option' ).hide();
$( '#wpforms-field-option-' + id ).show();
$( '.wpforms-add-fields' ).hide();
$( '.wpforms-field-options' ).show();
$builder.trigger( 'wpformsFieldOptionTabToggle', [ id ] );
}
},
/**
* Watches fields being added and listens for a pagebreak field.
*
* If a pagebreak field is added, and it's the first one, then we
* automatically add the top and bottom pagebreak elements to the
* builder.
*
* @param {Object} event Current DOM event.
* @param {number} id Field ID.
* @param {string} type Field type.
*
* @since 1.2.1
*/
fieldPagebreakAdd( event, id, type ) {
/* eslint-disable camelcase */
if ( 'pagebreak' !== type ) {
return;
}
let options;
if ( ! s.pagebreakTop ) {
s.pagebreakTop = true;
options = {
position: 'top',
scroll: false,
defaults: {
position: 'top',
nav_align: 'left',
},
};
app.fieldAdd( 'pagebreak', options ).done( function( res ) {
s.pagebreakTop = res.data.field.id;
const $preview = $( '#wpforms-field-' + res.data.field.id ),
$options = $( '#wpforms-field-option-' + res.data.field.id );
$options.find( '.wpforms-field-option-group' ).addClass( 'wpforms-pagebreak-top' );
$preview.addClass( 'wpforms-field-stick wpforms-pagebreak-top' );
} );
} else if ( ! s.pagebreakBottom ) {
s.pagebreakBottom = true;
options = {
position: 'bottom',
scroll: false,
defaults: {
position: 'bottom',
},
};
app.fieldAdd( 'pagebreak', options ).done( function( res ) {
s.pagebreakBottom = res.data.field.id;
const $preview = $( '#wpforms-field-' + res.data.field.id ),
$options = $( '#wpforms-field-option-' + res.data.field.id );
$options.find( '.wpforms-field-option-group' ).addClass( 'wpforms-pagebreak-bottom' );
$preview.addClass( 'wpforms-field-stick wpforms-pagebreak-bottom' );
} );
}
},
/**
* Watches fields being deleted and listens for a pagebreak field.
*
* If a pagebreak field is added, and it's the first one, then we
* automatically add the top and bottom pagebreak elements to the
* builder.
*
* @param {Object} event Current DOM event.
* @param {number} id Field ID.
* @param {string} type Field type.
*
* @since 1.2.1
*/
fieldPagebreakDelete( event, id, type ) {
if ( 'pagebreak' !== type ) {
return;
}
const pagebreaksRemaining = $( '#wpforms-panel-fields .wpforms-field-pagebreak' ).not( '.wpforms-pagebreak-top, .wpforms-pagebreak-bottom' ).length;
if ( pagebreaksRemaining ) {
return;
}
// All pagebreaks, excluding top/bottom, are gone.
// So we need to remove the top and bottom pagebreak.
const $preview = $( '#wpforms-panel-fields .wpforms-preview-wrap' ),
$top = $preview.find( '.wpforms-pagebreak-top' ),
topID = $top.data( 'field-id' ),
$bottom = $preview.find( '.wpforms-pagebreak-bottom' ),
bottomID = $bottom.data( 'field-id' );
$top.remove();
$( '#wpforms-field-option-' + topID ).remove();
s.pagebreakTop = false;
$bottom.remove();
$( '#wpforms-field-option-' + bottomID ).remove();
s.pagebreakBottom = false;
},
/**
* Init Display Previous option for Pagebreak field.
*
* @since 1.5.8
*
* @param {jQuery} $field Page Break field jQuery object.
*/
fieldPageBreakInitDisplayPrevious( $field ) {
const id = $field.data( 'field-id' ),
$prevToggle = $( '#wpforms-field-option-row-' + id + '-prev_toggle' ),
$prev = $( '#wpforms-field-option-row-' + id + '-prev' ),
$prevBtn = $field.find( '.wpforms-pagebreak-prev' );
if ( $field.prevAll( '.wpforms-field-pagebreak.wpforms-pagebreak-normal' ).length > 0 ) {
$prevToggle.removeClass( 'hidden' );
$prev.removeClass( 'hidden' );
if ( $prevToggle.find( 'input' ).is( ':checked' ) ) {
$prevBtn.removeClass( 'wpforms-hidden' ).text( $prev.find( 'input' ).val() );
}
} else {
$prevToggle.addClass( 'hidden' );
$prev.addClass( 'hidden' );
$prevBtn.addClass( 'wpforms-hidden' );
}
},
/**
* Field Dynamic Choice toggle.
*
* @since 1.2.8
*
* @param {Element} el Element.
*/
fieldDynamicChoiceToggle( el ) { // eslint-disable-line complexity, max-lines-per-function
let optionHTML;
const $this = $( el ),
$thisOption = $this.parent(),
value = $this.val(),
id = $thisOption.data( 'field-id' );
const $choices = $( '#wpforms-field-option-row-' + id + '-choices' ),
$images = $( '#wpforms-field-option-' + id + '-choices_images' ),
$icons = $( '#wpforms-field-option-' + id + '-choices_icons' ),
$basicOptions = $( `#wpforms-field-option-basic-${ id }` );
// Hide image and icon choices if "dynamic choices" is not off.
app.fieldDynamicChoiceToggleImageChoices();
app.fieldDynamicChoiceToggleIconChoices();
// Fire an event when a field's dynamic choices option was changed.
$builder.trigger( 'wpformsFieldDynamicChoiceToggle', [ id ] );
// Loading
wpf.fieldOptionLoading( $thisOption );
// Remove previous dynamic post type or taxonomy source options.
$( '#wpforms-field-option-row-' + id + '-dynamic_post_type' ).remove();
$( '#wpforms-field-option-row-' + id + '-dynamic_taxonomy' ).remove();
/*
* Post type- or Taxonomy-based dynamic populating.
*/
if ( '' !== value ) {
// Hide choice images and icons options, not applicable.
$images.addClass( 'wpforms-hidden' );
$icons.addClass( 'wpforms-hidden' );
// Hide `Bulk Add` toggle.
$choices.find( '.toggle-bulk-add-display' ).addClass( 'wpforms-hidden' );
// Hide AI Choices button.
$basicOptions.find( '.wpforms-ai-choices-button' ).addClass( 'wpforms-hidden' );
// Hide tooltip.
$choices.find( '.wpforms-help-tooltip' ).addClass( 'wpforms-hidden' );
const data = {
type: value,
field_id: id, // eslint-disable-line camelcase
action: 'wpforms_builder_dynamic_choices',
nonce: wpforms_builder.nonce,
};
$.post( wpforms_builder.ajax_url, data, function( res ) {
if ( res.success ) {
// New option markup.
$thisOption.after( res.data.markup );
} else {
// eslint-disable-next-line no-console
console.log( res );
}
// Hide loading indicator.
wpf.fieldOptionLoading( $thisOption, true );
// Re-init tooltips for new field.
wpf.initTooltips();
// Trigger Dynamic source updates.
const $dynamicValue = $( '#wpforms-field-option-' + id + '-dynamic_' + value );
$dynamicValue.find( 'option' ).first().prop( 'selected', true );
$dynamicValue.trigger( 'change' );
} ).fail( function( xhr, textStatus, e ) { // eslint-disable-line no-unused-vars
// eslint-disable-next-line no-console
console.log( xhr.responseText );
} );
return; // Nothing more for dynamic populating.
}
/*
* "Off" - no dynamic populating.
*/
let type = $( '#wpforms-field-option-' + id ).find( '.wpforms-field-option-hidden-type' ).val();
// Show choice images and icon options.
$images.removeClass( 'wpforms-hidden' );
$icons.removeClass( 'wpforms-hidden' );
// Show `Bulk Add` toggle.
$choices.find( '.toggle-bulk-add-display' ).removeClass( 'wpforms-hidden' );
// Show AI Choices button.
$basicOptions.find( '.wpforms-ai-choices-button' ).removeClass( 'wpforms-hidden' );
// Show tooltip.
$choices.find( '.wpforms-help-tooltip' ).removeClass( 'wpforms-hidden' );
const $wpformsField = $( '#wpforms-field-' + id );
$wpformsField.find( '.wpforms-alert' ).remove();
if ( [ 'checkbox', 'radio', 'payment-multiple', 'payment-checkbox' ].indexOf( type ) > -1 ) {
app.fieldChoiceUpdate( type, id );
// Toggle elements and hide loading indicator.
$choices.find( 'ul' ).removeClass( 'wpforms-hidden' );
$choices.find( '.wpforms-alert' ).addClass( 'wpforms-hidden' );
wpf.fieldOptionLoading( $thisOption, true );
return; // Nothing more for those types.
}
// Get original field choices.
const $field = $wpformsField;
const choices = [],
$primary = $field.find( '.primary-input' );
let key;
$( '#wpforms-field-option-row-' + id + '-choices li' ).each( function() {
const $this = $( this );
choices.push( {
label: wpf.sanitizeHTML( $this.find( '.label' ).val() ),
selected: $this.find( '.default' ).is( ':checked' ),
} );
} );
// Restore field to display original field choices.
if ( $field.hasClass( 'wpforms-field-select' ) ) {
const isModernSelect = app.dropdownField.helpers.isModernSelect( $primary );
let selected = false;
// Remove previous items.
$primary.find( 'option' ).not( '.placeholder' ).remove();
// Update Modern Dropdown.
if ( isModernSelect && choices.length ) {
app.dropdownField.helpers.update( id, false );
} else {
// Update Classic select field.
for ( key in choices ) {
selected = choices[ key ].selected;
optionHTML = '<option';
optionHTML += selected ? ' selected>' : '>';
optionHTML += choices[ key ].label + '</option>';
$primary.append( optionHTML );
}
}
} else {
type = 'radio';
if ( $field.hasClass( 'wpforms-field-checkbox' ) ) {
type = 'checkbox';
}
// Remove previous items.
$primary.empty();
// Add new items to radio or checkbox field.
for ( key in choices ) {
optionHTML = '<li><input type="' + type + '" disabled';
optionHTML += choices[ key ].selected ? ' selected>' : '>';
optionHTML += choices[ key ].label + '</li>';
$primary.append( optionHTML );
}
}
// Toggle elements and hide loading indicator.
$choices.find( 'ul' ).removeClass( 'wpforms-hidden' );
$choices.find( '.wpforms-alert' ).addClass( 'wpforms-hidden' );
$primary.removeClass( 'wpforms-hidden' );
wpf.fieldOptionLoading( $thisOption, true );
},
/**
* Field Dynamic Choice Source toggle.
*
* @since 1.2.8
*
* @param {Element} el Element.
*/
fieldDynamicChoiceSource( el ) { // eslint-disable-line max-lines-per-function
/* eslint-disable camelcase */
const $this = $( el ),
$thisOption = $this.parent(),
value = $this.val(),
id = $thisOption.data( 'field-id' ),
form_id = $( '#wpforms-builder-form' ).data( 'id' ),
$choices = $( '#wpforms-field-option-row-' + id + '-choices' ),
$field = $( '#wpforms-field-' + id ),
type = $( '#wpforms-field-option-' + id + '-dynamic_choices option:selected' ).val();
let limit = 20;
// Loading.
wpf.fieldOptionLoading( $thisOption );
const data = {
type,
source: value,
field_id: id,
form_id,
action: 'wpforms_builder_dynamic_source',
nonce: wpforms_builder.nonce,
};
$.post( wpforms_builder.ajax_url, data, function( res ) {
if ( ! res.success ) {
// eslint-disable-next-line no-console
console.log( res );
// Toggle elements and hide loading indicator.
wpf.fieldOptionLoading( $thisOption, true );
return;
}
// Update info box and remove old choices.
$choices.find( '.dynamic-name' ).text( res.data.source_name );
$choices.find( '.dynamic-type' ).text( res.data.type_name );
$choices.find( 'ul' ).addClass( 'wpforms-hidden' );
$choices.find( '.wpforms-alert' ).removeClass( 'wpforms-hidden' );
// Update items.
app.fieldDynamicChoiceSourceItems( $field, res.data.items );
if ( $field.hasClass( 'wpforms-field-select' ) ) {
limit = 200;
}
// Remove any previous empty message.
$field.find( '.wpforms-notice-dynamic-empty' ).remove();
// If the source has more items than the field type can
// ideally handle alert the user.
if ( Number( res.data.total ) > limit ) {
let msg = wpforms_builder.dynamic_choices.limit_message;
msg = msg.replace( '{source}', res.data.source_name );
msg = msg.replace( '{type}', res.data.type_name );
msg = msg.replace( '{limit}', limit );
msg = msg.replace( '{total}', res.data.total );
$.alert( {
title: wpforms_builder.heads_up,
content: msg,
icon: 'fa fa-info-circle',
type: 'blue',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
}
// Toggle limit choices alert message.
app.firstNChoicesAlert( id, res.data.total );
// Toggle empty choices notice message.
if ( Number( res.data.total ) === 0 ) {
app.emptyChoicesNotice( id, res.data.source_name, res.data.type );
}
// Toggle elements and hide loading indicator.
wpf.fieldOptionLoading( $thisOption, true );
} ).fail( function( xhr, textStatus, e ) { // eslint-disable-line no-unused-vars
// eslint-disable-next-line no-console
console.log( xhr.responseText );
} );
},
/**
* Update a Field Items when `Dynamic Choice` Source is toggled.
*
* @since 1.6.1
*
* @param {Object} $field jQuery selector for current field.
* @param {Object} items Items collection.
*/
fieldDynamicChoiceSourceItems( $field, items ) {
const $primary = $field.find( '.primary-input' );
let key = 0;
if ( $field.hasClass( 'wpforms-field-select' ) ) {
const isModernSelect = app.dropdownField.helpers.isModernSelect( $primary );
if ( isModernSelect ) {
app.fieldDynamicChoiceSourceForModernSelect( $primary, items );
} else {
app.fieldDynamicChoiceSourceForClassicSelect( $primary, items );
}
} else {
let type = 'radio';
if ( $field.hasClass( 'wpforms-field-checkbox' ) ) {
type = 'checkbox';
}
// Remove previous items.
$primary.empty();
// Add new items to radio or checkbox field.
for ( key in items ) {
$primary.append( '<li><input type="' + type + '" disabled> ' + wpf.sanitizeHTML( items[ key ] ) + '</li>' );
}
}
},
/**
* Update options for Modern style select when `Dynamic Choice` Source is toggled.
*
* @since 1.6.1
*
* @param {Object} $jquerySelector jQuery selector for primary input.
* @param {Object} items Items collection.
*/
fieldDynamicChoiceSourceForModernSelect( $jquerySelector, items ) {
const instance = app.dropdownField.helpers.getInstance( $jquerySelector ),
fieldId = $jquerySelector.closest( '.wpforms-field' ).data().fieldId;
// Destroy the instance of Choices.js.
instance.destroy();
// Update a placeholder.
app.dropdownField.helpers.updatePlaceholderChoice( instance, fieldId );
// Update options.
app.fieldDynamicChoiceSourceForClassicSelect( $jquerySelector, items );
// Choices.js init.
app.dropdownField.events.choicesInit( $jquerySelector );
},
/**
* Update options for Classic style select when `Dynamic Choice` Source is toggled.
*
* @since 1.6.1
*
* @param {Object} $jquerySelector jQuery selector for primary input.
* @param {Object} items Items collection.
*/
fieldDynamicChoiceSourceForClassicSelect( $jquerySelector, items ) {
let index = 0;
const itemsSize = items.length;
// Clear.
$jquerySelector.find( 'option' ).not( '.placeholder' ).remove();
// Add options (items) to a single <select> field.
for ( ; index < itemsSize; index++ ) {
const item = wpf.sanitizeHTML( items[ index ] );
$jquerySelector.append( '<option value="' + item + '">' + item + '</option>' );
}
$jquerySelector.toggleClass( 'wpforms-hidden', ! itemsSize );
},
/**
* Image choice toggle, hide image choices, image choices style, choices if Dynamic choices is not OFF.
*
* @since 1.5.8
*/
fieldDynamicChoiceToggleImageChoices() {
$( '#wpforms-builder .wpforms-field-options .wpforms-field-option' ).each( function( key, value ) {
const $option = $( value ),
dynamicChoices = $option.find( '.wpforms-field-option-row-dynamic_choices select' ).val(),
isDynamicChoices = typeof dynamicChoices !== 'undefined' && '' !== dynamicChoices,
isImageChoices = $option.find( '.wpforms-field-option-row-choices_images input' ).is( ':checked' );
$option
.find( '.wpforms-field-option-row-choices_images' )
.toggleClass( 'wpforms-hidden', isDynamicChoices );
if ( ! isImageChoices || isDynamicChoices ) {
$option
.find( '.wpforms-field-option-row-choices_images_style' )
.addClass( 'wpforms-hidden' );
}
} );
},
/**
* Hide icon choice toggle, icon choices, color, size and style options if Dynamic choices is not OFF.
*
* @since 1.7.9
*/
fieldDynamicChoiceToggleIconChoices() {
$( '#wpforms-builder .wpforms-field-options .wpforms-field-option' ).each( function( key, value ) {
const $option = $( value ),
dynamicChoices = $option.find( '.wpforms-field-option-row-dynamic_choices select' ).val(),
isDynamicChoices = typeof dynamicChoices !== 'undefined' && '' !== dynamicChoices,
isIconChoices = $option.find( '.wpforms-field-option-row-choices_icons input' ).is( ':checked' );
$option
.find( '.wpforms-field-option-row-choices_icons' )
.toggleClass( 'wpforms-hidden', isDynamicChoices );
if ( ! isIconChoices || isDynamicChoices ) {
$option
.find( '.wpforms-field-option-row-choices_icons_color' )
.addClass( 'wpforms-hidden' );
$option
.find( '.wpforms-field-option-row-choices_icons_size' )
.addClass( 'wpforms-hidden' );
$option
.find( '.wpforms-field-option-row-choices_icons_style' )
.addClass( 'wpforms-hidden' );
}
} );
},
/**
* Show choices limit alert message.
*
* @since 1.6.9
*
* @param {number} fieldId Field ID.
* @param {number} total Total number of choices.
*/
firstNChoicesAlert: ( fieldId, total ) => {
const $field = $( '#wpforms-field-' + fieldId );
// Don't show a message for select fields.
if ( $field.hasClass( 'wpforms-field-select' ) ) {
return;
}
const tmpl = wp.template( 'wpforms-choices-limit-message' ),
data = {
total,
},
limit = app.settings.choicesLimit;
$field.find( '.wpforms-alert-dynamic' ).remove();
if ( total > limit ) {
$field.find( '.primary-input' ).after( tmpl( data ) );
}
},
/**
* Show an empty choice notice message.
*
* @since 1.8.2
*
* @param {number} fieldId Field ID.
* @param {string} source Source name.
* @param {string} type Source type.
*/
emptyChoicesNotice( fieldId, source, type ) {
const field = $( '#wpforms-field-' + fieldId );
const emptyDynamicChoicesMessage = wpforms_builder.dynamic_choices.empty_message
.replace( '{source}', source )
.replace( '{type}', wpforms_builder.dynamic_choices.entities[ type ] );
const template = wp.template( 'wpforms-empty-choice-message' ),
data = {
message: emptyDynamicChoicesMessage,
};
field.find( '.label-title' ).after( template( data ) );
},
/**
* Field layout selector toggling.
*
* @since 1.3.7
*
* @param {Element} el Layout selector toggle link element.
*/
fieldLayoutSelectorToggle( el ) {
const $this = $( el ),
$layoutSelectorDisplay = $this.closest( 'label' ).next( '.layout-selector-display' );
if ( $this.hasClass( 'layout-selector-showing' ) ) {
// Selector is showing, so hide it
$layoutSelectorDisplay.stop().slideUp( 400 );
$this.find( 'span' ).text( wpforms_builder.layout_selector_show );
} else {
$layoutSelectorDisplay.stop().slideDown();
$this.find( 'span' ).text( wpforms_builder.layout_selector_hide );
}
$this.toggleClass( 'layout-selector-showing' );
},
/**
* Init legacy field layout selector.
*
* @since 1.7.7
*
* @param {number} fieldId Field id.
*/
fieldLayoutSelectorInit( fieldId ) { // eslint-disable-line max-lines-per-function
const $layoutSelector = $( `#wpforms-field-option-row-${ fieldId }-css > .layout-selector-display` );
// Bail if already initialized.
if ( $layoutSelector.length ) {
return;
}
const layouts = {
'layout-1': [
{
class: 'one-half',
data: 'wpforms-one-half wpforms-first',
},
{
class: 'one-half',
data: 'wpforms-one-half',
},
],
'layout-2': [
{
class: 'one-third',
data: 'wpforms-one-third wpforms-first',
},
{
class: 'one-third',
data: 'wpforms-one-third',
},
{
class: 'one-third',
data: 'wpforms-one-third',
},
],
'layout-3': [
{
class: 'one-fourth',
data: 'wpforms-one-fourth wpforms-first',
},
{
class: 'one-fourth',
data: 'wpforms-one-fourth',
},
{
class: 'one-fourth',
data: 'wpforms-one-fourth',
},
{
class: 'one-fourth',
data: 'wpforms-one-fourth',
},
],
'layout-4': [
{
class: 'one-third',
data: 'wpforms-one-third wpforms-first',
},
{
class: 'two-third',
data: 'wpforms-two-thirds',
},
],
'layout-5': [
{
class: 'two-third',
data: 'wpforms-two-thirds wpforms-first',
},
{
class: 'one-third',
data: 'wpforms-one-third',
},
],
'layout-6': [
{
class: 'one-fourth',
data: 'wpforms-one-fourth wpforms-first',
},
{
class: 'one-fourth',
data: 'wpforms-one-fourth',
},
{
class: 'two-fourth',
data: 'wpforms-two-fourths',
},
],
'layout-7': [
{
class: 'two-fourth',
data: 'wpforms-two-fourths wpforms-first',
},
{
class: 'one-fourth',
data: 'wpforms-one-fourth',
},
{
class: 'one-fourth',
data: 'wpforms-one-fourth',
},
],
'layout-8': [
{
class: 'one-fourth',
data: 'wpforms-one-fourth wpforms-first',
},
{
class: 'two-fourth',
data: 'wpforms-two-fourths',
},
{
class: 'one-fourth',
data: 'wpforms-one-fourth',
},
],
};
// Create selector options.
let layout,
layoutOptions = `<div class="layout-selector-display unfoldable-cont">
<p class="heading">${ wpforms_builder.layout_selector_layout }</p>
<div class="layouts">`;
for ( const key in layouts ) {
layout = layouts[ key ];
layoutOptions += '<div class="layout-selector-display-layout">';
for ( const i in layout ) {
layoutOptions += `<span class="${ layout[ i ].class }" data-classes="${ layout[ i ].data }"></span>`;
}
layoutOptions += '</div>';
}
layoutOptions += '</div></div>';
$( `#wpforms-field-option-row-${ fieldId }-css > label` ).after( layoutOptions );
},
/**
* Legacy field layout selector, selecting a layout.
*
* @since 1.3.7
*
* @param {Element} el Layout selector toggle link.
*/
fieldLayoutSelectorLayout( el ) {
const $this = $( el );
$this.parent().find( '.layout-selector-display-layout' ).not( $this ).remove();
$this.parent().find( '.heading' ).text( wpforms_builder.layout_selector_column );
$this.toggleClass( 'layout-selector-display-layout layout-selector-display-columns' );
},
/**
* Field layout selector, insert into class field.
*
* @since 1.3.7
*
* @param {Element} el Element.
*/
fieldLayoutSelectorInsert( el ) {
const $this = $( el ),
$selector = $this.closest( '.layout-selector-display' ),
$parent = $selector.parent(),
$id = $parent.data( 'field-id' ),
$label = $parent.find( 'label' ),
$input = $parent.find( 'input[type=text]' ),
layoutClassList = [
'wpforms-one-half',
'wpforms-first',
'wpforms-one-third',
'wpforms-one-fourth',
'wpforms-two-thirds',
'wpforms-two-fourths',
];
let classes = $this.data( 'classes' ),
inputVal = $input.val();
if ( inputVal ) {
// Remove existing wpforms layout classes before adding new.
layoutClassList.forEach( ( cls ) => {
inputVal = inputVal.replace( new RegExp( '\\b' + cls + '\\b', 'gi' ), '' );
} );
// Remove any extra spaces.
inputVal = inputVal.replace( /\s\s+/g, ' ' ).trim();
// Add new layout classes.
classes += ' ' + inputVal;
}
$input.val( classes );
// Remove the list, all done!
$selector.slideUp( 400, function() {
$selector.remove();
app.fieldLayoutSelectorInit( $id );
} );
$label.find( '.toggle-layout-selector-display' ).removeClass( 'layout-selector-showing' );
$label.find( '.toggle-layout-selector-display span' ).text( wpforms_builder.layout_selector_show );
},
/**
* Toggle Order Summary Confirmation settings option.
*
* @since 1.8.7
*
* @param {Event} event Event.
* @param {string} id Field ID.
* @param {string} type Field type.
*/
toggleOrderSummaryConfirmation( event, id, type ) {
if ( type !== 'payment-total' ) {
return;
}
$( '.wpforms-confirmation' ).each( function() {
$( this ).find( '.wpforms-panel-field-confirmations-message_order_summary' ).closest( '.wpforms-panel-field' ).toggle( $( '#wpforms-panel-fields .wpforms-field-payment-total' ).length !== 0 );
} );
},
//--------------------------------------------------------------------//
// Settings Panel
//--------------------------------------------------------------------//
/**
* Element bindings for Settings panel.
*
* @since 1.0.0
*/
bindUIActionsSettings() { // eslint-disable-line max-lines-per-function
// Clicking form title/desc opens Settings panel.
$builder.on( 'click', '#wpforms-panel-fields .wpforms-title-desc, #wpforms-panel-fields .wpforms-field-submit-button, .wpforms-center-form-name', function( e ) {
e.preventDefault();
app.panelSwitch( 'settings' );
if ( $( this ).hasClass( 'wpforms-center-form-name' ) || $( this ).hasClass( 'wpforms-title-desc' ) ) {
setTimeout( function() {
$( '#wpforms-panel-field-settings-form_title' ).trigger( 'focus' );
}, 300 );
}
} );
// Clicking form previous page break button.
$builder.on( 'click', '.wpforms-field-pagebreak-last button', function( e ) {
e.preventDefault();
app.panelSwitch( 'settings' );
$( '#wpforms-panel-field-settings-pagebreak_prev' ).trigger( 'focus' );
} );
// Trigger Custom Captcha adding when clicking on its block in the Also Available section.
$builder.on( 'click', '.wpforms-panel-content-also-available-item-add-captcha', function( e ) {
e.preventDefault();
const customCaptcha = $builder.find( '#wpforms-add-fields-captcha' );
// Show educational modal if Custom Captcha is not installed or activated.
if ( customCaptcha.data( 'action' ) ) {
customCaptcha.trigger( 'click' );
return;
}
app.fieldAdd( 'captcha', {} ).done( function( res ) {
app.panelSwitch( 'fields' );
$( `#wpforms-field-${ res.data.field.id }` ).trigger( 'click' );
} );
} );
// Clicking form last page break button.
$builder.on( 'input', '#wpforms-panel-field-settings-pagebreak_prev', function() {
$( '.wpforms-field-pagebreak-last button' ).text( $( this ).val() );
} );
// Real-time updates for editing the form title.
$builder.on( 'input', '#wpforms-panel-field-settings-form_title, #wpforms-setup-name', function() {
const title = $( this ).val().toString().trim();
$( '.wpforms-preview .wpforms-form-name' ).text( title );
$( '.wpforms-center-form-name.wpforms-form-name' ).text( title );
app.trimFormTitle();
} );
// Real-time updates for editing the form description.
$builder.on( 'input', '#wpforms-panel-field-settings-form_desc', function() {
$( '.wpforms-form-desc' ).text( $( this ).val() );
} );
// Real-time updates for editing the form submit button.
$builder.on( 'input', '#wpforms-panel-field-settings-submit_text', function() {
const submitText = $( this ).val() || wpforms_builder.submit_text;
$( '.wpforms-field-submit input[type=submit]' ).val( submitText );
} );
// Toggle form reCAPTCHA setting.
$builder.on( 'change', '#wpforms-panel-field-settings-recaptcha', function() {
app.captchaToggle();
} );
// Toggle form confirmation setting fields.
$builder.on( 'change', '.wpforms-panel-field-confirmations-type', function() {
app.confirmationFieldsToggle( $( this ) );
} );
$builder.on( 'change', '.wpforms-panel-field-confirmations-message_entry_preview', app.confirmationEntryPreviewToggle );
// Toggle form notification setting fields.
$builder.on( 'change', '#wpforms-panel-field-settings-notification_enable', app.notificationToggle );
// Add new settings block.
$builder.on( 'click', '.wpforms-builder-settings-block-add', function( e ) {
e.preventDefault();
if ( ! wpforms_builder.pro ) {
return;
}
app.settingsBlockAdd( $( this ) );
} );
// Edit settings block name.
$builder.on( 'click', '.wpforms-builder-settings-block-edit', function( e ) {
e.preventDefault();
const $el = $( this );
if ( $el.parents( '.wpforms-builder-settings-block-header' ).find( '.wpforms-builder-settings-block-name' ).hasClass( 'editing' ) ) {
app.settingsBlockNameEditingHide( $el );
} else {
app.settingsBlockNameEditingShow( $el );
}
} );
// Update settings block name and close editing interface.
$builder.on( 'blur', '.wpforms-builder-settings-block-name-edit input', function( e ) {
// Do not fire if for onBlur user clicked on the edit button - it has its own event processing.
if ( ! $( e.relatedTarget ).hasClass( 'wpforms-builder-settings-block-edit' ) ) {
app.settingsBlockNameEditingHide( $( this ) );
}
} );
// Close settings block editing interface with pressed Enter.
$builder.on( 'keypress', '.wpforms-builder-settings-block-name-edit input', function( e ) {
// On Enter - hide editing interface.
if ( e.keyCode === 13 ) {
app.settingsBlockNameEditingHide( $( this ) );
// We need this preventDefault() to stop jumping to form name editing input.
e.preventDefault();
}
} );
// Clone settings block.
$builder.on( 'click', '.wpforms-builder-settings-block-clone', function( e ) {
e.preventDefault();
app.settingsBlockPanelClone( $( this ) );
} );
// Toggle settings block - slide up or down.
$builder.on( 'click', '.wpforms-builder-settings-block-toggle', function( e ) {
e.preventDefault();
app.settingsBlockPanelToggle( $( this ) );
} );
// Remove settings block.
$builder.on( 'click', '.wpforms-builder-settings-block-delete', function( e ) {
e.preventDefault();
app.settingsBlockDelete( $( this ) );
} );
$builder.on( 'wpformsSettingsBlockAdded wpformsSettingsBlockCloned', function( e, $element ) {
if ( ! $element.hasClass( 'wpforms-notification' ) ) {
return;
}
app.notificationsUpdateElementsVisibility();
app.notificationUpdateStatus( $element );
} );
$builder.on( 'wpformsSettingsBlockDeleted', function( e, type ) {
if ( type !== 'notification' ) {
return;
}
app.notificationsUpdateElementsVisibility();
} );
$builder.on( 'change', '.js-wpforms-enabled-notification input', function() {
app.notificationUpdateStatus( $( this ).closest( '.wpforms-notification' ) );
} );
// Toggle Open Confirmations in New Tab options on AJAX form submit setting change.
$builder.on( 'change', '#wpforms-panel-field-settings-ajax_submit', function() {
app.hideOpenConfirmationsInNewTabOptions( ! $( this ).is( ':checked' ) );
} );
},
/**
* Toggle Open Confirmations in New Tab options.
*
* @since 1.9.2
*
* @param {boolean} hide Whether to hide the options.
*/
hideOpenConfirmationsInNewTabOptions( hide ) {
const $confirmationSection = $builder.find( '.wpforms-panel-content-section-confirmation' ),
$blocks = $confirmationSection.find( '.wpforms-builder-settings-block' ),
$options = $blocks.find( '.wpforms-panel-field-confirmations-redirect_new_tab' );
if ( ! $options.length ) {
return;
}
$options.each( function() {
$( this ).closest( '.wpforms-panel-field' ).toggle( ! hide );
} );
},
/**
* Toggle displaying the CAPTCHA.
*
* @since 1.6.4
*/
captchaToggle() {
const $preview = $builder.find( '.wpforms-field-recaptcha' ),
$setting = $( '#wpforms-panel-field-settings-recaptcha' );
let provider = $setting.data( 'provider' );
provider = provider || 'recaptcha';
if ( ! $preview.length ) {
return;
}
if ( $setting.is( ':checked' ) ) {
$preview
.show()
.toggleClass( 'is-recaptcha', 'recaptcha' === provider );
} else {
$preview.hide();
}
},
/**
* Init confirmations' type.
*
* @since 1.9.2
*/
initConfirmationsType() {
// Toggle the setting fields in each confirmation block.
$( '.wpforms-panel-field-confirmations-type' ).each( function() {
app.confirmationFieldsToggle( $( this ) );
} );
},
/**
* Init TinyMCE in given elements.
*
* @since 1.9.2
*
* @param {jQuery} $elements Elements.
*/
initElementsTinyMCE( $elements ) {
if ( typeof tinymce === 'undefined' || typeof wp.editor === 'undefined' ) {
return;
}
$elements.each( function() {
const id = $( this ).attr( 'id' );
// Destroy previously initialized editor.
wp.editor.remove( id );
// Initialize new editor.
wp.editor.initialize( id, s.tinymceDefaults );
} );
},
/**
* Set up the Confirmation blocks.
*
* @since 1.4.8
*/
confirmationsSetup() {
// Toggle the setting fields in each confirmation block.
app.initConfirmationsType();
// Init TinyMCE in each confirmation block.
app.initElementsTinyMCE( $( '.wpforms-panel-field-confirmations-message' ) );
// Validate Confirmation Redirect URL.
$builder.on( 'focusout', '.wpforms-panel-field-confirmations-redirect', function( event ) { // eslint-disable-line no-unused-vars
const $field = $( this );
const url = $field.val().trim();
$field.val( url );
// The value is either a valid URL or empty, we're done.
if ( wpf.isURL( url ) || url === '' ) {
return;
}
// Show the error modal and focus the field.
app.confirmationRedirectValidationError( function() {
$field.trigger( 'focus' );
} );
} );
// Make sure Confirmation Redirect URL is not empty, verify before leaving the panel or saving.
$builder.on( 'wpformsBeforeSave wpformsPanelSectionSwitch wpformsPanelSwitch', function( event ) {
const $confirmations = $( '.wpforms-confirmation' );
$confirmations.each( function( _index, confirmation ) {
const $confirmation = $( confirmation );
const $urlField = $confirmation.find( '.wpforms-panel-field-confirmations-redirect' );
// We're starting on a panel other than Settings > Confirmations, bail.
if ( $urlField.is( ':hidden' ) ) {
return;
}
const $typeField = $confirmation.find( '.wpforms-panel-field-confirmations-type' );
// The Confirmation type is not redirect, bail.
// If the URL value is non-empty, `focusout` validation takes over.
if ( $typeField.val() !== 'redirect' || $urlField.val().trim().length > 0 ) {
return;
}
app.confirmationRedirectValidationError( function() {
$urlField.trigger( 'focus' );
} );
event.stopImmediatePropagation();
event.preventDefault();
return false;
} );
} );
},
/**
* Display confirmation popup for empty or invalid Confirmation Redirect URL.
*
* @since 1.7.6
*
* @param {Function} onDestroyCallback Callback to execute when popup is closed and removed from DOM.
*/
confirmationRedirectValidationError( onDestroyCallback ) {
$.confirm( {
title: wpforms_builder.heads_up,
content: wpforms_builder.redirect_url_field_error,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
onDestroy: onDestroyCallback,
} );
},
/**
* Toggle the different form Confirmation setting fields.
*
* @since 1.4.8
*
* @param {jQuery} $el Element.
*/
confirmationFieldsToggle( $el ) {
if ( ! $el.length ) {
return;
}
const type = $el.val(),
$block = $el.closest( '.wpforms-builder-settings-block-content' );
$block.find( '.wpforms-panel-field' )
.not( $el.parent() )
.not( '.wpforms-conditionals-enable-toggle' )
.hide();
$block.find( '.wpforms-panel-field-confirmations-' + type ).closest( '.wpforms-panel-field' ).show();
if ( type === 'message' ) {
$block.find( '.wpforms-panel-field-confirmations-message_scroll' ).closest( '.wpforms-panel-field' ).show();
$block.find( '.wpforms-panel-field-confirmations-message_entry_preview' ).trigger( 'change' ).closest( '.wpforms-panel-field' ).show();
$block.find( '.wpforms-panel-field-confirmations-message_order_summary' ).closest( '.wpforms-panel-field' ).toggle( $( '#wpforms-panel-fields .wpforms-field-payment-total' ).length !== 0 );
}
if ( $( '#wpforms-panel-field-settings-ajax_submit' ).is( ':checked' ) ) {
$block.find( '.wpforms-panel-field-confirmations-redirect_new_tab' ).closest( '.wpforms-panel-field' ).toggle( [ 'redirect', 'page' ].includes( type ) );
}
},
/**
* Show/hide an entry preview message.
*
* @since 1.6.9
*/
confirmationEntryPreviewToggle() {
const $this = $( this ),
$styleField = $this.closest( '.wpforms-builder-settings-block-content' ).find( '.wpforms-panel-field-confirmations-message_entry_preview_style' ).parent();
$this.is( ':checked' ) ? $styleField.show() : $styleField.hide();
},
/**
* Toggle the displaying notification settings depending on if the
* notifications are enabled.
*
* @since 1.1.9
*/
notificationToggle() {
const $notification = $( '#wpforms-panel-field-settings-notification_enable' ),
$settingsBlock = $notification.closest( '.wpforms-panel-content-section' ).find( '.wpforms-builder-settings-block' ),
$enabled = $notification.is( ':checked' );
// Toggle Add new notification button.
$( '.wpforms-notifications-add' ).toggleClass( 'wpforms-hidden', ! $enabled );
$builder.trigger( 'wpformsNotificationsToggle', [ $enabled ] );
$enabled ? $settingsBlock.show() : $settingsBlock.hide();
},
/**
* Notifications by status alerts.
*
* @since 1.6.6
*/
notificationsByStatusAlerts() {
$builder.on( 'change', '.wpforms-panel-content-section-notifications .wpforms-notification-by-status-alert', function( e ) { // eslint-disable-line no-unused-vars
const $input = $( this );
if ( ! $input.prop( 'checked' ) ) {
return;
}
const $enabled = $( '.wpforms-radio-group-' + $input.attr( 'data-radio-group' ) + ':checked:not(#' + $input.attr( 'id' ) + ')' );
let alertText;
if ( $enabled.length === 0 ) {
alertText = wpforms_builder.notification_by_status_enable_alert;
alertText = alertText.replace( /%1\$s/g, $input.data( 'provider-title' ) );
} else {
alertText = wpforms_builder.notification_by_status_switch_alert;
alertText = alertText.replace( /%2\$s/g, $enabled.data( 'provider-title' ) );
alertText = alertText.replace( /%1\$s/g, $input.data( 'provider-title' ) );
}
$.confirm( {
title: wpforms_builder.heads_up,
content: alertText,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
},
},
} );
} );
},
/**
* Add new settings block.
*
* @since 1.4.8
* @since 1.6.1 Added processing for Field Map table.
* @since 1.6.1.2 Registered `wpformsSettingsBlockAdded` trigger.
*
* @param {jQuery} $el Settings Block jQuery object.
*/
settingsBlockAdd( $el ) { // eslint-disable-line max-lines-per-function
const nextID = Number( $el.attr( 'data-next-id' ) ),
panelID = $el.closest( '.wpforms-panel-content-section' ).data( 'panel' ),
blockType = $el.data( 'block-type' ),
namePrompt = wpforms_builder[ blockType + '_prompt' ],
nameField = '<input autofocus="" type="text" id="settings-block-name" placeholder="' + wpforms_builder[ blockType + '_ph' ] + '">',
nameError = '<p class="error">' + wpforms_builder[ blockType + '_error' ] + '</p>',
modalContent = namePrompt + nameField + nameError;
const modal = $.confirm( {
container: $builder,
title: false,
content: modalContent,
icon: 'fa fa-info-circle',
type: 'blue',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() { // eslint-disable-line complexity, max-lines-per-function
const settingsBlockName = this.$content.find( 'input#settings-block-name' ).val().toString().trim(),
error = this.$content.find( '.error' );
if ( settingsBlockName === '' ) {
error.show();
return false;
}
const $firstSettingsBlock = $el.closest( '.wpforms-panel-content-section' ).find( '.wpforms-builder-settings-block' ).first();
// Restore tooltips before cloning.
wpf.restoreTooltips( $firstSettingsBlock );
const $newSettingsBlock = $firstSettingsBlock.clone(),
blockID = $firstSettingsBlock.data( 'block-id' );
let newSettingsBlock;
$newSettingsBlock.attr( 'data-block-id', nextID );
$newSettingsBlock.find( '.wpforms-builder-settings-block-header span' ).text( settingsBlockName );
/**
* Fires to reset settings block elements on adding new settings block.
*
* @param {jQuery} $element jQuery object of an element.
*/
const resetFormElement = function( $element ) {
if ( $element.attr( 'name' ) ) {
$element.val( '' ).attr( 'name', $element.attr( 'name' ).replace( /\[(\d+)\]/, '[' + nextID + ']' ) );
if ( $element.is( 'select' ) ) {
$element.find( 'option' ).prop( 'selected', false ).attr( 'selected', false );
$element.find( 'option' ).first().prop( 'selected', true ).attr( 'selected', 'selected' );
} else if ( $element.attr( 'type' ) === 'checkbox' ) {
const isChecked = $element.closest( '.wpforms-panel-field' ).hasClass( 'js-wpforms-enabled-notification' );
$element.prop( 'checked', isChecked ).attr( 'checked', isChecked ).val( '1' );
} else {
$element.val( '' ).attr( 'value', '' );
}
}
};
$newSettingsBlock.find( 'input, textarea, select' ).each( function() {
const $this = $( this );
const $parent = $this.parent();
if ( $this.hasClass( 'wpforms-disabled' ) && ( $parent.hasClass( 'from-name' ) || $parent.hasClass( 'from-email' ) ) ) {
return;
}
resetFormElement( $this );
} );
// Update elements IDs.
const idPrefixPanel = 'wpforms-panel-field-' + panelID + '-',
idPrefixBlock = idPrefixPanel + blockID;
$newSettingsBlock.find( '[id^="' + idPrefixBlock + '"], [for^="' + idPrefixBlock + '"]' ).each( function( index, el ) { // eslint-disable-line no-unused-vars
const $el = $( this ),
attr = $el.prop( 'tagName' ) === 'LABEL' ? 'for' : 'id',
elID = $el.attr( attr ).replace( new RegExp( idPrefixBlock, 'g' ), idPrefixPanel + nextID );
$el.attr( attr, elID );
} );
// Update `notification by status` checkboxes.
const radioGroup = blockID + '-notification-by-status';
$newSettingsBlock.find( '[data-radio-group="' + radioGroup + '"]' ).each( function( index, el ) { // eslint-disable-line no-unused-vars
$( this )
.removeClass( 'wpforms-radio-group-' + radioGroup )
.addClass( 'wpforms-radio-group-' + nextID + '-notification-by-status' )
.attr( 'data-radio-group', nextID + '-notification-by-status' );
} );
$newSettingsBlock.find( '.wpforms-builder-settings-block-header input' ).val( settingsBlockName ).attr( 'value', settingsBlockName );
if ( blockType === 'notification' ) {
$newSettingsBlock.find( '.email-msg textarea' ).val( '{all_fields}' ).attr( 'value', '{all_fields}' );
$newSettingsBlock.find( '.email-recipient input' ).val( '{admin_email}' ).attr( 'value', '{admin_email}' );
}
$newSettingsBlock.removeClass( 'wpforms-builder-settings-block-default' );
if ( blockType === 'confirmation' ) {
$newSettingsBlock.find( '.wpforms-panel-field-tinymce' ).remove();
if ( typeof WPForms !== 'undefined' ) {
$newSettingsBlock.find( '.wpforms-panel-field-confirmations-type-wrap' )
.after( WPForms.Admin.Builder.Templates
.get( 'wpforms-builder-confirmations-message-field' )( {
id: nextID,
} )
);
}
}
// Conditional logic, if present
const $conditionalLogic = $newSettingsBlock.find( '.wpforms-conditional-block' );
if ( $conditionalLogic.length && typeof WPForms !== 'undefined' ) {
$conditionalLogic
.html( WPForms.Admin.Builder.Templates
.get( 'wpforms-builder-conditional-logic-toggle-field' )( {
id: nextID,
type: blockType,
actions: JSON.stringify( $newSettingsBlock.find( '.wpforms-panel-field-conditional_logic-checkbox' ).data( 'actions' ) ),
actionDesc: $newSettingsBlock.find( '.wpforms-panel-field-conditional_logic-checkbox' ).data( 'action-desc' ),
reference: $newSettingsBlock.find( '.wpforms-panel-field-conditional_logic-checkbox' ).data( 'reference' ),
} )
);
}
// Fields Map Table, if present.
const $fieldsMapTable = $newSettingsBlock.find( '.wpforms-field-map-table' );
if ( $fieldsMapTable.length ) {
$fieldsMapTable.each( function( index, el ) {
const $table = $( el );
// Clean table fields.
$table.find( 'tr:not(:first-child)' ).remove();
const $input = $table.find( '.key input' ),
$select = $table.find( '.field select' ),
name = $select.data( 'name' );
$input.attr( 'value', '' );
$select
.attr( 'name', '' )
.attr( 'data-name', name.replace( /\[(\d+)\]/, '[' + nextID + ']' ) );
} );
}
newSettingsBlock = $newSettingsBlock.wrap( '<div>' ).parent().html();
newSettingsBlock = newSettingsBlock.replace( /\[conditionals\]\[(\d+)\]\[(\d+)\]/g, '[conditionals][0][0]' );
$firstSettingsBlock.before( newSettingsBlock );
const $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 a 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( $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
*
* @param {jQuery} $el Element.
*/
settingsBlockNameEditingShow( $el ) {
const 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
*
* @param {jQuery} $el Element.
*/
settingsBlockNameEditingHide( $el ) {
const 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' );
let currentName = editHolder.find( 'input' ).val().trim();
const 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( $el ) { // eslint-disable-line max-lines-per-function
const $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 );
const $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() {
const $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() {
const $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 copy elements since jQuery doesn't clone the current selected state.
$settingsBlock.find( 'select' ).each( function() {
const 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 a 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( $el ) {
const $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() {
// Send it early to save fast.
// It's an animation start, so we should save the state for the animation end (reversed).
app.settingsBlockUpdateState( isVisible, settingsBlockId, settingsBlockType );
},
always() {
if ( $content.is( ':visible' ) ) {
$el.html( '<i class="fa fa-chevron-circle-up"></i>' );
} else {
$el.html( '<i class="fa fa-chevron-circle-down"></i>' );
}
},
} );
},
/**
* Delete settings block.
*
* @since 1.4.8
* @since 1.6.1.2 Registered `wpformsSettingsBlockDeleted` trigger.
*
* @param {jQuery} $el Delete button element.
*/
settingsBlockDelete( $el ) {
const $contentSection = $el.closest( '.wpforms-panel-content-section' );
// Skip if only one block persist.
// This condition should not execute in normal circumstances.
if ( $contentSection.find( '.wpforms-builder-settings-block' ).length < 2 ) {
return;
}
const $currentBlock = $el.closest( '.wpforms-builder-settings-block' ),
blockType = $currentBlock.data( 'block-type' );
$.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() {
const 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( isVisible, settingsBlockId, settingsBlockType ) {
/* eslint-disable camelcase */
$.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,
} );
},
/**
* Change visibility for notification elements, e.g.,
* the Enable This Notification toggle and notification status.
* The elements invisible when form has only one notification
* and customers can turn off all notifications instead.
*
* @since 1.9.2
*/
notificationsUpdateElementsVisibility() {
const $notifications = $( '.wpforms-panel-content-section-notifications .wpforms-notification' );
const $statuses = $notifications.find( '.wpforms-builder-settings-block-status' );
const isVisible = $notifications.length > 1;
const $fieldWrappers = $notifications.find( '.js-wpforms-enabled-notification' );
if ( isVisible ) {
$fieldWrappers.removeClass( 'wpforms-hidden' );
$statuses.removeClass( 'wpforms-hidden' );
return;
}
const $inputs = $fieldWrappers.find( 'input' );
$statuses.addClass( 'wpforms-hidden' );
$fieldWrappers.addClass( 'wpforms-hidden' );
$inputs.prop( 'checked', true );
},
/**
* Update notification status to display if the notification is active or inactive.
*
* @since 1.9.2
*
* @since 1.9.2
*
* @param {jQuery} $notification Notification element.
*/
notificationUpdateStatus( $notification ) {
const isNotificationsEnabled = $( '#wpforms-panel-field-settings-notification_enable' ).is( ':checked' );
const isEnabledNotification = $notification.find( '.js-wpforms-enabled-notification input' ).is( ':checked' );
const $status = $notification.find( '.wpforms-builder-settings-block-status' );
if ( isNotificationsEnabled && isEnabledNotification ) {
$status.removeClass( 'wpforms-badge-silver' );
$status.addClass( 'wpforms-badge-green' );
$status.text( $status.data( 'active' ) );
return;
}
$status.removeClass( 'wpforms-badge-green' );
$status.addClass( 'wpforms-badge-silver' );
$status.text( $status.data( 'inactive' ) );
},
//--------------------------------------------------------------------//
// Revisions Panel
//--------------------------------------------------------------------//
/**
* Element bindings for Revisions panel.
*
* @since 1.7.3
*/
bindUIActionsRevisions() {
// Update a 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 a revisions panel being active.
$builder.on( 'wpformsSaved', function( event ) { // eslint-disable-line no-unused-vars
if ( wpf.getQueryString( 'view' ) !== 'revisions' ) {
return;
}
app.updateRevisionsList();
} );
},
/**
* Fetch and update a list of form revisions.
*
* @since 1.7.3
*/
updateRevisionsList() {
const $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 ) {
// eslint-disable-next-line no-unused-expressions
response.success ? $revisionsButtonBadge.remove() : wpf.debug( response );
} )
.fail( function( xhr, textStatus, e ) { // eslint-disable-line no-unused-vars
wpf.debug( xhr.responseText || textStatus || '' );
} );
}
// Revisions are disabled, no need to fetch a list of revisions.
if ( ! $builder.hasClass( 'wpforms-revisions-enabled' ) ) {
return;
}
const $revisionsList = $( '#wpforms-panel-revisions .wpforms-revisions-content' );
// 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 ) {
// eslint-disable-next-line no-unused-expressions
response.success ? $revisionsList.replaceWith( response.data.html ) : wpf.debug( response );
} )
.fail( function( xhr, textStatus, e ) { // eslint-disable-line no-unused-vars
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() {
// Clone preview DOM from a Fields panel.
const $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 a 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() {
$.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() {
// 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() {
WPFormsBuilder.setCloseConfirmation( true );
},
},
},
} );
},
/**
* When a modified revision was saved as a current version, reload the Form Builder with the current tab active.
*
* @since 1.7.3
*/
revisionSavedReload() {
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() {
// Embed form.
$builder.on( 'click', '#wpforms-embed', function( e ) {
e.preventDefault();
if ( $( this ).hasClass( 'wpforms-disabled' ) || $( this ).hasClass( 'wpforms-btn-light-grey-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 ) { // eslint-disable-line no-unused-vars
/**
* 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( redirect ) { // eslint-disable-line max-lines-per-function
// 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();
}
const event = WPFormsUtils.triggerEvent( $builder, 'wpformsBeforeSave' );
// Allow callbacks on `wpformsBeforeSave` to cancel form submission by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return;
}
const $saveBtn = elements.$saveButton,
$icon = $saveBtn.find( 'i.fa-check' ),
$spinner = $saveBtn.find( 'i.wpforms-loading-spinner' ),
$label = $saveBtn.find( 'span' ),
text = $label.text();
$label.text( wpforms_builder.saving );
$saveBtn.prop( 'disabled', true );
$icon.addClass( 'wpforms-hidden' );
$spinner.removeClass( 'wpforms-hidden' );
const data = {
action: 'wpforms_save_form',
data: JSON.stringify( app.serializeAllData( $( '#wpforms-builder-form' ) ) ),
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 ) { // eslint-disable-line no-unused-vars
wpf.debug( xhr );
app.formSaveError();
} ).always( function() {
$label.text( text );
$saveBtn.prop( 'disabled', false );
$spinner.addClass( 'wpforms-hidden' );
$icon.removeClass( 'wpforms-hidden' );
} );
},
/**
* Serialize all form data including checkboxes that are not checked.
*
* @since 1.9.0
*
* @param {Object} $form Form jQuery object.
*
* @return {Array} Form data.
*/
serializeAllData( $form ) {
const formData = $form.serializeArray();
$form.find( '.wpforms-field-option-layout .wpforms-field-option-row-label_hide input[type=checkbox]' ).each( function() {
const $checkbox = $( this );
const name = $checkbox.attr( 'name' );
const value = $checkbox.is( ':checked' ) ? '1' : '';
if ( ! value ) {
formData.push( { name, value } );
}
} );
return formData;
},
/**
* Form save error.
*
* @since 1.6.3
*
* @param {string} error Error message.
*/
formSaveError( error = '' ) {
// Default error message.
if ( wpf.empty( error ) ) {
error = wpforms_builder.error_save_form;
}
// Display error in a modal window.
$.confirm( {
title: wpforms_builder.heads_up,
content: '<p>' + error + '</p><p>' + wpforms_builder.error_contact_support + '</p>',
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() {
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() {
app.formSave( true );
},
},
cancel: {
text: wpforms_builder.exit,
action() {
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( confirm ) {
closeConfirmation = !! confirm;
},
/**
* Check the current form state.
*
* @since 1.0.0
*
* @return {boolean} True if the form is saved.
*/
formIsSaved() {
return wpf.savedState === wpf.getFormState( '#wpforms-builder-form' );
},
/**
* Check if the builder opened in the popup (iframe).
*
* @since 1.6.2
*
* @return {boolean} True if builder opened in the popup.
*/
isBuilderInPopup() {
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( 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() { // eslint-disable-line max-lines-per-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() {
const 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 ) { // eslint-disable-line no-unused-vars
const $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 ) { // eslint-disable-line no-unused-vars
const $this = $( this ),
amount = $this.val();
if ( ! amount ) {
return amount;
}
const 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() {
const $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 ) { // eslint-disable-line no-unused-vars
const $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 ) {
const 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 );
}
} );
// Disable entries toggle.
$builder.on( 'change', '#wpforms-panel-field-settings-disable_entries', function( event ) { // eslint-disable-line no-unused-vars
const $this = $( this );
// Toggle the store spam entries toggle.
$( '#wpforms-panel-field-settings-store_spam_entries-wrap' ).toggleClass( 'wpforms-hidden', $this.prop( 'checked' ) );
// Don't allow users to disable entries if payments have been enabled.
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();
const $this = $( this );
const $container = $this.parent();
const mediaFrame = wpf.initMediaLibrary( {
title: wpforms_builder.upload_image_title,
extensions: wpforms_builder.upload_image_extensions,
extensionsError: wpforms_builder.upload_image_extensions_error,
buttonText: wpforms_builder.upload_image_button,
} );
mediaFrame.on( 'select', function() {
const mediaAttachment = mediaFrame.state().get( 'selection' ).first().toJSON();
const $preview = $container.find( '.preview' );
$container.find( '.source' ).val( mediaAttachment.url );
$preview.empty();
$preview.prepend( '<img src="' + mediaAttachment.url + '" alt=""><a href="#" title="' + wpforms_builder.upload_image_remove + '" class="wpforms-image-upload-remove"><i class="fa fa-trash-o"></i></a>' );
if ( $this.data( 'after-upload' ) === 'hide' ) {
$this.hide();
}
$builder.trigger( 'wpformsImageUploadAdd', [ $this, $container ] );
} ).on( 'close', function() {
mediaFrame.off( 'library:selection:add' );
} );
// Now that everything has been set, let's open up the frame.
mediaFrame.open();
} );
// Remove and uploaded image.
$builder.on( 'click', '.wpforms-image-upload-remove', function( event ) {
event.preventDefault();
const $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 ) { // eslint-disable-line no-unused-vars
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() {
$this.prop( 'checked', false );
},
} );
}
} );
// Re-init Show More button for multiselect instances when it's visible.
$builder.on( 'wpformsPanelSectionSwitch wpformsPanelSwitched', function() {
wpf.reInitShowMoreChoices( $( '#wpforms-panel-providers, #wpforms-panel-settings' ) );
} );
},
/**
* Notification section switch event handler.
*
* @since 1.8.2.3
*
* @param {Object} e Event object.
* @param {string} panel Panel name.
*/
notificationsPanelSectionSwitch( 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
*
* @return {boolean} True if one of the payment addons payment enabled.
*/
isPaymentsEnabled() {
let paymentEnabled = false;
$( app.getPaymentsTogglesSelector() ).each( function() {
if ( $( this ).prop( 'checked' ) ) {
paymentEnabled = true;
return false;
}
} );
return paymentEnabled;
},
/**
* Get Payments toggles selector.
*
* @since 1.7.5
*
* @return {string} List of selectors.
*/
getPaymentsTogglesSelector() {
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( $input ) {
const name = $input.attr( 'name' );
let value = '';
const $body = $( '.wpforms-panel-field-toggle-body[data-toggle="' + name + '"]' ),
enableInput = function() {
$input.prop( 'disabled', false );
};
app.toggleProviderActiveIcon( $input );
if ( $body.length === 0 ) {
enableInput();
return;
}
const type = $input.attr( 'type' );
if ( 'checkbox' === type || 'radio' === type ) {
value = $input.prop( 'checked' ) ? $input.val() : '0';
} else {
value = $input.val();
}
$body.each( function() {
const $this = $( this );
// eslint-disable-next-line no-unused-expressions
$this.attr( 'data-toggle-value' ).toString() === value.toString()
? $this.slideDown( '', enableInput )
: $this.slideUp( '', enableInput );
} );
},
/**
* Toggle Provider Active icon.
*
* @since 1.9.3
*
* @param {Object} $input Toggled field.
*/
toggleProviderActiveIcon( $input ) {
const provider = $input.closest( '.wpforms-panel-content-section' ).data( 'provider' );
const wrappers = [
'wpforms-panel-field-' + provider + '-enable-wrap',
'wpforms-panel-field-' + provider + '-enable_one_time-wrap',
'wpforms-panel-field-' + provider + '-enable_recurring-wrap',
];
if ( ! provider || ! wrappers.includes( $input.attr( 'id' ) ) ) {
return;
}
let isActive = false;
wrappers.forEach( ( wrapper ) => {
const $wrapper = $( '#' + wrapper );
if ( $wrapper.length && $wrapper.find( 'input' ).is( ':checked' ) ) {
isActive = true;
}
} );
const $sidebar = $( `.wpforms-panel-sidebar-section[data-section=${ provider }]` ),
$check_icon = $sidebar.find( '.fa-check-circle-o' );
$check_icon.toggleClass( 'wpforms-hidden', ! isActive );
},
/**
* Toggle all option groups.
*
* @since 1.6.3
*
* @param {jQuery} $context Context container jQuery object.
*/
toggleAllOptionGroups( $context ) {
$context = $context || $builder || $( '#wpforms-builder' ) || $( 'body' );
if ( ! $context ) {
return;
}
// Show a toggled bodies.
$context.find( '.wpforms-panel-field-toggle' ).each( function() {
const $input = $( this );
$input.prop( 'disabled', true );
app.toggleOptionsGroup( $input );
} );
},
/**
* Toggle unfoldable group of fields.
*
* @since 1.6.8
*
* @param {Object} e Event object.
*/
toggleUnfoldableGroup( e ) {
e.preventDefault();
const $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( e ) {
e.preventDefault();
e.stopPropagation();
const $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( e ) {
e.preventDefault();
// Prevent ajax to validate the default email queued on focusout event.
elements.$focusOutTarget = null;
const $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( $el ) {
const $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( $el ) {
const $wrapper = $el.closest( '.wpforms-panel-field,.wpforms-field-option-row' );
let $label = $el.closest( 'label' ),
insideLabel = true;
if ( ! $label.length ) {
$label = $wrapper.find( 'label' );
insideLabel = false;
}
const smartTagList = app.getSmartTagsList( $el, $label.attr( 'for' ).indexOf( 'wpforms-field-option-' ) !== -1 );
// eslint-disable-next-line no-unused-expressions
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.
*
* @return {string} Smart Tags list markup.
*/
getSmartTagsList( $el, isFieldOption ) {
let smartTagList;
smartTagList = '<ul class="smart-tags-list-display unfoldable-cont">';
smartTagList += app.getSmartTagsListFieldsElements( $el );
smartTagList += app.getSmartTagsListOtherElements( $el, isFieldOption );
smartTagList += '</ul>';
return smartTagList;
},
/**
* Get Smart Tag fields elements markup.
*
* @since 1.6.9
*
* @param {jQuery} $el Toggle element.
*
* @return {string} Smart Tags list elements markup.
*/
getSmartTagsListFieldsElements( $el ) {
const type = $el.data( 'type' );
if ( ! [ 'fields', 'all' ].includes( type ) ) {
return '';
}
const fields = app.getSmartTagsFields( $el );
if ( ! fields ) {
return '<li class="heading">' + wpforms_builder.fields_unavailable + '</li>';
}
let smartTagListElements = '';
smartTagListElements += '<li class="heading">' + wpforms_builder.fields_available + '</li>';
for ( const fieldKey in fields ) {
smartTagListElements += app.getSmartTagsListFieldsElement( fields[ fieldKey ] );
}
return smartTagListElements;
},
/**
* Get fields that possible to create smart tag.
*
* @since 1.6.9
*
* @param {jQuery} $el Toggle element.
*
* @return {Array} Fields for smart tags.
*/
getSmartTagsFields( $el ) {
const allowed = $el.data( 'fields' );
const isAllowedRepeater = $el.data( 'allow-repeated-fields' );
const allowedFields = allowed ? allowed.split( ',' ) : undefined;
return wpf.getFields( allowedFields, true, isAllowedRepeater );
},
/**
* Get field markup for the Smart Tags list.
*
* @since 1.6.9
*
* @param {Object} field A field.
*
* @return {string} Smart Tags field markup.
*/
getSmartTagsListFieldsElement( field ) {
const label = field.label
? wpf.encodeHTMLEntities( wpf.sanitizeHTML( field.label ) )
: wpforms_builder.field + ' #' + field.id;
let html = `<li><a href="#" data-type="field" data-meta="${ field.id }">${ label }</a></li>`;
const additionalTags = field.additional || [];
// Add additional tags for `name`, `date/time` and `address` fields.
if ( additionalTags.length > 1 ) {
additionalTags.forEach( ( additionalTag ) => {
// Capitalize the first letter and add space before numbers.
const additionalTagLabel = additionalTag.charAt( 0 ).toUpperCase() + additionalTag.slice( 1 ).replace( /(\D)(\d)/g, '$1 $2' );
html += `<li><a href="#" data-type="field" data-meta="${ field.id }" data-additional='${ additionalTag }'>${ label } ${ additionalTagLabel }</a></li>`;
} );
}
return html;
},
/**
* Get Smart Tag other elements markup.
*
* @since 1.6.9
*
* @param {jQuery} $el Toggle element.
* @param {boolean} isFieldOption Is a field option.
*
* @return {string} Smart Tags list elements markup.
*/
getSmartTagsListOtherElements( $el, isFieldOption ) {
const type = $el.data( 'type' );
let smartTagListElements;
if ( type !== 'other' && type !== 'all' ) {
return '';
}
smartTagListElements = '<li class="heading">' + wpforms_builder.other + '</li>';
for ( const smartTagKey in wpforms_builder.smart_tags ) {
if (
( isFieldOption && wpforms_builder.smart_tags_disabled_for_fields.includes( smartTagKey ) ) ||
(
$el.data( 'location' ) === 'confirmations' &&
wpforms_builder.smart_tags_disabled_for_confirmations.includes( smartTagKey )
)
) {
continue;
}
smartTagListElements += '<li><a href="#" data-type="other" data-meta=\'' + smartTagKey + '\'>' + wpforms_builder.smart_tags[ smartTagKey ] + '</a></li>';
}
return smartTagListElements;
},
/**
* Smart Tag insert.
*
* @since 1.0.1
* @since 1.6.9 TinyMCE compatibility.
*
* @param {Event} e Event.
*/
smartTagInsert( e ) { // eslint-disable-line complexity
e.preventDefault();
const $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' ),
additional = $this.data( 'additional' ) ? '|' + $this.data( 'additional' ) : '',
type = $this.data( 'type' );
let smartTag = type === 'field' ? '{field_id="' + meta + additional + '"}' : '{' + 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 the 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.
*
* @param {Event} e Event.
* @param {Element} el Element.
*/
fieldMapTableDeleteRow( e, el ) {
const $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.
*
* @param {Event} e Event.
* @param {Element} el Element.
*/
fieldMapTableAddRow( e, el ) {
const $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.
*
* @param {Event} e Event.
* @param {Object} fields Fields.
*/
fieldMapSelect( e, fields ) { // eslint-disable-line max-lines-per-function
const event = WPFormsUtils.triggerEvent( $builder, 'wpformsBeforeFieldMapSelectUpdate' );
// Allow callbacks on `wpformsBeforeFieldMapSelectUpdate` to cancel adding field
// by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return;
}
$( '.wpforms-field-map-select' ).each( function( index, el ) { // eslint-disable-line complexity, no-unused-vars
const $this = $( this );
let 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;
}
// If allowed, fields are not defined, bail.
if ( typeof allowedFields !== 'undefined' && allowedFields ) {
allowedFields = allowedFields.split( ' ' );
} else {
return;
}
const selected = $this.find( 'option:selected' ).val();
// Reset select and add a placeholder option.
$this.empty().append( $( '<option>', { value: '', text: placeholder } ) );
// Loop through the current fields, if we have fields for the form.
if ( fields && ! $.isEmptyObject( fields ) ) {
for ( const fieldID in fields ) {
let label = '';
if ( ! fields[ fieldID ] ) {
continue;
}
// Prepare the label.
if ( typeof fields[ fieldID ].label !== 'undefined' && fields[ fieldID ].label.toString().trim() !== '' ) {
label = wpf.sanitizeHTML( fields[ fieldID ].label.toString().trim() );
} else {
label = wpforms_builder.field + ' #' + fieldID;
}
// Add to select if it is a field type allowed.
if ( $.inArray( fields[ fieldID ].type, allowedFields ) >= 0 || $.inArray( 'all-fields', allowedFields ) >= 0 ) {
$this.append( $( '<option>', { value: fields[ fieldID ].id, text: label } ) );
}
}
}
// Restore previous value if found.
if ( selected ) {
$this.find( 'option[value="' + selected + '"]' ).prop( 'selected', true );
}
// Add a "Custom Value" option if it is supported.
const customValueSupport = $this.data( 'custom-value-support' );
if ( typeof customValueSupport === 'boolean' && customValueSupport ) {
$this.append(
$( '<option>', {
value: 'custom_value',
text: wpforms_builder.add_custom_value_label,
class: 'wpforms-field-map-option-custom-value',
} )
);
}
$builder.trigger( 'wpformsFieldSelectMapped', [ $this ] );
} );
},
/**
* Validate email smart tags in Notifications fields.
*
* @param {Object} $el Input field to check the value for.
*
* @since 1.4.9
*/
validateEmailSmartTags( $el ) {
let val = $el.val();
if ( ! val ) {
return;
}
// Turns '{email@domain.com}' into 'email@domain.com'.
// Email RegEx inspired by http://emailregex.com
val = val.replace( /{(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))}/g, function( x ) {
return x.slice( 1, -1 );
} );
$el.val( val );
},
/**
* Validate From Email in Notification block.
*
* @since 1.8.1
*/
validateFromEmail() {
const $field = $( this );
const value = $field.val().trim();
const $fieldWrapper = $field.parent();
const $warning = $fieldWrapper.find( '.wpforms-alert-warning-wide' );
const warningClass = 'wpforms-panel-field-warning';
const blockedSymbolsRegex = /[\s,;]/g;
if ( blockedSymbolsRegex.test( value ) ) {
$warning.remove();
$fieldWrapper.addClass( warningClass );
app.validationErrorNotificationPopup( wpforms_builder.allow_only_one_email );
return;
}
const data = {
form_id: s.formID, // eslint-disable-line camelcase
email: $field.val(),
nonce: wpforms_builder.nonce,
action: 'wpforms_builder_notification_from_email_validate',
};
$.post(
wpforms_builder.ajax_url, data, function( res ) {
if ( res.success ) {
$warning.remove();
$fieldWrapper.removeClass( warningClass );
return;
}
$fieldWrapper.addClass( warningClass );
if ( $warning.length ) {
$warning.replaceWith( res.data );
return;
}
$fieldWrapper.append( res.data );
} )
.fail( function( xhr, textStatus, e ) { // eslint-disable-line no-unused-vars
// eslint-disable-next-line no-console
console.log( xhr.responseText );
} );
},
/**
* Disabled fields.
* Addon fields in Lite initialization.
*
* @since 1.9.4
*/
disabledFields: {
init() {
app.disabledFields.initCouponsChoicesJS();
app.disabledFields.initFileUploadChoicesJS();
},
/**
* Initialize Choices.js for the Coupon field.
*
* @since 1.9.4
*/
initCouponsChoicesJS() {
if ( typeof window.Choices !== 'function' || WPForms.Admin.Builder.Coupons ) {
return;
}
$( '.wpforms-field-option-row-allowed_coupons select:not(.choices__input)' ).each( function() {
const $select = $( this );
const choicesInstance = new Choices(
$select.get( 0 ),
{
shouldSort: false,
removeItemButton: true,
renderChoicesLimit: 5,
callbackOnInit() {
wpf.showMoreButtonForChoices( this.containerOuter.element );
},
} );
// Save Choices.js instance for future access.
$select.data( 'choicesjs', choicesInstance );
} );
},
/**
* Initialize Choices.js for the File Upload field.
*
* @since 1.9.4
*/
initFileUploadChoicesJS() {
if ( typeof window.Choices !== 'function' || WPForms.Admin.Builder.FieldFileUpload ) {
return;
}
const $selects = $( '.wpforms-file-upload-user-roles-select, .wpforms-file-upload-user-names-select' );
$selects.each( function() {
new Choices( $( this )[ 0 ], { removeItemButton: true } );
} );
},
},
//--------------------------------------------------------------------//
// Icon Choices
//--------------------------------------------------------------------//
/**
* Icon Choices component.
*
* @since 1.7.9
*/
iconChoices: {
/**
* Runtime component cache.
*
* Field "toggle": "Use icon choices" toggle that initiated the installation.
* Field "previousModal": Last open modal that may need to be closed.
*
* @since 1.7.9
*/
cache: {},
/**
* Component configuration settings.
*
* @since 1.7.9
*/
config: {
colorPropertyName: '--wpforms-icon-choices-color',
},
/**
* Initialize the component.
*
* @since 1.7.9
*/
init() {
// Extend jquery-confirm plugin with max-height support for the content area.
app.iconChoices.extendJqueryConfirm();
$builder.on( 'wpformsBuilderReady', function( event ) {
// If there are Icon Choices fields but the library is not installed - force install prompt.
if ( wpforms_builder.icon_choices.is_active && ! wpforms_builder.icon_choices.is_installed ) {
app.iconChoices.openInstallPromptModal( true );
// Prevent the Form Builder from getting ready (hold the loading state).
event.preventDefault();
}
} );
// Toggle Icon Choices on or off.
$builder.on( 'change', '.wpforms-field-option-row-choices_icons input', app.iconChoices.toggleIconChoices );
// Change accent color.
$builder.on( 'change', '.wpforms-field-option-row-choices_icons_color .wpforms-color-picker', app.iconChoices.changeIconsColor );
// Update field preview when option value is changed (style, size).
$builder.on( 'change', '.wpforms-field-option-row-choices_icons_style select, .wpforms-field-option-row-choices_icons_size select', function() {
const fieldID = $( this ).parent().data( 'field-id' ),
fieldType = $( '#wpforms-field-option-' + fieldID ).find( '.wpforms-field-option-hidden-type' ).val();
app.fieldChoiceUpdate( fieldType, fieldID );
} );
// Open Icon Picker modal.
$builder.on( 'click', '.wpforms-field-option-row-choices .choices-list .wpforms-icon-select', app.iconChoices.openIconPickerModal );
},
/**
* Turn the feature on or off.
*
* @since 1.7.9
*/
toggleIconChoices() { // eslint-disable-line complexity
const $this = $( this ),
checked = $this.is( ':checked' );
// Check if a required icon library is installed.
if ( checked && ! wpforms_builder.icon_choices.is_installed ) {
app.iconChoices.cache.toggle = $this;
app.iconChoices.openInstallPromptModal();
return;
}
const fieldID = $this.closest( '.wpforms-field-option-row' ).data( 'field-id' );
const $fieldOptions = $( `#wpforms-field-option-${ fieldID }` ),
$imageChoices = $fieldOptions.find( `#wpforms-field-option-${ fieldID }-choices_images` ),
$choicesList = $fieldOptions.find( `#wpforms-field-option-row-${ fieldID }-choices ul` );
// Turn Image Choice off.
if ( checked && $imageChoices.is( ':checked' ) ) {
$imageChoices.prop( 'checked', false ).trigger( 'change' );
}
// Toggle Advanced > Dynamic Choices on or off.
$fieldOptions.find( `#wpforms-field-option-row-${ fieldID }-dynamic_choices` ).toggleClass( 'wpforms-hidden', checked );
// Toggle subfields.
$fieldOptions.find( `#wpforms-field-option-row-${ fieldID }-choices_icons_color` ).toggleClass( 'wpforms-hidden' );
$fieldOptions.find( `#wpforms-field-option-row-${ fieldID }-choices_icons_size` ).toggleClass( 'wpforms-hidden' );
$fieldOptions.find( `#wpforms-field-option-row-${ fieldID }-choices_icons_style` ).toggleClass( 'wpforms-hidden' );
const $colorOption = $fieldOptions.find( `#wpforms-field-option-${ fieldID }-choices_icons_color` ),
colorValue = _.isEmpty( $colorOption.val() ) ? wpforms_builder.icon_choices.default_color : $colorOption.val();
// Set accent color for all choices.
$choicesList.prop( 'style', `${ app.iconChoices.config.colorPropertyName }: ${ colorValue };` );
// Toggle icon selectors with previews for all choices.
$choicesList.toggleClass( 'show-icons', checked );
// Set layout to inline on activation, revert to one column on deactivation.
$fieldOptions.find( `#wpforms-field-option-${ fieldID }-input_columns` ).val( checked ? 'inline' : '' ).trigger( 'change' );
// Finally, update the preview.
app.fieldChoiceUpdate( $fieldOptions.find( '.wpforms-field-option-hidden-type' ).val(), fieldID );
},
/**
* Change accent color and update previews.
*
* @since 1.7.9
*/
changeIconsColor() {
const $this = $( this ),
fieldID = $this.parents( '.wpforms-field-option-row' ).data( 'field-id' ),
$field = $( '#wpforms-field-option-' + fieldID ),
type = $field.find( '.wpforms-field-option-hidden-type' ).val(),
$choicesList = $field.find( '.wpforms-field-option-row-choices .choices-list' ),
colorValue = app.getValidColorPickerValue( $this );
// Update icons color in options panel.
$choicesList.prop( 'style', `${ app.iconChoices.config.colorPropertyName }: ${ colorValue };` );
// Update preview.
app.fieldChoiceUpdate( type, fieldID );
},
/**
* Open a modal prompting to install the icon library for Icon Choices.
*
* @since 1.7.9
*
* @param {boolean} force Whether it's a normal installation procedure or forced if the library is necessary but is missing.
*/
openInstallPromptModal( force = false ) {
const content = force
? wpforms_builder.icon_choices.strings.reinstall_prompt_content
: wpforms_builder.icon_choices.strings.install_prompt_content;
const modal = $.confirm( {
title: wpforms_builder.heads_up,
content,
icon: 'fa fa-info-circle',
type: 'orange',
buttons: {
continue: {
text: wpforms_builder.continue,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
this.setIcon( 'fa fa-cloud-download' );
this.setTitle( wpforms_builder.icon_choices.strings.install_title );
this.setContent( wpforms_builder.icon_choices.strings.install_content );
$.each( this.buttons, function( _index, button ) {
button.hide();
} );
app.iconChoices.installIconLibrary();
// Do not close the modal.
return false;
},
},
},
onOpen() {
// Turn the toggle off during normal installation.
if ( ! force && app.iconChoices.cache.toggle ) {
app.iconChoices.cache.toggle.prop( 'checked', false );
}
app.iconChoices.cache.previousModal = this;
},
} );
// Add a Cancel button for normal installation routine only.
if ( ! force ) {
modal.buttons.cancel = {
text: wpforms_builder.cancel,
keys: [ 'esc' ],
action() {
app.iconChoices.cache.toggle.prop( 'checked', false );
},
};
}
},
/**
* Silently download and install the icon library on the server.
*
* @since 1.7.9
*/
installIconLibrary() {
const data = {
// eslint-disable-next-line camelcase
_wp_http_referer: wpf.updateQueryString( '_wp_http_referer', null ),
nonce: wpforms_builder.nonce,
action: 'wpforms_icon_choices_install',
};
$.ajaxSetup( {
type: 'POST',
timeout: 120000, // 2 minutes.
} );
$.post( wpforms_builder.ajax_url, data, function( response ) {
// eslint-disable-next-line no-unused-expressions
response.success
? app.iconChoices.openInstallSuccessModal()
: app.iconChoices.openInstallErrorModal( response );
} ).fail( function( jqXHR ) {
app.iconChoices.openInstallErrorModal( jqXHR );
} );
},
/**
* Open a modal on icon library installation success.
*
* @since 1.7.9
*/
openInstallSuccessModal() {
$.confirm( {
title: wpforms_builder.done,
content: wpforms_builder.icon_choices.strings.install_success_content,
icon: 'fa fa-check-circle',
type: 'green',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
if ( app.iconChoices.cache.toggle ) {
app.iconChoices.cache.toggle.prop( 'checked', true );
const fieldId = app.iconChoices.cache.toggle.parents( '.wpforms-field-option-row' ).data( 'field-id' );
const $imageChoices = $builder.find( `#wpforms-field-option-${ fieldId }-choices_images` );
// Turn Image Choice off, if needed, without triggering change event.
if ( $imageChoices.is( ':checked' ) ) {
$imageChoices.prop( 'checked', false );
}
}
app.formSave( false ).done( function() {
window.location.reload();
} );
},
},
},
onOpen() {
if ( app.iconChoices.cache.toggle ) {
const fieldId = app.iconChoices.cache.toggle.parents( '.wpforms-field-option-row-choices_icons' ).data( 'field-id' );
$builder.find( `#wpforms-field-option-${ fieldId }-input_columns` ).val( 'inline' );
}
app.iconChoices.cache.previousModal.close();
},
} );
},
/**
* Open a modal on icon library installation failure.
*
* @since 1.7.9
*
* @param {Object} errorData Unsuccessful ajax JSON response or jqXHR object.
*/
openInstallErrorModal( errorData ) {
$.confirm( {
title: wpforms_builder.uh_oh,
content: wpforms_builder.icon_choices.strings.install_error_content,
icon: 'fa fa-exclamation-circle',
type: 'red',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
if ( app.iconChoices.cache.toggle ) {
app.iconChoices.cache.toggle.prop( 'checked', false );
} else {
app.formSaveError();
}
},
},
},
onOpen() {
wpf.debug( errorData );
app.iconChoices.cache.previousModal.close();
},
onDestroy() {
// Clean up the cache, we're done.
delete app.iconChoices.cache.previousModal;
delete app.iconChoices.cache.toggle;
},
} );
},
/**
* Extend jquery-confirm plugin with support of max-height for the content area.
*
* @since 1.7.9
*/
extendJqueryConfirm() {
// Extend a method of global instance.
window.Jconfirm.prototype._updateContentMaxHeight = function() { // noinspection JSUnusedGlobalSymbols
const height = $( window ).height() - ( this.$jconfirmBox.outerHeight() - this.$contentPane.outerHeight() ) - ( this.offsetTop + this.offsetBottom );
// Custom property, if set via jquery-confirm options.
const maxHeight = this.contentMaxHeight || height;
this.$contentPane.css( {
'max-height': Math.min( maxHeight, height ) + 'px',
} );
};
},
/**
* Open Icon Picker modal.
*
* @since 1.7.9
*/
openIconPickerModal() {
const $this = $( this );
const data = {
fieldId: $this.parents( '.wpforms-field-option-row' ).data( 'field-id' ),
choiceId: $this.parent().data( 'key' ),
selectedIcon: $this.find( '.source-icon' ).val(),
selectedIconStyle: $this.find( '.source-icon-style' ).val(),
};
const title = `
${ wpforms_builder.icon_choices.strings.icon_picker_title }
<span class="wpforms-icon-picker-description">${ wpforms_builder.icon_choices.strings.icon_picker_description }</span>
<input type="text" placeholder="${ wpforms_builder.icon_choices.strings.icon_picker_search_placeholder }" class="search" id="wpforms-icon-picker-search">
`;
const content = `
<div class="wpforms-icon-picker-container" id="wpforms-icon-picker-icons">
<ul class="wpforms-icon-picker-icons" data-field-id="${ data.fieldId }" data-choice-id="${ data.choiceId }"></ul>
<ul class="wpforms-icon-picker-pagination"></ul>
<p class="wpforms-icon-picker-not-found wpforms-hidden" data-message="${ wpforms_builder.icon_choices.strings.icon_picker_not_found }"></>
</div>`;
$.confirm( {
title,
titleClass: 'wpforms-icon-picker-title',
content,
icon: false,
closeIcon: true,
type: 'orange',
backgroundDismiss: true,
boxWidth: 800,
contentMaxHeight: 368, // Custom property, see app.iconChoices.extendJqueryConfirm().
smoothContent: false,
buttons: false,
onOpenBefore() {
// Add custom classes to target various elements.
this.$body.addClass( 'wpforms-icon-picker-jconfirm-box' );
this.$contentPane.addClass( 'wpforms-icon-picker-jconfirm-content-pane' );
},
onContentReady() {
const modal = this;
// Initialize the list of icons with List.js and display the 1st page.
app.iconChoices.initIconsList( data );
// Focus the search input.
modal.$title.find( '.search' ).focus();
// Listen for clicks on icons to select them.
modal.$content.find( '.wpforms-icon-picker-icons' ).on( 'click', 'li', function() {
app.iconChoices.selectIcon( modal, $( this ) );
} );
},
} );
},
/**
* Initialize List.js in the Icon Selector modal on demand and cache it.
*
* @since 1.7.9
*
* @param {Object} data Source option data - field and choice IDs, selected icon name and style.
*/
initIconsList( data ) {
const options = {
valueNames: [ 'name' ],
listClass: 'wpforms-icon-picker-icons',
page: wpforms_builder.icon_choices.icons_per_page,
pagination: {
paginationClass: 'wpforms-icon-picker-pagination',
},
item( values ) {
const maybeSelectedClass = ( values.icon === data.selectedIcon && values.style === data.selectedIconStyle ) ? 'class="selected"' : '';
return `
<li data-icon="${ values.icon }" data-icon-style="${ values.style }"${ maybeSelectedClass }>
<i class="ic-fa-${ values.style } ic-fa-${ values.icon }"></i>
<span class="name">${ values.icon }</span>
</li>`;
},
indexAsync: true,
};
// Initialize List.js instance.
const iconsList = new List( 'wpforms-icon-picker-icons', options, wpforms_builder.icon_choices.icons );
// Initialize infinite scroll pagination on the list instance.
app.iconChoices.infiniteScrollPagination( iconsList );
// Bind search to custom input.
$( '#wpforms-icon-picker-search' ).on( 'keyup', function() {
// Custom partial match search.
iconsList.search( $( this ).val(), [ 'name' ], function( searchString, columns ) { // eslint-disable-line no-unused-vars
for ( let index = 0, length = iconsList.items.length; index < length; index++ ) {
iconsList.items[ index ].found = ( new RegExp( searchString ) ).test( iconsList.items[ index ].values().icon );
}
} );
} );
// Show "nothing found" message if the search returned no results.
iconsList.on( 'searchComplete', function() {
const $element = $( '.wpforms-icon-picker-not-found' );
$element.html( $element.data( 'message' ).replace( '{keyword}', $( '#wpforms-icon-picker-search' ).val() ) );
$element.toggleClass( 'wpforms-hidden', ! _.isEmpty( iconsList.matchingItems ) );
} );
},
/**
* Handle infinite scroll on the list of icons.
*
* @since 1.7.9
*
* @param {Object} list List.js instance.
*/
infiniteScrollPagination( list ) {
let page = 1;
const options = {
root: document.querySelector( '.wpforms-icon-picker-jconfirm-content-pane' ),
rootMargin: '600px', // 5 rows of icons. Formula: 20 + ( (96 + 20) * rows ).
};
const observer = new IntersectionObserver( function( entries ) {
if ( ! entries[ 0 ].isIntersecting ) {
return;
}
page++;
list.show( 0, page * wpforms_builder.icon_choices.icons_per_page );
}, options );
observer.observe( document.querySelector( '.wpforms-icon-picker-pagination' ) );
},
/**
* When an icon is selected, update the choice and the field preview.
*
* @since 1.7.9
*
* @param {Object} modal Current jQuery Confirm modal instance.
* @param {jQuery} $this The list item (icon) that was clicked.
*/
selectIcon( modal, $this ) {
const fieldId = $this.parent().data( 'field-id' );
const choiceId = $this.parent().data( 'choice-id' );
const icon = $this.data( 'icon' );
const iconStyle = $this.data( 'icon-style' );
const $choice = $( '#wpforms-field-option-row-' + fieldId + '-choices ul li[data-key=' + choiceId + ']' );
const fieldType = $( '#wpforms-field-option-row-' + fieldId + '-choices ul' ).data( 'field-type' );
$this.addClass( 'selected' );
$this.siblings( '.selected' ).removeClass( 'selected' );
$choice.find( '.wpforms-icon-select span' ).text( icon );
$choice.find( '.wpforms-icon-select .ic-fa-preview' ).removeClass().addClass( `ic-fa-preview ic-fa-${ iconStyle } ic-fa-${ icon }` );
$choice.find( '.wpforms-icon-select .source-icon' ).val( icon );
$choice.find( '.wpforms-icon-select .source-icon-style' ).val( iconStyle );
app.fieldChoiceUpdate( fieldType, fieldId );
modal.close();
},
},
//--------------------------------------------------------------------//
// Alerts (notices).
//--------------------------------------------------------------------//
/**
* Click on the Dismiss notice button.
*
* @since 1.6.7
*/
dismissNotice() {
$builder.on( 'click', '.wpforms-alert-field-not-available .wpforms-dismiss-button', function( e ) {
e.preventDefault();
const $button = $( this ),
$alert = $button.closest( '.wpforms-alert' ),
fieldId = $button.data( 'field-id' );
$alert.addClass( 'out' );
setTimeout( function() {
$alert.remove();
}, 250 );
if ( fieldId ) {
$( '#wpforms-field-option-' + fieldId ).remove();
}
} );
},
//--------------------------------------------------------------------//
// Other functions.
//--------------------------------------------------------------------//
/**
* Trim long form titles.
*
* @since 1.0.0
*/
trimFormTitle() {
const $title = $( '.wpforms-center-form-name' );
if ( $title.text().length > 38 ) {
const shortTitle = $title.text().trim().substring( 0, 38 ).split( ' ' ).slice( 0, -1 ).join( ' ' ) + '...';
$title.text( shortTitle );
}
},
/**
* Load or refresh color picker.
*
* @since 1.2.1
* @since 1.7.9 Added default value support.
*/
loadColorPickers() {
$( '.wpforms-color-picker' ).each( function() {
const $this = $( this );
// If it appears to be already initialized, reset. This is necessary when duplicating fields with color pickers.
if ( $this.hasClass( 'minicolors-input' ) ) {
$this.minicolors( 'destroy' );
}
$this.minicolors( {
defaultValue: $this.data( 'fallback-color' ) || '',
} );
} );
},
/**
* Get a valid color value from color picker or a default one.
*
* @since 1.7.9
*
* @param {Object} $colorPicker Current field.
*
* @return {string} Always valid color value.
*/
getValidColorPickerValue( $colorPicker ) {
const color = $colorPicker.minicolors( 'value' );
// jQuery MiniColors returns "black" RGB object if the color value is invalid.
const isInvalid = _.isEqual( $colorPicker.minicolors( 'rgbObject' ), { r: 0, g: 0, b: 0 } );
const isBlack = _.includes( [ '#000', '#000000' ], color );
// If default value isn't provided via the data attribute, use black.
const defaultValue = $colorPicker.data( 'fallback-color' ) || '#000000';
return isInvalid && ! isBlack ? defaultValue : color;
},
/**
* Hotkeys:
* Ctrl+H - Help.
* Ctrl+P - Preview.
* Ctrl+B - Embed.
* Ctrl+E - Entries.
* Ctrl+S - Save.
* Ctrl+Q - Exit.
* Ctrl+/ - Keyboard Shortcuts modal.
* Ctrl+F - Focus search fields input.
* Ctrl+T - Toggle sidebar (Alt+S on Windows and Linux).
*
* @since 1.2.4
*/
builderHotkeys() {
$( document ).on( 'keydown', function( e ) { // eslint-disable-line complexity
// Toggle sidebar on Alt+S (on Windows and Linux).
if ( ( browser.isLinux || browser.isWindows ) && e.altKey && e.keyCode === 83 ) {
$( elements.$sidebarToggle, $builder ).trigger( 'click' );
return;
}
if ( ! e.ctrlKey ) {
return;
}
switch ( e.keyCode ) {
case 72: // Open the Help screen on Ctrl+H.
$( elements.$helpButton, $builder ).trigger( 'click' );
break;
case 80: // Open the Form Preview tab on Ctrl+P.
window.open( wpforms_builder.preview_url );
break;
case 66: // Trigger the Embed modal on Ctrl+B.
$( elements.$embedButton, $builder ).trigger( 'click' );
break;
case 69: // Open the Entries tab on Ctrl+E.
window.open( wpforms_builder.entries_url );
break;
case 83: // Trigger the Builder save on Ctrl+S.
$( elements.$saveButton, $builder ).trigger( 'click' );
break;
case 81: // Trigger the Exit on Ctrl+Q.
$( elements.$exitButton, $builder ).trigger( 'click' );
break;
case 191: // Keyboard shortcuts modal on Ctrl+/.
app.openKeyboardShortcutsModal();
break;
case 84: // Toggle sidebar on Ctrl+T.
$( elements.$sidebarToggle, $builder ).trigger( 'click' );
break;
case 70: // Focus search fields input on Ctrl+F.
elements.$addFieldsTab.trigger( 'click' );
elements.$fieldsSidebar.scrollTop( 0 );
elements.$searchInput.focus();
break;
default:
return;
}
return false;
} );
},
/**
* Open Keyboard Shortcuts modal.
*
* @since 1.6.9
*/
openKeyboardShortcutsModal() {
// Close already opened instance.
if ( $( '.wpforms-builder-keyboard-shortcuts' ).length ) {
jconfirm.instances[ jconfirm.instances.length - 1 ].close();
return;
}
$.alert( {
title: wpforms_builder.shortcuts_modal_title,
content: wpforms_builder.shortcuts_modal_msg + wp.template( 'wpforms-builder-keyboard-shortcuts' )(),
icon: 'fa fa-keyboard-o',
type: 'blue',
boxWidth: '550px',
smoothContent: false,
buttons: {
confirm: {
text: wpforms_builder.close,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
onOpenBefore() {
this.$body.addClass( 'wpforms-builder-keyboard-shortcuts' );
// Dynamically change the shortcut key documentation on Windows/Linux.
if ( browser.isLinux || browser.isWindows ) {
this.$body.find( '.shortcut-key.shortcut-key-ctrl-t' ).html( '<i>alt</i><i>s</i>' );
}
},
} );
},
/**
* Register JS templates for various elements.
*
* @since 1.4.8
*/
registerTemplates() {
if ( typeof WPForms === 'undefined' ) {
return;
}
WPForms.Admin.Builder.Templates.add( [
'wpforms-builder-confirmations-message-field',
'wpforms-builder-conditional-logic-toggle-field',
] );
},
/**
* Exit builder.
*
* @since 1.5.7
* @since 1.7.8 Deprecated.
*/
exitBack() {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsBuilder.exitBack()" has been deprecated.' );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
WPFormsBuilder.init();