Snapshot: MLS sync fixes, image refresh, plugin/theme updates

MLS plugin fixes from this session:
- Fix silent insert failures: location column NOT NULL was rejecting wpdb->insert calls,
  causing ~18k new properties since Dec 2025 to be lost. Inserts now build raw SQL
  with ST_PointFromText so the spatial column is populated atomically.
- Auto-refresh expired media URLs in MLS_Media_Handler::fetch_and_cache(), guarded by
  a property-level GET_LOCK so concurrent fetches share one API refresh.
- Normalize WP_Error to null in mls_get_property_image() so callers can rely on the
  documented string|null contract.
- Support comma-separated property_type filters in MLS_Query and MLS_Cluster so the
  homepage "View All Commercial" link (?property_type=Commercial+Sale,Land,Farm)
  actually filters correctly.
- Incremental sync now looks back 10 minutes past the latest modification timestamp
  as a safety margin against missed records.
- Smart sync exits silently (info-level, not warning) when a full sync is in progress.

Operational:
- New cron: weekly full sync Sundays at 3 AM (/usr/local/bin/mls-full-sync).
- New cron: hourly 2GB cap on mls-thumbnails/ and cache/transformed-images/
  (/usr/local/bin/mls-image-cache-cap).
- Logrotate config for wp-content/debug.log (2-day retention, daily rotation,
  delaycompress).

Repo policy:
- CLAUDE.md updated with explicit "commit everything except build artifacts" policy.
- .gitignore: untrack runtime image caches and debug.log rotations.

Other modifications in this snapshot are pre-existing in-flight theme/plugin/db_content_updates work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-04-29 15:32:23 +00:00
parent 57b752f54e
commit b6df4dbb92
5385 changed files with 838580 additions and 2416 deletions
@@ -0,0 +1,594 @@
/* global wpforms_builder_providers, wpforms_builder, wpf, WPForms, WPFormsBuilder */
( function( $ ) {
var s;
var WPFormsProviders = {
settings: {
spinner: '<i class="wpforms-loading-spinner wpforms-loading-inline"></i>',
spinnerWhite: '<i class="wpforms-loading-spinner wpforms-loading-inline wpforms-loading-white"></i>',
},
/**
* Start the engine.
*
* @since 1.0.0
*/
init: function() {
s = this.settings;
// Document ready.
$( WPFormsProviders.ready );
WPFormsProviders.bindUIActions();
},
/**
* Document ready.
*
* @since 1.1.1
*/
ready: function() {
// Setup/cache some vars not available before.
s.form = $( '#wpforms-builder-form' );
},
/**
* Element bindings.
*
* @since 1.0.0
*/
bindUIActions: function() {
// Delete connection.
$( document ).on( 'click', '.wpforms-provider-connection-delete', function( e ) {
WPFormsProviders.connectionDelete( this, e );
} );
// Add new connection.
$( document ).on( 'click', '.wpforms-provider-connections-add', function( e ) {
WPFormsProviders.connectionAdd( this, e );
} );
// Add new provider account.
$( document ).on( 'click', '.wpforms-provider-account-add button', function( e ) {
WPFormsProviders.accountAdd( this, e );
} );
// Select provider account.
$( document ).on( 'change', '.wpforms-provider-accounts select', function( e ) {
WPFormsProviders.accountSelect( this, e );
} );
// Select account list.
$( document ).on( 'change', '.wpforms-provider-lists select', function( e ) {
WPFormsProviders.accountListSelect( this, e );
} );
// BC: Constant Contact v2, Aweber v1 and Campaign Monitor don't have JS logic for updating select fields with form fields options.
// That's why we have to refresh the form every time when change something in fields and visit the Marketing tab.
$( document ).on( 'wpformsPanelSwitch', function( e, targetPanel ) {
const legacyProviders = [ 'aweber', 'campaign-monitor', 'constant-contact' ];
const hasConfiguredLegacyProvider = legacyProviders.some( ( legacyProvider ) => $( `.wpforms-panel-content-section-${ legacyProvider } .wpforms-provider-connection` ).length > 0 );
if ( hasConfiguredLegacyProvider ) {
WPFormsProviders.providerPanelConfirm( targetPanel );
}
} );
// Alert users if they save a form and do not configure required
// fields.
$( document ).on( 'wpformsSaved', function( e, data ) {
var providerAlerts = [];
var $connectionBlocks = $( '#wpforms-panel-providers' ).find( '.wpforms-connection-block' );
if ( ! $connectionBlocks.length ) {
return;
}
$connectionBlocks.each( function() {
var requiredEmpty = false,
providerName;
$( this ).find( 'table span.required' ).each( function() {
var $element = $( this ).parent().parent().find( 'select' );
if ( $element.val() === '' ) {
requiredEmpty = true;
}
} );
if ( requiredEmpty ) {
var $titleArea = $( this ).closest( '.wpforms-panel-content-section' ).find( '.wpforms-panel-content-section-title' ).clone();
$titleArea.find( 'button' ).remove();
providerName = $titleArea.text().trim();
var msg = wpforms_builder.provider_required_flds;
if ( -1 < providerAlerts.indexOf( providerName ) ) {
return;
}
$.alert( {
title: wpforms_builder.heads_up,
content: msg.replace( '{provider}', providerName ),
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
providerAlerts.push( providerName );
}
} );
} );
},
/**
* Delete provider connection
*
* @since 1.0.0
*/
connectionDelete: function( el, e ) {
e.preventDefault();
var $this = $( el );
$.confirm( {
title: false,
content: wpforms_builder_providers.confirm_connection,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action: function() {
const $section = $this.closest( '.wpforms-panel-content-section' );
$this.closest( '.wpforms-provider-connection' ).remove();
// Update sidebar icon near the provider.
const provider = $this.closest( '.wpforms-provider-connection' ).data( 'provider' ),
$sidebarItem = $( '.wpforms-panel-sidebar-section-' + provider );
$sidebarItem.find( '.fa-check-circle-o' ).toggleClass( 'wpforms-hidden', $( $section ).find( '.wpforms-provider-connection' ).length <= 0 );
if ( ! $section.find( '.wpforms-provider-connection' ).length ) {
$section.find( '.wpforms-builder-provider-connections-default' ).removeClass( 'wpforms-hidden' );
}
},
},
cancel: {
text: wpforms_builder.cancel,
},
},
} );
},
/**
* Add new provider connection.
*
* @since 1.0.0
*/
connectionAdd: function( el, e ) {
e.preventDefault();
var $this = $( el ),
$connections = $this.parent().parent(),
$container = $this.parent(),
provider = $this.data( 'provider' ),
defaultValue = WPFormsProviders.getDefaultConnectionName( provider ).trim(),
type = $this.data( 'type' ),
namePrompt = wpforms_builder_providers.prompt_connection,
nameField = '<input ' + ( defaultValue === '' ? ' autofocus=""' : '' ) + ' type="text" id="provider-connection-name" placeholder="' + wpforms_builder_providers.prompt_placeholder + '" value="' + defaultValue + '">',
nameError = '<p class="error">' + wpforms_builder_providers.error_name + '</p>',
modalContent = namePrompt + nameField + nameError;
modalContent = modalContent.replace( /%type%/g, type );
$.confirm( {
title: false,
content: modalContent,
icon: 'fa fa-info-circle',
type: 'blue',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action: function() {
var name = this.$content.find( 'input#provider-connection-name' ).val().trim();
var error = this.$content.find( '.error' );
if ( name === '' ) {
error.show();
return false;
} else {
// Disable button.
WPFormsProviders.inputToggle( $this, 'disable' );
// Fire AJAX.
var data = {
action : 'wpforms_provider_ajax_' + provider,
provider: provider,
task : 'new_connection',
name : name,
id : s.form.data( 'id' ),
nonce : wpforms_builder.nonce,
};
WPFormsProviders.fireAJAX( $this, data, function( res ) {
if ( res.success ) {
$connections.find( '.wpforms-builder-provider-connections-default' ).addClass( 'wpforms-hidden' );
$connections.find( '.wpforms-provider-connections' ).prepend( res.data.html );
// Process and load the accounts if they exist.
var $connection = $connections.find( '.wpforms-provider-connection' ).first();
if ( $connection.find( '.wpforms-provider-accounts option:selected' ) ) {
$connection.find( '.wpforms-provider-accounts option' ).first().prop( 'selected', true );
$connection.find( '.wpforms-provider-accounts select' ).trigger( 'change' );
}
} else {
WPFormsProviders.errorDisplay( res.data.error, $container );
}
} );
}
},
},
cancel: {
text: wpforms_builder.cancel,
},
},
} );
},
/**
* Add and authorize provider account.
*
* @since 1.0.0
*/
accountAdd: function( el, e ) {
e.preventDefault();
var $this = $( el ),
provider = $this.data( 'provider' ),
$connection = $this.closest( '.wpforms-provider-connection' ),
$container = $this.parent(),
$fields = $container.find( ':input' ),
errors = WPFormsProviders.requiredCheck( $fields, $container );
// Disable button.
WPFormsProviders.inputToggle( $this, 'disable' );
// Bail if we have any errors.
if ( errors ) {
$this.prop( 'disabled', false ).find( 'i' ).remove();
return false;
}
// Fire AJAX.
var data = {
action : 'wpforms_provider_ajax_' + provider,
provider : provider,
connection_id: $connection.data( 'connection_id' ),
task : 'new_account',
data : WPFormsProviders.fakeSerialize( $fields ),
};
WPFormsProviders.fireAJAX( $this, data, function( res ) {
if ( res.success ) {
$container.nextAll( '.wpforms-connection-block' ).remove();
$container.nextAll( '.wpforms-conditional-block' ).remove();
$container.after( res.data.html );
$container.slideUp();
$connection.find( '.wpforms-provider-accounts select' ).trigger( 'change' );
} else {
WPFormsProviders.errorDisplay( res.data.error, $container );
}
} );
},
/**
* Selecting a provider account
*
* @since 1.0.0
*/
accountSelect: function( el, e ) {
e.preventDefault();
var $this = $( el ),
$connection = $this.closest( '.wpforms-provider-connection' ),
$container = $this.parent(),
provider = $connection.data( 'provider' );
// Disable select, show loading.
WPFormsProviders.inputToggle( $this, 'disable' );
// Remove any blocks that might exist as we prep for new account.
$container.nextAll( '.wpforms-connection-block' ).remove();
$container.nextAll( '.wpforms-conditional-block' ).remove();
if ( ! $this.val() ) {
// User selected to option to add new account.
$connection.find( '.wpforms-provider-account-add input' ).val( '' );
$connection.find( '.wpforms-provider-account-add' ).slideDown();
WPFormsProviders.inputToggle( $this, 'enable' );
} else {
$connection.find( '.wpforms-provider-account-add' ).slideUp();
// Fire AJAX.
var data = {
action : 'wpforms_provider_ajax_' + provider,
provider : provider,
connection_id: $connection.data( 'connection_id' ),
task : 'select_account',
account_id : $this.find( ':selected' ).val(),
};
WPFormsProviders.fireAJAX( $this, data, function( res ) {
if ( res.success ) {
$container.after( res.data.html );
// Process first list found.
$connection.find( '.wpforms-provider-lists option' ).first().prop( 'selected', true );
$connection.find( '.wpforms-provider-lists select' ).trigger( 'change' );
} else {
WPFormsProviders.errorDisplay( res.data.error, $container );
}
} );
}
},
/**
* Selecting a provider account list.
*
* @since 1.0.0
*/
accountListSelect: function( el, e ) {
e.preventDefault();
var $this = $( el ),
$connection = $this.closest( '.wpforms-provider-connection' ),
$container = $this.parent(),
provider = $connection.data( 'provider' );
// Disable select, show loading.
WPFormsProviders.inputToggle( $this, 'disable' );
// Remove any blocks that might exist as we prep for new account.
$container.nextAll( '.wpforms-connection-block' ).remove();
$container.nextAll( '.wpforms-conditional-block' ).remove();
var data = {
action : 'wpforms_provider_ajax_' + provider,
provider : provider,
connection_id: $connection.data( 'connection_id' ),
task : 'select_list',
account_id : $connection.find( '.wpforms-provider-accounts option:selected' ).val(),
list_id : $this.find( ':selected' ).val(),
form_id : s.form.data( 'id' ),
};
WPFormsProviders.fireAJAX( $this, data, function( res ) {
if ( res.success ) {
$container.after( res.data.html );
// Re-init tooltips for new fields.
wpf.initTooltips();
} else {
WPFormsProviders.errorDisplay( res.data.error, $container );
}
} );
},
/**
* Confirm form save before loading Provider panel.
* If confirmed, save and reload panel.
*
* @since 1.0.0
*/
providerPanelConfirm: function( targetPanel ) {
wpforms_panel_switch = true;
if ( targetPanel === 'providers' && ! s.form.data( 'revision' ) ) {
if ( ! WPFormsBuilder.formIsSaved() ) {
wpforms_panel_switch = false;
$.confirm( {
title: false,
content: wpforms_builder_providers.confirm_save,
icon: 'fa fa-info-circle',
type: 'blue',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action: function() {
$( '#wpforms-save' ).trigger( 'click' );
$( document ).on( 'wpformsSaved', function() {
let wpforms_builder_provider_url = wpforms_builder_providers.url;
const $section = $( `#wpforms-panel-${ targetPanel } .wpforms-panel-sidebar-section.active` );
const section = $section.length && $section.data( 'section' ) !== 'default' ? $section.data( 'section' ) : null;
// Adding an active section parameter.
if ( section ) {
wpforms_builder_provider_url += `&section=${ section }`;
}
window.location.href = wpforms_builder_provider_url;
} );
},
},
cancel: {
text: wpforms_builder.cancel,
},
},
} );
}
}
},
//--------------------------------------------------------------------//
// Helper functions.
//--------------------------------------------------------------------//
/**
* Fire AJAX call.
*
* @since 1.0.0
*/
fireAJAX: function( el, d, success ) {
var $this = $( el );
var data = {
id : $( '#wpforms-builder-form' ).data( 'id' ),
nonce : wpforms_builder.nonce,
};
$.extend( data, d );
$.post( wpforms_builder.ajax_url, data, function( res ) {
success( res );
WPFormsProviders.inputToggle( $this, 'enable' );
} ).fail( function( xhr, textStatus, e ) {
console.log( xhr.responseText );
} );
},
/**
* Toggle input with loading indicator.
*
* @since 1.0.0
*/
inputToggle: function( el, status ) {
var $this = $( el );
if ( status === 'enable' ) {
if ( $this.is( 'select' ) ) {
$this.prop( 'disabled', false ).next( 'i' ).remove();
} else {
$this.prop( 'disabled', false ).find( 'i' ).remove();
}
} else if ( status === 'disable' ) {
if ( $this.is( 'select' ) ) {
$this.prop( 'disabled', true ).after( s.spinner );
} else {
$this.prop( 'disabled', true ).prepend( s.spinnerWhite );
}
}
},
/**
* Display error.
*
* @since 1.0.0
*/
errorDisplay: function( msg, location ) {
location.find( '.wpforms-error-msg' ).remove();
location.prepend( '<p class="wpforms-alert-danger wpforms-alert wpforms-error-msg">' + msg + '</p>' );
},
/**
* Check for required fields.
*
* @since 1.0.0
*/
requiredCheck: function( fields, location ) {
var error = false;
// Remove any previous errors.
location.find( '.wpforms-alert-required' ).remove();
// Loop through input fields and check for values.
fields.each( function( index, el ) {
if ( $( el ).hasClass( 'wpforms-required' ) && $( el ).val().length === 0 ) {
$( el ).addClass( 'wpforms-error' );
error = true;
} else {
$( el ).removeClass( 'wpforms-error' );
}
} );
if ( error ) {
location.prepend( '<p class="wpforms-alert-danger wpforms-alert wpforms-alert-required">' + wpforms_builder_providers.required_field + '</p>' );
}
return error;
},
/**
* Pseudo serializing. Fake it until you make it.
*
* @since 1.0.0
*/
fakeSerialize: function( els ) {
var fields = els.clone();
fields.each( function( index, el ) {
if ( $( el ).data( 'name' ) ) {
$( el ).attr( 'name', $( el ).data( 'name' ) );
}
} );
return fields.serialize();
},
/**
* Get the default name for a new connection.
*
* @since 1.9.3
*
* @param {string} provider Current provider slug.
*
* @return {string} Returns the default name for a new connection.
*/
getDefaultConnectionName( provider ) {
const providerName = $( `#${ provider }-provider` ).data( 'provider-name' );
const numberOfConnections = WPFormsProviders.getCountConnectionsOf( provider );
const defaultName = `${ providerName } ${ wpforms_builder.connection_label }`;
return numberOfConnections < 1 ? defaultName : '';
},
/**
* Get the number of connections for the provider.
*
* @since 1.9.3
*
* @param {string} provider Current provider slug.
*
* @return {number} Returns the number of connections for the provider.
*/
getCountConnectionsOf( provider ) {
return $( `#${ provider }-provider .wpforms-provider-connection` ).length;
},
/**
* Get a provider JS object.
*
* @since 1.9.3
* @deprecated 1.9.5 Not used anymore.
*
* @param {string} provider Provider name.
*
* @return {Object|null} Return provider object or null.
*/
getProviderClass( provider ) {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "WPFormsProviders.getProviderClass()" has been deprecated!' );
const upperProviderPart = ( providerPart ) => (
providerPart.charAt( 0 ).toUpperCase() + providerPart.slice( 1 )
);
const getClassName = provider.split( '-' ).map( upperProviderPart ).join( '' );
if ( typeof WPForms?.Admin?.Builder?.Providers?.[ getClassName ] === 'undefined' ) {
return null;
}
return WPForms.Admin.Builder.Providers[ getClassName ];
},
};
WPFormsProviders.init();
} )( jQuery );
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,81 @@
/**
* Chocolate choices' functionality.
*
* @since 1.9.6.1
*/
// eslint-disable-next-line no-unused-vars
const WPFormsChocolateChoices = {
/**
* Initializes the chocolate choices component.
*
* @since 1.9.6.1
*
* @param {Object} $grid The grid container for the choices.
* @param {Object} options Object with name and choices properties.
* @param {string} options.name The name attribute for the checkbox inputs.
* @param {Array} options.choices Array of choice objects with label and value properties.
* @param {Array} options.selected Array of selected choice values.
*/
init( $grid, options ) {
const selected = options.selected?.map?.( String ) ?? [];
const $ = jQuery;
/**
* Generate a random ID string.
* The ID is based on current timestamp and converted to a base-16 string.
*
* @since 1.9.6.1
*
* @return {string} A hexadecimal string representation of the current timestamp.
*/
const getRandomId = () => new Date().getTime().toString( 16 );
/**
* Creates a single choice item.
*
* @since 1.9.6.1
*
* @param {Object|string|number} itemData The choice data object with label and value.
* @param {number} index The index of the choice item.
*
* @return {jQuery} The created choice item element.
*/
const createChoiceItem = ( itemData, index ) => {
const id = `choice-${ index }-${ getRandomId() }`;
const itemValue = String( typeof itemData === 'object' ? itemData.value : itemData );
// Create the container div.
const $itemDiv = $( '<div>', {
class: 'choice-item',
} );
// Create the checkbox input.
const $checkbox = $( '<input>', {
type: 'checkbox',
id,
value: itemValue,
checked: selected.includes( itemValue ),
name: options.name.replace( '{index}', index ),
} );
// Create the label.
const $label = $( '<label>', { for: id } );
$label.text( itemData.label ?? itemData );
// Append elements.
$itemDiv.append( $checkbox, $label );
return $itemDiv;
};
// Clear existing content.
$grid.html( '' );
const choices = [];
// Populate the grid with items.
$.each( options.choices, function( index, choiceData ) {
choices.push( createChoiceItem( choiceData, index ) );
} );
$grid.append( choices );
},
};
@@ -0,0 +1 @@
let WPFormsChocolateChoices={init(e,n){let l=n.selected?.map?.(String)??[],o=jQuery,c=(e,t)=>{var c=`choice-${t}-`+(new Date).getTime().toString(16),i=String("object"==typeof e?e.value:e),a=o("<div>",{class:"choice-item"}),i=o("<input>",{type:"checkbox",id:c,value:i,checked:l.includes(i),name:n.name.replace("{index}",t)}),t=o("<label>",{for:c});return t.text(e.label??e),a.append(i,t),a},i=(e.html(""),[]);o.each(n.choices,function(e,t){i.push(c(t,e))}),e.append(i)}};
@@ -0,0 +1,131 @@
/**
* Checkbox selection functionality.
*
* @since 1.9.6.1
*/
// eslint-disable-next-line no-unused-vars
const WPFormsChoicesList = {
/**
* Creates and returns a checkbox selection manager.
*
* @since 1.9.6.1
*
* @param {jQuery} $container The container element with checkboxes.
*/
init( $container ) {
if ( $container.data( 'choices-list-initialized' ) ) {
return;
}
$container.data( 'choices-list-initialized', true );
// Private variables to store state.
const $selectAllCheckbox = $container.find( 'input[value="select-all"]' );
const $itemCheckboxes = $container.find( '.item-checkbox' );
/**
* Checks if the container has the necessary elements to initialize.
*
* @since 1.9.6.1
*
* @param {Object} $rootContainer The root container element to query for required elements.
*
* @return {boolean} True if the root container contains specific elements required for initialization, otherwise false.
*/
const canInitialize = ( $rootContainer ) => {
const hasSelectAll = $rootContainer.find( 'input[value="select-all"]' ).length > 0;
const hasItemCheckboxes = $rootContainer.find( '.item-checkbox' ).length > 0;
return hasSelectAll && hasItemCheckboxes;
};
/**
* Updates the state of the "Select All" checkbox based on the current
* state of individual item checkboxes. Determines whether all, none,
* or some items are selected and updates the "Select All" checkbox.
*
* @since 1.9.6.1
*/
const updateSelectAllState = () => {
this.updateSelectAllState( $container );
};
/**
* Handles the change event for the "Select All" checkbox. Updates the state
* of individual item checkboxes and clears the indeterminate state of the
* "Select All" checkbox.
*
* @since 1.9.6.1
*
* @param {Event} event The event object associated with the change event triggered on the "Select All" checkbox.
*/
const handleSelectAllChange = ( event ) => {
const isChecked = jQuery( event.target ).prop( 'checked' );
// Update all item checkboxes.
$itemCheckboxes.prop( 'checked', isChecked );
// Clear indeterminate state.
$selectAllCheckbox.prop( 'indeterminate', false );
};
/**
* Binds event listeners to the "select all" checkbox and item checkboxes.
*
* @since 1.9.6.1
*/
const bindEvents = () => {
// Add event listener to "select all" checkbox.
$selectAllCheckbox.on( 'change', handleSelectAllChange );
// Add event listeners to item checkboxes.
$itemCheckboxes.on( 'change', updateSelectAllState );
};
// Return early if required elements aren't found.
if ( ! canInitialize( $container ) ) {
return;
}
// Initialize event bindings.
bindEvents();
// Initialize the state.
updateSelectAllState();
},
/**
* Updates the select all checkbox state based on the current checked state of item checkboxes.
* This method can be called from outside the class to refresh the state after DOM changes.
*
* @since 1.9.6.1
*
* @param {jQuery} $container The container element with checkboxes.
*/
updateSelectAllState( $container ) {
const $selectAllCheckbox = $container.find( 'input[value="select-all"]' );
const $itemCheckboxes = $container.find( '.item-checkbox' );
const totalItems = $itemCheckboxes.length;
const checkedItems = $itemCheckboxes.filter( ':checked' ).length;
if ( checkedItems === 0 ) {
// None checked.
$selectAllCheckbox.prop( {
checked: false,
indeterminate: false,
} );
} else if ( checkedItems === totalItems ) {
// All checked.
$selectAllCheckbox.prop( {
checked: true,
indeterminate: false,
} );
} else {
// Some checked.
$selectAllCheckbox.prop( {
checked: false,
indeterminate: true,
} );
}
},
};
@@ -0,0 +1 @@
let WPFormsChoicesList={init(l){if(!l.data("choices-list-initialized")){l.data("choices-list-initialized",!0);let t=l.find('input[value="select-all"]'),i=l.find(".item-checkbox");let e=()=>{this.updateSelectAllState(l)},c=e=>{e=jQuery(e.target).prop("checked");i.prop("checked",e),t.prop("indeterminate",!1)};var n,a;a=0<(n=l).find('input[value="select-all"]').length,n=0<n.find(".item-checkbox").length,a&&n&&(t.on("change",c),i.on("change",e),e())}},updateSelectAllState(e){var t=e.find('input[value="select-all"]'),e=e.find(".item-checkbox"),i=e.length,e=e.filter(":checked").length;0===e?t.prop({checked:!1,indeterminate:!1}):e===i?t.prop({checked:!0,indeterminate:!1}):t.prop({checked:!1,indeterminate:!0})}};
@@ -0,0 +1,714 @@
// noinspection ES6ConvertVarToLetConst
/* global wpf, WPFormsBuilder, WPSplash */
/**
* Context menu module.
*
* @since 1.8.6
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.ContextMenu = WPForms.Admin.Builder.ContextMenu || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.8.6
*
* @type {Object}
*/
const el = {};
/**
* Public functions and properties.
*
* @since 1.8.6
*
* @type {Object}
*/
const app = {
/**
* CSS selectors.
*
* @since 1.8.6
*
* @type {Object}
*/
selectors: {
contextMenu: '.wpforms-context-menu',
mainContextMenuContainer: '#wpforms-context-menu-container',
mainContextMenu: '#wpforms-context-menu',
fieldContextMenu: '#wpforms-field-context-menu',
contextMenuItem: '.wpforms-context-menu:not(.wpforms-context-menu-dropdown) .wpforms-context-menu-list-item',
contextMenuSelectiveItem: '.wpforms-context-menu-list-item-selective',
contextMenuDivider: '.wpforms-context-menu .wpforms-context-menu-list-divider',
builder: '#wpforms-builder',
sidebarToggle: '.wpforms-panels .wpforms-panel-sidebar-content .wpforms-panel-sidebar-toggle',
},
/**
* Start the engine. DOM is not ready yet, use only to init something.
*
* @since 1.8.6
*/
init() {
$( app.ready );
},
/**
* DOM is fully loaded.
*
* @since 1.8.6
*/
ready() {
app.setup();
app.events();
},
/**
* Setup. Prepare some variables.
*
* @since 1.8.6
*/
setup() {
// Cache DOM elements.
el.$document = $( document );
el.$contextMenu = $( app.selectors.contextMenu );
el.$mainContextMenuContainer = $( app.selectors.mainContextMenuContainer );
el.$mainContextMenu = $( app.selectors.mainContextMenu );
el.$fieldContextMenu = $( app.selectors.fieldContextMenu );
el.$contextMenuItem = $( app.selectors.contextMenuItem );
el.$contextMenuSelectiveItem = $( app.selectors.contextMenuSelectiveItem );
el.$contextMenuDivider = $( app.selectors.contextMenuDivider );
el.$builder = $( app.selectors.builder );
el.$sidebarToggle = $( app.selectors.sidebarToggle );
},
/**
* Bind events.
*
* @since 1.8.6
*/
events() {
// Display a main menu on click on the icon in the toolbar.
el.$mainContextMenuContainer.on( 'click', ( event ) => {
event.preventDefault();
el.$mainContextMenu.fadeToggle( 150, () => {
el.$mainContextMenuContainer.toggleClass( 'wpforms-context-menu-active' );
} );
} );
// Handle clicks on the main menu items.
el.$mainContextMenu.on( 'click', '.wpforms-context-menu-list-item', app.mainMenuItemClickAction );
// Hide the main menu if it's visible when clicking outside it.
el.$builder.on( 'click contextmenu', app.hideMainContextMenu );
// Display a context menu on right-click on the form field in the preview area.
el.$document.on( 'contextmenu', app.rightClickContextMenuHandler );
el.$document.on( 'click', app.hideMenuOnClick );
el.$builder.on( 'wpformsFieldTabToggle', app.hideMenuOnClick );
},
/**
* Right-click context menu handler.
*
* @since 1.8.8
*
* @param {KeyboardEvent} e Event object.
*/
rightClickContextMenuHandler( e ) {
const $field = $( e.target ).closest( '.wpforms-field' );
const $panel = $field.closest( '#wpforms-panel-fields' );
if ( ! $panel.length || ! $field.length || $( e.target ).closest( app.selectors.contextMenu ).length ) {
return;
}
app.hideMenu();
if ( e.ctrlKey ) {
return;
}
e.preventDefault();
setTimeout( function() {
app.checkMenuItemsVisibility( $field );
app.checkDividerVisibility();
app.menuPositioning( e );
app.menuItemClickAction( $field );
app.checkSelectiveMenuItemsState( $field );
}, 150 );
},
/**
* Hide the main context menu when clicking outside it.
*
* @since 1.8.8
*
* @param {Event} event Event object.
*/
hideMainContextMenu( event ) {
if ( el.$mainContextMenu.is( ':hidden' ) || $( event.target ).closest( app.selectors.mainContextMenuContainer ).length > 0 ) {
return;
}
el.$mainContextMenu.fadeOut( 150, () => {
el.$mainContextMenuContainer.removeClass( 'wpforms-context-menu-active' );
} );
},
/**
* Main menu item click action.
*
* @since 1.8.8
*/
mainMenuItemClickAction() {
const $item = $( this );
const action = $item.data( 'action' );
const actionUrl = $item.data( 'action-url' ) ?? '';
const actionHandlers = {
'duplicate-form': () => app.handleUrlAction( actionUrl, false, true ),
'save-as-template': () => app.handleUrlAction( actionUrl, false, true ),
'duplicate-template': () => app.handleUrlAction( actionUrl, false, true ),
'view-entries': () => app.handleUrlAction( actionUrl, true ),
'view-payments': () => app.handleUrlAction( actionUrl, true ),
'keyboard-shortcuts': WPFormsBuilder.openKeyboardShortcutsModal,
'whats-new': app.handleWhatsNewAction,
};
const handler = actionHandlers[ action ];
if ( handler ) {
handler();
}
},
/**
* Menu item click action.
*
* @since 1.8.6
*
* @param {Object} $field Field object.
*/
menuItemClickAction( $field ) {
const fieldId = $field.data( 'field-id' );
el.$contextMenuItem.off( 'click' ).on( 'click', function() {
const $item = $( this );
if ( $item.hasClass( 'wpforms-context-menu-list-item-has-child' ) ) {
return;
}
const action = $item.data( 'action' );
const actionHandlers = {
edit: () => app.handleEditAction( $field, fieldId ),
duplicate: () => app.handleDuplicateAction( $field ),
delete: () => app.handleDeleteAction( $field ),
required: () => app.handleRequiredAction( $item, fieldId ),
label: () => app.handleLabelAction( $item, fieldId ),
'smart-logic': () => app.handleSmartLogicAction( $field, fieldId ),
'field-size': () => app.handleSizeAction( $item, fieldId ),
};
const handler = actionHandlers[ action ];
if ( handler ) {
handler();
}
app.hideMenu();
} );
},
/**
* Handle edit action.
*
* @since 1.8.6
*
* @param {Object} $field Field object.
* @param {string} fieldId Field ID.
*/
handleEditAction( $field, fieldId ) {
$field.trigger( 'click' );
// This is needed to make sure the sidebar is open when the "Edit" button is clicked.
app.maybeOpenSidebar();
$( `#wpforms-field-option-basic-${ fieldId } .wpforms-field-option-group-toggle` ).trigger( 'click' );
},
/**
* Handle duplicate action.
*
* @since 1.8.6
*
* @param {Object} $field Field object.
*/
handleDuplicateAction( $field ) {
$field.find( '.wpforms-field-duplicate' ).first().trigger( 'click' );
},
/**
* Handle delete action.
*
* @since 1.8.6
*
* @param {Object} $field Field object.
*/
handleDeleteAction( $field ) {
$field.find( '.wpforms-field-delete' ).first().trigger( 'click' );
},
/**
* Handle required action.
*
* @since 1.8.6
*
* @param {Object} $item Menu item object.
* @param {string} fieldId Field ID.
*/
handleRequiredAction( $item, fieldId ) {
$( `#wpforms-field-option-${ fieldId }-required` ).trigger( 'click' );
const state = app.checkRequiredState( fieldId ) ? 'active' : 'inactive';
app.toggleItemText( $item, state );
},
/**
* Handle label action.
*
* @since 1.8.6
*
* @param {Object} $item Menu item object.
* @param {string} fieldId Field ID.
*/
handleLabelAction( $item, fieldId ) {
$( `#wpforms-field-option-${ fieldId }-label_hide` ).trigger( 'click' );
const state = app.checkLabelState( fieldId ) ? 'active' : 'inactive';
app.toggleItemText( $item, state );
},
/**
* Handle smart logic action.
*
* @since 1.8.6
*
* @param {Object} $field Field object.
* @param {string} fieldId Field ID.
*/
handleSmartLogicAction( $field, fieldId ) {
// This is needed to make sure the sidebar is open when the "Edit Conditional Logic" button is clicked.
app.maybeOpenSidebar();
$field.trigger( 'click' );
$( `#wpforms-field-option-conditionals-${ fieldId } .wpforms-field-option-group-toggle` ).trigger( 'click' );
$( `#wpforms-field-option-${ fieldId } .wpforms-field-option-group-conditionals .education-modal` ).trigger( 'click' );
},
/**
* Handle size action.
*
* @since 1.8.6
*
* @param {Object} $item Menu item object.
* @param {string} fieldId Field ID.
*/
handleSizeAction( $item, fieldId ) {
const value = $item.data( 'value' );
$( `#wpforms-field-option-${ fieldId }-size` ).val( value ).trigger( 'change' );
$item.addClass( 'wpforms-context-menu-list-item-active' ).siblings().removeClass( 'wpforms-context-menu-list-item-active' );
},
/**
* Handle "What's New" action.
*
* @since 1.8.8
*/
handleWhatsNewAction() {
const modal = $( '#tmpl-wpforms-splash-modal-content' );
if ( modal.length && typeof WPSplash !== 'undefined' ) {
WPSplash.openModal();
}
},
/**
* Handle a simple URL action.
*
* @since 1.8.8
*
* @param {string} actionUrl URL.
* @param {boolean} newTab Whether to open the URL in a new tab.
* @param {boolean} saveForm Whether to save the form before following the action URL.
*/
handleUrlAction( actionUrl, newTab = false, saveForm = false ) {
if ( ! actionUrl ) {
return;
}
// The form does not need to be saved, open the URL.
if ( ! saveForm ) {
newTab ? window.open( actionUrl ) : window.location.assign( actionUrl ); // eslint-disable-line no-unused-expressions
return;
}
// The form was changed and must be saved before following the action URL.
if ( ! WPFormsBuilder.formIsSaved() ) {
el.$builder.on( 'wpformsSaved', () => {
newTab ? window.open( actionUrl ) : window.location.assign( actionUrl ); // eslint-disable-line no-unused-expressions
} );
WPFormsBuilder.formSave( false );
return;
}
// The form was not changed, open the URL.
newTab ? window.open( actionUrl ) : window.location.assign( actionUrl ); // eslint-disable-line no-unused-expressions
},
/**
* Toggle item text.
*
* @since 1.8.6
*
* @param {Object} $item Menu item object.
* @param {string} state State.
*/
toggleItemText( $item, state ) {
const $text = $item.find( '.wpforms-context-menu-list-item-text' );
const activeText = $text.data( 'active-text' );
const inactiveText = $text.data( 'inactive-text' ) || $text.text();
if ( ! activeText ) {
return;
}
$text.data( 'inactive-text', inactiveText );
$text.text( state === 'active' ? activeText : inactiveText );
},
/**
* Check selective menu items state.
*
* @since 1.8.6
*
* @param {Object} $field Field object.
*/
checkSelectiveMenuItemsState( $field ) {
const fieldId = $field.data( 'field-id' );
el.$contextMenuSelectiveItem.each( function() {
const $item = $( this );
const action = $item.data( 'action' );
const value = $item.data( 'value' );
const shouldChangeStateHandlers = {
required: () => app.checkRequiredState( fieldId ),
label: () => app.checkLabelState( fieldId ),
'field-size': () => app.checkFieldSizeState( fieldId, value ),
};
const handler = shouldChangeStateHandlers[ action ];
if ( handler() ) {
$item.addClass( 'wpforms-context-menu-list-item-active' );
app.toggleItemText( $item, 'active' );
} else {
$item.removeClass( 'wpforms-context-menu-list-item-active' );
app.toggleItemText( $item, 'inactive' );
}
} );
},
/**
* Check the required state.
*
* @since 1.8.6
*
* @param {string} fieldId Field ID.
*
* @return {boolean} True if option checked.
*/
checkRequiredState( fieldId ) {
return $( `#wpforms-field-option-${ fieldId }-required[type="checkbox"]` ).is( ':checked' );
},
/**
* Check label state.
*
* @since 1.8.6
*
* @param {string} fieldId Field ID.
*
* @return {boolean} True if option checked.
*/
checkLabelState( fieldId ) {
return $( `#wpforms-field-option-${ fieldId }-label_hide[type="checkbox"]` ).is( ':checked' );
},
/**
* Check field size state.
*
* @since 1.8.6
*
* @param {string} fieldId Field ID.
* @param {string} value Value.
*
* @return {boolean} True if value equals.
*/
checkFieldSizeState( fieldId, value ) {
return $( `#wpforms-field-option-${ fieldId }-size` ).val() === value;
},
/**
* Menu positioning.
*
* @since 1.8.6
*
* @param {Object} e Event object.
*/
menuPositioning( e ) {
const menuWidth = el.$fieldContextMenu.width();
const menuHeight = el.$fieldContextMenu.height();
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
el.$fieldContextMenu.removeClass( 'wpforms-context-menu-selective-left' );
let topPosition = e.pageY;
let leftPosition = e.pageX;
if ( e.pageY + menuHeight > windowHeight ) {
topPosition = windowHeight - menuHeight - 15;
}
if ( e.pageX + menuWidth > windowWidth ) {
leftPosition = windowWidth - menuWidth - 15;
el.$fieldContextMenu.addClass( 'wpforms-context-menu-selective-left' );
}
el.$fieldContextMenu.css( {
top: topPosition + 'px',
left: leftPosition + 'px',
} );
el.$fieldContextMenu.fadeIn( 150 );
},
/**
* Check menu items visibility.
*
* @since 1.8.6
*
* @param {Object} $field jQuery object.
*/
checkMenuItemsVisibility( $field ) {
const fieldId = $field.data( 'field-id' );
const shouldHideHandlers = {
edit: () => app.shouldHideEdit( $field ),
duplicate: () => app.shouldHideDuplicate( $field ),
delete: () => app.shouldHideDelete( $field ),
required: () => app.shouldHideRequired( fieldId ),
label: () => app.shouldHideLabel( fieldId ),
'smart-logic': () => app.shouldHideSmartLogic( fieldId ),
'field-size': () => app.shouldHideFieldSize( fieldId, $field ),
};
el.$contextMenuItem.each( function() {
const $item = $( this );
const action = $item.data( 'action' );
const handler = shouldHideHandlers[ action ];
if ( handler() ) {
$item.hide();
}
} );
},
/**
* Check edit visibility.
*
* @since 1.8.7
*
* @param {Object} $field Field object.
*
* @return {boolean} True when should hide.
*/
shouldHideEdit( $field ) {
return $field.hasClass( 'internal-information-not-editable' );
},
/**
* Check duplicate visibility.
*
* @since 1.8.6
*
* @param {Object} $field Field object.
*
* @return {boolean} True when should hide.
*/
shouldHideDuplicate( $field ) {
const $duplicate = $field.find( '.wpforms-field-duplicate' );
return $duplicate.length === 0 || $duplicate.css( 'display' ) === 'none';
},
/**
* Check delete visibility.
*
* @since 1.8.6
*
* @param {Object} $field Field object.
*
* @return {boolean} True when should hide.
*/
shouldHideDelete( $field ) {
const $delete = $field.find( '.wpforms-field-delete' );
return $delete.length === 0 || $delete.css( 'display' ) === 'none';
},
/**
* Check the required visibility.
*
* @since 1.8.6
*
* @param {string} fieldId Field ID.
*
* @return {boolean} True when should hide.
*/
shouldHideRequired( fieldId ) {
return $( `#wpforms-field-option-${ fieldId }-required[type="checkbox"]` ).length === 0;
},
/**
* Check label visibility.
*
* @since 1.8.6
*
* @param {string} fieldId Field ID.
*
* @return {boolean} True when should hide.
*/
shouldHideLabel( fieldId ) {
const $label = $( `#wpforms-field-option-${ fieldId }-label_hide[type="checkbox"]` );
return $label.length === 0 || $label.parents( '.wpforms-field-option-row' ).hasClass( 'wpforms-disabled' );
},
/**
* Check field size visibility.
*
* @since 1.8.6
*
* @param {string} fieldId Field ID.
* @param {Object} $field Field object.
*
* @return {boolean} True when should hide.
*/
shouldHideFieldSize( fieldId, $field ) {
const isFieldInColumn = $field.closest( '.wpforms-layout-column' ).length > 0;
const isRepeaterField = $field.closest( '.wpforms-field-repeater' ).length > 0;
const $size = $( `#wpforms-field-option-${ fieldId }-size` );
return $size.length === 0 || isFieldInColumn || isRepeaterField || $size.parent().hasClass( 'wpforms-hidden' );
},
/**
* Check smart logic visibility.
*
* @since 1.8.6
*
* @param {string} fieldId Field ID.
*
* @return {boolean} True when should hide.
*/
shouldHideSmartLogic( fieldId ) {
return $( `#wpforms-field-option-conditionals-${ fieldId }` ).length === 0 && $( `#wpforms-field-option-${ fieldId } .wpforms-field-option-group-conditionals .education-modal` ).length === 0;
},
/**
* Check divider visibility.
*
* @since 1.8.6
*/
checkDividerVisibility() {
el.$contextMenuDivider.each( function() {
const $divider = $( this );
const visibility = $divider.data( 'visibility' ) ?? '';
let shouldHide = true;
visibility.split( ',' ).forEach( function( item ) {
if ( $( '.wpforms-context-menu-list-item[data-action="' + item.trim() + '"]' ).css( 'display' ) !== 'none' ) {
shouldHide = false;
}
} );
if ( shouldHide ) {
$divider.hide();
} else {
$divider.show();
}
} );
},
/**
* Hide menu.
*
* @since 1.8.6
*/
hideMenu() {
el.$fieldContextMenu.fadeOut( 150 );
setTimeout( function() {
el.$contextMenuItem.show();
}, 150 );
},
/**
* Hide menu on click.
*
* @since 1.8.6
*
* @param {Object} e Event object.
*/
hideMenuOnClick( e ) {
if ( $( e.target ).closest( app.selectors.contextMenu ).length ) {
return;
}
app.hideMenu();
},
/**
* Maybe open the sidebar.
*
* @since 1.8.8
*/
maybeOpenSidebar() {
// If the sidebar is already open, do nothing.
if ( ! el.$sidebarToggle.parent().hasClass( 'wpforms-panel-sidebar-closed' ) ) {
return;
}
el.$sidebarToggle.trigger( 'click' );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.ContextMenu.init();
File diff suppressed because one or more lines are too long
@@ -0,0 +1,919 @@
/* global wpforms_builder, WPFormsBuilder, WPFormsUtils */
/**
* @param wpforms_builder.field_cannot_be_reordered
*/
// noinspection ES6ConvertVarToLetConst
/**
* Form Builder Fields Drag-n-Drop module.
*
* @since 1.7.7
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.DragFields = WPForms.Admin.Builder.DragFields || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.7.7
*
* @type {Object}
*/
let el = {};
/**
* Runtime variables.
*
* @since 1.7.7
*
* @type {Object}
*/
const vars = {};
/**
* Layout field functions wrapper.
*
* @since 1.7.7
*
* @type {Object}
*/
let fieldLayout; // eslint-disable-line prefer-const
/**
* Public functions and properties.
*
* @since 1.7.7
*
* @type {Object}
*/
const app = {
/**
* Start the engine.
*
* @since 1.7.7
*/
init() {
$( app.ready );
},
/**
* DOM is fully loaded.
*
* @since 1.7.7
*/
ready() {
app.setup();
app.initSortableFields();
app.events();
},
/**
* Setup. Prepare some variables.
*
* @since 1.7.7
*/
setup() {
// Cache DOM elements.
el = {
$builder: $( '#wpforms-builder' ),
$sortableFieldsWrap: $( '#wpforms-panel-fields .wpforms-field-wrap' ),
$addFieldsButtons: $( '.wpforms-add-fields-button' ).not( '.not-draggable' ).not( '.warning-modal' ).not( '.education-modal' ),
};
},
/**
* Bind events.
*
* @since 1.7.7
*/
events() {
el.$builder
.on( 'wpformsFieldDragToggle', app.fieldDragToggleEvent )
.on( 'wpformsFieldAdd', function( e, id, type ) {
// If a layout field is added, initialize its columns.
if ( type === 'layout' ) {
setTimeout( function() {
$( '#wpforms-field-' + id ).find( '.wpforms-layout-column' ).each( function() {
app.initSortableHandler( $( this ) );
$( this ).sortable( 'enable' );
} );
}, 100 );
}
} );
$( document ).on( 'wpformsLayoutPresetChanged', app.layoutPresetChanged );
},
/**
* Disable drag & drop.
*
* @since 1.7.1
* @since 1.7.7 Moved from admin-builder.js.
*/
disableDragAndDrop() {
el.$sortableFieldsWrap.trigger( 'initSortableImmediately' );
el.$addFieldsButtons.filter( '.ui-draggable' ).draggable( 'disable' );
el.$sortableFieldsWrap.sortable( 'disable' );
if ( el.$sortableFieldsWrap.find( '.wpforms-layout-column.ui-sortable' ).data( 'ui-sortable' ) ) {
el.$sortableFieldsWrap.find( '.wpforms-layout-column.ui-sortable' ).sortable( 'disable' );
}
},
/**
* Enable drag & drop.
*
* @since 1.7.1
* @since 1.7.7 Moved from admin-builder.js.
*/
enableDragAndDrop() {
el.$addFieldsButtons.filter( '.ui-draggable' ).draggable( 'enable' );
el.$sortableFieldsWrap.sortable( 'enable' );
el.$sortableFieldsWrap.find( '.wpforms-layout-column.ui-sortable' ).sortable( 'enable' );
},
/**
* Show popup in case if field is not draggable, and cancel moving.
*
* @since 1.7.5
* @since 1.7.7 Moved from admin-builder.js.
*
* @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 ) {
if ( $field.hasClass( 'ui-draggable-disabled' ) ) {
// noinspection JSUnresolvedReference
$field.draggable( 'enable' );
return;
}
let startTopPosition;
// noinspection JSUnresolvedReference
$field.draggable( {
revert: true,
axis: 'y',
delay: 100,
opacity: 1,
cursor: 'move',
start( event, ui ) {
startTopPosition = ui.position.top;
},
drag( event, ui ) {
if ( Math.abs( ui.position.top ) - Math.abs( startTopPosition ) > 15 ) {
if ( showPopUp ) {
app.youCantReorderFieldPopup();
}
return false;
}
},
} );
},
/**
* Allow field dragging.
*
* @since 1.7.5
* @since 1.7.7 Moved from admin-builder.js.
*
* @param {jQuery} $field A field or list of fields.
*/
fieldDragEnable( $field ) {
if ( $field.hasClass( 'ui-draggable' ) ) {
return;
}
// noinspection JSUnresolvedReference
$field.draggable( 'disable' );
},
/**
* Show the error message in the popup that you cannot reorder the field.
*
* @since 1.7.1
* @since 1.7.7 Moved from admin-builder.js.
*/
youCantReorderFieldPopup() {
$.confirm( {
title: wpforms_builder.heads_up,
content: wpforms_builder.field_cannot_be_reordered,
icon: 'fa fa-exclamation-circle',
type: 'red',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Event handler for `wpformsFieldDragToggle` event.
*
* @since 1.7.7
*
* @param {Object} e Event object.
* @param {number|string} id Field ID.
*/
fieldDragToggleEvent( e, id ) {
const $field = $( `#wpforms-field-${ id }` );
if (
$field.hasClass( 'wpforms-field-not-draggable' ) ||
$field.hasClass( 'wpforms-field-stick' )
) {
app.fieldDragDisable( $field );
return;
}
app.fieldDragEnable( $field );
},
/**
* Initialize sortable fields in the builder form preview area.
*
* @since 1.7.7
*/
initSortableFields() {
app.initSortableContainer( el.$sortableFieldsWrap );
// Function to initialize all layout columns.
const initAllLayoutColumns = function() {
el.$builder.find( '.wpforms-layout-column' ).each( function() {
app.initSortableHandler( $( this ) );
$( this ).sortable( 'enable' );
} );
};
// Initialize immediately.
initAllLayoutColumns();
// And again after a short delay to ensure all DOM elements are loaded.
setTimeout( initAllLayoutColumns, 500 );
app.fieldDragDisable( $( '.wpforms-field-not-draggable, .wpforms-field-stick' ) );
app.initDraggableFields();
},
/**
* Initialize sortable container with fields.
*
* @since 1.7.7
*
* @param {jQuery} $sortable Container to make sortable.
*/
async initSortableContainer( $sortable ) {
app.initSortableHandler( $sortable );
},
/**
* Event handler for `wpformsLayoutPresetChanged` event.
*
* @since 1.9.6
*
* @param {Object} event Event object.
* @param {Object} fieldOptions Field options.
*/
async layoutPresetChanged( event, fieldOptions ) { // eslint-disable-line no-unused-vars
const $fieldOptions = $( fieldOptions ),
fieldId = $fieldOptions.data( 'field-id' ),
$sortable = $( `#wpforms-field-${ fieldId } .wpforms-layout-column` );
// Immediately initialize all columns in this layout.
$sortable.each( function() {
app.initSortableHandler( $( this ) );
// Make sure sortable is not disabled, prevents from double initialization.
$( this ).sortable( 'enable' );
} );
},
/**
* Initialize sortable handler.
*
* @since 1.9.6
*
* @param {jQuery} $sortable Sortable container.
*/
initSortableHandler( $sortable ) { // eslint-disable-line max-lines-per-function
const $fieldOptions = $( '#wpforms-field-options' );
const $scrollContainer = $( '#wpforms-panel-fields .wpforms-panel-content-wrap' );
let fieldId,
fieldType,
isNewField,
$fieldOption,
$prevFieldOption,
prevFieldId,
currentlyScrolling = false;
$sortable.sortable( {
items: '> .wpforms-field:not(.wpforms-field-stick):not(.no-fields-preview)',
connectWith: '.wpforms-field-wrap, .wpforms-layout-column',
delay: 100,
opacity: 1,
cursor: 'move',
cancel: '.wpforms-field-not-draggable',
placeholder: 'wpforms-field-drag-placeholder',
appendTo: '#wpforms-panel-fields',
zindex: 10000,
tolerance: 'pointer',
distance: 1,
start( e, ui ) {
fieldId = ui.item.data( 'field-id' );
fieldType = ui.item.data( 'field-type' );
isNewField = typeof fieldId === 'undefined';
$fieldOption = $( '#wpforms-field-option-' + fieldId );
vars.fieldReceived = false;
vars.fieldRejected = false;
vars.$sortableStart = $sortable;
vars.startPosition = ui.item.first().index();
el.$builder.trigger( 'wpformsFieldDragStart', [ fieldId ] );
},
beforeStop( e, ui ) {
if ( ! vars.glitchChange ) {
return;
}
// Before processing in the `stop` method, we need to perform the last check.
if ( ! fieldLayout.isFieldAllowedInColum( fieldType, ui.item.first().parent() ) ) {
vars.fieldRejected = true;
}
},
stop( e, ui ) { // eslint-disable-line complexity
const $field = ui.item.first();
const $parent = $field.parent();
// If this is a layout field, initialize its columns.
if ( $field.hasClass( 'wpforms-field-layout' ) ) {
$field.find( '.wpforms-layout-column' ).each( function() {
app.initSortableHandler( $( this ) );
$( this ).sortable( 'enable' );
} );
}
// If the field is in the main container but was attempted to be added to a column, move it to the column.
if (
$parent.hasClass( 'wpforms-field-wrap' ) &&
window.wpformsLastReceive &&
window.wpformsLastReceive.isColumn &&
! $field.hasClass( 'wpforms-field-layout' ) &&
! $field.hasClass( 'wpforms-field-repeater' )
) {
// Move the field to the column that was last trying to receive it.
$field.detach();
window.wpformsLastReceive.sortable.append( $field );
// Mark the field as rejected to prevent further processing in the main container.
vars.fieldRejected = true;
// Reset tracking variables.
window.wpformsLastReceive = null;
window.wpformsLastReceiveForMainWrap = null;
}
ui.placeholder.removeClass( 'wpforms-field-drag-not-allowed' );
$field.removeClass( 'wpforms-field-drag-not-allowed' );
// Reject not allowed fields.
if ( vars.fieldRejected ) {
const $targetColumn = isNewField ? $sortable : $field.parent();
app.revertMoveFieldToColumn( $field );
el.$builder.trigger( 'wpformsFieldMoveRejected', [ $field, ui, $targetColumn ] );
return;
}
prevFieldId = $field.prev( '.wpforms-field, .wpforms-alert' ).data( 'field-id' );
$prevFieldOption = $( `#wpforms-field-option-${ prevFieldId }` );
if ( $prevFieldOption.length > 0 ) {
$prevFieldOption.after( $fieldOption );
} else {
$fieldOptions.prepend( $fieldOption );
}
// In the case of changing fields' order inside the same column,
// we just need to change the position of the field.
if ( ! isNewField && $field.closest( '.wpforms-layout-column' ).is( $sortable ) ) {
fieldLayout.positionFieldInColumn(
fieldId,
$field.index() - 1,
$sortable
);
}
const $layoutField = $field.closest( '.wpforms-field-layout, .wpforms-field-repeater' );
fieldLayout.fieldOptionsUpdate( null, fieldId );
fieldLayout.reorderLayoutFieldsOptions( $layoutField );
if ( ! isNewField ) {
$field
.removeClass( 'wpforms-field-dragging' )
.removeClass( 'wpforms-field-drag-over' );
}
$field.attr( 'style', '' );
el.$builder.trigger( 'wpformsFieldMove', ui );
vars.fieldReceived = false;
},
over( e, ui ) { // eslint-disable-line complexity
const $field = ui.item.first(),
$target = $( e.target ),
$placeholder = $target.find( '.wpforms-field-drag-placeholder' ),
isColumn = $target.hasClass( 'wpforms-layout-column' ),
helper = {
width: $target.outerWidth(),
height: $field.outerHeight(),
};
let targetClass = isColumn ? ' wpforms-field-drag-to-column' : '';
if ( isColumn ) {
const columnSize = $target.attr( 'class' ).match( /wpforms-layout-column-(\d+)/ )[ 1 ];
targetClass += ` wpforms-field-drag-to-column-${ columnSize }`;
targetClass += ` wpforms-field-drag-to-${ $target.parents( '.wpforms-field' ).data( 'field-type' ) }`;
}
fieldId = $field.data( 'field-id' );
fieldType = $field.data( 'field-type' ) || vars.fieldType;
isNewField = typeof fieldId === 'undefined';
// Adjust helper size according to the placeholder size.
$field
.addClass( 'wpforms-field-dragging' + targetClass );
if ( ! isColumn || ! fieldLayout.isLayoutBasedField( fieldType ) ) {
$field
.css( {
width: isColumn ? helper.width - 5 : helper.width,
height: 'auto',
} );
}
const placeholderHeight = isColumn ? 90 : helper.height;
// Adjust placeholder height according to the height of the helper.
$placeholder
.removeClass( 'wpforms-field-drag-not-allowed' )
.css( {
height: isNewField ? placeholderHeight + 18 : helper.height,
} );
// Drop to this place is not allowed.
if ( isColumn && ! fieldLayout.isFieldAllowedInColum( fieldType, $target ) ) {
$placeholder.addClass( 'wpforms-field-drag-not-allowed' );
$field.addClass( 'wpforms-field-drag-not-allowed' );
}
el.$builder.trigger( 'wpformsFieldDragOver', [ fieldId, $target ] );
// Skip if it is the existing field.
if ( ! isNewField ) {
return;
}
$field
.addClass( 'wpforms-field-drag-over' )
.removeClass( 'wpforms-field-drag-out' );
},
out( e, ui ) {
const $field = ui.item.first(),
// eslint-disable-next-line no-shadow
fieldId = $field.data( 'field-id' ),
// eslint-disable-next-line no-shadow
isNewField = typeof fieldId === 'undefined';
$field
.removeClass( 'wpforms-field-drag-not-allowed' )
.removeClass( 'wpforms-field-drag-to-repeater' )
.removeClass( 'wpforms-field-drag-to-layout' )
.removeClass( app.getDragColumnClasses( $field.attr( 'class' ) ) );
if ( vars.fieldReceived ) {
$field.attr( 'style', '' );
return;
}
// Skip if it is the existing field.
if ( ! isNewField ) {
// Remove extra class from the parent layout field.
// Fixes disappearing of duplicate/delete field icons
// after moving the field outside the layout field.
$( ui.sender )
.closest( '.wpforms-field-layout, .wpforms-field-repeater' )
.removeClass( 'wpforms-field-child-hovered' );
return;
}
$field
.addClass( 'wpforms-field-drag-out' )
.removeClass( 'wpforms-field-drag-over' );
},
receive( e, ui ) { // eslint-disable-line complexity
const $field = $( ui.helper || ui.item );
const isColumn = $sortable.hasClass( 'wpforms-layout-column' );
const isMainWrap = $sortable.hasClass( 'wpforms-field-wrap' );
// Save current field receive as the last one.
window.wpformsLastReceive = {
isColumn,
isMainWrap,
sender: ui.sender ? $( ui.sender ).attr( 'class' ) : null,
sortable: $sortable,
time: new Date().getTime(),
};
// Check if this is a second receive for a field that was already handled by the main container.
if (
isColumn &&
window.wpformsLastReceiveForMainWrap &&
( new Date().getTime() - window.wpformsLastReceiveForMainWrap.time < 100 )
) {
// We need to stop this receive and cancel the operation for the main container.
// Mark the field as rejected, which will cause it to be removed from the main container.
vars.fieldRejected = true;
window.wpformsLastReceiveForMainWrap = null;
window.wpformsLastReceive = null;
return;
}
// If this is the main container, remember this event.
if ( isMainWrap ) {
window.wpformsLastReceiveForMainWrap = window.wpformsLastReceive;
}
fieldId = $field.data( 'field-id' );
fieldType = $field.data( 'field-type' ) || vars.fieldType;
// eslint-disable-next-line no-shadow
const isNewField = typeof fieldId === 'undefined';
// Drop to this place is not allowed.
if (
isColumn &&
! fieldLayout.isFieldAllowedInColum( fieldType, $sortable )
) {
vars.fieldRejected = true;
return;
}
vars.fieldReceived = true;
$field.removeClass( 'wpforms-field-drag-over' );
// Move existing field.
if ( ! isNewField ) {
fieldLayout.receiveFieldToColumn(
fieldId,
ui.item.index() - 1,
$field.parent()
);
return;
}
// Add new field.
const position = $sortable.data( 'ui-sortable' )?.currentItem?.index() || 0;
$field
.addClass( 'wpforms-field-drag-over wpforms-field-drag-pending' )
.removeClass( 'wpforms-field-drag-out' )
.append( WPFormsBuilder.settings.spinnerInline )
.css( 'width', '100%' );
el.$builder.find( '.no-fields-preview' ).remove();
WPFormsBuilder.fieldAdd(
vars.fieldType,
{
position: isColumn ? position - 1 : position,
placeholder: $field,
$sortable,
}
);
vars.fieldType = undefined;
},
change( e, ui ) {
const $placeholderSortable = ui.placeholder.parent();
const $targetSortable = $( e.target );
vars.glitchChange = false;
// In some cases sortable widget display placeholder in wrong sortable instance.
// It's happens when you drag the field over/out the last column of the last Layout field.
if (
! $sortable.is( $placeholderSortable ) &&
$sortable.hasClass( 'wpforms-field-wrap' ) &&
$placeholderSortable.hasClass( 'wpforms-layout-column' )
) {
vars.glitchChange = true;
}
el.$builder.trigger( 'wpformsFieldDragChange', [ fieldId, $targetSortable ] );
},
sort( e ) {
if ( currentlyScrolling ) {
return;
}
const scrollAreaHeight = 50,
mouseYPosition = e.clientY,
containerOffset = $scrollContainer.offset(),
containerHeight = $scrollContainer.height(),
containerBottom = containerOffset.top + containerHeight;
let operator;
if (
mouseYPosition > containerOffset.top &&
mouseYPosition < ( containerOffset.top + scrollAreaHeight )
) {
operator = '-=';
} else if (
mouseYPosition > ( containerBottom - scrollAreaHeight ) &&
mouseYPosition < containerBottom
) {
operator = '+=';
} else {
return;
}
currentlyScrolling = true;
$scrollContainer.animate(
{
scrollTop: operator + ( containerHeight / 3 ) + 'px',
},
800,
function() {
currentlyScrolling = false;
}
);
},
} );
},
/**
* Remove all classes starting with `wpforms-field-drag-to-column`.
*
* @since 1.9.6
*
* @param {string} className The class name of the field.
*
* @return {string} The class name of the field.
*/
getDragColumnClasses( className ) {
return ( className.match( /wpforms-field-drag-to-column(-\d+|)/g ) || [] ).join( ' ' );
},
/**
* Initialize draggable fields buttons.
*
* @since 1.7.7
*/
initDraggableFields() {
el.$addFieldsButtons.draggable( {
connectToSortable: '.wpforms-field-wrap, .wpforms-layout-column',
delay: 200,
cancel: false,
scroll: false,
opacity: 1,
appendTo: '#wpforms-panel-fields',
zindex: 10000,
helper() {
const $this = $( this );
const $el = $( '<div class="wpforms-field-drag-out wpforms-field-drag">' );
vars.fieldType = $this.data( 'field-type' );
return $el.html( $this.html() );
},
start( e, ui ) {
const event = WPFormsUtils.triggerEvent(
el.$builder,
'wpformsFieldAddDragStart',
[ vars.fieldType, ui ]
);
// Allow callbacks on `wpformsFieldAddDragStart` to cancel dragging the field
// by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return false;
}
},
stop( e, ui ) {
const event = WPFormsUtils.triggerEvent(
el.$builder,
'wpformsFieldAddDragStop',
[ vars.fieldType, ui ]
);
// Allow callbacks on `wpformsFieldAddDragStop` to cancel dragging the field
// by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return false;
}
},
} );
},
/**
* Revert moving the field to the column.
*
* @since 1.7.7
*
* @param {jQuery} $field Field object.
*/
revertMoveFieldToColumn( $field ) {
const isNewField = $field.data( 'field-id' ) === undefined;
if ( isNewField ) {
// Remove the field.
$field.remove();
return;
}
// Restore existing field on the previous position.
$field = $field.detach();
const $fieldInStartPosition = vars.$sortableStart
.find( '> .wpforms-field' )
.eq( vars.startPosition );
$field
.removeClass( 'wpforms-field-dragging' )
.removeClass( 'wpforms-field-drag-over' )
.attr( 'style', '' );
if ( $fieldInStartPosition.length ) {
$fieldInStartPosition.before( $field );
return;
}
vars.$sortableStart.append( $field );
},
};
/**
* Layout field functions holder.
*
* @since 1.7.7
*
* @type {Object}
*/
fieldLayout = {
/**
* Position field in the column inside the Layout Field.
*
* @since 1.7.7
*
* @param {number} fieldId Field ID.
* @param {number} position The new position of the field inside the column.
* @param {jQuery} $sortable Sortable column container.
*/
positionFieldInColumn( fieldId, position, $sortable ) {
if ( ! WPForms.Admin.Builder.FieldLayout ) {
return;
}
WPForms.Admin.Builder.FieldLayout.positionFieldInColumn( fieldId, position, $sortable );
},
/**
* Receive field to column inside the Layout Field.
*
* @since 1.7.7
*
* @param {number} fieldId Field ID.
* @param {number} position Field position inside the column.
* @param {jQuery} $sortable Sortable column container.
*/
receiveFieldToColumn( fieldId, position, $sortable ) {
if ( ! WPForms.Admin.Builder.FieldLayout ) {
return;
}
WPForms.Admin.Builder.FieldLayout.receiveFieldToColumn( fieldId, position, $sortable );
},
/**
* Update field options according to the position of the field.
* Event `wpformsFieldOptionTabToggle` handler.
*
* @since 1.7.7
*
* @param {Event} e Event.
* @param {number} fieldId Field id.
*/
fieldOptionsUpdate( e, fieldId ) {
if ( ! WPForms.Admin.Builder.FieldLayout ) {
return;
}
WPForms.Admin.Builder.FieldLayout.fieldOptionsUpdate( e, fieldId );
},
/**
* Reorder fields options of the fields in columns.
* It is not critical, but it's better to keep some order in the `fields` data array.
*
* @since 1.7.7
*
* @param {jQuery} $layoutField Layout field object.
*/
reorderLayoutFieldsOptions( $layoutField ) {
if ( ! WPForms.Admin.Builder.FieldLayout ) {
return;
}
WPForms.Admin.Builder.FieldLayout.reorderLayoutFieldsOptions( $layoutField );
},
/**
* Whether the field type is allowed to be in column.
*
* @since 1.7.7
*
* @param {string} fieldType Field type to check.
* @param {jQuery} $targetColumn Target column element.
*
* @return {boolean} True if allowed.
*/
isFieldAllowedInColum( fieldType, $targetColumn ) {
if ( ! WPForms.Admin.Builder.FieldLayout ) {
return true;
}
const isAllowed = WPForms.Admin.Builder.FieldLayout.isFieldAllowedInColum( fieldType, $targetColumn );
/**
* Allows developers to determine whether the field is allowed to be dragged in column.
*
* @since 1.8.9
*
* @param {boolean} isAllowed Whether the field is allowed to be placed in the column.
* @param {string} fieldType Field type.
* @param {jQuery} $targetColumn Target column element.
*
* @return {boolean} True if allowed.
*/
return wp.hooks.applyFilters( 'wpforms.LayoutField.isFieldAllowedDragInColumn', isAllowed, fieldType, $targetColumn );
},
/**
* Determine whether the field type is a layout-based field.
*
* @since 1.8.9
*
* @param {string} fieldType Field type to check.
*
* @return {boolean} True if it is the Layout-based field.
*/
isLayoutBasedField( fieldType ) {
if ( ! WPForms.Admin.Builder.FieldLayout ) {
return false;
}
return WPForms.Admin.Builder.FieldLayout.isLayoutBasedField( fieldType );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.DragFields.init();
File diff suppressed because one or more lines are too long
@@ -0,0 +1,389 @@
/* global List, wpforms_builder */
/**
* WPForms Builder Dropdown List module.
*
* @since 1.8.4
*/
/*
Usage:
dropdownList = WPForms.Admin.Builder.DropdownList.init( {
class: 'insert-field-dropdown', // Additional CSS class.
title: 'Dropdown Title', // Dropdown title.
list: [ // Items list.
{ value: '1', text: 'Item 1' },
{ value: '2', text: 'Item 2' },
{ value: '3', text: 'Item 3' },
],
container: $( '.holder-container' ), // Holder container. Optional.
scrollableContainer: $( '.scrollable-container' ), // Scrollable container. Optional.
search: {
enabled: false, // Enable search. Optional.
searchBy : [], // Search by fields.
placeholder: 'Search', // Search input placeholder.
noResultsText: 'Sorry, no results found', // No results text.
},
button: $( '.button' ), // Button.
buttonDistance: 21, // Distance from dropdown to the button.
noLeftOffset: false, // Disable left offset for the dropdown.
itemFormat( item ) { // Item element renderer. Optional.
return `<span>${ item.text }</span>`;
},
onSelect( event, value, text, $item, instance ) { // On select event handler.
console.log( 'Item selected:', text );
instance.close();
$button.removeClass( 'active' );
},
} );
*/
// noinspection ES6ConvertVarToLetConst
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.DropdownList = WPForms.Admin.Builder.DropdownList || ( function( document, window, $ ) {
/**
* DropdownList object constructor.
*
* @since 1.8.4
*
* @type {Object}
*/
function DropdownList( options ) { // eslint-disable-line max-lines-per-function
const self = this;
/**
* Default options.
*
* @since 1.8.4
*
* @type {Object}
*/
const defaultOptions = {
class: '',
title: '',
list: [],
container: null,
scrollableContainer: null,
search: {
enabled: false,
searchBy : [],
placeholder: wpforms_builder.search,
noResultsText: wpforms_builder.no_results_found,
},
button: null,
buttonDistance: 10,
noLeftOffset: false,
onSelect: null,
itemFormat( item ) {
return item.text;
},
};
/**
* Options.
*
* @since 1.8.4
*
* @type {jQuery}
*/
self.options = $.extend( defaultOptions, options );
/**
* Main dropdown container.
*
* @since 1.8.4
*
* @type {jQuery}
*/
self.$el = null;
/**
* Form builder container.
*
* @since 1.8.4
*
* @type {jQuery}
*/
self.$builder = $( '#wpforms-builder' );
/**
* List.js instance.
*
* @since 1.9.5
*
* @type {Object}
*/
self.searchItems = null;
/**
* Close the dropdown.
*
* @since 1.8.4
*/
self.close = function() {
self.$el.addClass( 'closed' );
// Clear search input.
if ( self.options.search.enabled ) {
self.clearSearch();
}
};
/**
* Open the dropdown.
*
* @since 1.8.4
*/
self.open = function() {
self.$el.removeClass( 'closed open-down' );
self.setPosition();
// Close dropdown on click outside.
self.$builder.on( 'click.DropdownList', function( e ) {
const $target = $( e.target );
const excludedSelectors = '.button-insert-field, .wpforms-smart-tags-enabled, .wpforms-show-smart-tags, .mce-ico';
if ( $target.closest( self.$el ).length || $target.is( excludedSelectors ) ) {
return;
}
self.$builder.off( 'click.DropdownList' );
const $button = $( self.options.button );
if ( $button.hasClass( 'active' ) ) {
$button.trigger( 'click' );
}
} );
};
/**
* Generate the dropdown HTML.
*
* @since 1.8.4
*
* @return {string} HTML.
*/
self.generateHtml = function() {
const list = self.options.list;
if ( ! list || list.length === 0 ) {
return '';
}
const itemFormat = typeof self.options.itemFormat === 'function' ? self.options.itemFormat : defaultOptions.itemFormat;
// Generate HTML list items.
const items = list.map( ( item ) => `<li data-value='${ item.value }'>${ itemFormat( item ) }</li>` );
// Generate search HTML if enabled.
const searchHtml = self.options.search.enabled
? `<div class="wpforms-builder-dropdown-list-search-container">
<input type="search" class="wpforms-builder-dropdown-list-search-input" placeholder="${ self.options.search.placeholder }">
<i class="fa fa-times-circle wpforms-builder-dropdown-list-search-close" aria-hidden="true"></i>
</div>`
: '';
const listClass = self.options.search.enabled ? 'list' : '';
return `<div class="wpforms-builder-dropdown-list closed ${ self.options.class }">
<div class="title">${ self.options.title }</div>
${ searchHtml }
<ul class="${ listClass }">${ items.join( '' ) }</ul>
<div class="wpforms-no-results">${ self.options.search.noResultsText }</div>
</div>`;
};
/**
* Attach dropdown to DOM.
*
* @since 1.8.4
*/
self.attach = function() {
const html = self.generateHtml();
// Remove old dropdown.
if ( self.$el && self.$el.length ) {
self.$el.remove();
}
// Create jQuery objects.
self.$el = $( html );
self.$button = $( self.options.button );
self.$container = self.options.container ? $( self.options.container ) : self.$button.parent();
self.$scrollableContainer = self.options.scrollableContainer ? $( self.options.scrollableContainer ) : null;
// Init List.js if search is enabled.
if ( self.options.search.enabled ) {
self.searchItems = new List( self.$el[ 0 ], {
valueNames: self.options.search.searchBy,
} );
}
// Add the dropdown to the container.
self.$container.append( self.$el );
self.setPosition();
};
/**
* Set dropdown position.
*
* @since 1.8.4
*/
self.setPosition = function() {
// Calculate position.
const buttonOffset = self.$button.offset(),
containerOffset = self.$container.offset(),
containerPosition = self.$container.position(),
dropdownHeight = self.$el.height(),
scrollTop = self.$scrollableContainer ? self.$scrollableContainer.scrollTop() : 0;
let top = buttonOffset.top - containerOffset.top - dropdownHeight - self.options.buttonDistance;
// In the case of the dropdown doesn't fit into the scrollable container to top,
// it is necessary to open the dropdown to the bottom.
if ( scrollTop + containerPosition.top - dropdownHeight < 0 ) {
top = buttonOffset.top - containerOffset.top + self.$button.height() + self.options.buttonDistance - 11;
self.$el.addClass( 'open-down' );
}
self.$el.css( 'top', top );
// If noLeftOffset is set, do not set `left` positioning value.
if ( self.options.noLeftOffset ) {
return;
}
// The dropdown is outside the field options, it is necessary to set `left` positioning value.
if ( self.$container.closest( '.wpforms-field-option' ).length === 0 ) {
self.$el.css( 'left', buttonOffset.left - containerOffset.left );
}
};
/**
* Events.
*
* @since 1.8.4
*/
self.events = function() {
// Click (select) the item.
self.$el.find( 'li' ).off()
.on( 'click', function( event ) {
// Bail if callback is not a function.
if ( typeof self.options.onSelect !== 'function' ) {
return;
}
const $item = $( this );
// Clear search input.
if ( self.options.search.enabled ) {
self.clearSearch();
}
// Trigger callback.
self.options.onSelect( event, $item.data( 'value' ), $item.text(), $item, self );
} );
// Search.
if ( self.options.search.enabled ) {
self.$el.find( 'input[type="search"]' ).on( 'keyup search', self.search );
self.$el.find( '.wpforms-builder-dropdown-list-search-close' ).on( 'click', self.clearSearch );
}
};
/**
* Initialize.
*
* @since 1.8.4
*
* @param {Array} list List of items.
*/
self.init = function( list = null ) {
self.options.list = list ? list : self.options.list;
self.attach();
self.events();
self.$button.data( 'dropdown-list', self );
};
/**
* Destroy.
*
* @since 1.8.4
*/
self.destroy = function() {
self.$button.data( 'dropdown-list', null );
self.$el.remove();
};
/**
* Search.
*
* @since 1.9.5
* @param {Object } event Event.
*/
self.search = function( event ) {
const searchTerm = event.target.value.toLowerCase();
const $noResults = self.$el.find( '.wpforms-no-results' );
// Show/hide close button.
if ( searchTerm !== '' ) {
self.$el.find( '.wpforms-builder-dropdown-list-search-close' ).addClass( 'active' );
}
// Search.
self.searchItems.search( searchTerm );
// Show/hide no result message.
$noResults.toggle( self.searchItems.visibleItems.length === 0 );
};
/**
* Clear search input.
*
* @since 1.9.5
*/
self.clearSearch = function() {
// Clear search input.
self.$el.find( 'input[type="search"]' ).val( '' );
self.$el.find( '.wpforms-no-results' ).hide();
self.$el.find( '.wpforms-builder-dropdown-list-search-close' ).removeClass( 'active' );
// Clear search results.
self.searchItems.search();
};
// Initialize.
self.init();
}
/**
* Public functions and properties.
*
* @since 1.8.4
*
* @type {Object}
*/
return {
/**
* Start the engine. DOM is not ready yet, use only to init something.
*
* @since 1.8.4
*
* @param {Object} options Options.
*
* @return {Object} DropdownList instance.
*/
init( options ) {
return new DropdownList( options );
},
};
}( document, window, jQuery ) );
@@ -0,0 +1,9 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.DropdownList=WPForms.Admin.Builder.DropdownList||(t=>{function o(e){let i=this,s={class:"",title:"",list:[],container:null,scrollableContainer:null,search:{enabled:!1,searchBy:[],placeholder:wpforms_builder.search,noResultsText:wpforms_builder.no_results_found},button:null,buttonDistance:10,noLeftOffset:!1,onSelect:null,itemFormat(e){return e.text}};i.options=t.extend(s,e),i.$el=null,i.$builder=t("#wpforms-builder"),i.searchItems=null,i.close=function(){i.$el.addClass("closed"),i.options.search.enabled&&i.clearSearch()},i.open=function(){i.$el.removeClass("closed open-down"),i.setPosition(),i.$builder.on("click.DropdownList",function(e){var e=t(e.target);e.closest(i.$el).length||e.is(".button-insert-field, .wpforms-smart-tags-enabled, .wpforms-show-smart-tags, .mce-ico")||(i.$builder.off("click.DropdownList"),(e=t(i.options.button)).hasClass("active")&&e.trigger("click"))})},i.generateHtml=function(){var e=i.options.list;if(!e||0===e.length)return"";let o=("function"==typeof i.options.itemFormat?i.options:s).itemFormat;var e=e.map(e=>`<li data-value='${e.value}'>${o(e)}</li>`),t=i.options.search.enabled?`<div class="wpforms-builder-dropdown-list-search-container">
<input type="search" class="wpforms-builder-dropdown-list-search-input" placeholder="${i.options.search.placeholder}">
<i class="fa fa-times-circle wpforms-builder-dropdown-list-search-close" aria-hidden="true"></i>
</div>`:"",n=i.options.search.enabled?"list":"";return`<div class="wpforms-builder-dropdown-list closed ${i.options.class}">
<div class="title">${i.options.title}</div>
${t}
<ul class="${n}">${e.join("")}</ul>
<div class="wpforms-no-results">${i.options.search.noResultsText}</div>
</div>`},i.attach=function(){var e=i.generateHtml();i.$el&&i.$el.length&&i.$el.remove(),i.$el=t(e),i.$button=t(i.options.button),i.$container=i.options.container?t(i.options.container):i.$button.parent(),i.$scrollableContainer=i.options.scrollableContainer?t(i.options.scrollableContainer):null,i.options.search.enabled&&(i.searchItems=new List(i.$el[0],{valueNames:i.options.search.searchBy})),i.$container.append(i.$el),i.setPosition()},i.setPosition=function(){var e=i.$button.offset(),o=i.$container.offset(),t=i.$container.position(),n=i.$el.height(),s=i.$scrollableContainer?i.$scrollableContainer.scrollTop():0;let l=e.top-o.top-n-i.options.buttonDistance;s+t.top-n<0&&(l=e.top-o.top+i.$button.height()+i.options.buttonDistance-11,i.$el.addClass("open-down")),i.$el.css("top",l),i.options.noLeftOffset||0===i.$container.closest(".wpforms-field-option").length&&i.$el.css("left",e.left-o.left)},i.events=function(){i.$el.find("li").off().on("click",function(e){var o;"function"==typeof i.options.onSelect&&(o=t(this),i.options.search.enabled&&i.clearSearch(),i.options.onSelect(e,o.data("value"),o.text(),o,i))}),i.options.search.enabled&&(i.$el.find('input[type="search"]').on("keyup search",i.search),i.$el.find(".wpforms-builder-dropdown-list-search-close").on("click",i.clearSearch))},i.init=function(e=null){i.options.list=e||i.options.list,i.attach(),i.events(),i.$button.data("dropdown-list",i)},i.destroy=function(){i.$button.data("dropdown-list",null),i.$el.remove()},i.search=function(e){var e=e.target.value.toLowerCase(),o=i.$el.find(".wpforms-no-results");""!==e&&i.$el.find(".wpforms-builder-dropdown-list-search-close").addClass("active"),i.searchItems.search(e),o.toggle(0===i.searchItems.visibleItems.length)},i.clearSearch=function(){i.$el.find('input[type="search"]').val(""),i.$el.find(".wpforms-no-results").hide(),i.$el.find(".wpforms-builder-dropdown-list-search-close").removeClass("active"),i.searchItems.search()},i.init()}return{init(e){return new o(e)}}})((document,window,jQuery));
@@ -0,0 +1,217 @@
/* eslint-disable camelcase */
/* global wpforms_builder_email_template */
// noinspection ES6ConvertVarToLetConst
/**
* Script for manipulating DOM events in the "Builder" settings page.
* This script will be accessible in the "WPForms" → "Builder" → "Notifications" tab/page.
*
* @since 1.8.5
*/
// eslint-disable-next-line no-var
var WPFormsBuilderEmailTemplate = window.WPFormsBuilderEmailTemplate || ( function( document, window, $, l10n ) {
/**
* Elements holder.
*
* @since 1.8.5
*
* @type {Object}
*/
const el = {};
/**
* Runtime variables.
*
* @since 1.8.5
*
* @type {Object}
*/
const vars = {
/**
* Modal instance.
*
* @since 1.8.5
*/
modal: null,
/**
* Generic CSS class names for applying visual changes.
*
* @since 1.8.5
*/
classNames: {
modalBox: 'wpforms-modal-content-box',
modalOpen: 'wpforms-email-template-modal-open',
},
};
/**
* Public functions and properties.
*
* @since 1.8.5
*/
const app = {
/**
* Start the engine.
*
* @since 1.8.5
*/
init() {
$( app.ready );
},
/**
* Document ready.
*
* @since 1.8.5
*/
ready() {
app.setup();
app.bindEvents();
},
/**
* Setup. Prepare some variables.
*
* @since 1.8.5
*/
setup() {
// Cache DOM elements.
el.$document = $( document );
el.$body = $( 'body' );
},
/**
* Bind events.
*
* @since 1.8.5
*/
bindEvents() {
el.$document
.on( 'change', '.wpforms-email-template-modal-content input[type="radio"]', app.handleOnChangeTemplate )
.on( 'click', '.wpforms-all-email-template-modal', app.handleOnOpenModal );
},
/**
* Handle the "change" event for the template radio buttons.
* This function updates the select field based on the selected radio button.
*
* @since 1.8.5
*
* @param {Object} event The DOM event that triggered the function.
*/
handleOnChangeTemplate( event ) {
// Prevent the default action, which is to handle the change event.
event.preventDefault();
// Extract the ID of the field from the element.
const id = app.getIdFromElm( $( this ) );
// Get the corresponding select field.
const $field = $( `#wpforms-panel-field-notifications-${ id }-template` );
// If the select field doesn't exist, no further action is needed.
if ( ! $field.length ) {
return;
}
// If the modal doesn't exist, no further action is needed.
if ( ! vars.modal ) {
return;
}
// Get the value of the radio button that triggered the change.
const value = $( this ).val();
// Update the select field with the selected value and trigger the change event.
$field.val( value ).trigger( 'change' );
// Close the modal.
vars.modal.close();
},
/**
* Handle the "click" event for opening the modal.
* This will open the modal with the available templates.
*
* @since 1.8.5
*/
handleOnOpenModal() {
// Get the email template modal template.
const template = wp.template( 'wpforms-email-template-modal' );
// If the template doesn't exist, exit the function.
if ( ! template.length ) {
return;
}
// Find the closest wrapper and select element.
const $wrapper = $( this ).closest( '.wpforms-panel-field-email-template-wrap' );
const $select = $wrapper.find( 'select' );
// Get the selected value from the select element and its ID.
const selected = $select.val() || '';
const id = app.getIdFromElm( $select );
// Extract relevant data from l10n.
const { templates, is_pro } = l10n;
// Prepare the data to be passed to the template.
const data = { templates, selected, is_pro, id };
// Generate the modal's content using the template and data.
const content = template( data );
// Open the modal.
vars.modal = $.confirm( {
content,
title: '',
boxWidth: 800,
contentMaxHeight: 'none',
backgroundDismiss: true,
smoothContent: false,
closeIcon: true,
buttons: false,
// Callback function before the modal opens.
onOpenBefore() {
this.$body.addClass( vars.classNames.modalBox );
el.$body.addClass( vars.classNames.modalOpen );
},
// Callback function when the modal is closed.
onClose() {
el.$body.removeClass( vars.classNames.modalOpen );
},
} );
},
/**
* Get the ID from the element.
* This is a helper function for extracting the numeric ID from an element's ID attribute.
*
* @since 1.8.5
*
* @param {Object} $elm jQuery object representing the element.
*
* @return {number} The numeric ID extracted from the element's ID attribute.
*/
getIdFromElm( $elm ) {
// Get the ID attribute from the element.
const id = $elm.attr( 'id' );
// If no ID attribute is found, return 0.
if ( ! id ) {
return 0;
}
// Extract and parse the numeric part from the ID.
return parseInt( id.match( /\d+/ )[ 0 ], 10 );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery, wpforms_builder_email_template ) );
// Initialize.
WPFormsBuilderEmailTemplate.init();
@@ -0,0 +1 @@
var WPFormsBuilderEmailTemplate=window.WPFormsBuilderEmailTemplate||((e,m,n)=>{let d={},s={modal:null,classNames:{modalBox:"wpforms-modal-content-box",modalOpen:"wpforms-email-template-modal-open"}},i={init(){m(i.ready)},ready(){i.setup(),i.bindEvents()},setup(){d.$document=m(e),d.$body=m("body")},bindEvents(){d.$document.on("change",'.wpforms-email-template-modal-content input[type="radio"]',i.handleOnChangeTemplate).on("click",".wpforms-all-email-template-modal",i.handleOnOpenModal)},handleOnChangeTemplate(e){e.preventDefault();var a,e=i.getIdFromElm(m(this)),e=m(`#wpforms-panel-field-notifications-${e}-template`);e.length&&s.modal&&(a=m(this).val(),e.val(a).trigger("change"),s.modal.close())},handleOnOpenModal(){var e,a,l,t,o=wp.template("wpforms-email-template-modal");o.length&&(e=(a=m(this).closest(".wpforms-panel-field-email-template-wrap").find("select")).val()||"",a=i.getIdFromElm(a),{templates:l,is_pro:t}=n,o=o({templates:l,selected:e,is_pro:t,id:a}),s.modal=m.confirm({content:o,title:"",boxWidth:800,contentMaxHeight:"none",backgroundDismiss:!0,smoothContent:!1,closeIcon:!0,buttons:!1,onOpenBefore(){this.$body.addClass(s.classNames.modalBox),d.$body.addClass(s.classNames.modalOpen)},onClose(){d.$body.removeClass(s.classNames.modalOpen)}}))},getIdFromElm(e){e=e.attr("id");return e?parseInt(e.match(/\d+/)[0],10):0}};return i})(document,(window,jQuery),wpforms_builder_email_template);WPFormsBuilderEmailTemplate.init();
@@ -0,0 +1,236 @@
/* global wpforms_builder, WPFormsUtils, wpf */
/**
* @param wpforms_builder.add_custom_value_label
* @param wpforms_builder.select_field
*/
// noinspection ES6ConvertVarToLetConst
/**
* Form Builder Field Map.
*
* @since 1.9.5
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.FieldMap = WPForms.Admin.Builder.FieldMap || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.9.5
*
* @type {Object}
*/
const el = {};
/**
* Public functions and properties.
*
* @since 1.9.5
*
* @type {Object}
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.5
*/
init() {
$( app.ready );
},
/**
* DOM is fully loaded.
*
* @since 1.9.5
*/
ready() {
app.setup();
app.events();
},
/**
* Setup. Prepare some variables.
*
* @since 1.9.5
*/
setup() {
// Cache DOM elements.
el.$builder = $( '#wpforms-builder' );
},
/**
* Bind events.
*
* @since 1.9.5
*/
events() {
// Field map table, update a key source.
el.$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
el.$builder.on( 'click', '.wpforms-field-map-table .remove', function( e ) {
e.preventDefault();
app.fieldMapTableDeleteRow( e, $( this ) );
} );
// Field map table, Add row
el.$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 );
},
/**
* Field map table - Delete row.
*
* @since 1.2.0
* @since 1.6.1.2 Registered `wpformsFieldMapTableDeletedRow` trigger.
*
* @param {Event} e Event.
* @param {Element} element Element.
*/
fieldMapTableDeleteRow( e, element ) {
const $this = $( element ),
$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();
el.$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} element Element.
*/
fieldMapTableAddRow( e, element ) {
const $this = $( element ),
$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', '' );
el.$builder.trigger( 'wpformsFieldMapTableAddedRow', [ $block, choice ] );
},
/**
* Update field mapped select items on form updates.
* Use data attributes to configure field mapping:
* - data-field-map-placeholder - A custom placeholder text shown in the select dropdown field.
* - data-field-map-allowed - Space-separated list of allowed field types (e.g. "email textarea"). Use "all-fields" to allow all available form fields.
* - data-field-map-allow-repeated-fields - Controls whether fields inside repeater blocks are included in the options (true/false).
* - data-custom-value-support - When true, adds a "Custom Value" option at the end of the dropdown list.
*
* @since 1.2.0
* @since 1.6.1.2 Registered `wpformsFieldSelectMapped` trigger.
* @since 1.9.7 Removed all passed arguments.
* @since 1.9.7 The list of fields is received via the `wpf.getFields` function.
* @since 1.9.7 Added multiple fields support.
*/
fieldMapSelect() {
const event = WPFormsUtils.triggerEvent( el.$builder, 'wpformsBeforeFieldMapSelectUpdate' );
// Allow callbacks on `wpformsBeforeFieldMapSelectUpdate` to cancel adding field
// by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
return;
}
// eslint-disable-next-line complexity
$( '.wpforms-field-map-select' ).each( function() {
const $this = $( this );
let placeholder = $this.data( 'field-map-placeholder' );
// Check if a custom placeholder was provided.
if ( typeof placeholder === 'undefined' || ! placeholder ) {
placeholder = wpforms_builder.select_field;
}
let allowedFields = $this.data( 'field-map-allowed' );
// If allowed, fields are not defined, bail.
if ( typeof allowedFields === 'undefined' || ! allowedFields ) {
return;
}
allowedFields = allowedFields.split( ' ' );
allowedFields = $.inArray( 'all-fields', allowedFields ) >= 0 ? false : allowedFields;
const isAllowedRepeatedFields = Boolean( $this.data( 'field-map-allow-repeated-fields' ) );
const selectedValue = $this.val();
const fields = wpf.getFields( allowedFields, true, isAllowedRepeatedFields );
$this.empty();
$this.append( $( '<option>', { value: '', text: placeholder } ) );
if ( fields && ! $.isEmptyObject( fields ) ) {
for ( const fieldID in fields ) {
if ( ! fields[ fieldID ]?.id ) {
continue;
}
const field = fields[ fieldID ];
const label = wpf.sanitizeHTML( field?.label?.toString().trim() || wpforms_builder.field + ' #' + field.id );
$this.append( $( '<option>', { value: field.id, text: label } ) );
}
}
// 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',
} )
);
}
if ( selectedValue ) {
$this.val( selectedValue );
}
el.$builder.trigger( 'wpformsFieldSelectMapped', [ $this ] );
} );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.FieldMap.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.FieldMap=WPForms.Admin.Builder.FieldMap||((e,s)=>{let p={},l={init(){s(l.ready)},ready(){l.setup(),l.events()},setup(){p.$builder=s("#wpforms-builder")},events(){p.$builder.on("input",".wpforms-field-map-table .key-source",function(){var e=s(this).val(),l=s(this).parent().parent().find(".key-destination"),i=l.data("name");e&&l.attr("name",i.replace("{source}",e.replace(/[^0-9a-zA-Z_-]/gi,"")))}),p.$builder.on("click",".wpforms-field-map-table .remove",function(e){e.preventDefault(),l.fieldMapTableDeleteRow(e,s(this))}),p.$builder.on("click",".wpforms-field-map-table .add",function(e){e.preventDefault(),l.fieldMapTableAddRow(e,s(this))}),s(e).on("wpformsFieldUpdate",l.fieldMapSelect)},fieldMapTableDeleteRow(e,l){var l=s(l),i=l.closest("tr"),l=l.closest("table"),t=i.closest(".wpforms-builder-settings-block");"1"<l.find("tr").length&&(i.remove(),p.$builder.trigger("wpformsFieldMapTableDeletedRow",[t]))},fieldMapTableAddRow(e,l){var l=s(l).closest("tr"),i=l.closest(".wpforms-builder-settings-block"),l=l.clone().insertAfter(l);l.find("input").val(""),l.find("select :selected").prop("selected",!1),l.find(".key-destination").attr("name",""),p.$builder.trigger("wpformsFieldMapTableAddedRow",[i,l])},fieldMapSelect(){WPFormsUtils.triggerEvent(p.$builder,"wpformsBeforeFieldMapSelectUpdate").isDefaultPrevented()||s(".wpforms-field-map-select").each(function(){var e=s(this);let l=e.data("field-map-placeholder"),i=(void 0!==l&&l||(l=wpforms_builder.select_field),e.data("field-map-allowed"));if(void 0!==i&&i){i=i.split(" "),i=!(0<=s.inArray("all-fields",i))&&i;var t,d=Boolean(e.data("field-map-allow-repeated-fields")),a=e.val(),r=wpf.getFields(i,!0,d);if(e.empty(),e.append(s("<option>",{value:"",text:l})),r&&!s.isEmptyObject(r))for(var o in r)r[o]?.id&&(o=r[o],t=wpf.sanitizeHTML(o?.label?.toString().trim()||wpforms_builder.field+" #"+o.id),e.append(s("<option>",{value:o.id,text:t})));d=e.data("custom-value-support");"boolean"==typeof d&&d&&e.append(s("<option>",{value:"custom_value",text:wpforms_builder.add_custom_value_label,class:"wpforms-field-map-option-custom-value"})),a&&e.val(a),p.$builder.trigger("wpformsFieldSelectMapped",[e])}})}};return l})(document,(window,jQuery)),WPForms.Admin.Builder.FieldMap.init();
@@ -0,0 +1,486 @@
/* global wpforms_builder, wpf, WPFormsBuilder, WPForms, md5 */
'use strict';
/**
* WPForms Internal Information Field builder functions.
*
* @since 1.7.6
*/
var WPFormsInternalInformationField = window.WPFormsInternalInformationField || ( function( document, window, $ ) { // eslint-disable-line
/**
* WPForms builder element.
*
* @since 1.7.6
*
* @type {jQuery}
*/
let $builder;
/**
* Public functions and properties.
*
* @since 1.7.6
*
* @type {object}
*/
let app = {
/**
* Start the engine.
*
* @since 1.7.6
*/
init: function() {
$( app.ready );
},
/**
* Initialized once the DOM is fully loaded.
*
* @since 1.7.6
*/
ready: function() {
$builder = $( '#wpforms-builder' );
app.bindUIActionsFields();
},
/**
* Element bindings.
*
* @since 1.7.6
*/
bindUIActionsFields: function() {
app.dragDisable();
$builder
.on( 'wpformsFieldAdd', app.dragDisable )
.on( 'input', '.wpforms-field-option-row-heading input[type="text"]', app.headingUpdates )
.on( 'input', '.wpforms-field-option-row-expanded-description textarea', app.expandedDescriptionUpdates )
.on( 'input', '.wpforms-field-option-row-cta-label input[type="text"]', app.ctaButtonLabelUpdates )
.on( 'input', '.wpforms-field-option-row-cta-link input[type="text"]', app.ctaButtonLinkUpdates )
.on( 'click', '.cta-button.cta-expand-description a', app.showExpandedDescription )
.on( 'focusout', '.wpforms-field-option-row-cta-link input[type="text"]', app.validateCTAlinkField )
.on( 'mousedown', '.wpforms-field-internal-information-checkbox', app.handleCheckboxClick )
.on( 'wpformsDescriptionFieldUpdated', app.descriptionFieldUpdated )
.on( 'wpformsBeforeFieldDeleteAlert', app.preventDeleteFieldAlert )
.on( 'mouseenter', '.internal-information-not-editable .wpforms-field-delete', app.showDismissTitle );
},
/**
* Save checkbox state as a post meta.
*
* @since 1.7.6
*
* @param {string} name Checkbox name.
* @param {int} checked Checkbox state.
*/
saveInternalInformationCheckbox: function( name, checked ) {
$.post(
wpforms_builder.ajax_url,
{
action: 'wpforms_builder_save_internal_information_checkbox',
formId: $( '#wpforms-builder-form' ).data( 'id' ),
name: name,
checked: checked,
nonce: wpforms_builder.nonce,
}
);
},
/**
* Replace checkboxes.
*
* @since 1.7.6
* @since 1.7.9 Added ID parameter.
*
* @param {string} description Expanded description.
* @param {int} id Field ID.
*
* @returns {string} Expanded description with checkboxes HTML.
*/
replaceCheckboxes: function( description, id ) {
const lines = description.split( /\r?\n/ ),
replaced = [],
needle = '[] ';
let lineNumber = -1;
for ( let line of lines ) {
lineNumber++;
line = line.trim();
if ( ! line.startsWith( needle ) ) {
replaced.push( line );
continue;
}
const hash = md5( line ),
name = `iif-${id}-${hash}-${lineNumber}`;
line = line.replace( '[] ', `<div class="wpforms-field-internal-information-checkbox-wrap"><div class="wpforms-field-internal-information-checkbox-input"><input type="checkbox" name="${name}" value="1" class="wpforms-field-internal-information-checkbox" /></div><div class="wpforms-field-internal-information-checkbox-label">` ); line += '</div></div>';
replaced.push( line );
}
return ( wpf.wpautop( replaced.join( '\n' ) ) ).replace( /<br \/>\n$/, '' );
},
/**
* Do not allow field to be draggable.
*
* @since 1.7.9
*/
dragDisable: function() {
WPForms.Admin.Builder.DragFields.fieldDragDisable( $( '.internal-information-not-draggable' ), false );
},
/**
* Real-time updates for "Heading" field option.
*
* @since 1.7.6
*/
headingUpdates: function() {
let $this = $( this ),
value = wpf.sanitizeHTML( $this.val() ),
$head = $( '#wpforms-field-' + $this.parent().data( 'field-id' ) ).find( '.wpforms-field-internal-information-row-heading .heading' );
$head.toggle( value.length !== 0 );
WPFormsBuilder.updateDescription( $head.find( '.text' ), value );
},
/**
* Real-time updates for "Expanded Description" field option.
*
* @since 1.7.6
*/
expandedDescriptionUpdates: function() {
const $this = $( this ),
value = wpf.sanitizeHTML( $this.val() ),
id = $this.parent().data( 'field-id' ),
$field = $( '#wpforms-field-' + id ),
$wrapper = $field.find( '.internal-information-wrap' ),
$buttonContainer = $field.find( '.wpforms-field-internal-information-row-cta-button' ),
$options = $( '#wpforms-field-option-' + id ),
link = $options.find( '.wpforms-field-option-row-cta-link input[type="text"]' ).val(),
label = $options.find( '.wpforms-field-option-row-cta-label input[type="text"]' ).val().length !== 0 ? $options.find( '.wpforms-field-option-row-cta-label input[type="text"]' ).val() : wpforms_builder.empty_label,
$expandable = $wrapper.find( '.wpforms-field-internal-information-row-expanded-description' );
const newLines = app.replaceCheckboxes( value, id );
WPFormsBuilder.updateDescription( $wrapper.find( '.expanded-description' ), newLines );
if ( value.length !== 0 ) { // Expanded description has content.
if ( $expandable.hasClass( 'expanded' ) ) {
return;
}
// Update CTA button.
$buttonContainer.html( app.notExpandedButton() );
return;
}
$expandable.hide().removeClass( 'expanded' );
if ( link.length === 0 ) { // Expanded description does not have value and button has no link.
$buttonContainer.html( '' );
return;
}
$buttonContainer.html( app.standardCtaButton( link, label ) );
},
/**
* Expand additional description on button click.
*
* @since 1.7.6
*
* @param {object} event Click event.
*/
showExpandedDescription: function( event ) {
event.preventDefault();
const $this = $( this ),
id = $this.closest( '.wpforms-field-internal-information' ).data( 'field-id' ),
$expandable = $this.closest( '.internal-information-content' ).find( '.wpforms-field-internal-information-row-expanded-description' ),
$buttonContainer = $( '#wpforms-field-' + id ).find( '.wpforms-field-internal-information-row-cta-button' ),
isExpanded = $expandable.hasClass( 'expanded' );
$expandable.toggleClass( 'expanded' );
if ( ! isExpanded ) {
$expandable.slideDown( 400 );
$buttonContainer.html( app.expandedButton() );
return;
}
$expandable.slideUp( 400 );
$buttonContainer.html( app.notExpandedButton() );
},
/**
* Validate if the CTA Link field has correct url.
*
* @since 1.7.6
*/
validateCTAlinkField: function() {
const $field = $( this ),
url = $field.val().trim();
$field.val( url );
if ( url === '' || wpf.isURL( url ) ) {
return;
}
$.confirm(
{
title: wpforms_builder.heads_up,
content: wpforms_builder.iif_redirect_url_field_error,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action: function() {
$field.trigger( 'focus' );
},
},
},
}
);
},
/**
* Handle checkbox checking.
*
* @since 1.7.6
*
* @param {object} event Click event.
*/
handleCheckboxClick: function( event ) {
event.preventDefault();
const $this = $( this ),
checked = ! $this.prop( 'checked' );
$this.prop( 'checked', checked );
app.saveInternalInformationCheckbox( $this.prop( 'name' ), Number( checked ) );
},
/**
* Replace checkboxes on description field.
*
* @since 1.7.6
*
* @param {object} event Triggered event.
* @param {object} data Field element and field value.
*/
descriptionFieldUpdated: function( event, data ) {
const type = $( '#wpforms-field-' + data.id ).data( 'field-type' );
if ( type !== 'internal-information' || data.value.length === 0 ) {
return;
}
data.value = app.replaceCheckboxes( data.value, data.id );
WPFormsBuilder.updateDescription( data.descField, data.value );
},
/**
* Prevent delete field alert to show.
*
* @since 1.7.6
*
* @param {object} event Triggered event.
* @param {object} fieldData Field data.
* @param {string} type Field type.
*/
preventDeleteFieldAlert: function( event, fieldData, type ) {
if ( type === 'internal-information' ) {
event.preventDefault();
WPFormsBuilder.fieldDeleteById( fieldData.id, type, 50 );
}
},
/**
* Replace Delete field button title with Dismiss.
*
* @since 1.7.6
*/
showDismissTitle: function() {
$( this ).attr( 'title', wpforms_builder.iif_dismiss );
},
/**
* Real-time updates for "CTA button" link.
*
* @since 1.7.6
*/
ctaButtonLinkUpdates() {
let $this = $( this ),
id = $this.parent().data( 'field-id' ),
$field = $( '#wpforms-field-' + id ),
$buttonContainer = $field.find( '.wpforms-field-internal-information-row-cta-button' ),
$expandable = $field.find( '.wpforms-field-internal-information-row-expanded-description' ),
desc = $this.closest( '#wpforms-field-option-' + id ).find( '.wpforms-field-option-row-expanded-description textarea' ).val(),
label = $this.closest( '#wpforms-field-option-' + id ).find( '.wpforms-field-option-row-cta-label input[type="text"]' ).val();
if ( desc.length !== 0 ) {
if ( $expandable.hasClass( 'expanded' ) ) {
$buttonContainer.html( app.expandedButton() );
return;
}
$buttonContainer.html( app.notExpandedButton() );
return;
}
if ( wpf.isURL( $this.val() ) && label.length !== 0 ) {
$buttonContainer.html( app.standardCtaButton( $this.val(), label ) );
return;
}
$buttonContainer.html( '' );
},
/**
* Real-time updates for "CTA button" label.
*
* @since 1.7.6
*/
ctaButtonLabelUpdates: function() {
let $this = $( this ),
value = wpf.sanitizeHTML( $this.val() ),
id = $this.parent().data( 'field-id' ),
$field = $( '#wpforms-field-' + id ),
$buttonContainer = $field.find( '.wpforms-field-internal-information-row-cta-button' ),
$expandable = $field.find( '.wpforms-field-internal-information-row-expanded-description' ),
desc = $this.closest( '#wpforms-field-option-' + id ).find( '.wpforms-field-option-row-expanded-description textarea' ).val(),
link = $this.closest( '#wpforms-field-option-' + id ).find( '.wpforms-field-option-row-cta-link input[type="text"]' ).val();
if ( desc.length !== 0 && value.length !== 0 ) {
if ( $expandable.hasClass( 'expanded' ) ) {
$buttonContainer.html( app.expandedButton() );
return;
}
$buttonContainer.html( app.notExpandedButton() );
return;
}
if ( value.length !== 0 && wpf.isURL( link ) ) {
$buttonContainer.html( app.standardCtaButton( link, value ) );
return;
}
if ( desc.length === 0 ) {
$buttonContainer.html( '' );
}
},
/**
* Standard CTA button template.
*
* @since 1.7.6
*
* @param {string} url Button URL.
* @param {string} label Button label.
*
* @returns {string} Button HTML.
*/
standardCtaButton: function( url, label ) {
let button = `<div class="cta-button cta-link-external ">
<a href="%url%" target="_blank" rel="noopener noreferrer">
<span class="button-label">%label%</span>
</a></div>`;
return button.replace( '%url%', wpf.sanitizeHTML( url ) ).replace( '%label%', wpf.sanitizeHTML( label ) );
},
/**
* Not expanded button.
*
* @since 1.7.6
*
* @returns {string} Not expanded button HTML.
*/
notExpandedButton: function() {
let button = `<div class="cta-button cta-expand-description not-expanded">
<a href="#" target="_blank" rel="noopener noreferrer">
<span class="button-label">%label%</span>
<span class="icon not-expanded">
<svg viewBox="0 0 10 7">
<path d="M4.91016 5.90234C5.15625 6.14844 5.56641 6.14844 5.8125 5.90234L9.53125 2.18359C9.80469 1.91016 9.80469 1.5 9.53125 1.25391L8.92969 0.625C8.65625 0.378906 8.24609 0.378906 8 0.625L5.34766 3.27734L2.72266 0.625C2.47656 0.378906 2.06641 0.378906 1.79297 0.625L1.19141 1.25391C0.917969 1.5 0.917969 1.91016 1.19141 2.18359L4.91016 5.90234Z"></path>
<path d="M4.91016 5.90234C5.15625 6.14844 5.56641 6.14844 5.8125 5.90234L9.53125 2.18359C9.80469 1.91016 9.80469 1.5 9.53125 1.25391L8.92969 0.625C8.65625 0.378906 8.24609 0.378906 8 0.625L5.34766 3.27734L2.72266 0.625C2.47656 0.378906 2.06641 0.378906 1.79297 0.625L1.19141 1.25391C0.917969 1.5 0.917969 1.91016 1.19141 2.18359L4.91016 5.90234Z"></path>
</svg>
</span>
</a></div>`;
return button.replace( '%label%', wpforms_builder.iif_more );
},
/**
* Expanded button.
*
* @since 1.7.6
*
* @returns {string} Expanded button HTML.
*/
expandedButton: function() {
let button = `<div class="cta-button cta-expand-description expanded">
<a href="#" target="_blank" rel="noopener noreferrer">
<span class="button-label">%label%</span>
<span class="icon expanded">
<svg viewBox="0 0 10 7">
<path d="M5.83984 0.625C5.56641 0.378906 5.15625 0.378906 4.91016 0.625L1.19141 4.34375C0.917969 4.61719 0.917969 5.02734 1.19141 5.27344L1.79297 5.90234C2.06641 6.14844 2.47656 6.14844 2.72266 5.90234L5.375 3.25L8 5.90234C8.24609 6.14844 8.68359 6.14844 8.92969 5.90234L9.55859 5.27344C9.80469 5.02734 9.80469 4.61719 9.55859 4.34375L5.83984 0.625Z" fill="red"></path>
</svg>
</span>
</a></div>`;
return button.replace( '%label%', wpforms_builder.close );
},
};
return app;
}( document, window, jQuery ) );
WPFormsInternalInformationField.init();
File diff suppressed because one or more lines are too long
@@ -0,0 +1,317 @@
/* global wpf */
/**
* Form Builder Field Numbers module.
*
* @since 1.9.4
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.FieldNumbers = WPForms.Admin.Builder.FieldNumbers || ( function( document, window, $ ) { // eslint-disable-line
/**
* Public functions and properties.
*
* @since 1.9.4
*
* @type {Object}
*/
const app = {
/**
* WPForms builder element.
*
* @since 1.9.4
*
* @type {jQuery}
*/
$builder: null,
/**
* Track if a tag was clicked recently.
*
* @since 1.9.5
*
* @type {boolean}
*/
tagClicked: false,
/**
* Initialize the application.
*
* @since 1.9.4
*/
init() {
$( app.ready );
},
/**
* Called when the DOM is fully loaded.
*
* @since 1.9.4
*/
ready() {
app.$builder = $( '#wpforms-builder' );
app.numbersEvents();
},
/**
* Binds separate events for min, max, and default value inputs.
*
* @since 1.9.4
*/
numbersEvents() {
app.$builder.on(
'change',
'.wpforms-field-option-number .wpforms-numbers-min',
app.onChangeNumbersMin
);
app.$builder.on(
'change',
'.wpforms-field-option-number .wpforms-numbers-max',
app.onChangeNumbersMax
);
app.$builder.on(
'input',
'.wpforms-field-option-number .wpforms-field-option-row-default_value .wpforms-smart-tags-widget-original',
_.debounce( app.onChangeNumbersDefaultValue, 500 )
);
app.$builder.on(
'click',
'.wpforms-smart-tags-widget .tag',
app.smartTagClickTracking
);
},
/**
* Track clicks on the smart tag bricks.
*
* @since 1.9.5
*/
smartTagClickTracking() {
app.tagClicked = true;
// Reset the flag after a short delay.
setTimeout( () => {
app.tagClicked = false;
}, 200 );
},
/**
* Parses the numeric value of a field, returning null if invalid or empty.
*
* @since 1.9.4
*
* @param {jQuery} $field The jQuery object for the input field.
*
* @return {number|null} The parsed numeric value or null.
*/
parseFieldValue( $field ) {
if ( ! $field.length || $field.val() === '' ) {
return null;
}
const value = parseFloat( $field.val() );
return isNaN( value ) ? null : value;
},
/**
* Determines if the min value is greater than the max value.
*
* @since 1.9.4
*
* @param {jQuery} $minField jQuery object for the min input field.
* @param {jQuery} $maxField jQuery object for the max input field.
*
* @return {boolean} True if min is greater than max, otherwise false.
*/
isInvalidMinMaxRange( $minField, $maxField ) {
const min = app.parseFieldValue( $minField ),
max = app.parseFieldValue( $maxField );
return min !== null && max !== null && min > max;
},
/**
* Synchronizes the min attribute on the max field.
*
* @since 1.9.4
*
* @param {jQuery} $minField jQuery object for the min input field.
* @param {jQuery} $maxField jQuery object for the max input field.
*/
syncNumberMinAttribute( $minField, $maxField ) {
$maxField.attr( 'min', app.parseFieldValue( $minField ) );
},
/**
* Synchronizes the max attribute on the min field.
*
* @since 1.9.4
*
* @param {jQuery} $minField jQuery object for the min input field.
* @param {jQuery} $maxField jQuery object for the max input field.
*/
syncNumberMaxAttribute( $minField, $maxField ) {
$minField.attr( 'max', app.parseFieldValue( $maxField ) );
},
/**
* Adjusts the target field's value to match the source field's value.
*
* @since 1.9.4
*
* @param {jQuery} $sourceField jQuery object for the field with the value to copy.
* @param {jQuery} $targetField jQuery object for the field to update.
*/
adjustValue( $sourceField, $targetField ) {
$targetField.val( app.parseFieldValue( $sourceField ) ).trigger( 'input' ).trigger( 'wpformsSmartTagsInputSync' );
},
/**
* Handles the 'input' event for the min field, ensuring correct min <= max and default value.
*
* @since 1.9.4
*
* @param {Event} event The input event object.
*/
onChangeNumbersMin( event ) {
const $minField = $( event.target ),
$container = $minField.closest( '.wpforms-field-option-group' ),
$maxField = $container.find( '.wpforms-numbers-max' ),
$defaultValueField = $container.find( '.wpforms-field-option-row-default_value input.wpforms-smart-tags-widget-original' );
if ( app.isInvalidMinMaxRange( $minField, $maxField ) ) {
app.adjustValue( $maxField, $minField );
}
if ( app.isNeedAdjustDefaultValueByMinValue( $defaultValueField, $minField ) ) {
app.adjustValue( $minField, $defaultValueField );
}
app.syncNumberMinAttribute( $minField, $maxField );
},
/**
* Handles the 'change' event for the max field, ensuring correct min <= max and default value.
*
* @since 1.9.4
*
* @param {Event} event The change event object.
*
* @return {void}
*/
onChangeNumbersMax( event ) {
const $maxField = $( event.target ),
$container = $maxField.closest( '.wpforms-field-option-group' ),
$minField = $container.find( '.wpforms-numbers-min' ),
$defaultValueField = $container.find( '.wpforms-field-option-row-default_value input.wpforms-smart-tags-widget-original' );
if ( app.isInvalidMinMaxRange( $minField, $maxField ) ) {
app.adjustValue( $minField, $maxField );
}
if ( app.isNeedAdjustDefaultValueByMaxValue( $defaultValueField, $maxField ) ) {
app.adjustValue( $maxField, $defaultValueField );
}
app.syncNumberMaxAttribute( $minField, $maxField );
},
/**
* Normalize a float value of the input field by replacing commas with dots.
* If the normalized value differs from the original,
* the input field will be updated and the 'input' event will be triggered.
* Non-numeric values are ignored and remain unchanged.
*
* @since 1.9.4
*
* @param {jQuery} $field The input field to normalize.
*
* @return {void}
*/
normalizeFloatValue( $field ) {
const value = $field.val(),
valueWithoutComma = value.replace( ',', '.' );
if ( wpf.isNumber( valueWithoutComma ) && value !== parseFloat( value ).toString() ) {
$field.val( parseFloat( valueWithoutComma ) ).trigger( 'input' );
}
},
/**
* Checks if the default value is below the current min value.
*
* @since 1.9.4
*
* @param {jQuery} $defaultValueField jQuery object for the default value input.
* @param {jQuery} $minField jQuery object for the min input field.
*
* @return {boolean} True if default value is less than min, otherwise false.
*/
isNeedAdjustDefaultValueByMinValue( $defaultValueField, $minField ) {
const defaultValue = app.parseFieldValue( $defaultValueField ),
min = app.parseFieldValue( $minField );
return wpf.isNumber( defaultValue ) && min !== null && defaultValue < min;
},
/**
* Checks if the default value is above the current max value.
*
* @since 1.9.4
*
* @param {jQuery} $defaultValueField jQuery object for the default value input.
* @param {jQuery} $maxField jQuery object for the max input field.
*
* @return {boolean} True if default value is greater than max, otherwise false.
*/
isNeedAdjustDefaultValueByMaxValue( $defaultValueField, $maxField ) {
const defaultValue = app.parseFieldValue( $defaultValueField ),
max = app.parseFieldValue( $maxField );
return wpf.isNumber( defaultValue ) && max !== null && defaultValue > max;
},
/**
* Handles the 'change' event for the default value field, keeping it in range.
*
* @since 1.9.4
*
* @param {Event} event The change event object.
*/
onChangeNumbersDefaultValue( event ) {
if (
app.tagClicked || // Tag was recently clicked to prevent unnecessary updates.
event.handleObj?.type === 'focusout' // Event was triggered when editable tag was changed.
) {
return;
}
const $defaultValueField = $( event.target );
const $container = $defaultValueField.closest( '.wpforms-field-option-group' );
const $minField = $container.find( '.wpforms-numbers-min' );
const $maxField = $container.find( '.wpforms-numbers-max' );
app.normalizeFloatValue( $defaultValueField );
if ( app.isNeedAdjustDefaultValueByMinValue( $defaultValueField, $minField ) ) {
app.adjustValue( $minField, $defaultValueField );
}
if ( app.isNeedAdjustDefaultValueByMaxValue( $defaultValueField, $maxField ) ) {
app.adjustValue( $maxField, $defaultValueField );
}
},
};
return app;
}( document, window, jQuery ) );
WPForms.Admin.Builder.FieldNumbers.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.FieldNumbers=WPForms.Admin.Builder.FieldNumbers||(u=>{let i={$builder:null,tagClicked:!1,init(){u(i.ready)},ready(){i.$builder=u("#wpforms-builder"),i.numbersEvents()},numbersEvents(){i.$builder.on("change",".wpforms-field-option-number .wpforms-numbers-min",i.onChangeNumbersMin),i.$builder.on("change",".wpforms-field-option-number .wpforms-numbers-max",i.onChangeNumbersMax),i.$builder.on("input",".wpforms-field-option-number .wpforms-field-option-row-default_value .wpforms-smart-tags-widget-original",_.debounce(i.onChangeNumbersDefaultValue,500)),i.$builder.on("click",".wpforms-smart-tags-widget .tag",i.smartTagClickTracking)},smartTagClickTracking(){i.tagClicked=!0,setTimeout(()=>{i.tagClicked=!1},200)},parseFieldValue(e){return!e.length||""===e.val()||(e=parseFloat(e.val()),isNaN(e))?null:e},isInvalidMinMaxRange(e,a){e=i.parseFieldValue(e),a=i.parseFieldValue(a);return null!==e&&null!==a&&a<e},syncNumberMinAttribute(e,a){a.attr("min",i.parseFieldValue(e))},syncNumberMaxAttribute(e,a){e.attr("max",i.parseFieldValue(a))},adjustValue(e,a){a.val(i.parseFieldValue(e)).trigger("input").trigger("wpformsSmartTagsInputSync")},onChangeNumbersMin(e){var e=u(e.target),a=e.closest(".wpforms-field-option-group"),r=a.find(".wpforms-numbers-max"),a=a.find(".wpforms-field-option-row-default_value input.wpforms-smart-tags-widget-original");i.isInvalidMinMaxRange(e,r)&&i.adjustValue(r,e),i.isNeedAdjustDefaultValueByMinValue(a,e)&&i.adjustValue(e,a),i.syncNumberMinAttribute(e,r)},onChangeNumbersMax(e){var e=u(e.target),a=e.closest(".wpforms-field-option-group"),r=a.find(".wpforms-numbers-min"),a=a.find(".wpforms-field-option-row-default_value input.wpforms-smart-tags-widget-original");i.isInvalidMinMaxRange(r,e)&&i.adjustValue(r,e),i.isNeedAdjustDefaultValueByMaxValue(a,e)&&i.adjustValue(e,a),i.syncNumberMaxAttribute(r,e)},normalizeFloatValue(e){var a=e.val(),r=a.replace(",",".");wpf.isNumber(r)&&a!==parseFloat(a).toString()&&e.val(parseFloat(r)).trigger("input")},isNeedAdjustDefaultValueByMinValue(e,a){e=i.parseFieldValue(e),a=i.parseFieldValue(a);return wpf.isNumber(e)&&null!==a&&e<a},isNeedAdjustDefaultValueByMaxValue(e,a){e=i.parseFieldValue(e),a=i.parseFieldValue(a);return wpf.isNumber(e)&&null!==a&&a<e},onChangeNumbersDefaultValue(e){var a,r;i.tagClicked||"focusout"===e.handleObj?.type||(a=(r=(e=u(e.target)).closest(".wpforms-field-option-group")).find(".wpforms-numbers-min"),r=r.find(".wpforms-numbers-max"),i.normalizeFloatValue(e),i.isNeedAdjustDefaultValueByMinValue(e,a)&&i.adjustValue(a,e),i.isNeedAdjustDefaultValueByMaxValue(e,r)&&i.adjustValue(r,e))}};return i})((document,window,jQuery)),WPForms.Admin.Builder.FieldNumbers.init();
@@ -0,0 +1,117 @@
/**
* WPForms Rating Field Builder Script
*
* @since 1.9.8
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.FieldRating = WPForms.Admin.Builder.FieldRating || ( function( document, window, $ ) { // eslint-disable-line
const app = {
/**
* Initialize the application.
*/
init() {
$( app.ready );
},
/**
* Called when the DOM is fully loaded.
*/
ready() {
$( document )
.on( 'input', '.wpforms-field-option-row-lowest_label input', app.updateLowestLabel )
.on( 'input', '.wpforms-field-option-row-highest_label input', app.updateHighestLabel )
.on( 'change', '.wpforms-field-option-row-label_position select', app.updateLabelPosition );
},
/**
* Update the lowest label in the preview when the input changes.
*
* @since 1.9.8
*
* @param {Event} event The input event.
*/
updateLowestLabel( event ) {
app.updateLabel( event, 'lowest' );
},
/**
* Update the highest label in the preview when the input changes.
*
* @since 1.9.8
*
* @param {Event} event The input event.
*/
updateHighestLabel( event ) {
app.updateLabel( event, 'highest' );
},
/**
* Update the label in the preview based on the input value.
*
* @since 1.9.8
*
* @param {Event} event The input event.
* @param {string} type The type of label being updated ('lowest' or 'highest').
*/
updateLabel( event, type ) {
const $input = $( event.target ),
label = $input.val(),
$inputContainer = $input.closest( `.wpforms-field-option-row-${ type }_label` ),
fieldId = $inputContainer.data( 'field-id' ),
$previewField = $( `#wpforms-field-${ fieldId }` ),
$previewLabel = $previewField.find( `.wpforms-rating-field-${ type }-label` );
// Update the label in the preview.
$previewLabel.text( label );
// Show or hide the labels container based on whether any labels are set.
app.toggleLabelsVisibility( $previewField );
},
/**
* Show or hide the labels container based on whether any labels are set.
*
* @since 1.9.8
*
* @param {jQuery} $previewField The jQuery object representing the preview field.
*/
toggleLabelsVisibility( $previewField ) {
const labelsContainer = $previewField.find( '.wpforms-rating-field-labels' ),
labels = labelsContainer.find( '.wpforms-sub-label' );
const labelsArray = labels.map( ( _, el ) => $( el ).text() ).get(),
filteredLabels = labelsArray.filter( ( ratingLabel ) => ratingLabel.trim() !== '' );
labelsContainer.toggleClass( 'wpforms-hidden', filteredLabels.length === 0 );
},
/**
* Update the label position in the preview when the select changes.
*
* @since 1.9.8
*
* @param {Event} event The change event.
*/
updateLabelPosition( event ) {
const $select = $( event.target ),
labelPosition = $select.val(),
$inputContainer = $select.closest( '.wpforms-field-option-row-label_position' ),
fieldId = $inputContainer.data( 'field-id' ),
$previewField = $( `#wpforms-field-${ fieldId }` );
// Remove existing label position classes.
$previewField.find( '.wpforms-rating-field-labels' ).toggleClass( 'wpforms-rating-field-labels-position-above', labelPosition === 'above' );
},
};
return app;
}( document, window, jQuery ) ); // eslint-disable-line no-undef
// Initialize the application.
WPForms.Admin.Builder.FieldRating.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.FieldRating=WPForms.Admin.Builder.FieldRating||((e,t)=>{let o={init(){t(o.ready)},ready(){t(e).on("input",".wpforms-field-option-row-lowest_label input",o.updateLowestLabel).on("input",".wpforms-field-option-row-highest_label input",o.updateHighestLabel).on("change",".wpforms-field-option-row-label_position select",o.updateLabelPosition)},updateLowestLabel(e){o.updateLabel(e,"lowest")},updateHighestLabel(e){o.updateLabel(e,"highest")},updateLabel(e,i){var e=t(e.target),l=e.val(),e=e.closest(`.wpforms-field-option-row-${i}_label`).data("field-id"),e=t("#wpforms-field-"+e);e.find(`.wpforms-rating-field-${i}-label`).text(l),o.toggleLabelsVisibility(e)},toggleLabelsVisibility(e){var e=e.find(".wpforms-rating-field-labels"),i=e.find(".wpforms-sub-label").map((e,i)=>t(i).text()).get().filter(e=>""!==e.trim());e.toggleClass("wpforms-hidden",0===i.length)},updateLabelPosition(e){var e=t(e.target),i=e.val(),e=e.closest(".wpforms-field-option-row-label_position").data("field-id");t("#wpforms-field-"+e).find(".wpforms-rating-field-labels").toggleClass("wpforms-rating-field-labels-position-above","above"===i)}};return o})(document,(window,jQuery)),WPForms.Admin.Builder.FieldRating.init();
@@ -0,0 +1,788 @@
/* global List, wpforms_form_templates, wpforms_addons, wpf, WPFormsUtils */
/**
* @param wpforms_form_templates.admin_nonce
* @param wpforms_form_templates.delete_template
* @param wpforms_form_templates.delete_template_content
* @param wpforms_form_templates.delete_template_title
* @param wpforms_form_templates.template_addon_activate
* @param wpforms_form_templates.template_addon_prompt
* @param wpforms_form_templates.template_addons_error
* @param wpforms_form_templates.template_addons_prompt
* @param wpforms_form_templates.use_template
*/
// noinspection ES6ConvertVarToLetConst
/**
* Form Templates function.
*
* @since 1.7.7
*/
// eslint-disable-next-line no-var
var WPFormsFormTemplates = window.WPFormsFormTemplates || ( function( document, window, $ ) {
/**
* Runtime variables.
*
* @since 1.7.7
*
* @type {Object}
*/
const vars = {};
/**
* Public functions and properties.
*
* @since 1.7.7
*
* @type {Object}
*/
const app = {
/**
* Start the engine.
*
* @since 1.7.7
*/
init() {
$( app.ready );
$( window ).on( 'load', function() {
// in the case of jQuery 3.+ we need to wait for the `ready` event first.
if ( typeof $.ready.then === 'function' ) {
$.ready.then( app.load );
} else {
app.load();
}
} );
},
/**
* Document ready.
*
* @since 1.7.7
*/
ready() {
app.events();
},
/**
* Window load.
*
* @since 1.7.7
*/
load() {
app.showUpgradeBanner();
},
/**
* Setup. Prepare some variables.
*
* @since 1.7.7
*/
setup() {
// Trigger event before initializing the template list.
WPFormsUtils.triggerEvent( $( document ), 'wpformsSetupPanelBeforeInitTemplatesList' );
// Template list object.
vars.templateList = new List( 'wpforms-setup-templates-list', {
valueNames: [
'wpforms-template-name',
'wpforms-template-desc',
{
name: 'fields',
attr: 'data-fields',
},
{
name: 'slug',
attr: 'data-slug',
},
{
name: 'categories',
attr: 'data-categories',
},
{
name: 'subcategories',
attr: 'data-subcategories',
},
{
name: 'has-access',
attr: 'data-has-access',
},
{
name: 'favorite',
attr: 'data-favorite',
},
],
} );
},
/**
* Bind events.
*
* @since 1.7.7
*/
events() {
$( document )
.on( 'click', '#wpforms-setup-templates-list .wpforms-template-favorite i', app.selectFavorite )
.on( 'click', '#wpforms-setup-templates-list .wpforms-template-remove i', app.removeTemplate );
},
/**
* Select Favorite Templates.
*
* @since 1.7.7
*/
// eslint-disable-next-line max-lines-per-function
selectFavorite() {
const $heartIcon = $( this ),
favorite = $heartIcon.hasClass( 'fa-heart-o' ),
$favorite = $heartIcon.closest( '.wpforms-template-favorite' ),
$template = $heartIcon.closest( '.wpforms-template' ),
$templateName = $template.find( '.wpforms-template-name' ),
templateSlug = $template.find( '.wpforms-template-select' ).data( 'slug' ),
$favoritesCategory = $( '.wpforms-setup-templates-categories' ).find( '[data-category=\'favorites\']' ),
$favoritesCount = $favoritesCategory.find( 'span' ),
data = {
action: 'wpforms_templates_favorite',
slug: templateSlug,
favorite,
nonce: wpforms_form_templates.nonce,
};
let favoritesCount = parseInt( $favoritesCount.html(), 10 );
const item = vars.templateList.get( 'slug', templateSlug )[ 0 ],
values = item.values();
const toggleHeartIcon = function() {
$favorite.find( '.fa-heart-o' ).toggleClass( 'wpforms-hidden', values.favorite );
$favorite.find( '.fa-heart' ).toggleClass( 'wpforms-hidden', ! values.favorite );
};
const unMarkFavorite = function() {
values.favorite = false;
favoritesCount = favoritesCount - 1;
item.values( values );
toggleHeartIcon();
$templateName.data( 'data-favorite', 0 );
$favoritesCount.html( favoritesCount );
app.maybeHideFavoritesCategory();
};
const markFavorite = function() {
values.favorite = true;
favoritesCount = favoritesCount + 1;
item.values( values );
toggleHeartIcon();
$templateName.data( 'data-favorite', 1 );
$favoritesCount.html( favoritesCount );
app.maybeHideFavoritesCategory();
};
$.post( wpforms_form_templates.ajaxurl, data, function( res ) {
if ( ! res.success ) {
if ( favorite ) {
unMarkFavorite();
return;
}
markFavorite();
}
} );
if ( favorite ) {
markFavorite();
return;
}
unMarkFavorite();
},
/**
* Remove Template.
*
* @since 1.8.8
*/
removeTemplate() {
const $trashIcon = $( this ),
$template = $trashIcon.closest( '.wpforms-template-remove' ),
$templateId = $template.data( 'template' );
$.alert( {
title: wpforms_form_templates.delete_template_title,
content: wpforms_form_templates.delete_template_content,
icon: 'fa fa-exclamation-circle',
type: 'red',
buttons: {
confirm: {
text: wpforms_form_templates.delete_template,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
app.removeUserTemplate( $templateId );
},
},
cancel: {
text: wpforms_form_templates.cancel,
},
},
} );
},
/**
* Remove User Template.
*
* @since 1.8.8
*
* @param {number} templateId Template ID.
*/
removeUserTemplate( templateId ) {
vars.templateList.remove( 'slug', 'wpforms-user-template-' + templateId );
$.post( wpforms_form_templates.ajaxurl, {
action: 'wpforms_user_template_remove',
template: templateId,
nonce: wpforms_form_templates.nonce,
}, function( res ) {
if ( res.success ) {
$( '#wpforms-template-wpforms-user-template-' + templateId ).remove();
app.updateCategoryCount( 'all' );
app.updateCategoryCount( 'user' );
}
} );
},
/**
* Update category count.
*
* @since 1.8.8
*
* @param {string} category Category name.
*/
updateCategoryCount( category ) {
const categoriesList = $( '.wpforms-setup-templates-categories' ),
$category = categoriesList.find( `[data-category='${ category }']` ),
$count = $category.find( 'span' ),
count = parseInt( $count.html(), 10 );
$count.html( count - 1 );
$category.data( 'count', count - 1 );
if ( count - 1 === 0 && category === 'user' && $category.hasClass( 'active' ) ) {
$( '.wpforms-user-templates-empty-state' ).removeClass( 'wpforms-hidden' );
}
},
/**
* Maybe hide favorites category if there are no templates.
*
* @since 1.7.7
*/
maybeHideFavoritesCategory() {
const $categoriesList = $( '.wpforms-setup-templates-categories' ),
$favoritesCategory = $categoriesList.find( '[data-category=\'favorites\']' ),
favoritesCount = parseInt( $favoritesCategory.find( 'span' ).html(), 10 );
$favoritesCategory.toggleClass( 'wpforms-hidden', ! favoritesCount );
if ( $favoritesCategory.hasClass( 'active' ) ) {
if ( ! favoritesCount ) {
$categoriesList.find( '[data-category=\'all\']' ).trigger( 'click' );
return;
}
$favoritesCategory.trigger( 'click' );
}
},
/**
* Search template callback.
*
* @since 1.7.7
*/
searchTemplate() {
app.performSearch( $( this ).val() );
app.showUpgradeBanner();
},
/**
* Perform search value.
*
* @since 1.7.7.2
*
* @param {string} query Value to search.
*/
performSearch( query ) {
const searchResult = vars.templateList.search( query, [ 'name' ], function( searchString ) {
for ( let index = 0, length = vars.templateList.items.length; index < length; index++ ) {
const values = vars.templateList.items[ index ].values();
const templateName = values[ 'wpforms-template-name' ].toLowerCase();
const templateDesc = values[ 'wpforms-template-desc' ].toLowerCase();
const fields = values.fields.toLowerCase();
const searchRegex = new RegExp( searchString );
vars.templateList.items[ index ].found = searchRegex.test( templateName ) || searchRegex.test( templateDesc ) || searchRegex.test( fields );
}
} );
$( '.wpforms-templates-no-results' ).toggle( ! searchResult.length );
},
/**
* Select subcategory.
*
* @since 1.8.4
*
* @param {Object} e Event object.
*/
selectSubCategory( e ) {
e.preventDefault();
const $item = $( this );
const $activeCategory = $item.parent( 'ul' ).parent( 'li' ).parent( 'ul' ).children( 'li.active' );
const $activeSubcategory = $( '.wpforms-setup-templates-subcategories li.active' );
const subcategory = $item.data( 'subcategory' );
const category = $item.parents( 'li' ).data( 'category' );
const searchQuery = $( '#wpforms-setup-template-search' ).val();
// Clear active class from the parent category and current subcategory.
$activeSubcategory.removeClass( 'active' );
$activeCategory.removeClass( 'active' );
// Add active class to the parent category and current subcategory.
$item.parents( 'li' ).addClass( 'active' );
$item.addClass( 'active' );
vars.templateList.filter( function( item ) {
return category === 'all' || ( item.values().categories.split( ',' ).indexOf( category ) > -1 && item.values().subcategories.split( ',' ).indexOf( subcategory ) > -1 );
} );
if ( searchQuery !== '' ) {
app.performSearch( searchQuery );
}
app.showUpgradeBanner();
},
/**
* Select category.
*
* @since 1.7.7
*
* @param {Object} e Event object.
*/
selectCategory( e ) {
e.preventDefault();
const $item = $( this ).parent(),
$active = $item.closest( 'ul' ).find( '.active' ),
category = $item.data( 'category' ),
count = $item.data( 'count' ),
searchQuery = $( '#wpforms-setup-template-search' ).val();
$active.removeClass( 'active' );
$item.addClass( 'active opened' );
vars.templateList.filter( function( item ) {
if ( category === 'available' ) {
return item.values()[ 'has-access' ];
}
if ( category === 'favorites' ) {
return item.values().favorite;
}
return category === 'all' || item.values().categories.split( ',' ).indexOf( category ) > -1;
} );
// Display/hide User Templates empty state message.
$( '.wpforms-user-templates-empty-state' ).toggleClass( 'wpforms-hidden', category !== 'user' || count !== 0 );
if ( searchQuery !== '' ) {
app.performSearch( searchQuery );
}
app.showUpgradeBanner();
},
/**
* Show/hide the subcategory list by clicking on the chevron icon.
*
* @since 1.8.7
*
* @param {Object} e Event object.
*/
toggleSubcategoriesList( e ) {
e.stopPropagation();
const $item = $( this ).parent().parent();
$item.toggleClass( 'opened' );
},
/**
* Cancel button click routine.
*
* @since 1.7.7
*/
selectTemplateCancel( ) {
const $template = $( '#wpforms-setup-templates-list' ).find( '.wpforms-template.active' ),
$button = $template.find( '.wpforms-template-select' );
$template.removeClass( 'active' );
$button.html( $button.data( 'labelOriginal' ) );
},
/**
* Show upgrade banner if a license type is less than Pro.
*
* @since 1.7.7
*/
showUpgradeBanner() {
if ( ! $( '#tmpl-wpforms-templates-upgrade-banner' ).length ) {
return;
}
const template = wp.template( 'wpforms-templates-upgrade-banner' );
if ( ! template ) {
return;
}
const $templates = $( '#wpforms-setup-templates-list .wpforms-template' );
if ( $templates.length > 5 ) {
$templates.eq( 5 ).after( template() );
return;
}
$templates.last().after( template() );
},
/**
* Select template.
*
* @since 1.8.2
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
* @param {jQuery} $button Use a template button object.
* @param {Function} callback The function to set the template.
*/
selectTemplateProcess( formName, template, $button, callback ) {
if ( $button.data( 'addons' ) ) {
app.addonsModal( formName, template, $button, callback );
return;
}
callback( formName, template );
},
/**
* Open required addons alert.
*
* @since 1.8.2
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
* @param {jQuery} $button Use a template button object.
* @param {Function} callback The function to set the template.
*/
addonsModal( formName, template, $button, callback ) {
const templateName = $button.data( 'template-name-raw' );
const addonsNames = $button.data( 'addons-names' );
const addonsSlugs = $button.data( 'addons' );
const installedSlugs = $button.data( 'installed' );
const addons = addonsSlugs.split( ',' );
let prompt;
switch ( app.action( addons, installedSlugs ) ) {
case 'multiple':
prompt = wpforms_form_templates.template_addons_prompt;
break;
case 'activate':
prompt = wpforms_form_templates.template_addon_activate;
break;
case 'install':
prompt = wpforms_form_templates.template_addon_prompt;
break;
default:
prompt = wpforms_form_templates.template_addons_prompt;
break;
}
prompt = prompt.replace( /%template%/g, templateName ).replace( /%addons%/g, addonsNames );
if ( ! addons.length ) {
return;
}
if ( ! wpforms_form_templates.can_install_addons ) {
app.userCannotInstallAddonsModal( prompt );
return;
}
app.userCanInstallAddonsModal( formName, template, addons, prompt, callback, installedSlugs );
},
/**
* Open the template addon alert for admins.
*
* @since 1.8.2
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
* @param {Array} addons Array of addon slugs.
* @param {string} prompt Modal content.
* @param {Function} callback The function to set the template.
* @param {string} installedSlugs Installed slug.
*/
userCanInstallAddonsModal( formName, template, addons, prompt, callback, installedSlugs = '' ) {
const spinner = '<i class="wpforms-loading-spinner wpforms-loading-white wpforms-loading-inline"></i>';
let confirm;
switch ( app.action( addons, installedSlugs ) ) {
case 'multiple':
case 'install':
confirm = wpforms_form_templates.install_confirm;
break;
case 'activate':
confirm = wpforms_form_templates.activate_confirm;
break;
default:
confirm = wpforms_form_templates.install_confirm;
break;
}
$.confirm( {
title: wpforms_form_templates.heads_up,
content: prompt,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: confirm,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
this.$$confirm
.prop( 'disabled', true )
.html( spinner + wpforms_form_templates.activating );
this.$$cancel
.prop( 'disabled', true );
app.installActivateAddons( addons, this, formName, template, callback );
return false;
},
},
cancel: {
text: wpforms_form_templates.cancel,
action() {
WPFormsFormTemplates.selectTemplateCancel();
},
},
},
} );
},
/**
* Get the action for the addons.
*
* @since 1.9.0
*
* @param {Array} addons Addons slugs.
* @param {string} installed Installed addon slug.
*
* @return {string} Action.
*/
action( addons, installed = '' ) {
if ( addons.length > 1 ) {
return 'multiple';
}
if ( installed.split( ',' ).indexOf( addons[ 0 ] ) > -1 ) {
return 'activate';
}
return 'install';
},
/**
* Open the template addon alert for non-admins.
*
* @since 1.8.2
*
* @param {string} prompt Modal content.
*/
userCannotInstallAddonsModal( prompt ) {
$.alert( {
title: wpforms_form_templates.heads_up,
content: prompt,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
ok: {
text: wpforms_form_templates.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
WPFormsFormTemplates.selectTemplateCancel();
},
},
},
} );
},
/**
* Install & Activate addons via AJAX.
*
* @since 1.8.2
*
* @param {Array} addons Addons slugs.
* @param {Object} previousModal Previous modal instance.
* @param {string} formName Name of the form.
* @param {string} template Template slug.
* @param {Function} callback The function to set the template.
*/
installActivateAddons( addons, previousModal, formName, template, callback ) {
const ajaxResults = [];
const ajaxErrors = [];
let promiseChain = false;
// Put each of the ajax call promises to the chain.
addons.forEach( function( addon ) {
if ( typeof promiseChain.done !== 'function' ) {
promiseChain = app.installActivateAddonAjax( addon );
return;
}
promiseChain = promiseChain
.done( function( value ) {
ajaxResults.push( value );
return app.installActivateAddonAjax( addon );
} )
.fail( function( error ) {
ajaxErrors.push( error );
} );
} );
promiseChain
// Latest promise result and error.
.done( function( value ) {
ajaxResults.push( value );
} )
.fail( function( error ) {
ajaxErrors.push( error );
} )
// Finally, resolve all the promises.
.always( function() {
previousModal.close();
if (
ajaxResults.length > 0 &&
wpf.listPluck( ajaxResults, 'success' ).every( Boolean ) && // Check if every `success` is true.
ajaxErrors.length === 0
) {
callback( formName, template );
return;
}
app.installActivateAddonsError( formName, template, callback );
} );
},
/**
* Install & Activate addons error modal.
*
* @since 1.8.2
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
* @param {Function} callback The function to set the template.
*/
installActivateAddonsError( formName, template, callback ) {
$.confirm( {
title: wpforms_form_templates.heads_up,
content: wpforms_form_templates.template_addons_error,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_form_templates.use_template,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
callback( formName, template );
},
},
cancel: {
text: wpforms_form_templates.cancel,
action() {
app.selectTemplateCancel();
},
},
},
} );
},
/**
* Install & Activate single addon via AJAX.
*
* @since 1.8.2
*
* @param {string} addon Addon slug.
*
* @return {Promise} jQuery ajax call promise.
*/
installActivateAddonAjax( addon ) {
const addonData = wpforms_addons[ addon ];
const deferred = new $.Deferred();
if (
! addonData ||
[ 'activate', 'install' ].indexOf( addonData.action ) < 0
) {
deferred.resolve( false );
return deferred.promise();
}
return $.post(
wpforms_form_templates.ajaxurl,
{
action: 'wpforms_' + addonData.action + '_addon',
nonce: wpforms_form_templates.admin_nonce,
plugin: addonData.action === 'activate' ? addon + '/' + addon + '.php' : addonData.url,
}
);
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPFormsFormTemplates.init();
File diff suppressed because one or more lines are too long
@@ -0,0 +1,554 @@
/* global wpforms_builder_help, wpf */
/**
* WPForms Builder Help screen module.
*
* @since 1.6.3
*/
'use strict';
var WPForms = window.WPForms || {};
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.Help = WPForms.Admin.Builder.Help || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.6.3
*
* @type {object}
*/
var el;
/**
* UI functions.
*
* @since 1.6.3
*
* @type {object}
*/
var ui;
/**
* Event handlers.
*
* @since 1.6.3
*
* @type {object}
*/
var event;
/**
* Public functions and properties.
*
* @since 1.6.3
*
* @type {object}
*/
var app = {
/**
* Start the engine. DOM is not ready yet, use only to init something.
*
* @since 1.6.3
*/
init: function() {
$( app.ready );
},
/**
* DOM is fully loaded.
*
* @since 1.6.3
*/
ready: function() {
app.setup();
app.initCategories();
app.events();
},
/**
* Setup. Prepare some variables.
*
* @since 1.6.3
*/
setup: function() {
// Cache DOM elements.
el = {
$builder: $( '#wpforms-builder' ),
$builderForm: $( '#wpforms-builder-form' ),
$helpBtn: $( '.js-wpforms-help' ),
$help: $( '#wpforms-builder-help' ),
$closeBtn: $( '#wpforms-builder-help-close' ),
$search: $( '#wpforms-builder-help-search' ),
$result: $( '#wpforms-builder-help-result' ),
$noResult: $( '#wpforms-builder-help-no-result' ),
$categories: $( '#wpforms-builder-help-categories' ),
$footer: $( '#wpforms-builder-help-footer' ),
};
},
/**
* Bind events.
*
* @since 1.6.3
*/
events: function() {
// Open/close help modal.
el.$helpBtn.on( 'click', event.openHelp );
el.$closeBtn.on( 'click', event.closeHelp );
// Expand/collapse category.
el.$categories.on( 'click', '.wpforms-builder-help-category header', event.toggleCategory );
// View all Category Docs button click.
el.$categories.on( 'click', '.wpforms-builder-help-category button.viewall', event.viewAllCategoryDocs );
// Input into search field.
el.$search.on( 'keyup', 'input', _.debounce( event.inputSearch, 250 ) );
// Clear search field.
el.$search.on( 'click', '#wpforms-builder-help-search-clear', event.clearSearch );
},
/**
* Init (generate) categories list.
*
* @since 1.6.3
*/
initCategories: function() {
// Display error if docs data is not available.
if ( wpf.empty( wpforms_builder_help.docs ) ) {
el.$categories.html( wp.template( 'wpforms-builder-help-categories-error' ) );
return;
}
var tmpl = wp.template( 'wpforms-builder-help-categories' ),
data = {
categories: wpforms_builder_help.categories,
docs: app.getDocsByCategories(),
};
el.$categories.html( tmpl( data ) );
},
/**
* Init categories list.
*
* @since 1.6.3
*
* @returns {object} Docs arranged by category.
*/
getDocsByCategories: function() {
var categories = wpforms_builder_help.categories,
docs = wpforms_builder_help.docs || [],
docsByCategories = {};
_.each( categories, function( categoryTitle, categorySlug ) {
var docsByCategory = [];
_.each( docs, function( doc ) {
if ( doc.categories && doc.categories.indexOf( categorySlug ) > -1 ) {
docsByCategory.push( doc );
}
} );
docsByCategories[ categorySlug ] = docsByCategory;
} );
return docsByCategories;
},
/**
* Get docs recommended by search term.
*
* @since 1.6.3
*
* @param {string} term Search term.
*
* @returns {Array} Recommended docs.
*/
getRecommendedDocs: function( term ) {
if ( wpf.empty( term ) ) {
return [];
}
term = term.toLowerCase();
var docs = wpforms_builder_help.docs,
recommendedDocs = [];
if ( wpf.empty( wpforms_builder_help.context.docs[ term ] ) ) {
return [];
}
_.each( wpforms_builder_help.context.docs[ term ], function( docId ) {
if ( ! wpf.empty( docs[ docId ] ) ) {
recommendedDocs.push( docs[ docId ] );
}
} );
return recommendedDocs;
},
/**
* Get docs filtered by search term.
*
* @since 1.6.3
*
* @param {string} term Search term.
*
* @returns {Array} Filtered docs.
*/
getFilteredDocs: function( term ) {
if ( wpf.empty( term ) ) {
return [];
}
var docs = wpforms_builder_help.docs,
filteredDocs = [];
term = term.toLowerCase();
_.each( docs, function( doc ) {
if ( doc.title && doc.title.toLowerCase().indexOf( term ) > -1 ) {
filteredDocs.push( doc );
}
} );
return filteredDocs;
},
/**
* Get the current context (state) of the form builder.
*
* @since 1.6.3
*
* @returns {string} Builder context string. For example 'fields/add_field' or 'settings/notifications'.
*/
getBuilderContext: function() {
// New (not saved) form.
if ( wpf.empty( el.$builderForm.data( 'id' ) ) ) {
return 'new_form';
}
// Determine builder panel and section.
var panel = el.$builder.find( '#wpforms-panels-toggle button.active' ).data( 'panel' ),
$panel = el.$builder.find( '#wpforms-panel-' + panel ),
section = '',
subsection = '',
context;
switch ( panel ) {
case 'fields':
section = $panel.find( '.wpforms-panel-sidebar .wpforms-tab a.active' ).parent().attr( 'id' );
break;
case 'setup':
section = '';
break;
default:
section = $panel.find( '.wpforms-panel-sidebar a.active' ).data( 'section' );
}
section = ! wpf.empty( section ) ? section.replace( /-/g, '_' ) : '';
// Detect field type.
if ( section === 'field_options' ) {
subsection = $panel.find( '#wpforms-field-options .wpforms-field-option:visible .wpforms-field-option-hidden-type' ).val();
}
// Combine to context array.
context = [ panel, section, subsection ].filter( function( el ) {
return ! wpf.empty( el ) && el !== 'default';
} );
// Return imploded string.
return context.join( '/' );
},
/**
* Get the search term for the current builder context.
*
* @since 1.6.3
*
* @returns {string} Builder context term string.
*/
getBuilderContextTerm: function() {
return wpforms_builder_help.context.terms[ app.getBuilderContext() ] || '';
},
};
/**
* UI functions.
*/
ui = {
/**
* Configuration.
*
* @since 1.6.3
*
* @type {object}
*/
config: {
speed: 300, // Fading/sliding duration in milliseconds.
},
/**
* Display the element by fading them to opaque using CSS.
*
* @since 1.6.3
*
* @param {jQuery} $el Element object.
*/
fadeIn: function( $el ) {
if ( ! $el.length ) {
return;
}
$el.css( {
display: '',
transition: `opacity ${ui.config.speed}ms ease-in 0s`,
} );
setTimeout( function() {
$el.css( 'opacity', '1' );
}, 0 );
},
/**
* Hide the element by fading them to transparent using CSS.
*
* @since 1.6.3
*
* @param {jQuery} $el Element object.
*/
fadeOut: function( $el ) {
if ( ! $el.length ) {
return;
}
$el.css( {
opacity: '0',
transition: `opacity ${ui.config.speed}ms ease-in 0s`,
} );
setTimeout( function() {
$el.css( 'display', 'none' );
}, ui.config.speed );
},
/**
* Collapse all categories.
*
* @since 1.6.3
*/
collapseAllCategories: function() {
el.$categories.find( '.wpforms-builder-help-category' ).removeClass( 'opened' );
el.$categories.find( '.wpforms-builder-help-docs' ).slideUp();
},
};
/**
* Event handlers.
*/
event = {
/**
* Open help modal.
*
* @since 1.6.3
*
* @param {object} e Event object.
*/
openHelp: function( e ) {
e.preventDefault();
$( 'body' ).addClass( 'wpforms-builder-help-open' );
var $firstCategory = el.$categories.find( '.wpforms-builder-help-category' ).first(),
builderContextTerm = app.getBuilderContextTerm();
if ( builderContextTerm === '' && ! $firstCategory.hasClass( 'opened' ) ) {
$firstCategory.find( 'header' ).first().trigger( 'click' );
} else {
ui.collapseAllCategories();
}
el.$search.find( 'input' ).val( builderContextTerm ).trigger( 'keyup' );
ui.fadeIn( el.$help );
setTimeout( function() {
ui.fadeIn( el.$result );
ui.fadeIn( el.$categories );
ui.fadeIn( el.$footer );
}, ui.config.speed );
},
/**
* Close help modal.
*
* @since 1.6.3
*
* @param {object} e Event object.
*/
closeHelp: function( e ) {
e.preventDefault();
$( 'body' ).removeClass( 'wpforms-builder-help-open' );
ui.fadeOut( el.$result );
ui.fadeOut( el.$categories );
ui.fadeOut( el.$footer );
ui.fadeOut( el.$help );
},
/**
* Toggle category.
*
* @since 1.6.3
*
* @param {object} e Event object.
*/
toggleCategory: function( e ) {
var $category = $( this ).parent(),
$categoryDocs = $category.find( '.wpforms-builder-help-docs' );
if ( ! $categoryDocs.is( ':visible' ) ) {
$category.addClass( 'opened' );
} else {
$category.removeClass( 'opened' );
}
$categoryDocs.stop().slideToggle( ui.config.speed );
},
/**
* View All Category Docs button click.
*
* @since 1.6.3
*
* @param {object} e Event object.
*/
viewAllCategoryDocs: function( e ) {
var $btn = $( this );
$btn.prev( 'div' ).stop().slideToggle( ui.config.speed, function() {
$btn.closest( '.wpforms-builder-help-category' ).addClass( 'viewall' );
} );
ui.fadeOut( $btn );
$btn.slideUp();
},
/**
* Input into search field.
*
* @since 1.6.3
*
* @param {object} e Event object.
*/
inputSearch: function( e ) {
var $input = $( this ),
term = $input.val();
var tmpl = wp.template( 'wpforms-builder-help-docs' ),
recommendedDocs = app.getRecommendedDocs( term ),
filteredDocs = event.removeDuplicates( recommendedDocs, app.getFilteredDocs( term ) ),
resultHTML = '';
el.$search.toggleClass( 'wpforms-empty', ! term );
if ( ! wpf.empty( recommendedDocs ) ) {
resultHTML += tmpl( {
docs: recommendedDocs,
} );
}
if ( ! wpf.empty( filteredDocs ) ) {
resultHTML += tmpl( {
docs: filteredDocs,
} );
}
el.$noResult.toggle( resultHTML === '' && term !== '' );
el.$result.html( resultHTML );
el.$help[0].scrollTop = 0;
},
/**
* Remove duplicated items in the filtered docs.
*
* @since 1.6.3
*
* @param {Array} recommendedDocs Recommended docs.
* @param {Array} filteredDocs Filtered docs.
*
* @returns {Array} Filtered docs without duplicated items in the recommended docs.
*/
removeDuplicates: function( recommendedDocs, filteredDocs ) {
if ( wpf.empty( recommendedDocs ) || wpf.empty( filteredDocs ) ) {
return filteredDocs;
}
var docs = [];
for ( var i = 0; i < recommendedDocs.length, i++; ) {
for ( var k = 0; k < filteredDocs.length, k++; ) {
if ( filteredDocs[ k ].url !== recommendedDocs[ i ].url ) {
docs.push( filteredDocs[ k ] );
}
}
}
return docs;
},
/**
* Clear search field.
*
* @since 1.6.3
*
* @param {object} e Event object.
*/
clearSearch: function( e ) {
el.$search.find( 'input' ).val( '' ).trigger( 'keyup' );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.Help.init();
File diff suppressed because one or more lines are too long
@@ -0,0 +1,185 @@
/* global WPFormsUtils, wpf, wpforms_builder */
// noinspection ES6ConvertVarToLetConst
/**
* WPForms Image Upload Control for Builder Settings.
*
* @since 1.9.7.3
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.Settings = WPForms.Admin.Builder.Settings || {};
/**
* Image Upload functionality for Settings.
*
* @since 1.9.7.3
*/
WPForms.Admin.Builder.Settings.ImageUpload = WPForms.Admin.Builder.Settings.ImageUpload || ( function( document, window, $ ) {
/**
* Image Upload Control methods and properties.
*
* @since 1.9.7.3
*
* @type {Object}
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.7.3
*/
init() {
$( document ).ready( app.ready );
},
/**
* Document ready.
*
* @since 1.9.7.3
*/
ready() {
app.$builder = $( '#wpforms-builder' );
app.bindEvents();
},
/**
* Bind events.
*
* @since 1.9.7.3
*/
bindEvents() {
$( document ).on( 'click', '.wpforms-image-upload-button', app.openMediaUploader );
$( document ).on( 'click', '.wpforms-image-remove-button', app.removeImage );
},
/**
* Open media uploader when upload button is clicked.
*
* @since 1.9.7.3
*
* @param {Object} e Event object.
*/
openMediaUploader( e ) {
e.preventDefault();
const $control = $( this ).closest( '.wpforms-image-upload-control' );
const controlId = $control.attr( 'id' );
app.frames = app.frames ?? {};
// If the media frame already exists, reopen it.
if ( app.frames[ controlId ] ) {
app.frame = app.frames[ controlId ];
app.frame.open();
return;
}
// Create a new media frame.
app.frame = wpf.initMediaLibrary( {
extensions: wpforms_builder.upload_image_extensions,
extensionsError: wpforms_builder.upload_image_extensions_error,
buttonText: wpforms_builder.upload_image_button,
} );
// When an image is selected in the media frame.
app.frame.on( 'select', function() {
// Get media attachment details.
const attachment = app.frame.state().get( 'selection' ).first().toJSON();
// Set image to the control.
app.setImage( $control, attachment );
} );
// Finally, open the modal.
app.frame.open();
// Store the frame.
app.frames[ controlId ] = app.frame;
},
/**
* Get control elements.
*
* @since 1.9.7.3
*
* @param {Object} $control Control element.
*
* @return {Object} Control elements.
*/
getControlElements( $control ) {
return {
$control,
$idField: $control.find( '.wpforms-image-upload-id' ),
$urlField: $control.find( '.wpforms-image-upload-url' ),
$preview: $control.find( '.wpforms-image-preview img' ),
$uploadBtn: $control.find( '.wpforms-image-upload-button' ),
$removeBtn: $control.find( '.wpforms-image-remove-button' ),
};
},
/**
* Set image to the control.
*
* @since 1.9.7.3
*
* @param {jQuery} $control Control element.
* @param {Object} attachment Attachment data.
*/
setImage( $control, attachment ) {
// Get control elements.
const { $idField, $urlField, $preview, $uploadBtn, $removeBtn } = app.getControlElements( $control );
// Update preview.
$preview.attr( 'src', attachment.url );
// Update fields.
$idField.val( attachment.id );
$urlField.val( attachment.url );
// Toggle buttons
$uploadBtn.addClass( 'wpforms-hidden' );
$removeBtn.removeClass( 'wpforms-hidden' );
// Trigger custom change event.
WPFormsUtils.triggerEvent( app.$builder, 'wpformsImageUploadChange', [ $control, attachment ] );
},
/**
* Remove image when remove button is clicked.
*
* @since 1.9.7.3
*
* @param {Object} e Event object.
*/
removeImage( e ) {
e.preventDefault();
const $control = $( this ).closest( '.wpforms-image-upload-control' );
const { $idField, $urlField, $preview, $uploadBtn, $removeBtn } = app.getControlElements( $control );
// Reset preview.
$preview.attr( 'src', '' );
// Clear fields.
$idField.val( '' );
$urlField.val( '' );
// Toggle buttons.
$uploadBtn.removeClass( 'wpforms-hidden' );
$removeBtn.addClass( 'wpforms-hidden' );
// Trigger custom change event.
WPFormsUtils.triggerEvent( app.$builder, 'wpformsImageUploadChange', [ $control, null ] );
},
};
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.Settings.ImageUpload.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.Settings=WPForms.Admin.Builder.Settings||{},WPForms.Admin.Builder.Settings.ImageUpload=WPForms.Admin.Builder.Settings.ImageUpload||((e,d)=>{let a={init(){d(e).ready(a.ready)},ready(){a.$builder=d("#wpforms-builder"),a.bindEvents()},bindEvents(){d(e).on("click",".wpforms-image-upload-button",a.openMediaUploader),d(e).on("click",".wpforms-image-remove-button",a.removeImage)},openMediaUploader(e){e.preventDefault();let r=d(this).closest(".wpforms-image-upload-control");e=r.attr("id");a.frames=a.frames??{},a.frames[e]?(a.frame=a.frames[e],a.frame.open()):(a.frame=wpf.initMediaLibrary({extensions:wpforms_builder.upload_image_extensions,extensionsError:wpforms_builder.upload_image_extensions_error,buttonText:wpforms_builder.upload_image_button}),a.frame.on("select",function(){var e=a.frame.state().get("selection").first().toJSON();a.setImage(r,e)}),a.frame.open(),a.frames[e]=a.frame)},getControlElements(e){return{$control:e,$idField:e.find(".wpforms-image-upload-id"),$urlField:e.find(".wpforms-image-upload-url"),$preview:e.find(".wpforms-image-preview img"),$uploadBtn:e.find(".wpforms-image-upload-button"),$removeBtn:e.find(".wpforms-image-remove-button")}},setImage(e,r){var{$idField:o,$urlField:i,$preview:m,$uploadBtn:t,$removeBtn:d}=a.getControlElements(e);m.attr("src",r.url),o.val(r.id),i.val(r.url),t.addClass("wpforms-hidden"),d.removeClass("wpforms-hidden"),WPFormsUtils.triggerEvent(a.$builder,"wpformsImageUploadChange",[e,r])},removeImage(e){e.preventDefault();var e=d(this).closest(".wpforms-image-upload-control"),{$idField:r,$urlField:o,$preview:i,$uploadBtn:m,$removeBtn:t}=a.getControlElements(e);i.attr("src",""),r.val(""),o.val(""),m.removeClass("wpforms-hidden"),t.addClass("wpforms-hidden"),WPFormsUtils.triggerEvent(a.$builder,"wpformsImageUploadChange",[e,null])}};return a})(document,(window,jQuery)),WPForms.Admin.Builder.Settings.ImageUpload.init();
@@ -0,0 +1,443 @@
/* global wpforms_builder, WPFormsBuilder */
// noinspection ES6ConvertVarToLetConst
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
/**
* Multiple Choices functionality.
*
* @since 1.9.8.3
*/
WPForms.Admin.Builder.MultipleChoices = WPForms.Admin.Builder.MultipleChoices || ( function( document, window, $ ) {
/**
* Multiple Choices methods and properties.
*
* @since 1.9.8.3
*
* @type {Object}
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.8.3
*/
init() {
$( app.ready );
},
/**
* Init.
*
* @since 1.9.8.3
*/
ready() {
app.bindEvents();
},
/**
* Bind actions.
*
* @since 1.9.8.3
*/
// eslint-disable-next-line max-lines-per-function
bindEvents() {
// Cache builder element.
app.$builder = $( '#wpforms-builder' );
// Switch Add other option toggle.
app.$builder.on( 'change', '.wpforms-field-option-row-choices_other input', app.toggleOtherOption );
// Real-time update Other input placeholder in preview.
app.$builder.on( 'input', '.wpforms-field-option-row-other_placeholder input', app.updateOtherOptionPlaceholder );
// Real-time update Other input size in preview.
app.$builder.on( 'change', '.wpforms-field-option-row-other_size select', app.updateOptionInputFieldSize );
// When AI inserts choices, append the Other choice if the toggle is enabled.
$( document ).on( 'wpformsAIModalAfterChoicesInsert', app.appendOtherOption );
// Real-time preview Other input value while typing in the Other choice Value when Show Values is enabled.
app.$builder.on( 'input', '.wpforms-field-option-row-choices li.wpforms-choice-other-option input.value', app.updateOptionInputPreview );
// When Dynamic Choices is toggled ON, force-disable Other option.
app.$builder.on( 'change', '.wpforms-field-option-row-dynamic_choices select', app.toggleDynamicChoices );
app.$builder.on( 'wpformsChoicesSetDefault', app.updateDefaultOptionState );
},
/**
* Updates the preview of the "Other" input field in a form field based on the updated option value.
* This method checks if the "Show Values" option is enabled, fetches the current value from the input field,
* and updates the corresponding preview input with the value or a placeholder.
*
* @since 1.9.8.3
*/
updateOptionInputPreview() {
const $val = $( this );
const $list = $val.closest( '.choices-list' );
const fieldID = $list.data( 'field-id' );
const $options = $( '#wpforms-field-option-' + fieldID );
const showValuesOn = $options.find( '.wpforms-field-option-row-show_values input' ).is( ':checked' );
if ( ! showValuesOn ) {
return;
}
const value = $val.val();
const $preview = $( '#wpforms-field-' + fieldID );
const $otherInput = $preview.find( '.wpforms-other-input' );
const placeholder = $options.find( '.wpforms-field-option-row-other_placeholder input' ).val() || '';
$otherInput.val( value ?? placeholder );
},
/**
* Updates the size of an option input field based on its selected value.
*
* This method retrieves the selected value of a dropdown associated with an option input field,
* determines its size, and applies the updated size to the respective container using the application logic.
*
* @since 1.9.8.3
*/
updateOptionInputFieldSize() {
const $select = $( this );
const fieldID = $select.closest( '.wpforms-field-option-row' ).data( 'field-id' );
const val = $select.val() || 'medium';
app.setOtherSizeOnContainer( fieldID, val );
},
/**
* Updates the default option state for a choice field, particularly handling behavior for Radio fields
* and the "Other" choice option. Manages UI adjustments, such as showing or hiding the "Other" input field
* in the preview based on the selected default option.
*
* @since 1.9.8.3
*
* @param {Event} e The event object triggered during the state change.
* @param {HTMLElement} el The HTML element representing the choice being toggled.
*/
updateDefaultOptionState( e, el ) {
const $this = $( el ),
$choicesList = $this.closest( '.choices-list' ),
fieldType = $choicesList.data( 'field-type' );
if ( fieldType !== 'radio' ) {
return;
}
const fieldId = $choicesList.data( 'field-id' );
// If toggling default for Radio field's Other choice, show/hide preview Other input accordingly.
const $li = $this.closest( 'li' ),
$previewField = $( '#wpforms-field-' + fieldId ),
$otherInput = $previewField.find( '.wpforms-other-input' );
if ( ! $li.hasClass( 'wpforms-choice-other-option' ) ) {
$otherInput.addClass( 'wpforms-hidden' ).val( '' );
return;
}
// Toggle visibility based on whether this radio is checked.
const checked = $this.is( ':checked' );
$otherInput.toggleClass( 'wpforms-hidden', ! checked );
},
/**
* Toggles the "Other" option functionality in the options interface of a form field.
* This includes adding or removing the "Other" choice, updating its placeholder and size options,
* and updating the preview state accordingly.
*
* @since 1.9.8.3
*
* @param {Event} e The event object triggered during the state change.
*/
toggleOtherOption( e ) {
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(),
$choicesList = $( '#wpforms-field-option-row-' + fieldID + '-choices .choices-list' );
let id = $choicesList.attr( 'data-next-id' );
if ( checked ) {
app.fieldChoiceAddOther( e, $( this ), id );
id++;
$choicesList.attr( 'data-next-id', id );
} else {
$choicesList.find( 'li.wpforms-choice-other-option' ).remove();
$( '#wpforms-field-' + fieldID ).find( '.wpforms-other-input' ).addClass( 'wpforms-hidden' );
}
// Toggle the visibility of the Other Placeholder and Field Size option rows.
$fieldOptions.find( '.wpforms-field-option-row-other_placeholder' ).toggleClass( 'wpforms-hidden', ! checked );
$fieldOptions.find( '.wpforms-field-option-row-other_size' ).toggleClass( 'wpforms-hidden', ! checked );
// Apply/remove container size class accordingly.
if ( checked ) {
const sizeVal = $fieldOptions.find( '.wpforms-field-option-row-other_size select' ).val() || 'medium';
app.setOtherSizeOnContainer( fieldID, sizeVal );
} else {
app.setOtherSizeOnContainer( fieldID, null );
}
// Update preview.
WPFormsBuilder.fieldChoiceUpdate( type, fieldID );
app.updatePreviewState( fieldID );
},
/**
* Toggles the placeholder attribute of the "other" input field in a form preview
* based on the value from the corresponding field option settings.
*
* Finds the relevant field ID based on the context of the input element, and
* updates the placeholder text of the "other" input field in the preview if it exists.
*
* @return {void} Does not return a value.
*/
updateOtherOptionPlaceholder() {
const $input = $( this );
const fieldID = $input.closest( '.wpforms-field-option-row' ).data( 'field-id' );
const value = $input.val();
const $previewOther = $( '#wpforms-field-' + fieldID + ' .wpforms-other-input' );
if ( $previewOther.length ) {
$previewOther.attr( 'placeholder', value );
}
},
/**
* Toggles the state of dynamic choices for a select input field.
* Verifies if a selected value exists, performs operations on associated field options,
* and updates the preview state based on user interaction.
*
* @since 1.9.8.3
*
* @return {void} Does not return a value.
*/
toggleDynamicChoices() {
const $select = $( this );
const dynamicOn = $select.val() !== '';
if ( ! dynamicOn ) {
return;
}
const fieldID = $select.closest( '.wpforms-field-option-row' ).data( 'field-id' );
const $fieldOptions = $( '#wpforms-field-option-' + fieldID );
const $otherToggle = $fieldOptions.find( '.wpforms-field-option-row-choices_other input' );
if ( $otherToggle.is( ':checked' ) ) {
$otherToggle.prop( 'checked', false ).trigger( 'change' );
}
app.updatePreviewState( fieldID );
},
/**
* Create a new "Other" choice element.
*
* @since 1.9.8.3
*
* @param {jQuery} $choicesList The choices list container.
* @param {string} fieldID Field ID.
* @param {number} key Next choice key.
*
* @return {jQuery} The cloned and prepared Other choice element.
*/
createOtherChoice( $choicesList, fieldID, key ) {
const $last = $choicesList.children( 'li' ).last();
const $clone = $last.clone();
const otherLabel = wpforms_builder.other;
$clone.attr( 'data-key', key );
$clone.find( 'input.label' ).val( otherLabel ).attr( 'name', `fields[${ fieldID }][choices][${ key }][label]` );
$clone.find( 'input.value' ).val( '' ).attr( 'name', `fields[${ fieldID }][choices][${ key }][value]` );
$clone.find( '.wpforms-image-upload input.source' ).val( '' ).attr( 'name', `fields[${ fieldID }][choices][${ key }][image]` );
$clone.find( '.wpforms-icon-select input.source-icon' ).val( wpforms_builder.icon_choices.default_icon ).attr( 'name', `fields[${ fieldID }][choices][${ key }][icon]` );
$clone.find( '.wpforms-icon-select input.source-icon-style' ).val( wpforms_builder.icon_choices.default_icon_style ).attr( 'name', `fields[${ fieldID }][choices][${ key }][icon_style]` );
$clone.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 }` );
$clone.find( '.wpforms-icon-select .ic-fa-preview + span' ).text( wpforms_builder.icon_choices.default_icon );
$clone.find( 'input.default' ).attr( 'name', `fields[${ fieldID }][choices][${ key }][default]` ).prop( 'checked', false );
$clone.find( '.preview' ).empty();
$clone.find( '.wpforms-image-upload-add' ).show();
// Mark as special "Other" item for clarity and sorting prevention.
$clone.addClass( 'wpforms-choice-other-option not-draggable' );
$clone.find( '.move, .add, .remove' ).addClass( 'wpforms-disabled' );
// Add hidden input flag to identify this choice as "Other".
$clone.find( 'input.other-flag' ).remove();
$clone.append(
`<input type="hidden" class="other-flag" name="fields[${ fieldID }][choices][${ key }][other]" value="1">`
);
return $clone;
},
/**
* Add Other choice to a field.
*
* @since 1.9.8.3
*
* @param {Event|null} event Event object.
* @param {Element} el The toggle element.
* @param {number} key Next choice key.
*/
fieldChoiceAddOther( event, el, key ) {
const $optionRow = $( el ).closest( '.wpforms-field-option-row' );
const fieldID = $optionRow.data( 'field-id' );
const $choicesList = $( `#wpforms-field-option-row-${ fieldID }-choices .choices-list` );
const $clone = app.createOtherChoice( $choicesList, fieldID, key );
$choicesList.append( $clone );
},
/**
* Append Other option at the end of the choices list when toggle is on.
*
* @since 1.9.8.3
*
* @param {Object} event Event object.
*/
appendOtherOption( event ) {
const fieldId = event?.detail?.fieldId;
const $fieldOptions = $( `#wpforms-field-option-${ fieldId }` );
const $toggle = $fieldOptions.find( '.wpforms-field-option-row-choices_other input' );
if ( ! $toggle.length || ! $toggle.is( ':checked' ) ) {
return;
}
const $choicesList = $( `#wpforms-field-option-row-${ fieldId }-choices .choices-list` );
// Prevent duplicate Other choice.
if ( $choicesList.find( 'li.wpforms-choice-other-option' ).length > 0 ) {
return;
}
let nextId = parseInt( $choicesList.attr( 'data-next-id' ), 10 );
nextId = isNaN( nextId ) ? 1 : nextId;
const $clone = app.createOtherChoice( $choicesList, fieldId, nextId );
$choicesList.append( $clone );
$choicesList.attr( 'data-next-id', nextId + 1 );
const type = $fieldOptions.find( '.wpforms-field-option-hidden-type' ).val();
WPFormsBuilder.fieldChoiceUpdate( type, fieldId );
},
/**
* Set size for the container of other option to reach the correct style changes.
*
* @since 1.9.8.3
*
* @param {string} fieldId Field ID.
* @param {string} size The size.
*/
setOtherSizeOnContainer( fieldId, size ) {
const $container = $( '#wpforms-field-' + fieldId + '.wpforms-field-radio' );
$container.removeClass( 'size-small size-medium size-large' );
if ( size ) {
$container.addClass( 'size-' + size );
}
},
/**
* Show other input on the preview.
*
* @since 1.9.8.3
*
* @param {jQuery} $field A field or list of fields.
*/
showPreviewOther( $field ) {
const $otherInput = $field.find( '.wpforms-other-input' );
if ( ! $otherInput.length ) {
return;
}
$otherInput.removeClass( 'wpforms-hidden' );
const $otherRadio = $field.find( 'li.wpforms-other-choice input[type="radio"]' );
if ( ! $otherRadio.length ) {
return;
}
$otherRadio.val( $otherInput.val() );
},
/**
* Hide other input on the preview.
*
* @since 1.9.8.3
*
* @param {jQuery} $field A field or list of fields.
*/
hidePreviewOther( $field ) {
const $otherInput = $field.find( '.wpforms-other-input' );
if ( ! $otherInput.length ) {
return;
}
$otherInput.addClass( 'wpforms-hidden' ).val( '' );
},
/**
* Update other input preview state configuration changes.
*
* @since 1.9.8.3
*
* @param {number|string} fieldId Field ID.
*/
updatePreviewState( fieldId ) {
const $options = $( '#wpforms-field-option-' + fieldId );
const $preview = $( '#wpforms-field-' + fieldId );
const addOtherOn = $options.find( '.wpforms-field-option-row-choices_other input' ).is( ':checked' );
// 1. Handle Add Other toggle.
if ( ! addOtherOn ) {
app.hidePreviewOther( $preview );
return;
}
// 2. Show/hide Other input in preview depending on radio state.
const $otherRadio = $options.find( '.choices-list li.wpforms-choice-other-option input[type="radio"]' );
if ( $otherRadio.length && $otherRadio.is( ':checked' ) ) {
app.showPreviewOther( $preview );
} else {
app.hidePreviewOther( $preview );
}
// 3. Handle the Show Values toggle.
const $otherInput = $preview.find( '.wpforms-other-input' );
if ( ! $otherInput.length ) {
return;
}
const showValuesOn = $options.find( '.wpforms-field-option-row-show_values input' ).is( ':checked' );
const placeholder = $options.find( '.wpforms-field-option-row-other_placeholder input' ).val() || '';
if ( ! showValuesOn ) {
$otherInput.val( '' ).attr( 'placeholder', placeholder );
return;
}
// 4. Sync value from choices into preview input.
const val = $options.find( '.choices-list li.wpforms-choice-other-option input.value' ).val() || '';
$otherInput.val( val ).attr( 'placeholder', placeholder );
},
};
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.MultipleChoices.init();
File diff suppressed because one or more lines are too long
@@ -0,0 +1,236 @@
/* global wpforms_builder, wpf */
/**
* Form Builder Panel Loader module.
*
* @since 1.8.6
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.PanelLoader = WPForms.Admin.Builder.PanelLoader || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.8.6
*
* @type {Object}
*/
const el = {};
/**
* Runtime variables.
*
* @since 1.8.6
*
* @type {Object}
*/
const vars = [];
/**
* Public functions and properties.
*
* @since 1.8.6
*
* @type {Object}
*/
const app = {
/**
* Start the engine.
*
* @since 1.8.6
*/
init() {
$( app.ready );
},
/**
* DOM is fully loaded.
*
* @since 1.8.6
*/
ready() {
app.setup();
app.events();
el.$builder.trigger( 'wpformsBuilderLoaderReady' );
},
/**
* Setup. Prepare some variables.
*
* @since 1.8.6
*/
setup() {
// Cache DOM elements.
el.$builder = $( '#wpforms-builder' );
el.$form = $( '#wpforms-builder-form' );
el.$panels = el.$builder.find( '.wpforms-panels' );
// Init vars.
vars.currentPanel = wpf.getQueryString( 'view' );
},
/**
* Bind events.
*
* @since 1.8.6
*/
events() {
// Panel switching.
el.$builder.on( 'wpformsPanelSwitch', function( e, panel ) {
// Skip if the panel is still loading.
if ( el.$builder.find( `.wpforms-panel-${ panel }-button .wpforms-loading-spinner` ).length ) {
e.preventDefault();
return;
}
// Open the panel if it is already loaded.
if ( el.$panels.find( '#wpforms-panel-' + panel ).length ) {
return;
}
// Load panel.
e.preventDefault();
app.loadPanel( panel );
} );
},
/**
* Load panel.
*
* @since 1.8.6
*
* @param {string} panel Panel name.
*/
loadPanel( panel ) {
app.showSpinner( panel );
// Load panel.
$.post( wpforms_builder.ajax_url, {
nonce: wpforms_builder.nonce,
action: 'wpforms_builder_load_panel',
panel,
form_id: wpf.getQueryString( 'form_id' ), // eslint-disable-line camelcase
} )
.done( function( response ) {
if ( ! response.success || ! response.data?.length ) {
// Show an error message.
app.displayErrorModal( `<p>${ wpforms_builder.error_load_templates }</p><p>${ wpforms_builder.error_contact_support }</p>` );
return;
}
// Append panel to the DOM.
app.embedPanel( panel, response.data );
// Finalize switching to the panel.
app.switchPanel( panel );
// Trigger panel loaded event.
el.$builder.trigger( 'wpformsBuilderPanelLoaded', [ panel ] );
} )
.fail( function() {
// Show an error message.
app.displayErrorModal( `<p>${ wpforms_builder.something_went_wrong }.</p><p>${ wpforms_builder.error_contact_support }</p>` );
} )
.always( function() {
// Hide loading spinner.
app.hideSpinner( panel );
} );
},
/**
* Show spinner.
*
* @since 1.8.6
*
* @param {string} panel Panel name.
*/
showSpinner( panel ) {
const $button = $( `.wpforms-panel-${ panel }-button` );
$button.find( `i.fa` ).addClass( 'wpforms-hidden' );
$button.prepend( '<i class="wpforms-loading-spinner wpforms-loading-white"></i>' );
},
/**
* Hide spinner.
*
* @since 1.8.6
*
* @param {string} panel Panel name.
*/
hideSpinner( panel ) {
const $button = $( `.wpforms-panel-${ panel }-button` );
$button.find( `i.fa` ).removeClass( 'wpforms-hidden' );
$button.find( `i.wpforms-loading-spinner` ).remove();
},
/**
* Embed panel to DOM.
*
* @since 1.8.6
*
* @param {string} panel Panel slug.
* @param {string} panelHtml Panel HTML.
*/
embedPanel( panel, panelHtml ) {
// Append panel to the DOM.
el.$panels.append( panelHtml );
},
/**
* Finalize switching to the panel.
*
* @since 1.8.6
*
* @param {string} panel Panel slug.
*/
switchPanel( panel ) {
$( '#wpforms-panels-toggle' ).find( 'button' ).removeClass( 'active' );
$( '.wpforms-panel' ).removeClass( 'active' );
$( `.wpforms-panel-${ panel }-button` ).addClass( 'active' );
$( `#wpforms-panel-${ panel }` ).addClass( 'active' );
history.replaceState( {}, null, wpf.updateQueryString( 'view', panel ) );
el.$builder.trigger( 'wpformsPanelSwitched', [ panel ] );
},
/**
* Display modal window with an error message.
*
* @since 1.8.6
*
* @param {string} content Modal content.
*/
displayErrorModal( content ) {
$.alert( {
title : wpforms_builder.uh_oh,
content,
icon : 'fa fa-exclamation-circle',
type : 'red',
buttons: {
cancel: {
text : wpforms_builder.close,
btnClass: 'btn-confirm',
keys : [ 'enter' ],
},
},
} );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.PanelLoader.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.PanelLoader=WPForms.Admin.Builder.PanelLoader||(n=>{let o={},e=[],i={init(){n(i.ready)},ready(){i.setup(),i.events(),o.$builder.trigger("wpformsBuilderLoaderReady")},setup(){o.$builder=n("#wpforms-builder"),o.$form=n("#wpforms-builder-form"),o.$panels=o.$builder.find(".wpforms-panels"),e.currentPanel=wpf.getQueryString("view")},events(){o.$builder.on("wpformsPanelSwitch",function(e,r){o.$builder.find(`.wpforms-panel-${r}-button .wpforms-loading-spinner`).length?e.preventDefault():o.$panels.find("#wpforms-panel-"+r).length||(e.preventDefault(),i.loadPanel(r))})},loadPanel(r){i.showSpinner(r),n.post(wpforms_builder.ajax_url,{nonce:wpforms_builder.nonce,action:"wpforms_builder_load_panel",panel:r,form_id:wpf.getQueryString("form_id")}).done(function(e){e.success&&e.data?.length?(i.embedPanel(r,e.data),i.switchPanel(r),o.$builder.trigger("wpformsBuilderPanelLoaded",[r])):i.displayErrorModal(`<p>${wpforms_builder.error_load_templates}</p><p>${wpforms_builder.error_contact_support}</p>`)}).fail(function(){i.displayErrorModal(`<p>${wpforms_builder.something_went_wrong}.</p><p>${wpforms_builder.error_contact_support}</p>`)}).always(function(){i.hideSpinner(r)})},showSpinner(e){e=n(`.wpforms-panel-${e}-button`);e.find("i.fa").addClass("wpforms-hidden"),e.prepend('<i class="wpforms-loading-spinner wpforms-loading-white"></i>')},hideSpinner(e){e=n(`.wpforms-panel-${e}-button`);e.find("i.fa").removeClass("wpforms-hidden"),e.find("i.wpforms-loading-spinner").remove()},embedPanel(e,r){o.$panels.append(r)},switchPanel(e){n("#wpforms-panels-toggle").find("button").removeClass("active"),n(".wpforms-panel").removeClass("active"),n(`.wpforms-panel-${e}-button`).addClass("active"),n("#wpforms-panel-"+e).addClass("active"),history.replaceState({},null,wpf.updateQueryString("view",e)),o.$builder.trigger("wpformsPanelSwitched",[e])},displayErrorModal(e){n.alert({title:wpforms_builder.uh_oh,content:e,icon:"fa fa-exclamation-circle",type:"red",buttons:{cancel:{text:wpforms_builder.close,btnClass:"btn-confirm",keys:["enter"]}}})}};return i})((document,window,jQuery)),WPForms.Admin.Builder.PanelLoader.init();
@@ -0,0 +1,119 @@
/* global wpforms_builder, wpforms_builder_payments_utils */
// eslint-disable-next-line no-unused-vars
const WPFormsBuilderPaymentsUtils = window.WPFormsBuilderPaymentsUtils || ( function( document, window, $ ) {
/**
* Public functions and properties.
*
* @since 1.9.5
*
* @type {Object}
*/
const app = {
/**
* Toggle payments content.
*
* @since 1.9.5
*/
// eslint-disable-next-line complexity
toggleContent() {
const $input = $( this ),
$paymentSettings = $input.closest( '.wpforms-payment-settings' );
if (
$paymentSettings.find( '.wpforms-panel-content-section-payment-toggle-one-time .wpforms-toggle-control > input' ).is( ':checked' ) &&
$paymentSettings.find( '.wpforms-panel-content-section-payment-toggle-recurring .wpforms-toggle-control > input' ).is( ':checked' )
) {
$input.prop( 'checked', false );
$.alert( {
title: wpforms_builder.heads_up,
content: $input.attr( 'name' ).includes( 'enable_recurring' ) ? wpforms_builder_payments_utils.payments_disabled_recurring : wpforms_builder_payments_utils.payments_disabled_one_time,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
}
const $wrapper = $input.closest( '.wpforms-panel-content-section-payment' ),
isChecked = $input.prop( 'checked' ) && ! $( '#wpforms-panel-field-settings-disable_entries' ).prop( 'checked' );
$wrapper.find( '.wpforms-panel-content-section-payment-toggled-body' ).toggle( isChecked );
$wrapper.toggleClass( 'wpforms-panel-content-section-payment-open', isChecked );
},
/**
* Check a plan name on empty value.
*
* @since 1.9.5
*/
checkPlanName() {
const $input = $( this ),
$plan = $input.closest( '.wpforms-panel-content-section-payment-plan' ),
$planName = $plan.find( '.wpforms-panel-content-section-payment-plan-head-title' );
if ( $input.val() ) {
$planName.html( $input.val() );
return;
}
const defaultValue = wpforms_builder_payments_utils.payments_plan_placeholder;
$planName.html( defaultValue );
$input.val( defaultValue );
},
/**
* Toggle a plan content.
*
* @since 1.9.5
*/
togglePlan() {
const $plan = $( this ).closest( '.wpforms-panel-content-section-payment-plan' ),
$icon = $plan.find( '.wpforms-panel-content-section-payment-plan-head-buttons-toggle' );
$icon.toggleClass( 'fa-chevron-circle-up fa-chevron-circle-down' );
$plan.find( '.wpforms-panel-content-section-payment-plan-body' ).toggle( $icon.hasClass( 'fa-chevron-circle-down' ) );
},
/**
* Delete a plan.
*
* @since 1.9.5
*/
deletePlan() {
// Trigger a warning modal when trying to delete a single plan without pro addon.
$( this ).closest( '.wpforms-panel-content-section-payment' ).find( '.wpforms-panel-content-section-payment-button-add-plan' ).trigger( 'click' );
},
/**
* Rename a plan.
*
* @since 1.9.5
*/
renamePlan() {
const $input = $( this ),
$plan = $input.closest( '.wpforms-panel-content-section-payment-plan' ),
$planName = $plan.find( '.wpforms-panel-content-section-payment-plan-head-title' );
if ( ! $input.val() ) {
$planName.html( '' );
return;
}
$planName.html( $input.val() );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
@@ -0,0 +1 @@
let WPFormsBuilderPaymentsUtils=window.WPFormsBuilderPaymentsUtils||(o=>({toggleContent(){var e=o(this),n=e.closest(".wpforms-payment-settings"),n=(n.find(".wpforms-panel-content-section-payment-toggle-one-time .wpforms-toggle-control > input").is(":checked")&&n.find(".wpforms-panel-content-section-payment-toggle-recurring .wpforms-toggle-control > input").is(":checked")&&(e.prop("checked",!1),o.alert({title:wpforms_builder.heads_up,content:e.attr("name").includes("enable_recurring")?wpforms_builder_payments_utils.payments_disabled_recurring:wpforms_builder_payments_utils.payments_disabled_one_time,icon:"fa fa-exclamation-circle",type:"orange",buttons:{confirm:{text:wpforms_builder.ok,btnClass:"btn-confirm",keys:["enter"]}}})),e.closest(".wpforms-panel-content-section-payment")),e=e.prop("checked")&&!o("#wpforms-panel-field-settings-disable_entries").prop("checked");n.find(".wpforms-panel-content-section-payment-toggled-body").toggle(e),n.toggleClass("wpforms-panel-content-section-payment-open",e)},checkPlanName(){var e,n=o(this),t=n.closest(".wpforms-panel-content-section-payment-plan").find(".wpforms-panel-content-section-payment-plan-head-title");n.val()?t.html(n.val()):(e=wpforms_builder_payments_utils.payments_plan_placeholder,t.html(e),n.val(e))},togglePlan(){var e=o(this).closest(".wpforms-panel-content-section-payment-plan"),n=e.find(".wpforms-panel-content-section-payment-plan-head-buttons-toggle");n.toggleClass("fa-chevron-circle-up fa-chevron-circle-down"),e.find(".wpforms-panel-content-section-payment-plan-body").toggle(n.hasClass("fa-chevron-circle-down"))},deletePlan(){o(this).closest(".wpforms-panel-content-section-payment").find(".wpforms-panel-content-section-payment-button-add-plan").trigger("click")},renamePlan(){var e=o(this),n=e.closest(".wpforms-panel-content-section-payment-plan").find(".wpforms-panel-content-section-payment-plan-head-title");e.val()?n.html(e.val()):n.html("")}}))((document,window,jQuery));
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,207 @@
/**
* WPForms Builder Search module.
*
* @since 1.8.3
*/
'use strict';
var WPForms = window.WPForms || {};
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.Search = WPForms.Admin.Builder.Search || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.8.3
*
* @type {object}
*/
const el = {};
/**
* Public functions and properties.
*
* @since 1.8.3
*
* @type {object}
*/
const app = {
/**
* Start the engine. DOM is not ready yet, use only to init something.
*
* @since 1.8.3
*/
init: function() {
$( app.ready );
},
/**
* DOM is fully loaded.
*
* @since 1.8.3
*/
ready: function() {
app.setup();
app.events();
app.scrollSidebar();
},
/**
* Scroll the sidebar to the height of the search.
*
* @since 1.8.3
*/
scrollSidebar: function() {
el.$sidebar.scrollTop( el.$searchWrapper.height() + 20 );
},
/**
* Setup. Prepare some variables.
*
* @since 1.8.3
*/
setup: function() {
// Cache DOM elements
el.$document = $( document );
el.$builder = $( '#wpforms-builder' );
el.$searchInput = $( '#wpforms-search-fields-input' );
el.$searchInputCloseBtn = $( '.wpforms-search-fields-input-close' );
el.$searchWrapper = $( '.wpforms-search-fields-wrapper' );
el.$noResults = $( '.wpforms-search-fields-no-results' );
el.$listWrapper = $( '.wpforms-search-fields-list' );
el.$list = $( '.wpforms-search-fields-list .wpforms-add-fields-buttons' );
el.$groups = $( '.wpforms-tab-content > .wpforms-add-fields-group' );
el.$sidebar = $( '#wpforms-panel-fields .wpforms-add-fields' );
},
/**
* Bind events.
*
* @since 1.8.3
*/
events: function() {
el.$searchInput.on( 'keyup', app.searchAction );
el.$searchInputCloseBtn.on( 'click', app.clearSearch );
el.$document.on( 'wpformsFieldAdd', app.clearSearch );
el.$document.on( 'wpformsFieldDelete', app.refreshSearchResults );
},
/**
* Search action.
*
* @since 1.8.3
*/
searchAction: function() {
const $fields = el.$builder.find( '.wpforms-tab-content > .wpforms-add-fields-group .wpforms-add-fields-button' );
const searchValue = el.$searchInput.val().toLowerCase();
el.$list.empty();
if ( searchValue ) {
el.$groups.hide();
el.$listWrapper.show();
el.$searchInputCloseBtn.addClass( 'active' );
$fields.each( function() {
const $item = $( this );
const titleText = $item.text().toLowerCase();
const keywords = $item.data( 'field-keywords' ) ? $item.data( 'field-keywords' ).toLowerCase() : '';
if ( titleText.indexOf( searchValue ) >= 0 || ( keywords && keywords.indexOf( searchValue ) >= 0 ) ) {
const $clone = $item.clone();
$clone.attr( 'data-target', $clone.attr( 'id' ) );
$clone.removeAttr( 'id' );
$clone.addClass( 'wpforms-add-fields-button-clone' );
el.$list.append( $clone );
}
} );
const $matchingItems = el.$list.find( '.wpforms-add-fields-button' );
const hasMatchingItems = $matchingItems.length > 0;
if ( hasMatchingItems ) {
el.$noResults.hide();
} else {
el.$noResults.show();
el.$listWrapper.hide();
}
} else {
el.$groups.show();
el.$listWrapper.hide();
el.$noResults.hide();
el.$searchInputCloseBtn.removeClass( 'active' );
}
WPForms.Admin.Builder.DragFields.setup();
WPForms.Admin.Builder.DragFields.initSortableFields();
app.cloneClickAction();
},
/**
* Clear search.
*
* @since 1.8.3
*/
clearSearch: function() {
if ( ! el.$searchInput.val() ) {
return;
}
el.$list.empty();
el.$listWrapper.hide();
el.$groups.show();
el.$noResults.hide();
el.$searchInput.val( '' ).focus();
el.$searchInputCloseBtn.removeClass( 'active' );
},
/**
* Refresh search results.
*
* @since 1.8.3
*/
refreshSearchResults: function() {
// We need to wait for the original field to be unlocked.
setTimeout( app.searchAction, 0 );
},
/**
* Clone click action.
*
* @since 1.8.3
*/
cloneClickAction() {
$( '.wpforms-add-fields-button-clone' ).on( 'click', function( e ) {
e.preventDefault();
e.stopPropagation();
const target = $( this ).attr( 'data-target' );
$( '#' + target ).trigger( 'click' );
} );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.Search.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.Search=WPForms.Admin.Builder.Search||((e,o)=>{let i={},s={init:function(){o(s.ready)},ready:function(){s.setup(),s.events(),s.scrollSidebar()},scrollSidebar:function(){i.$sidebar.scrollTop(i.$searchWrapper.height()+20)},setup:function(){i.$document=o(e),i.$builder=o("#wpforms-builder"),i.$searchInput=o("#wpforms-search-fields-input"),i.$searchInputCloseBtn=o(".wpforms-search-fields-input-close"),i.$searchWrapper=o(".wpforms-search-fields-wrapper"),i.$noResults=o(".wpforms-search-fields-no-results"),i.$listWrapper=o(".wpforms-search-fields-list"),i.$list=o(".wpforms-search-fields-list .wpforms-add-fields-buttons"),i.$groups=o(".wpforms-tab-content > .wpforms-add-fields-group"),i.$sidebar=o("#wpforms-panel-fields .wpforms-add-fields")},events:function(){i.$searchInput.on("keyup",s.searchAction),i.$searchInputCloseBtn.on("click",s.clearSearch),i.$document.on("wpformsFieldAdd",s.clearSearch),i.$document.on("wpformsFieldDelete",s.refreshSearchResults)},searchAction:function(){var e=i.$builder.find(".wpforms-tab-content > .wpforms-add-fields-group .wpforms-add-fields-button");let t=i.$searchInput.val().toLowerCase();i.$list.empty(),t?(i.$groups.hide(),i.$listWrapper.show(),i.$searchInputCloseBtn.addClass("active"),e.each(function(){var e=o(this),s=e.text().toLowerCase(),r=e.data("field-keywords")?e.data("field-keywords").toLowerCase():"";(0<=s.indexOf(t)||r&&0<=r.indexOf(t))&&((s=e.clone()).attr("data-target",s.attr("id")),s.removeAttr("id"),s.addClass("wpforms-add-fields-button-clone"),i.$list.append(s))}),(0<i.$list.find(".wpforms-add-fields-button").length?i.$noResults:(i.$noResults.show(),i.$listWrapper)).hide()):(i.$groups.show(),i.$listWrapper.hide(),i.$noResults.hide(),i.$searchInputCloseBtn.removeClass("active")),WPForms.Admin.Builder.DragFields.setup(),WPForms.Admin.Builder.DragFields.initSortableFields(),s.cloneClickAction()},clearSearch:function(){i.$searchInput.val()&&(i.$list.empty(),i.$listWrapper.hide(),i.$groups.show(),i.$noResults.hide(),i.$searchInput.val("").focus(),i.$searchInputCloseBtn.removeClass("active"))},refreshSearchResults:function(){setTimeout(s.searchAction,0)},cloneClickAction(){o(".wpforms-add-fields-button-clone").on("click",function(e){e.preventDefault(),e.stopPropagation();e=o(this).attr("data-target");o("#"+e).trigger("click")})}};return s})(document,(window,jQuery)),WPForms.Admin.Builder.Search.init();
@@ -0,0 +1,453 @@
/* global wpforms_builder_settings, Choices, wpforms_builder */
// noinspection ES6ConvertVarToLetConst
/**
* Form Builder Settings Panel module.
*
* @since 1.7.5
*/
// eslint-disable-next-line no-var
var WPForms = window.WPForms || {};
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.Settings = WPForms.Admin.Builder.Settings || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.7.5
*
* @type {Object}
*/
let el = {};
/**
* Runtime variables.
*
* @since 1.7.5
*
* @type {Object}
*/
const vars = {};
// noinspection JSUnusedLocalSymbols,ES6ConvertVarToLetConst
/**
* Public functions and properties.
*
* @since 1.7.5
*
* @type {Object}
*/
// eslint-disable-next-line no-var
var app = {
/**
* Start the engine.
*
* @since 1.7.5
*/
init() {
$( app.ready );
},
/**
* DOM is fully loaded.
*
* @since 1.7.5
*/
ready() {
app.setup();
app.initTags();
app.events();
},
/**
* Setup. Prepare some variables.
*
* @since 1.7.5
*/
setup() {
// Cache DOM elements.
el = {
$builder: $( '#wpforms-builder' ),
$panel: $( '#wpforms-panel-settings' ),
$selectTags: $( '#wpforms-panel-field-settings-form_tags' ),
};
// Give a chance to interact with "Disable entry..." option immediately.
app.allowEditDisabledEntriesOption();
},
/**
* Bind events.
*
* @since 1.7.5
*/
events() {
el.$panel
.on( 'keydown', '#wpforms-panel-field-settings-form_tags-wrap input', app.addCustomTagInput )
.on( 'removeItem', '#wpforms-panel-field-settings-form_tags-wrap select', app.editTagsRemoveItem )
.on( 'change', '#wpforms-panel-field-settings-antispam_v3', app.enableAntispamV3 )
.on( 'change', '#wpforms-panel-field-settings-disable_entries', app.disableEntries )
.on( 'change', '#wpforms-panel-field-settings-store_spam_entries', app.storeSpamEntries );
el.$selectTags
.on( 'change', app.changeTags );
$( document ).on( 'connectionsDataLoaded', app.allowEditDisabledEntriesOption );
},
/**
* Enable Anti spam v3 toggle change event.
*
* @since 1.9.0
*/
enableAntispamV3() {
// Hide and disable old anti-spam.
$( '#wpforms-panel-field-settings-antispam' )
.prop( 'checked', false )
.closest( '.wpforms-panel-field' )
.toggleClass( 'wpforms-hidden' );
},
/**
* Disable Entries toggle change event.
*
* @since 1.9.2
*/
disableEntries() {
const $this = $( this );
const isChecked = $this.prop( 'checked' );
const isStoreSpamEntriesChecked = $( '#wpforms-panel-field-settings-store_spam_entries' ).prop( 'checked' );
app.toggleFilteringMessages( ! isChecked && isStoreSpamEntriesChecked );
// Toggle the store spam entries toggle.
$( '#wpforms-panel-field-settings-store_spam_entries-wrap' ).toggleClass( 'wpforms-hidden', $this.prop( 'checked' ) );
if ( ! $this.prop( 'checked' ) ) {
return;
}
const entryRequirement = app.getEntryRequirement();
// Don't allow users to disable entries if some third-party integrations
// require it.
if ( entryRequirement.required ) {
$.confirm( {
title: wpforms_builder.entry_storage_required,
content: app.getDisabledEntryMessage( entryRequirement ),
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
$this.prop( 'checked', false );
return;
}
$.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' ],
},
},
} );
},
/**
* Store Spam Entries toggle change event.
*
* @since 1.9.2
*/
storeSpamEntries() {
app.toggleFilteringMessages( $( this ).prop( 'checked' ) );
},
/**
* Toggle Filtering Messages.
*
* @since 1.9.2
*
* @param {boolean} $hide Whether to hide or show messages.
*/
toggleFilteringMessages( $hide ) {
if ( ! $( '#wpforms-panel-field-anti_spam-filtering_store_spam' ).is( ':checked' ) ) {
return;
}
// Toggle Country Filter Message.
$( '#wpforms-panel-field-anti_spam-country_filter-message-wrap' ).toggleClass( 'wpforms-hidden', $hide );
// Toggle Keyywords Filter Message.
$( '#wpforms-panel-field-anti_spam-keyword_filter-message-wrap' ).toggleClass( 'wpforms-hidden', $hide );
},
/**
* Init Choices.js on the Tags select an input element.
*
* @param {Object} $el Element.
* @since 1.7.5
*/
initTags( $el = null ) {
$el = $el?.length ? $el : el.$selectTags;
// Skip in certain cases.
if (
! $el.length ||
typeof window.Choices !== 'function'
) {
return;
}
// Init Choices.js object instance.
vars.tagsChoicesObj = new Choices( $el[ 0 ], wpforms_builder_settings.choicesjs_config );
// Backup current value.
const currentValue = vars.tagsChoicesObj.getValue( true );
// Update all tags choices.
vars.tagsChoicesObj
.clearStore()
.setChoices(
wpforms_builder_settings.all_tags_choices,
'value',
'label',
true
)
.setChoiceByValue( currentValue );
$el.data( 'choicesjs', vars.tagsChoicesObj );
app.initTagsHiddenInput();
},
/**
* Init Tags hidden input element.
*
* @since 1.7.5
*/
initTagsHiddenInput() {
// Create additional hidden input.
el.$selectTagsHiddenInput = $( '<input type="hidden" name="settings[form_tags_json]">' );
el.$selectTags
.closest( '.wpforms-panel-field' )
.append( el.$selectTagsHiddenInput );
// Update hidden input value.
app.changeTags( null );
},
/**
* Add custom item to Tags dropdown on input.
*
* @since 1.7.5
*
* @param {Object} event Event object.
*/
addCustomTagInput( event ) {
if ( [ 'Enter', ',' ].indexOf( event.key ) < 0 ) {
return;
}
event.preventDefault();
event.stopPropagation();
if ( ! vars.tagsChoicesObj || event.target.value.length === 0 ) {
return;
}
const tagLabel = _.escape( event.target.value ).trim(),
labels = _.map( vars.tagsChoicesObj.getValue(), 'label' ).map( function( label ) {
return label.toLowerCase().trim();
} );
if ( tagLabel === '' || labels.indexOf( tagLabel.toLowerCase() ) >= 0 ) {
vars.tagsChoicesObj.clearInput();
return;
}
app.addCustomTagInputCreate( tagLabel );
app.changeTags( event );
},
/**
* Remove tag from Tags field event handler.
*
* @since 1.7.5
*
* @param {Object} event Event object.
*/
editTagsRemoveItem( event ) {
const allValues = _.map( wpforms_builder_settings.all_tags_choices, 'value' );
if ( allValues.indexOf( event.detail.value ) >= 0 ) {
return;
}
// We should remove new tag from the list of choices.
const choicesObj = $( event.target ).data( 'choicesjs' ),
currentValue = choicesObj.getValue( true ),
choices = _.filter( choicesObj._currentState.choices, function( item ) {
return item.value !== event.detail.value;
} );
choicesObj
.clearStore()
.setChoices( choices, 'value', 'label', true )
.setChoiceByValue( currentValue );
},
/**
* Add custom item to Tags dropdown on input (second part).
*
* @since 1.7.5
*
* @param {Object} tagLabel Event object.
*/
addCustomTagInputCreate( tagLabel ) {
const tag = _.find( wpforms_builder_settings.all_tags_choices, { label: tagLabel } );
if ( tag && tag.value ) {
vars.tagsChoicesObj.setChoiceByValue( tag.value );
} else {
vars.tagsChoicesObj.setChoices(
[
{
value: tagLabel,
label: tagLabel,
selected: true,
},
],
'value',
'label',
false
);
}
vars.tagsChoicesObj.clearInput();
},
/**
* Change Tags field event handler.
*
* @since 1.7.5
*
* @param {Object} event Event object.
*/
// eslint-disable-next-line no-unused-vars
changeTags( event ) {
const tagsValue = vars.tagsChoicesObj.getValue(),
tags = [];
for ( let i = 0; i < tagsValue.length; i++ ) {
tags.push( {
value: tagsValue[ i ].value,
label: tagsValue[ i ].label,
} );
}
// Update Tags field hidden input value.
el.$selectTagsHiddenInput.val(
JSON.stringify( tags )
);
},
/**
* Generates a message to indicate why certain entries are disabled,
* including necessary dependencies for enabling them.
*
* @since 1.9.6
*
* @param {Object} entryRequirement An object containing details about the requirements.
*
* @return {string} The customized message indicating why the entries are disabled.
*/
getDisabledEntryMessage( entryRequirement ) {
const dependencies = entryRequirement?.dependencies || {};
if ( ! Object.keys( dependencies ).length ) {
return wpforms_builder.payments_on_entries_off;
}
const dependenciesHTML = Object.values( dependencies ).map( ( { text, href }, index, arr ) => {
const linkHTML = `<a href="${ href }" target="_blank">${ text }</a>`;
if ( index === arr.length - 1 && arr.length > 1 ) {
// The very last item when array has multiple items, prepend with "and".
return `and ${ linkHTML }`;
} else if ( index < arr.length - 2 ) {
// Any item except the last two, append comma.
return `${ linkHTML },`;
}
// Second-to-last item, no comma needed as next item will prepend "and".
return linkHTML;
} ).join( ' ' );
return wpforms_builder.payments_on_entries_off.replace( '{integration}', dependenciesHTML );
},
/**
* Allows interacting with the option for disabled entries in the WPForms settings panel.
* This method ensures that the entry requirement is met before enabling the option.
* If some providers or gateways are still loading, the process is aborted.
*
* @since 1.9.6
*/
allowEditDisabledEntriesOption() {
const $toggleSpan = $( '#wpforms-panel-field-settings-disable_entries-wrap > span' );
if ( ! $toggleSpan.hasClass( 'wpforms-toggle-control-disabled' ) ) {
return;
}
const entryRequirement = app.getEntryRequirement();
if ( entryRequirement?.loadingStack?.size ) {
return;
}
$toggleSpan.removeClass( 'wpforms-toggle-control-disabled' );
},
/**
* Allows modifying the entry requirement configuration, including whether
* the entry is required and any dependencies associated with it.
*
* @since 1.9.6
*
* @property {boolean} required Indicates whether the entry is required.
* @property {Object} dependencies Specifies dependencies for the entry. See app.getDisabledEntryMessage for more details.
* @property {Set} loadingStack A set used to manage loading states.
*
* @return {Object} The entry requirement object.
*/
getEntryRequirement() {
return wp.hooks.applyFilters(
'wpforms.Builder.entryRequirement',
{ required: false, dependencies: {}, loadingStack: new Set() }
);
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.Settings.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.Settings=WPForms.Admin.Builder.Settings||((e,s,n)=>{let a={},i={};var r={init(){n(r.ready)},ready(){r.setup(),r.initTags(),r.events()},setup(){a={$builder:n("#wpforms-builder"),$panel:n("#wpforms-panel-settings"),$selectTags:n("#wpforms-panel-field-settings-form_tags")},r.allowEditDisabledEntriesOption()},events(){a.$panel.on("keydown","#wpforms-panel-field-settings-form_tags-wrap input",r.addCustomTagInput).on("removeItem","#wpforms-panel-field-settings-form_tags-wrap select",r.editTagsRemoveItem).on("change","#wpforms-panel-field-settings-antispam_v3",r.enableAntispamV3).on("change","#wpforms-panel-field-settings-disable_entries",r.disableEntries).on("change","#wpforms-panel-field-settings-store_spam_entries",r.storeSpamEntries),a.$selectTags.on("change",r.changeTags),n(e).on("connectionsDataLoaded",r.allowEditDisabledEntriesOption)},enableAntispamV3(){n("#wpforms-panel-field-settings-antispam").prop("checked",!1).closest(".wpforms-panel-field").toggleClass("wpforms-hidden")},disableEntries(){var e=n(this),t=e.prop("checked"),s=n("#wpforms-panel-field-settings-store_spam_entries").prop("checked");r.toggleFilteringMessages(!t&&s),n("#wpforms-panel-field-settings-store_spam_entries-wrap").toggleClass("wpforms-hidden",e.prop("checked")),e.prop("checked")&&((t=r.getEntryRequirement()).required?(n.confirm({title:wpforms_builder.entry_storage_required,content:r.getDisabledEntryMessage(t),icon:"fa fa-exclamation-circle",type:"orange",buttons:{confirm:{text:wpforms_builder.ok,btnClass:"btn-confirm",keys:["enter"]}}}),e.prop("checked",!1)):n.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"]}}}))},storeSpamEntries(){r.toggleFilteringMessages(n(this).prop("checked"))},toggleFilteringMessages(e){n("#wpforms-panel-field-anti_spam-filtering_store_spam").is(":checked")&&(n("#wpforms-panel-field-anti_spam-country_filter-message-wrap").toggleClass("wpforms-hidden",e),n("#wpforms-panel-field-anti_spam-keyword_filter-message-wrap").toggleClass("wpforms-hidden",e))},initTags(e=null){var t;(e=e?.length?e:a.$selectTags).length&&"function"==typeof s.Choices&&(i.tagsChoicesObj=new Choices(e[0],wpforms_builder_settings.choicesjs_config),t=i.tagsChoicesObj.getValue(!0),i.tagsChoicesObj.clearStore().setChoices(wpforms_builder_settings.all_tags_choices,"value","label",!0).setChoiceByValue(t),e.data("choicesjs",i.tagsChoicesObj),r.initTagsHiddenInput())},initTagsHiddenInput(){a.$selectTagsHiddenInput=n('<input type="hidden" name="settings[form_tags_json]">'),a.$selectTags.closest(".wpforms-panel-field").append(a.$selectTagsHiddenInput),r.changeTags(null)},addCustomTagInput(e){var t,s;["Enter",","].indexOf(e.key)<0||(e.preventDefault(),e.stopPropagation(),i.tagsChoicesObj&&0!==e.target.value.length&&(t=_.escape(e.target.value).trim(),s=_.map(i.tagsChoicesObj.getValue(),"label").map(function(e){return e.toLowerCase().trim()}),""===t||0<=s.indexOf(t.toLowerCase())?i.tagsChoicesObj.clearInput():(r.addCustomTagInputCreate(t),r.changeTags(e))))},editTagsRemoveItem(t){var e,s,a;0<=_.map(wpforms_builder_settings.all_tags_choices,"value").indexOf(t.detail.value)||(s=(e=n(t.target).data("choicesjs")).getValue(!0),a=_.filter(e._currentState.choices,function(e){return e.value!==t.detail.value}),e.clearStore().setChoices(a,"value","label",!0).setChoiceByValue(s))},addCustomTagInputCreate(e){var t=_.find(wpforms_builder_settings.all_tags_choices,{label:e});t&&t.value?i.tagsChoicesObj.setChoiceByValue(t.value):i.tagsChoicesObj.setChoices([{value:e,label:e,selected:!0}],"value","label",!1),i.tagsChoicesObj.clearInput()},changeTags(e){var t=i.tagsChoicesObj.getValue(),s=[];for(let e=0;e<t.length;e++)s.push({value:t[e].value,label:t[e].label});a.$selectTagsHiddenInput.val(JSON.stringify(s))},getDisabledEntryMessage(e){var e=e?.dependencies||{};return Object.keys(e).length?(e=Object.values(e).map(({text:e,href:t},s,a)=>{t=`<a href="${t}" target="_blank">${e}</a>`;return s===a.length-1&&1<a.length?"and "+t:s<a.length-2?t+",":t}).join(" "),wpforms_builder.payments_on_entries_off.replace("{integration}",e)):wpforms_builder.payments_on_entries_off},allowEditDisabledEntriesOption(){var e=n("#wpforms-panel-field-settings-disable_entries-wrap > span");e.hasClass("wpforms-toggle-control-disabled")&&!r.getEntryRequirement()?.loadingStack?.size&&e.removeClass("wpforms-toggle-control-disabled")},getEntryRequirement(){return wp.hooks.applyFilters("wpforms.Builder.entryRequirement",{required:!1,dependencies:{},loadingStack:new Set})}};return r})(document,window,jQuery),WPForms.Admin.Builder.Settings.init();
@@ -0,0 +1,749 @@
/* global wpforms_builder, wpf, WPFormsBuilder, WPFormsFormTemplates */
/* eslint-disable no-console */
/**
* @param wpforms_builder.blank_form
* @param wpforms_builder.error_select_template
* @param wpforms_builder.form_meta
* @param wpforms_builder.template_confirm
* @param wpforms_builder.use_default_template
*/
// noinspection ES6ConvertVarToLetConst
/**
* Form Builder Setup Panel module.
*
* @since 1.6.8
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.Setup = WPForms.Admin.Builder.Setup || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.6.8
*
* @type {Object}
*/
const el = {};
/**
* Runtime variables.
*
* @since 1.6.8
*
* @type {Object}
*/
const vars = {};
/**
* Active template name.
*
* @since 1.7.6
*/
const activeTemplateName = $( '.wpforms-template.selected .wpforms-template-name' ).text().trim();
/**
* Public functions and properties.
*
* @since 1.6.8
*
* @type {Object}
*/
const app = {
/**
* Start the engine.
*
* @since 1.6.8
*/
init() {
$( 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();
}
} );
},
/**
* DOM is fully loaded.
*
* @since 1.6.8
*/
ready() {
app.setup();
app.setPanelsToggleState();
app.setupTitleFocus();
app.setTriggerBlankLink();
app.events();
// Trigger `wpformsBuilderPanelLoaded` event only when the panel is available in DOM.
if ( el.$panel.length ) {
el.$builder.trigger( 'wpformsBuilderPanelLoaded', [ 'setup' ] );
}
el.$builder.trigger( 'wpformsBuilderSetupReady' );
},
/**
* Page load.
*
* @since 1.6.8
*/
load() {
app.applyTemplateOnRequest();
},
/**
* Setup. Prepare some variables.
*
* @since 1.6.8
*/
setup() {
// Cache DOM elements.
el.$builder = $( '#wpforms-builder' );
el.$form = $( '#wpforms-builder-form' );
el.$formName = $( '#wpforms-setup-name' );
el.$panel = $( '#wpforms-panel-setup' );
el.$categories = $( '#wpforms-panel-setup .wpforms-setup-templates-categories' );
el.$subcategories = $( '#wpforms-panel-setup .wpforms-setup-templates-subcategories' );
// Other values.
vars.spinner = '<i class="wpforms-loading-spinner wpforms-loading-white wpforms-loading-inline"></i>';
vars.formID = el.$form.data( 'id' );
},
/**
* Bind events.
*
* @since 1.6.8
*/
events() {
el.$builder.on( 'wpformsBuilderPanelLoaded', app.panelLoaded );
// Focus on the form title field when displaying a setup panel.
el.$builder
.on( 'wpformsPanelSwitched', app.setupTitleFocus );
// Sync Setup title and settings title.
el.$builder
.on( 'input', '#wpforms-panel-field-settings-form_title', app.syncTitle )
.on( 'input', '#wpforms-setup-name', app.syncTitle );
},
/**
* Bind panel events.
*
* @since 1.8.6
*/
panelEvents() {
el.$panel
.on( 'keyup', '#wpforms-setup-template-search', _.debounce( WPFormsFormTemplates.searchTemplate, 200 ) )
.on( 'click', '.wpforms-setup-templates-categories li div', WPFormsFormTemplates.selectCategory )
.on( 'click', '.wpforms-setup-templates-categories li .chevron', WPFormsFormTemplates.toggleSubcategoriesList )
.on( 'click', '.wpforms-setup-templates-subcategories li', WPFormsFormTemplates.selectSubCategory )
.on( 'click', '.wpforms-template-select', app.selectTemplate )
.on( 'click', '.wpforms-trigger-blank', app.selectBlankTemplate );
el.$builder
.on( 'wpformsBuilderReady wpformsBuilderPanelLoaded', app.filterTemplatesBySelectedCategory );
},
/**
* Panel loaded event.
*
* @since 1.8.6
*
* @param {Object} e Event object.
* @param {string} panel Panel name.
*/
panelLoaded( e, panel ) {
if ( panel !== 'setup' ) {
return;
}
WPFormsFormTemplates.setup();
app.setup();
app.setSelectedTemplate();
app.setSelectedCategories();
app.panelEvents();
},
/**
* Set panels toggle buttons state.
*
* @since 1.6.8
*/
setPanelsToggleState() {
el.$builder
.find( '#wpforms-panels-toggle button:not(.active)' )
.toggleClass( 'wpforms-disabled', vars.formID === '' );
},
/**
* Set attributes of "blank template" link.
*
* @since 1.6.8
*/
setTriggerBlankLink() {
el.$builder
.find( '.wpforms-trigger-blank' )
.attr( {
'data-template-name-raw': 'Blank Form',
'data-template': 'blank',
} );
},
/**
* Force focus on the form title field when switched to the Setup panel.
*
* @since 1.6.8
*
* @param {Object|null} e Event object.
* @param {string|null} view Current view.
*/
setupTitleFocus( e = null, view = null ) { // eslint-disable-line no-unused-vars
view = view || wpf.getQueryString( 'view' );
if ( view !== 'setup' ) {
return;
}
// Clone form title to the Setup page.
$( '#wpforms-setup-name' ).val( $( '#wpforms-panel-field-settings-form_title' ).val() );
el.$formName.trigger( 'focus' );
},
/**
* Mark the current form template as selected.
*
* @since 1.8.6
*/
setSelectedTemplate() {
if ( ! el.$panel.length || ! wpforms_builder.form_meta?.template ) {
return;
}
const $template = el.$builder
.find( `.wpforms-template-select[data-template="${ wpforms_builder.form_meta.template }"]` )
.closest( '.wpforms-template' );
if ( ! $template.length ) {
return;
}
$template
.addClass( 'selected' )
.addClass( 'badge' );
// Remove existing badge.
$template.find( '.wpforms-badge' ).remove();
// Remove edit and delete action buttons from current user template.
if ( $template.hasClass( 'wpforms-user-template' ) ) {
$template.find( '.wpforms-template-edit, .wpforms-template-remove' ).remove();
}
},
/**
* Set category and/or subcategory active if its template was selected.
*
* @since 1.8.9
*/
setSelectedCategories() {
if ( ! el.$panel.length || ! wpforms_builder.form_meta?.category ) {
return;
}
const $category = el.$categories.find( `li[data-category="${ wpforms_builder.form_meta.category }"]` );
if ( ! $category.length ) {
return;
}
el.$categories.find( 'li' ).removeClass( 'active opened' );
$category.addClass( 'active opened' );
const $subcategory = el.$subcategories.find( `li[data-subcategory="${ wpforms_builder.form_meta.subcategory }"]` );
if ( ! $subcategory.length ) {
return;
}
el.$subcategories.find( 'li' ).removeClass( 'active' );
$subcategory.addClass( 'active' );
},
/**
* Filter templates by selected category and subcategory.
*
* @since 1.8.9
*/
filterTemplatesBySelectedCategory() {
const $subCategory = el.$subcategories.find( 'li.active' );
// If subcategory is available, trigger its click it will update and category also.
if ( $subCategory.length ) {
$subCategory.trigger( 'click' );
}
const $category = el.$categories.find( '> li.active' );
// In another case, click on the category.
if (
! $subCategory.length &&
$category.length &&
$category.data( 'category' ) !== 'all'
) {
$category.find( 'div' ).trigger( 'click' );
}
},
/**
* Keep Setup title and settings title instances the same.
*
* @since 1.6.8
*
* @param {Object} e Event object.
*/
syncTitle( e ) {
if ( e.target.id === 'wpforms-setup-name' ) {
$( '#wpforms-panel-field-settings-form_title' ).val( e.target.value );
} else {
$( '#wpforms-setup-name' ).val( e.target.value );
}
},
/**
* Search template.
*
* @since 1.6.8
* @since 1.7.7 Deprecated.
*
* @deprecated Use `WPFormsFormTemplates.searchTemplate` instead.
*
* @param {Object} e Event object.
*/
searchTemplate( e ) {
console.warn( 'WARNING! Function "WPForms.Admin.Builder.Setup.searchTemplate( e )" has been deprecated, please use the new "WPFormsFormTemplates.searchTemplate( e )" function instead!' );
WPFormsFormTemplates.searchTemplate( e );
},
/**
* Select category.
*
* @since 1.6.8
* @since 1.7.7 Deprecated.
*
* @deprecated Use `WPFormsFormTemplates.selectCategory` instead.
*
* @param {Object} e Event object.
*/
selectCategory( e ) {
console.warn( 'WARNING! Function "WPForms.Admin.Builder.Setup.selectCategory( e )" has been deprecated, please use the new "WPFormsFormTemplates.selectCategory( e )" function instead!' );
WPFormsFormTemplates.selectCategory( e );
},
/**
* Select template.
*
* @since 1.6.8
*
* @param {Object} event Event object.
*/
selectTemplate( event ) {
event.preventDefault();
const $button = $( this );
// Don't do anything for templates that trigger education modal OR addons-modal.
if ( $button.hasClass( 'education-modal' ) ) {
return;
}
const template = $button.data( 'template' );
// User templates are applied differently for new forms.
if ( ! vars.formID && template.match( /wpforms-user-template-(\d+)/ ) && $button.data( 'create-url' ) ) {
window.location.href = $button.data( 'create-url' );
return;
}
el.$panel.find( '.wpforms-template' ).removeClass( 'active' );
$button.closest( '.wpforms-template' ).addClass( 'active' );
// Save the original label.
$button.data( 'labelOriginal', $button.html() );
// Display loading indicator.
$button.html( vars.spinner + wpforms_builder.loading );
const formName = app.getFormName( $button );
app.applyTemplate( formName, template, $button );
},
/**
* Get form name.
*
* @since 1.7.6
*
* @param {jQuery} $button Pressed template button.
*
* @return {string} A new form name.
*/
getFormName( $button ) {
const templateName = $button.data( 'template-name-raw' );
const formName = el.$formName.val();
if ( ! formName ) {
return templateName;
}
return activeTemplateName === formName ? templateName : formName;
},
/**
* Apply template.
*
* The final part of the select template routine.
*
* @since 1.6.9
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
* @param {jQuery} $button Use a template button object.
*/
applyTemplate( formName, template, $button ) {
el.$builder.trigger( 'wpformsTemplateSelect', template );
if ( vars.formID ) {
// Existing form.
app.selectTemplateExistingForm( formName, template, $button );
} else {
// Create a new form.
WPFormsFormTemplates.selectTemplateProcess( formName, template, $button, app.selectTemplateProcessAjax );
}
},
/**
* Select Blank template.
*
* @since 1.6.8
*
* @param {Object} e Event object.
*/
selectBlankTemplate( e ) {
e.preventDefault();
const $button = $( e.target ),
formName = el.$formName.val() || wpforms_builder.blank_form,
template = 'blank';
app.applyTemplate( formName, template, $button );
},
/**
* Select template. Existing form.
*
* @since 1.6.8
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
* @param {jQuery} $button Use a template button object.
*/
selectTemplateExistingForm( formName, template, $button ) {
$.confirm( {
title: wpforms_builder.heads_up,
content: wpforms_builder.template_confirm,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
WPFormsFormTemplates.selectTemplateProcess( formName, template, $button, app.selectTemplateProcessAjax );
},
},
cancel: {
text: wpforms_builder.cancel,
action() {
WPFormsFormTemplates.selectTemplateCancel();
},
},
},
} );
},
/**
* Select template.
*
* @since 1.6.8
* @since 1.8.2 Deprecated.
*
* @deprecated Use `WPFormsFormTemplates.selectTemplateProcess` instead.
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
* @param {jQuery} $button Use a template button object.
*/
selectTemplateProcess( formName, template, $button ) {
console.warn( 'WARNING! Function "WPForms.Admin.Builder.Setup.selectTemplateProcess( formName, template, $button )" has been deprecated, please use the new "WPFormsFormTemplates.selectTemplateProcess( formName, template, $button, callback )" function instead!' );
WPFormsFormTemplates.selectTemplateProcess( formName, template, $button, app.selectTemplateProcessAjax );
},
/**
* Cancel button click routine.
*
* @since 1.6.8
* @since 1.7.7 Deprecated.
*
* @deprecated Use `WPFormsFormTemplates.selectTemplateCancel` instead.
*/
selectTemplateCancel( ) {
console.warn( 'WARNING! Function "WPForms.Admin.Builder.Setup.selectTemplateCancel" has been deprecated, please use the new "WPFormsFormTemplates.selectTemplateCancel" function instead!' );
WPFormsFormTemplates.selectTemplateCancel();
},
/**
* Select template. Create or update form AJAX call.
*
* @since 1.6.8
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
*/
selectTemplateProcessAjax( formName, template ) {
WPFormsBuilder.showLoadingOverlay();
const data = {
title: formName,
action: vars.formID ? 'wpforms_update_form_template' : 'wpforms_new_form',
template,
form_id: vars.formID, // eslint-disable-line camelcase
nonce: wpforms_builder.nonce,
};
const category = $( '.wpforms-setup-templates-categories li.active' ).data( 'category' );
const subcategory = $( '.wpforms-setup-templates-subcategories li.active' ).data( 'subcategory' );
if ( category ) {
data.category = category;
}
if ( subcategory ) {
data.subcategory = subcategory;
}
if ( category === 'all' ) {
data.subcategory = 'all';
}
$.post( wpforms_builder.ajax_url, data )
.done( function( res ) {
if ( res.success ) {
// We have already warned the user that unsaved changes will be ignored.
WPFormsBuilder.setCloseConfirmation( false );
window.location.href = wpf.getQueryString( 'force_desktop_view' )
? wpf.updateQueryString( 'force_desktop_view', '1', res.data.redirect )
: res.data.redirect;
return;
}
wpf.debug( res );
if ( res.data.error_type === 'invalid_template' ) {
app.selectTemplateProcessInvalidTemplateError( res.data.message, formName );
return;
}
app.selectTemplateProcessError( res.data.message );
} )
.fail( function( xhr, textStatus ) {
wpf.debug( xhr.responseText || textStatus || '' );
app.selectTemplateProcessError( '' );
} );
},
/**
* Select template AJAX call error modal for invalid template using.
*
* @since 1.7.5.3
*
* @param {string} errorMessage Error message.
* @param {string} formName Name of the form.
*/
selectTemplateProcessInvalidTemplateError( errorMessage, formName ) {
$.alert( {
title: wpforms_builder.heads_up,
content: errorMessage,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.use_default_template,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
app.selectTemplateProcessAjax( formName, 'simple-contact-form-template' );
WPFormsBuilder.hideLoadingOverlay();
},
},
cancel: {
text: wpforms_builder.cancel,
action() {
WPFormsFormTemplates.selectTemplateCancel();
WPFormsBuilder.hideLoadingOverlay();
},
},
},
} );
},
/**
* Select template AJAX call error modal.
*
* @since 1.6.8
* @since 1.8.8 Replaced error message with error title.
*
* @param {string} errorTitle Error title.
*/
selectTemplateProcessError( errorTitle ) {
$.alert( {
title: errorTitle,
content: wpforms_builder.error_select_template,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_builder.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
WPFormsFormTemplates.selectTemplateCancel();
WPFormsBuilder.hideLoadingOverlay();
},
},
},
} );
},
/**
* Open required addons alert.
*
* @since 1.6.8
* @since 1.8.2 Deprecated.
*
* @deprecated Use `WPFormsFormTemplates.addonsModal` instead.
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
* @param {jQuery} $button Use a template button object.
*/
addonsModal( formName, template, $button ) {
console.warn( 'WARNING! Function "WPForms.Admin.Builder.Setup.addonsModal( formName, template, $button )" has been deprecated, please use the new "WPFormsFormTemplates.addonsModal( formName, template, $button, callback )" function instead!' );
WPFormsFormTemplates.addonsModal( formName, template, $button, app.selectTemplateProcessAjax );
},
/**
* Install & Activate addons via AJAX.
*
* @since 1.6.8
* @since 1.8.2 Deprecated.
*
* @deprecated Use `WPFormsFormTemplates.installActivateAddons` instead.
*
* @param {Array} addons Addons slugs.
* @param {Object} previousModal Previous modal instance.
* @param {string} formName Name of the form.
* @param {string} template Template slug.
*/
installActivateAddons( addons, previousModal, formName, template ) {
console.warn( 'WARNING! Function "WPForms.Admin.Builder.Setup.installActivateAddons( addons, previousModal, formName, template )" has been deprecated, please use the new "WPFormsFormTemplates.installActivateAddons( addons, previousModal, formName, template, callback )" function instead!' );
WPFormsFormTemplates.installActivateAddons( addons, previousModal, formName, template, app.selectTemplateProcessAjax );
},
/**
* Install & Activate addons error modal.
*
* @since 1.6.8
* @since 1.8.2 Deprecated.
*
* @deprecated Use `WPFormsFormTemplates.installActivateAddonsError` instead.
*
* @param {string} formName Name of the form.
* @param {string} template Template slug.
*/
installActivateAddonsError( formName, template ) {
console.warn( 'WARNING! Function "WPForms.Admin.Builder.Setup.installActivateAddonsError( formName, template )" has been deprecated, please use the new "WPFormsFormTemplates.installActivateAddonsError( formName, template, callback )" function instead!' );
WPFormsFormTemplates.installActivateAddonsError( formName, template, app.selectTemplateProcessAjax );
},
/**
* Install & Activate single addon via AJAX.
*
* @since 1.6.8
* @since 1.8.2 Deprecated.
*
* @deprecated Use `WPFormsFormTemplates.installActivateAddonAjax` instead.
*
* @param {string} addon Addon slug.
*
* @return {Promise} jQuery ajax call promise.
*/
installActivateAddonAjax( addon ) {
console.warn( 'WARNING! Function "WPForms.Admin.Builder.Setup.installActivateAddonAjax( addon )" has been deprecated, please use the new "WPFormsFormTemplates.installActivateAddonAjax( addon )" function instead!' );
return WPFormsFormTemplates.installActivateAddonAjax( addon );
},
/**
* Initiate template processing for a new form.
*
* @since 1.6.8
*/
applyTemplateOnRequest() {
const urlParams = new URLSearchParams( window.location.search ),
templateId = urlParams.get( 'template_id' );
if (
urlParams.get( 'view' ) !== 'setup' ||
urlParams.get( 'form_id' ) ||
! templateId
) {
return;
}
el.$panel.find( '.wpforms-template .wpforms-btn[data-template="' + templateId + '"]' ).trigger( 'click' );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.Setup.init();
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,158 @@
/* global wpforms_builder */
// eslint-disable-next-line no-var
var WPForms = window.WPForms || {};
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.Templates = WPForms.Admin.Builder.Templates || ( function( document, window, $ ) {
/**
* Private functions and properties.
*
* @since 1.4.8
*
* @type {Object}
*/
const __private = {
/**
* All templating functions for providers are stored here in a Map.
* Key is a template name, value - Underscore.js templating function.
*
* @since 1.4.8
*
* @type {Map}
*/
previews: new Map(),
/**
* Function to handle subfields for a given template's properties and extend
* the fields list if applicable. The function processes fields for specific
* types and formats, especially for "name" type fields, transforming them into
* an extended format with additional subfields (Full, First, Middle, Last).
*
* If the `isSupportSubfields` property is not enabled in the provided template's
* properties, the original `basePreview` function is executed without modification.
*
* @since 1.9.6
*
* @param {Function} basePreview The base preview function to execute the final output.
*
* @return {Function} A function that accepts `templateProps` and processes its fields.
*/
handleSubFields: ( basePreview ) => ( templateProps ) => {
if ( ! templateProps?.isSupportSubfields ) {
return basePreview( templateProps );
}
const extendedFieldsList = {};
let counter = 0;
_.each( templateProps.fields, function( field, key ) {
if ( _.isEmpty( field ) || ! _.has( field, 'id' ) || ! _.has( field, 'type' ) ) {
return;
}
if ( 'name' !== field.type || ! _.has( field, 'format' ) ) {
extendedFieldsList[ counter++ ] = field;
return;
}
field.id = field.id.toString();
const fieldLabel = ! _.isUndefined( field.label ) && field.label.toString().trim() !== ''
? field.label.toString().trim()
: wpforms_builder.field + ' #' + key;
// Add data for Name field in "extended" format (Full, First, Middle and Last).
_.each( wpforms_builder.name_field_formats, function( formatLabel, valueSlug ) {
if ( -1 !== field.format.indexOf( valueSlug ) || valueSlug === 'full' ) {
extendedFieldsList[ counter++ ] = {
id: field.id + '.' + valueSlug,
label: fieldLabel + ' (' + formatLabel + ')',
format: field.format,
};
}
} );
} );
templateProps.fields = extendedFieldsList;
return basePreview( templateProps );
},
};
/**
* Public functions and properties.
*
* @since 1.4.8
*
* @type {Object}
*/
const app = {
/**
* Start the engine. DOM is not ready yet, use only to init something.
*
* @since 1.4.8
*/
init() {
// Do that when DOM is ready.
$( app.ready );
},
/**
* DOM is fully loaded.
*
* @since 1.4.8
*/
ready() {
$( '#wpforms-panel-providers' ).trigger( 'WPForms.Admin.Builder.Templates.ready' );
},
/**
* Register and compile all templates.
* All data is saved in a Map.
*
* @since 1.4.8
*
* @param {string[]} templates Array of template names.
*/
add( templates ) {
templates.forEach( function( template ) {
if ( typeof template === 'string' ) {
__private.previews.set( template, wp.template( template ) );
}
} );
},
/**
* Get a templating function (to compile later with data).
*
* @since 1.4.8
*
* @param {string} template ID of a template to retrieve from a cache.
*
* @return {*} A callable that after compiling will always return a string.
*/
get( template ) {
const preview = __private.previews.get( template );
if ( typeof preview !== 'undefined' ) {
return __private.handleSubFields( preview );
}
return function() {
return '';
};
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.Templates.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.Templates=WPForms.Admin.Builder.Templates||(e=>{let i={previews:new Map,handleSubFields:i=>e=>{if(e?.isSupportSubfields){let t={},n=0;_.each(e.fields,function(d,e){if(!_.isEmpty(d)&&_.has(d,"id")&&_.has(d,"type"))if("name"===d.type&&_.has(d,"format")){d.id=d.id.toString();let r=_.isUndefined(d.label)||""===d.label.toString().trim()?wpforms_builder.field+" #"+e:d.label.toString().trim();_.each(wpforms_builder.name_field_formats,function(e,i){-1===d.format.indexOf(i)&&"full"!==i||(t[n++]={id:d.id+"."+i,label:r+" ("+e+")",format:d.format})})}else t[n++]=d}),e.fields=t}return i(e)}},r={init(){e(r.ready)},ready(){e("#wpforms-panel-providers").trigger("WPForms.Admin.Builder.Templates.ready")},add(e){e.forEach(function(e){"string"==typeof e&&i.previews.set(e,wp.template(e))})},get(e){e=i.previews.get(e);return void 0!==e?i.handleSubFields(e):function(){return""}}};return r})((document,window,jQuery)),WPForms.Admin.Builder.Templates.init();
@@ -0,0 +1,109 @@
/* global wpforms_builder_themes_no_access */
/**
* @param wpforms_builder_themes_no_access.strings.permission_modal.confirm
* @param wpforms_builder_themes_no_access.strings.permission_modal.content
* @param wpforms_builder_themes_no_access.strings.permission_modal.title
*/
// noinspection ES6ConvertVarToLetConst
/**
* WPForms Form builder themes - Non-admin version.
*
* @since 1.9.8
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.ThemesNoAccess = WPForms.Admin.Builder.ThemesNoAccess || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.9.8
*
* @type {Object}
*/
const el = {};
/**
* Public functions and properties.
*
* @since 1.9.8
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.8
*/
init() {
$( app.ready );
},
/**
* Start the engine.
*
* @since 1.9.8
*/
ready() {
app.setup();
app.events();
},
/**
* Setup. Prepare some variables.
*
* @since 1.9.8
*/
setup() {
// Cache DOM elements.
el.$builder = $( '#wpforms-builder' );
},
/**
* Setup events.
*
* @since 1.9.8
*/
events() {
el.$builder.on( 'wpformsPanelSectionSwitch', app.handlePanelSectionSwitch );
},
/**
* Handle panel section switch and show permission modal.
*
* @since 1.9.8
*
* @param {Object} _event The event object.
* @param {string} section The section that was switched to.
*/
handlePanelSectionSwitch( _event, section ) {
if ( section === 'themes' ) {
$.alert( {
title: wpforms_builder_themes_no_access.strings.permission_modal.title,
content: wpforms_builder_themes_no_access.strings.permission_modal.content,
icon: 'fa fa-exclamation-triangle',
type: 'red',
theme: 'modern',
buttons: {
confirm: {
text: wpforms_builder_themes_no_access.strings.permission_modal.confirm,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
_event.preventDefault();
}
},
};
// Return the public-facing methods.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.ThemesNoAccess.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.ThemesNoAccess=WPForms.Admin.Builder.ThemesNoAccess||(n=>{let e={},s={init(){n(s.ready)},ready(){s.setup(),s.events()},setup(){e.$builder=n("#wpforms-builder")},events(){e.$builder.on("wpformsPanelSectionSwitch",s.handlePanelSectionSwitch)},handlePanelSectionSwitch(e,s){"themes"===s&&(n.alert({title:wpforms_builder_themes_no_access.strings.permission_modal.title,content:wpforms_builder_themes_no_access.strings.permission_modal.content,icon:"fa fa-exclamation-triangle",type:"red",theme:"modern",buttons:{confirm:{text:wpforms_builder_themes_no_access.strings.permission_modal.confirm,btnClass:"btn-confirm",keys:["enter"]}}}),e.preventDefault())}};return s})((document,window,jQuery)),WPForms.Admin.Builder.ThemesNoAccess.init();
@@ -0,0 +1,264 @@
/* global wpforms_builder_themes, wpf, WPFormsUtils */
// noinspection ES6ConvertVarToLetConst
/**
* WPForms Form builder themes.
*
* @since 1.9.7
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.Themes = WPForms.Admin.Builder.Themes || ( function( document, window, $ ) {
/**
* Elements holder.
*
* @since 1.9.7
*
* @type {Object}
*/
const el = {};
/**
* Public functions and properties.
*
* @since 1.9.7
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.7
*/
init() {
$( app.ready );
},
/**
* Start the engine.
*
* @since 1.9.7
*/
ready() {
app.setup();
app.loadModules();
},
/**
* Setup. Prepare some variables.
*
* @since 1.9.7
*/
setup() {
// Cache DOM elements.
el.$builder = $( '#wpforms-builder' );
},
/**
* Centralized reactive store for theme style settings in the Form Builder.
*
* Provides a simple pub/sub mechanism to track and respond to setting changes.
* Automatically initializes its state from all inputs/selects matching `name="settings[themes][...]"`.
*
* Usage examples:
*
* // Subscribe to a specific setting change.
* app.store.subscribe('buttonTextColor', (value) => {
* console.log('Button text color changed to:', value);
* } );
*
* // Subscribe to all setting changes.
* app.store.subscribeAll(( value, key) => {
* console.log( Setting ${ key } changed to:`, value);
* });
*
* // Get current value.
* const padding = app.store.get('containerPadding');
*
* // Manually update a setting (will trigger listeners).
* // Use the 3rd argument as 'true' to set in a 'silent' mode when state and input will update,
* // but no listeners will be run.
* app.store.set('fieldSize', 'medium');
*
* @since 1.9.7
*
* @type {Object}
* @property {Function} get Get current value of a setting by key.
* @property {Function} set Set value of a setting by key and notify listeners.
* @property {Function} subscribe Subscribe to a specific setting change: (key, callback) => void
* @property {Function} subscribeAll Subscribe to all setting changes: (callback) => void
* @property {Function} initFromDOM Initialize the store from DOM inputs/selects.
* @property {Object} state Raw internal state object (mostly for debugging).
*/
store: ( () => {
const state = {};
const keyListeners = new Map();
const inputElements = new Map();
const globalListeners = [];
const debouncedSetters = {};
const DEBOUNCE_DELAY = 50;
// Settings getter.
const get = ( key ) => state[ key ];
// Settings setter.
const set = ( key, value, silent = false ) => {
if ( state[ key ] === value ) {
return;
}
state[ key ] = value;
const $input = inputElements.get( key );
if ( $input && $input.val() !== value ) {
$input.val( value );
if ( ! silent ) {
$input.trigger( 'input' );
}
}
if ( silent ) {
return;
}
if ( keyListeners.has( key ) ) {
$.each( keyListeners.get( key ), ( _, cb ) => cb( value, key ) );
}
$.each( globalListeners, ( _, cb ) => cb( value, key ) );
};
// Get bounced Setter.
const getDebouncedSetter = ( key ) => {
if ( ! debouncedSetters[ key ] ) {
debouncedSetters[ key ] = _.debounce( ( value ) => set( key, value ), DEBOUNCE_DELAY );
}
return debouncedSetters[ key ];
};
// Allow subscribing to specific setting change.
const subscribe = ( key, callback ) => {
if ( ! keyListeners.has( key ) ) {
keyListeners.set( key, [] );
}
keyListeners.get( key ).push( callback );
};
// Allow listening all settings change.
const subscribeAll = ( callback ) => {
globalListeners.push( callback );
};
// Initialize from DOM (should be called once during app init).
const initFromDOM = () => {
$( '[name^="settings[themes]"]' ).each( function() {
const $input = $( this );
const nameMatch = $input.attr( 'name' ).match( /\[themes]\[(.*?)]/ );
if ( ! nameMatch ) {
return;
}
const key = nameMatch[ 1 ];
state[ key ] = $input.val();
inputElements.set( key, $input );
const tag = $input.prop( 'tagName' ).toLowerCase();
const type = ( $input.attr( 'type' ) || '' ).toLowerCase();
const isDebouncedInput = (
( tag === 'input' && ( type === 'text' || type === 'number' || type === 'hidden' ) ) ||
tag === 'textarea'
);
$input.on( isDebouncedInput ? 'input' : 'change', function() {
const value = $( this ).val();
if ( isDebouncedInput ) {
getDebouncedSetter( key )( value );
} else {
set( key, value );
}
} );
} );
};
return {
get,
set,
subscribe,
subscribeAll,
initFromDOM,
inputElements,
state,
};
}
)(),
/**
* Get setting.
*
* @since 1.9.7
*
* @param {null|string} key Settings key.
*
* @return {Object} Setting value.
*/
getSettings( key = null ) {
if ( key ) {
return app.store.get( key );
}
return app.store.state;
},
/**
* Get controls.
*
* @since 1.9.7
*
* @param {null|string} key Settings key.
*
* @return {Object} Control
*/
getControls( key = null ) {
if ( key ) {
return app.store.inputElements.get( key );
}
return app.store.inputElements;
},
/**
* Load modules.
*
* @since 1.9.7
*/
loadModules() {
const modules = wpforms_builder_themes.modules || [];
// Import all modules dynamically.
Promise.all( modules.map( ( module ) => import( module.path ) ) )
.then( ( importedModules ) => {
importedModules.forEach( ( module, index ) => {
const moduleName = modules[ index ].name;
app[ moduleName ] = module.default( document, window, $ );
// Initialize module on `wpformsBuilderThemesLoaded` event.
el.$builder.on( `wpformsBuilderThemesLoaded`, app[ moduleName ].init );
} );
// Trigger `wpformsBuilderThemesLoaded` event.
WPFormsUtils.triggerEvent( el.$builder, 'wpformsBuilderThemesLoaded', [ importedModules ] );
} )
.catch( ( error ) => {
wpf.debug( 'Error importing modules:', error );
} );
},
};
// Return the public-facing methods.
return app;
}( document, window, jQuery ) );
// Initialize.
WPForms.Admin.Builder.Themes.init();
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.Themes=WPForms.Admin.Builder.Themes||((s,i,d)=>{let n={},o={init(){d(o.ready)},ready(){o.setup(),o.loadModules()},setup(){n.$builder=d("#wpforms-builder")},store:(()=>{let n={},i=new Map,o=new Map,m=[],a={},l=50;let u=(r,s,e=!1)=>{var t;n[r]===s||(n[r]=s,(t=o.get(r))&&t.val()!==s&&(t.val(s),e||t.trigger("input")),e)||(i.has(r)&&d.each(i.get(r),(e,t)=>t(s,r)),d.each(m,(e,t)=>t(s,r)))};return{get:e=>n[e],set:u,subscribe:(e,t)=>{i.has(e)||i.set(e,[]),i.get(e).push(t)},subscribeAll:e=>{m.push(e)},initFromDOM:()=>{d('[name^="settings[themes]"]').each(function(){var e=d(this),t=e.attr("name").match(/\[themes]\[(.*?)]/);if(t){let r=t[1];n[r]=e.val(),o.set(r,e);var t=e.prop("tagName").toLowerCase(),i=(e.attr("type")||"").toLowerCase();let s="input"===t&&("text"===i||"number"===i||"hidden"===i)||"textarea"===t;e.on(s?"input":"change",function(){var t,e=d(this).val();s?(t=r,a[t]||(a[t]=_.debounce(e=>u(t,e),l)),a[t])(e):u(r,e)})}})},inputElements:o,state:n}})(),getSettings(e=null){return e?o.store.get(e):o.store.state},getControls(e=null){return e?o.store.inputElements.get(e):o.store.inputElements},loadModules(){let r=wpforms_builder_themes.modules||[];Promise.all(r.map(e=>import(e.path))).then(e=>{e.forEach((e,t)=>{t=r[t].name;o[t]=e.default(s,i,d),n.$builder.on("wpformsBuilderThemesLoaded",o[t].init)}),WPFormsUtils.triggerEvent(n.$builder,"wpformsBuilderThemesLoaded",[e])}).catch(e=>{wpf.debug("Error importing modules:",e)})}};return o})(document,window,jQuery),WPForms.Admin.Builder.Themes.init();
@@ -0,0 +1,221 @@
/* global wpforms_builder_themes */
/**
* WPForms Form Builder Themes: Advanced settings module.
*
* @since 1.9.7
*
* @param {Object} document Document object.
* @param {Object} window Window object.
* @param {jQuery} $ jQuery object.
*
* @return {Object} Public functions and properties.
*/
export default function( document, window, $ ) { // eslint-disable-line max-lines-per-function
const WPForms = window.WPForms || {};
const WPFormsBuilderThemes = WPForms.Admin.Builder.Themes || {};
/**
* Elements holder.
*
* @since 1.9.7
*
* @type {Object}
*/
const el = {};
/**
* Public functions and properties.
*
* @since 1.9.7
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.7
*/
init() {
app.setup();
app.events();
// Subscribe to all settings change.
WPFormsBuilderThemes.store.subscribeAll( ( value, key ) => {
app.updateCopyPasteContent( value, key );
} );
app.disableSpellCheck();
app.updateCopyPasteContent();
},
/**
* Setup.
*
* @since 1.9.7
*/
setup() {
el.$builder = $( '#wpforms-builder' );
},
/**
* Setup.
*
* @since 1.9.7
*/
events() {
},
/**
* Get the list of the settings key allowed to show in the Copy/paste field.
*
* @since 1.9.7
* @return {Array} List of allowed settings.
*/
getAllowedKeys() {
const allowedKeys = [ 'themeName', 'isCustomTheme', 'wpformsTheme', 'customCss' ];
const styleSettings = WPFormsBuilderThemes.common.getStyleAttributesKeys();
return allowedKeys.concat( styleSettings );
},
/**
* Update the content of the "Copy/Paste" field.
*
* @since 1.9.7
*
* @param {string} value Setting value
* @param {string} key Setting key.
*/
updateCopyPasteContent( value = '', key = '' ) {
if ( key === 'copyPasteJsonValue' ) {
app.pasteSettings( value );
return;
}
const content = {};
const allowedKeys = app.getAllowedKeys();
const settings = WPFormsBuilderThemes.getSettings();
allowedKeys.forEach( ( settingKey ) => {
content[ settingKey ] = settings[ settingKey ];
} );
// Update field content in a 'silent' mode.
WPFormsBuilderThemes.store.set( 'copyPasteJsonValue', JSON.stringify( content ), true );
},
/**
* Paste settings handler.
*
* @since 1.9.7
*
* @param {string} value New attribute value.
*/
pasteSettings( value ) {
value = value.trim();
const pasteAttributes = app.parseValidateJson( value );
// Show the error modal if JSON is broken.
if ( ! pasteAttributes ) {
if ( value ) {
app.showJsonErrorModal();
}
return;
}
const themeSlug = pasteAttributes?.wpformsTheme ?? pasteAttributes?.theme;
const currentThemeSlug = WPFormsBuilderThemes.store.get( 'wpformsTheme' );
const theme = WPFormsBuilderThemes.themes.getTheme( themeSlug );
// If the theme already exists - set it.
if ( theme && themeSlug !== currentThemeSlug ) {
WPFormsBuilderThemes.themes.setFormTheme( themeSlug );
WPFormsBuilderThemes.themes.updateThemesList();
return;
}
// For not existed theme - parse and set settings.
const allowedKeys = app.getAllowedKeys();
allowedKeys.forEach( ( settingKey ) => {
if ( pasteAttributes[ settingKey ] !== undefined ) {
let settingValue = pasteAttributes[ settingKey ];
settingValue = typeof settingValue === 'string'
? settingValue.replace( /px$/, '' )
: settingValue;
WPFormsBuilderThemes.store.set( settingKey, settingValue );
}
} );
},
/**
* Parse and validate JSON string.
*
* @since 1.9.7
*
* @param {string} value JSON string.
*
* @return {boolean|object} Parsed JSON object OR false on error.
*/
parseValidateJson( value ) {
if ( typeof value !== 'string' ) {
return false;
}
let atts;
try {
atts = JSON.parse( value.trim() );
} catch ( error ) {
atts = false;
}
return atts;
},
/**
* Show the error when pasted JSON is broken.
*
* @since 1.9.7
*/
showJsonErrorModal() {
$.alert( {
title: wpforms_builder_themes.strings.uhoh,
content: wpforms_builder_themes.strings.copy_paste_error,
icon: 'fa fa-exclamation-circle',
type: 'red',
buttons: {
cancel: {
text: wpforms_builder_themes.strings.close,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Disable spellcheck for the textarea settings fields.
*
* @since 1.9.7
*/
disableSpellCheck() {
const customCssControl = WPFormsBuilderThemes.getControls( 'customCss' );
const copyPasteControl = WPFormsBuilderThemes.getControls( 'copyPasteJsonValue' );
if ( ! customCssControl || ! copyPasteControl ) {
return;
}
copyPasteControl.attr( 'spellcheck', 'false' );
customCssControl.attr( 'spellcheck', 'false' );
},
};
return app;
}
@@ -0,0 +1 @@
export default function(e,t,s){let o=(t.WPForms||{}).Admin.Builder.Themes||{},r={},l={init(){l.setup(),l.events(),o.store.subscribeAll((e,t)=>{l.updateCopyPasteContent(e,t)}),l.disableSpellCheck(),l.updateCopyPasteContent()},setup(){r.$builder=s("#wpforms-builder")},events(){},getAllowedKeys(){var e=o.common.getStyleAttributesKeys();return["themeName","isCustomTheme","wpformsTheme","customCss"].concat(e)},updateCopyPasteContent(e="",r=""){if("copyPasteJsonValue"===r)l.pasteSettings(e);else{let t={};r=l.getAllowedKeys();let s=o.getSettings();r.forEach(e=>{t[e]=s[e]}),o.store.set("copyPasteJsonValue",JSON.stringify(t),!0)}},pasteSettings(e){e=e.trim();let s=l.parseValidateJson(e);var t,r;s?(t=s?.wpformsTheme??s?.theme,r=o.store.get("wpformsTheme"),o.themes.getTheme(t)&&t!==r?(o.themes.setFormTheme(t),o.themes.updateThemesList()):l.getAllowedKeys().forEach(t=>{if(void 0!==s[t]){let e=s[t];e="string"==typeof e?e.replace(/px$/,""):e,o.store.set(t,e)}})):e&&l.showJsonErrorModal()},parseValidateJson(e){if("string"!=typeof e)return!1;let t;try{t=JSON.parse(e.trim())}catch(e){t=!1}return t},showJsonErrorModal(){s.alert({title:wpforms_builder_themes.strings.uhoh,content:wpforms_builder_themes.strings.copy_paste_error,icon:"fa fa-exclamation-circle",type:"red",buttons:{cancel:{text:wpforms_builder_themes.strings.close,btnClass:"btn-confirm",keys:["enter"]}}})},disableSpellCheck(){var e=o.getControls("customCss"),t=o.getControls("copyPasteJsonValue");e&&t&&(t.attr("spellcheck","false"),e.attr("spellcheck","false"))}};return l}
@@ -0,0 +1,367 @@
/**
* WPForms Form Builder Themes: Background module.
*
* @since 1.9.7
*
* @param {Object} document Document object.
* @param {Object} window Window object.
* @param {jQuery} $ jQuery object.
*
* @return {Object} Public functions and properties.
*/
export default function( document, window, $ ) {// eslint-disable-line max-lines-per-function
const WPForms = window.WPForms || {};
const WPFormsBuilderThemes = WPForms.Admin.Builder.Themes || {};
/**
* Elements holder.
*
* @since 1.9.7
*
* @type {Object}
*/
const el = {};
/**
* Public functions and properties.
*
* @since 1.9.7
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.7
*/
init() {
app.setup();
app.events();
WPFormsBuilderThemes.store.subscribe( 'backgroundUrl', ( value ) => {
app.setImagePreview( value );
app.maybeShowChooseButton();
} );
WPFormsBuilderThemes.store.subscribe( 'backgroundImage', ( value ) => {
app.maybeShowImageSelector( value );
app.maybeShowChooseButton();
} );
WPFormsBuilderThemes.store.subscribe( 'backgroundSizeMode', ( value ) => {
app.handleSizeFromDimensions( value );
} );
},
/**
* Setup.
*
* @since 1.9.7
*/
setup() {
el.$builder = $( '#wpforms-builder' );
el.$preview = $( '#wpforms-builder-themes-preview' );
el.$imageSelector = $( '.wpforms-builder-themes-background-selector' );
el.$imagePreview = el.$imageSelector.find( '.wpforms-builder-themes-bg-image-preview' );
el.$chooseButton = el.$imageSelector.find( '.wpforms-builder-themes-bg-image-choose' );
el.$removeButton = el.$imageSelector.find( '.wpforms-builder-themes-bg-image-remove' );
app.initImageSelector();
},
/**
* Events.
*
* @since 1.9.7
*/
events() {
el.$builder
.on( 'click', '.wpforms-builder-themes-bg-image-remove', app.removeImage )
.on( 'click', '.wpforms-builder-themes-bg-image-choose, .wpforms-builder-themes-bg-image-preview', app.chooseImage );
},
/**
* Init the Image Selector control.
*
* @since 1.9.7
*/
initImageSelector() {
const settings = WPFormsBuilderThemes.getSettings();
el.$imageSelector.removeClass( 'wpforms-hidden' );
app.setImagePreview( settings.backgroundUrl );
app.maybeShowChooseButton();
},
/**
* Handle image selector control state.
*
* @since 1.9.7
* @param {string} value `backgroundImage` setting value.
*/
maybeShowImageSelector( value ) {
if ( value === 'none' ) {
el.$imageSelector.addClass( 'wpforms-hidden' );
} else {
el.$imageSelector.removeClass( 'wpforms-hidden' );
const backgroundUrl = WPFormsBuilderThemes.store.get( 'backgroundUrl' );
// Here we need to clean the url value and set a new one.
// Otherwise, the picture preview won't be updated.
WPFormsBuilderThemes.store.set( 'backgroundUrl', 'url()' );
WPFormsBuilderThemes.store.set( 'backgroundUrl', backgroundUrl );
}
},
/**
* Remove an image button handler.
*
* @param {Object} e Event object
*
* @since 1.9.7
*/
removeImage( e ) {
e.preventDefault();
WPFormsBuilderThemes.store.set( 'backgroundUrl', 'url()' );
el.$chooseButton.removeClass( 'wpforms-hidden' );
},
/**
* Choose an image button handler.
*
* @param {Object} e Event object
*
* @since 1.9.7
*/
chooseImage( e ) {
e.preventDefault();
const settings = WPFormsBuilderThemes.getSettings();
if ( settings.backgroundImage === 'library' ) {
app.openMediaLibrary();
} else {
WPFormsBuilderThemes.stockPhotos.openModal( 'bg-styles' );
}
app.maybeShowChooseButton();
},
/**
* Set the image preview.
*
* @param {null|string} value Image preview url value.
*
* @since 1.9.7
*/
setImagePreview( value = null ) {
const isHidden = ! value || value === 'url()';
const imageValue = isHidden ? 'url()' : `url(${ value })`;
el.$imagePreview.css( 'background-image', imageValue );
el.$imagePreview.toggleClass( 'wpforms-hidden', isHidden );
el.$removeButton.toggleClass( 'wpforms-hidden', isHidden );
},
/**
* Conditionally show or hide the `Choose Image` button.
*
* @since 1.9.7
*/
maybeShowChooseButton() {
const settings = WPFormsBuilderThemes.getSettings();
if ( settings.backgroundImage !== 'none' && settings.backgroundUrl === 'url()' ) {
el.$chooseButton.removeClass( 'wpforms-hidden' );
} else {
el.$chooseButton.addClass( 'wpforms-hidden' );
}
},
/**
* Open media library modal and handle image selection.
*
* @since 1.9.7
*/
openMediaLibrary() {
const frame = wp.media( {
multiple: false,
library: {
type: 'image',
},
} );
frame.on( 'select', () => {
const attachment = frame.state().get( 'selection' ).first().toJSON();
if ( attachment.url ) {
WPFormsBuilderThemes.store.set( 'backgroundUrl', attachment.url );
}
} );
frame.open();
},
/**
* Handle the real size from image dimensions.
*
* @since 1.9.7
*
* @param {string} value Value.
*/
handleSizeFromDimensions( value ) {
const settings = WPFormsBuilderThemes.getSettings();
const $container = el.$preview.find( '.wpforms-container' )[ 0 ];
const backgroundWidth = WPFormsBuilderThemes.common.prepareComplexAttrValues( settings.backgroundWidth, 'backgroundWidth' );
const backgroundHeight = WPFormsBuilderThemes.common.prepareComplexAttrValues( settings.backgroundHeight, 'backgroundHeight' );
const $backgroundSizeControl = WPFormsBuilderThemes.getControls( 'backgroundSize' );
if ( value === 'cover' ) {
app.setContainerBackgroundWidth( $container, backgroundWidth );
app.setContainerBackgroundHeight( $container, backgroundHeight );
$container.style.setProperty( `--wpforms-background-size`, 'cover' );
$backgroundSizeControl.val( 'cover' );
} else {
$container.style.setProperty( `--wpforms-background-size`, backgroundWidth + ' ' + backgroundHeight );
$backgroundSizeControl.val( backgroundWidth + ' ' + backgroundHeight );
}
$backgroundSizeControl.trigger( 'input' );
},
/**
* Handle real size from height.
*
* @since 1.9.7
*
* @param {HTMLElement} container Form preview container
* @param {string} value Value.
* @param {Object} atts Form style settings.
*/
handleSizeFromHeight( container, value, atts ) {
const backgroundWidth = WPFormsBuilderThemes.common.prepareComplexAttrValues( atts.backgroundWidth, 'backgroundWidth' );
const $backgroundSizeControl = WPFormsBuilderThemes.getControls( 'backgroundSize' );
app.setContainerBackgroundHeight( container, value );
if ( atts.backgroundSizeMode !== 'cover' ) {
$backgroundSizeControl.val( backgroundWidth + ' ' + value );
container.style.setProperty( `--wpforms-background-size`, backgroundWidth + ' ' + value );
$backgroundSizeControl.trigger( 'input' );
}
},
/**
* Handle real size from width.
*
* @since 1.9.7
*
* @param {HTMLElement} container Form preview container
* @param {string} value Value.
* @param {Object} atts Form style settings.
*/
handleSizeFromWidth( container, value, atts ) {
const backgroundWidth = WPFormsBuilderThemes.common.prepareComplexAttrValues( atts.backgroundWidth, 'backgroundWidth' );
const backgroundHeight = WPFormsBuilderThemes.common.prepareComplexAttrValues( atts.backgroundHeight, 'backgroundHeight' );
const $backgroundSizeControl = WPFormsBuilderThemes.getControls( 'backgroundSize' );
app.setContainerBackgroundWidth( container, backgroundWidth );
if ( atts.backgroundSizeMode !== 'cover' ) {
$backgroundSizeControl.val( value + ' ' + backgroundHeight );
container.style.setProperty( `--wpforms-background-size`, value + ' ' + backgroundHeight );
$backgroundSizeControl.trigger( 'input' );
}
},
/**
* Set the container background color.
*
* @since 1.9.7
*
* @param {HTMLElement} container Container element.
* @param {string} value Value.
*/
setBackgroundColor( container, value ) {
container.style.setProperty( `--wpforms-background-color`, value );
},
/**
* Set the container background url.
*
* @since 1.9.7
*
* @param {HTMLElement} container Container element.
* @param {string} value Value.
*/
setBackgroundUrl( container, value ) {
container.style.setProperty( `--wpforms-background-url`, value );
},
/**
* Set the container background height.
*
* @since 1.9.7
*
* @param {HTMLElement} container Container element.
* @param {string} value Value.
*/
setContainerBackgroundHeight( container, value ) {
container.style.setProperty( `--wpforms-background-height`, value );
},
/**
* Set the container background image.
*
* @since 1.9.7
*
* @param {HTMLElement} container Container element.
* @param {string} value Value.
*/
setContainerBackgroundImage( container, value ) {
if ( value === 'none' ) {
container.style.setProperty( `--wpforms-background-url`, 'url()' );
}
},
/**
* Set the container background position.
*
* @since 1.9.7
*
* @param {HTMLElement} container Container element.
* @param {string} value Value.
*/
setContainerBackgroundPosition( container, value ) {
container.style.setProperty( `--wpforms-background-position`, value );
},
/**
* Set container background repeat.
*
* @since 1.9.7
*
* @param {HTMLElement} container Container element.
* @param {string} value Value.
*/
setContainerBackgroundRepeat( container, value ) {
container.style.setProperty( `--wpforms-background-repeat`, value );
},
/**
* Set the container background width.
*
* @since 1.9.7
*
* @param {HTMLElement} container Container element.
* @param {string} value Value.
*/
setContainerBackgroundWidth( container, value ) {
container.style.setProperty( `--wpforms-background-width`, value );
},
};
return app;
}
@@ -0,0 +1 @@
export default function(e,r,o){let n=(r.WPForms||{}).Admin.Builder.Themes||{},a={},i={init(){i.setup(),i.events(),n.store.subscribe("backgroundUrl",e=>{i.setImagePreview(e),i.maybeShowChooseButton()}),n.store.subscribe("backgroundImage",e=>{i.maybeShowImageSelector(e),i.maybeShowChooseButton()}),n.store.subscribe("backgroundSizeMode",e=>{i.handleSizeFromDimensions(e)})},setup(){a.$builder=o("#wpforms-builder"),a.$preview=o("#wpforms-builder-themes-preview"),a.$imageSelector=o(".wpforms-builder-themes-background-selector"),a.$imagePreview=a.$imageSelector.find(".wpforms-builder-themes-bg-image-preview"),a.$chooseButton=a.$imageSelector.find(".wpforms-builder-themes-bg-image-choose"),a.$removeButton=a.$imageSelector.find(".wpforms-builder-themes-bg-image-remove"),i.initImageSelector()},events(){a.$builder.on("click",".wpforms-builder-themes-bg-image-remove",i.removeImage).on("click",".wpforms-builder-themes-bg-image-choose, .wpforms-builder-themes-bg-image-preview",i.chooseImage)},initImageSelector(){var e=n.getSettings();a.$imageSelector.removeClass("wpforms-hidden"),i.setImagePreview(e.backgroundUrl),i.maybeShowChooseButton()},maybeShowImageSelector(e){"none"===e?a.$imageSelector.addClass("wpforms-hidden"):(a.$imageSelector.removeClass("wpforms-hidden"),e=n.store.get("backgroundUrl"),n.store.set("backgroundUrl","url()"),n.store.set("backgroundUrl",e))},removeImage(e){e.preventDefault(),n.store.set("backgroundUrl","url()"),a.$chooseButton.removeClass("wpforms-hidden")},chooseImage(e){e.preventDefault(),"library"===n.getSettings().backgroundImage?i.openMediaLibrary():n.stockPhotos.openModal("bg-styles"),i.maybeShowChooseButton()},setImagePreview(e=null){var r=!e||"url()"===e,e=r?"url()":`url(${e})`;a.$imagePreview.css("background-image",e),a.$imagePreview.toggleClass("wpforms-hidden",r),a.$removeButton.toggleClass("wpforms-hidden",r)},maybeShowChooseButton(){var e=n.getSettings();"none"!==e.backgroundImage&&"url()"===e.backgroundUrl?a.$chooseButton.removeClass("wpforms-hidden"):a.$chooseButton.addClass("wpforms-hidden")},openMediaLibrary(){let r=wp.media({multiple:!1,library:{type:"image"}});r.on("select",()=>{var e=r.state().get("selection").first().toJSON();e.url&&n.store.set("backgroundUrl",e.url)}),r.open()},handleSizeFromDimensions(e){var r=n.getSettings(),o=a.$preview.find(".wpforms-container")[0],t=n.common.prepareComplexAttrValues(r.backgroundWidth,"backgroundWidth"),r=n.common.prepareComplexAttrValues(r.backgroundHeight,"backgroundHeight"),s=n.getControls("backgroundSize");"cover"===e?(i.setContainerBackgroundWidth(o,t),i.setContainerBackgroundHeight(o,r),o.style.setProperty("--wpforms-background-size","cover"),s.val("cover")):(o.style.setProperty("--wpforms-background-size",t+" "+r),s.val(t+" "+r)),s.trigger("input")},handleSizeFromHeight(e,r,o){var t=n.common.prepareComplexAttrValues(o.backgroundWidth,"backgroundWidth"),s=n.getControls("backgroundSize");i.setContainerBackgroundHeight(e,r),"cover"!==o.backgroundSizeMode&&(s.val(t+" "+r),e.style.setProperty("--wpforms-background-size",t+" "+r),s.trigger("input"))},handleSizeFromWidth(e,r,o){var t=n.common.prepareComplexAttrValues(o.backgroundWidth,"backgroundWidth"),s=n.common.prepareComplexAttrValues(o.backgroundHeight,"backgroundHeight"),a=n.getControls("backgroundSize");i.setContainerBackgroundWidth(e,t),"cover"!==o.backgroundSizeMode&&(a.val(r+" "+s),e.style.setProperty("--wpforms-background-size",r+" "+s),a.trigger("input"))},setBackgroundColor(e,r){e.style.setProperty("--wpforms-background-color",r)},setBackgroundUrl(e,r){e.style.setProperty("--wpforms-background-url",r)},setContainerBackgroundHeight(e,r){e.style.setProperty("--wpforms-background-height",r)},setContainerBackgroundImage(e,r){"none"===r&&e.style.setProperty("--wpforms-background-url","url()")},setContainerBackgroundPosition(e,r){e.style.setProperty("--wpforms-background-position",r)},setContainerBackgroundRepeat(e,r){e.style.setProperty("--wpforms-background-repeat",r)},setContainerBackgroundWidth(e,r){e.style.setProperty("--wpforms-background-width",r)}};return i}
@@ -0,0 +1,955 @@
/* global wpf, wpforms_builder_themes, WPFormsBuilder, wpforms_education, WPFormsEducation, WPFormsUtils */
/**
* WPForms Form Builder Themes: Common module.
*
* @since 1.9.7
*
* @param {Object} document Document object.
* @param {Object} window Window object.
* @param {jQuery} $ jQuery object.
*
* @return {Object} Public functions and properties.
*/
export default function( document, window, $ ) {// eslint-disable-line max-lines-per-function
const WPForms = window.WPForms || {};
const WPFormsBuilderThemes = WPForms.Admin.Builder.Themes || {};
/**
* Localized data aliases.
*
* @since 1.9.7
*/
const { isPro, isLicenseActive, isModern, isFullStyles, isLowFormPagesVersion, strings } = wpforms_builder_themes;
/**
* Elements holder.
*
* @since 1.9.7
*
* @type {Object}
*/
const el = {};
/**
* Field dependencies configuration.
*
* @since 1.9.7
*
* @type {Object}
*/
const fieldDependencies = {
fieldBorderStyle: {
none: {
disable: [ 'fieldBorderSize', 'fieldBorderColor' ],
},
},
buttonBorderStyle: {
none: {
disable: [ 'buttonBorderSize', 'buttonBorderColor' ],
},
},
containerBorderStyle: {
none: {
disable: [ 'containerBorderWidth', 'containerBorderColor' ],
},
},
backgroundImage: {
none: {
hide: [ 'backgroundPosition', 'backgroundRepeat', 'backgroundSizeMode', 'backgroundWidth', 'backgroundHeight' ],
},
},
backgroundSizeMode: {
cover: {
hide: [ 'backgroundWidth', 'backgroundHeight' ],
},
},
};
/**
* Public functions and properties.
*
* @since 1.9.7
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.7
*/
init() {
app.setup();
app.events();
// Maybe show the sidebar after page reload.
app.handlePanelSwitch();
// Init color pickers.
app.loadColorPickers();
// Init settings store.
WPFormsBuilderThemes.store.initFromDOM();
// Subscribe to all settings change.
WPFormsBuilderThemes.store.subscribeAll( ( value, key ) => {
app.changeStyleSettings( value, key );
app.handleFieldDependencies( key, value );
} );
// Render already saved settings.
app.renderSavedSettings();
// Apply initial dependencies.
app.applyAllDependencies();
// Block PRO controls.
app.blockProSections();
// Run checks.
app.runChecks();
},
/**
* Setup.
*
* @since 1.9.7
*/
setup() {
el.$builder = $( '#wpforms-builder' );
el.$settings = $( '.wpforms-panel-content-section-themes' );
el.$sidebar = $( '#wpforms-builder-themes-sidebar' );
el.$preview = $( '#wpforms-builder-themes-preview' );
el.$tabs = $( '#wpforms-builder-themes-sidebar-tabs > a' );
// Set the custom class to sidebar content for macOS.
if ( app.isMac() ) {
el.$sidebar.find( '.wpforms-builder-themes-sidebar-content' ).addClass( 'wpforms-is-mac' );
}
},
/**
* Setup.
*
* @since 1.9.7
*/
events() {
el.$builder
.on( 'click', '#wpforms-builder-themes-back', app.handleClosePreviewSidebar )
.on( 'click', '.wpforms-panel-sidebar-section-themes', app.handleOpenPreviewSidebar )
.on( 'wpformsPanelSwitched', '.wpforms-panel-sidebar-section-themes', app.handlePanelSwitch )
.on( 'wpformsPanelSectionSwitch', app.handlePanelSectionSwitch )
.on( 'click', '.wpforms-panel-settings-button.active[data-panel="settings"]', app.handleSettingsTabClick );
el.$tabs.on( 'click', app.handleTabClick );
},
/**
* Handle sidebar closing when the 'Settings' tab button is clicked.
*
* @since 1.9.7
*/
handleSettingsTabClick() {
if ( el.$sidebar.hasClass( 'wpforms-hidden' ) ) {
return;
}
app.handleClosePreviewSidebar( null );
},
/**
* Handle field dependencies when a field value changes.
*
* @since 1.9.7
*
* @param {string} fieldKey The field key that changed.
* @param {string} fieldValue The new field value.
*/
handleFieldDependencies( fieldKey, fieldValue ) {
// After handling the specific field dependency, re-apply all dependencies
// to ensure all conditions are properly evaluated with current values.
app.applyFieldDependency( fieldKey, fieldValue );
app.applyAllDependencies();
},
/**
* Apply dependency for a specific field.
*
* @since 1.9.7
*
* @param {string} fieldKey The field key that changed.
* @param {string} fieldValue The new field value.
*/
applyFieldDependency( fieldKey, fieldValue ) {
if ( ! fieldDependencies[ fieldKey ] ) {
return;
}
const dependencies = fieldDependencies[ fieldKey ];
// Check each condition for the field.
// eslint-disable-next-line complexity
Object.keys( dependencies ).forEach( ( conditionValue ) => {
const condition = dependencies[ conditionValue ];
const shouldApply = fieldValue === conditionValue;
// Handle disable conditions.
if ( condition.disable && shouldApply ) {
condition.disable.forEach( ( dependentField ) => {
app.disableField( dependentField );
} );
} else if ( condition.disable ) {
condition.disable.forEach( ( dependentField ) => {
app.enableField( dependentField );
} );
}
// Handle enable conditions.
if ( condition.enable && shouldApply ) {
condition.enable.forEach( ( dependentField ) => {
app.enableField( dependentField );
} );
} else if ( condition.enable ) {
condition.enable.forEach( ( dependentField ) => {
app.disableField( dependentField );
} );
}
// Handle hide conditions.
if ( condition.hide && shouldApply ) {
condition.hide.forEach( ( dependentField ) => {
app.hideField( dependentField );
} );
} else if ( condition.hide ) {
condition.hide.forEach( ( dependentField ) => {
app.showField( dependentField );
} );
}
// Handle show conditions.
if ( condition.show && shouldApply ) {
condition.show.forEach( ( dependentField ) => {
app.showField( dependentField );
} );
} else if ( condition.show ) {
condition.show.forEach( ( dependentField ) => {
app.hideField( dependentField );
} );
}
} );
},
/**
* Apply all dependencies based on current settings.
*
* @since 1.9.7
*/
applyAllDependencies() {
const settings = WPFormsBuilderThemes.getSettings();
Object.keys( fieldDependencies ).forEach( ( fieldKey ) => {
const fieldValue = settings[ fieldKey ];
if ( fieldValue !== undefined ) {
app.applyFieldDependency( fieldKey, fieldValue );
}
} );
},
/**
* Disable a field and its wrapper.
*
* @since 1.9.7
*
* @param {string} fieldKey The field key to disable.
*/
disableField( fieldKey ) {
const $field = el.$sidebar.find( `[name*="${ fieldKey }"]` );
if ( $field.length ) {
$field.addClass( 'wpforms-builder-themes-disabled' );
}
},
/**
* Enable a field and its wrapper.
*
* @since 1.9.7
*
* @param {string} fieldKey The field key to enable.
*/
enableField( fieldKey ) {
const $field = el.$sidebar.find( `[name*="${ fieldKey }"]` );
if ( $field.length ) {
$field.removeClass( 'wpforms-builder-themes-disabled' );
}
},
/**
* Hide a field and its wrapper.
*
* @since 1.9.7
*
* @param {string} fieldKey The field key to hide.
*/
hideField( fieldKey ) {
const $field = el.$sidebar.find( `[name*="${ fieldKey }"]` );
const $wrapper = $field.parent().parent().hasClass( 'wpforms-builder-themes-conditional-hide' )
? $field.parent().parent()
: $field.parent( '.wpforms-panel-field' );
if ( $field.length ) {
$field.prop( 'disabled', true );
$wrapper.addClass( 'wpforms-builder-themes-hidden' );
}
},
/**
* Show a field and its wrapper.
*
* @since 1.9.7
*
* @param {string} fieldKey The field key to show.
*/
showField( fieldKey ) {
const $field = el.$sidebar.find( `[name*="${ fieldKey }"]` );
const $wrapper = $field.parent().parent().hasClass( 'wpforms-builder-themes-conditional-hide' )
? $field.parent().parent()
: $field.parent( '.wpforms-panel-field' );
if ( $field.length ) {
$field.prop( 'disabled', false );
$wrapper.removeClass( 'wpforms-builder-themes-hidden' );
}
},
/**
* Handle opening the custom settings sidebar.
*
* @since 1.9.7
*
* @param {Object} event The event object.
*/
handleOpenPreviewSidebar( event ) {
el.$sidebar?.removeClass( 'wpforms-hidden' );
event?.preventDefault();
},
/**
* Handle closing the custom settings sidebar.
*
* @since 1.9.7
*
* @param {Object} event The event object.
*/
handleClosePreviewSidebar( event ) {
el.$sidebar?.addClass( 'wpforms-hidden' );
event?.preventDefault();
},
/**
* Handle panel switch and maybe open the sidebar.
*
* @since 1.9.7
*/
handlePanelSwitch() {
if ( wpf.getQueryString( 'section' ) === 'themes' ) {
app.handleOpenPreviewSidebar( null );
}
},
/**
* Handle panel section switch and maybe open the sidebar.
*
* @since 1.9.7
*
* @param {Object} _event The event object.
* @param {string} section The section that was switched to.
*/
handlePanelSectionSwitch( _event, section ) {
if ( section === 'themes' ) {
app.checkForFormFeatures();
}
},
/**
* Handle tabs click.
*
* @since 1.9.7
*
* @param {Object} e Event object.
*/
handleTabClick( e ) {
e.preventDefault();
el.$tabs.toggleClass( 'active' );
$( '.wpforms-builder-themes-sidebar-tab-content' ).toggleClass( 'wpforms-hidden' );
},
/**
* Get a list of the style settings keys.
*
* @since 1.9.7
*
* @return {Array} Settings keys
*/
getStyleAttributesKeys() {
return [
'containerPadding',
'containerBorderStyle',
'containerBorderWidth',
'containerBorderRadius',
'containerShadowSize',
'containerBorderColor',
'fieldSize',
'fieldBorderStyle',
'fieldBorderRadius',
'fieldBorderSize',
'fieldBackgroundColor',
'fieldBorderColor',
'fieldTextColor',
'fieldMenuColor',
'pageBreakColor',
'labelSize',
'labelColor',
'labelSublabelColor',
'labelErrorColor',
'buttonSize',
'buttonBorderStyle',
'buttonBorderSize',
'buttonBorderRadius',
'buttonBackgroundColor',
'buttonBorderColor',
'buttonTextColor',
'backgroundColor',
'backgroundPosition',
'backgroundUrl',
'backgroundRepeat',
'backgroundSize',
'backgroundSizeMode',
'backgroundWidth',
'backgroundHeight',
'backgroundImage',
];
},
/**
* Get style handlers.
*
* @since 1.9.7
*
* @return {Object} Style handlers.
*/
getStyleHandlers() {
return {
'background-url': WPFormsBuilderThemes.background.setBackgroundUrl,
'background-image': WPFormsBuilderThemes.background.setContainerBackgroundImage,
'background-position': WPFormsBuilderThemes.background.setContainerBackgroundPosition,
'background-repeat': WPFormsBuilderThemes.background.setContainerBackgroundRepeat,
'background-color': WPFormsBuilderThemes.background.setBackgroundColor,
'background-height': WPFormsBuilderThemes.background.handleSizeFromHeight,
'background-width': WPFormsBuilderThemes.background.handleSizeFromWidth,
};
},
/**
* Change style setting handler.
*
* @since 1.9.7
*
* @param {string} settingValue Setting value.
* @param {string} settingKey Setting key.
*/
changeStyleSettings( settingValue, settingKey ) {// eslint-disable-line complexity
const wpformsContainer = el.$preview.find( '.wpforms-container' )[ 0 ];
if ( ! wpformsContainer ) {
return;
}
// Process only styles related settings
if ( ! app.getStyleAttributesKeys().includes( settingKey ) ) {
return;
}
const settings = WPFormsBuilderThemes.getSettings();
/**
* @type {Object}
*/
const property = settingKey.replace( /[A-Z]/g, ( letter ) => `-${ letter.toLowerCase() }` );
settingValue = app.prepareComplexAttrValues( settingValue, settingKey );
// Check for custom handlers.
if ( typeof app.getStyleHandlers()[ property ] === 'function' ) {
app.getStyleHandlers()[ property ]( wpformsContainer, settingValue, settings );
return;
}
switch ( property ) {
case 'field-size':
case 'label-size':
case 'button-size':
case 'container-shadow-size':
for ( const key in wpforms_builder_themes.sizes[ property ][ settingValue ] ) {
wpformsContainer.style.setProperty(
`--wpforms-${ property }-${ key }`,
wpforms_builder_themes.sizes[ property ][ settingValue ][ key ],
);
}
break;
case 'button-background-color':
app.maybeUpdateAccentColor( settings.buttonBorderColor, settingValue, wpformsContainer );
settingValue = app.maybeSetButtonAltBackgroundColor( settingValue, settings.buttonBorderColor, wpformsContainer );
app.maybeSetButtonAltTextColor( settings.buttonTextColor, settingValue, settings.buttonBorderColor, wpformsContainer );
wpformsContainer.style.setProperty( `--wpforms-${ property }`, settingValue );
break;
case 'button-border-color':
app.maybeUpdateAccentColor( settingValue, settings.buttonBackgroundColor, wpformsContainer );
app.maybeSetButtonAltTextColor( settings.buttonTextColor, settings.buttonBackgroundColor, settingValue, wpformsContainer );
wpformsContainer.style.setProperty( `--wpforms-${ property }`, settingValue );
break;
case 'button-text-color':
app.maybeSetButtonAltTextColor( settingValue, settings.buttonBackgroundColor, settings.buttonBorderColor, wpformsContainer );
wpformsContainer.style.setProperty( `--wpforms-${ property }`, settingValue );
break;
default:
wpformsContainer.style.setProperty( `--wpforms-${ property }`, settingValue );
wpformsContainer.style.setProperty( `--wpforms-${ property }-spare`, settingValue );
}
},
/**
* Maybe update accent color.
*
* @since 1.9.7
*
* @param {string} color Color value.
* @param {string} buttonBackgroundColor Button background color.
* @param {Object} container Form container.
*/
maybeUpdateAccentColor( color, buttonBackgroundColor, container ) {
// Setting the CSS property value to the child element overrides the parent property value.
const formWrapper = container.querySelector( '#builder-themes-form-preview-wrapper' );
// Fallback to the default color if the border color is transparent.
color = WPFormsUtils.cssColorsUtils.isTransparentColor( color ) ? '#066aab' : color;
if ( WPFormsUtils.cssColorsUtils.isTransparentColor( buttonBackgroundColor ) ) {
formWrapper.style.setProperty( '--wpforms-button-background-color-alt', 'rgba( 0, 0, 0, 0 )' );
formWrapper.style.setProperty( '--wpforms-button-background-color', color );
} else {
container.style.setProperty( '--wpforms-button-background-color-alt', buttonBackgroundColor );
formWrapper.style.setProperty( '--wpforms-button-background-color-alt', null );
formWrapper.style.setProperty( '--wpforms-button-background-color', null );
}
},
/**
* Maybe set the button's alternative background color.
*
* @since 1.9.7
*
* @param {string} value Setting value.
* @param {string} buttonBorderColor Button border color.
* @param {Object} container Form container.
*
* @return {string|*} New background color.
*/
maybeSetButtonAltBackgroundColor( value, buttonBorderColor, container ) {
// Setting the CSS property value to the child element overrides the parent property value.
const formWrapper = container.querySelector( '#builder-themes-form-preview-wrapper' );
formWrapper.style.setProperty( '--wpforms-button-background-color-alt', value );
if ( WPFormsUtils.cssColorsUtils.isTransparentColor( value ) ) {
return WPFormsUtils.cssColorsUtils.isTransparentColor( buttonBorderColor ) ? '#066aab' : buttonBorderColor;
}
return value;
},
/**
* Maybe set the button's alternative text color.
*
* @since 1.9.7
*
* @param {string} value Setting value.
* @param {string} buttonBackgroundColor Button background color.
* @param {string} buttonBorderColor Button border color.
* @param {Object} container Form container.
*/
maybeSetButtonAltTextColor( value, buttonBackgroundColor, buttonBorderColor, container ) {
const formWrapper = container.querySelector( '#builder-themes-form-preview-wrapper' );
let altColor = null;
value = value.toLowerCase();
if (
WPFormsUtils.cssColorsUtils.isTransparentColor( value ) ||
value === buttonBackgroundColor ||
(
WPFormsUtils.cssColorsUtils.isTransparentColor( buttonBackgroundColor ) &&
value === buttonBorderColor
)
) {
altColor = WPFormsUtils.cssColorsUtils.getContrastColor( buttonBackgroundColor );
}
container.style.setProperty( `--wpforms-button-text-color-alt`, value );
formWrapper.style.setProperty( `--wpforms-button-text-color-alt`, altColor );
},
/**
* Prepare complex setting values.
*
* @since 1.9.7
*
* @param {string|Object} value Setting value.
* @param {string} key Attribute key.
*
* @return {string|Object} Prepared setting value.
*/
prepareComplexAttrValues( value, key ) {
const pxItems = [
'fieldBorderRadius',
'fieldBorderSize',
'buttonBorderRadius',
'buttonBorderSize',
'containerPadding',
'containerBorderWidth',
'containerBorderRadius',
'backgroundWidth',
'backgroundHeight',
];
if ( pxItems.includes( key ) ) {
if ( typeof value === 'number' || ( typeof value === 'string' && ! value.trim().endsWith( 'px' ) ) ) {
value = `${ value }px`;
}
}
if ( key === 'backgroundUrl' ) {
if ( typeof value === 'string' && ! value.trim().startsWith( 'url(' ) ) {
value = value ? `url( ${ value } )` : 'url()';
}
}
// Remove spaces after/before braces in rgb/rgba colors.
value = app.removeRgbaSpaces( value );
return value;
},
/**
* Remove extra spaces in rgba/rgb values.
*
* @since 1.9.7
*
* @param {string} value Setting value.
*
* @return {string} Prepared setting value.
*/
removeRgbaSpaces( value ) {
if ( typeof value !== 'string' || ! value.includes( 'rgb' ) ) {
return value;
}
return value
.replace( /\(\s*/g, '(' )
.replace( /\s*\)/g, ')' );
},
/**
* Render already saved settings.
*
* @since 1.9.7
*/
renderSavedSettings() {
const wpformsContainer = el.$preview.find( '.wpforms-container' )[ 0 ];
if ( ! wpformsContainer ) {
return;
}
const settings = WPFormsBuilderThemes.getSettings();
_.each( settings, ( value, key ) => {
app.changeStyleSettings( value, key );
} );
},
/**
* Custom loader for color pickers.
*
* @since 1.9.7
*/
loadColorPickers() {
WPFormsBuilder.loadColorPickers( el.$sidebar, {
position: 'top left',
} );
},
/**
* Disable PRO sections.
*
* @since 1.9.7
*/
blockProSections() {
if ( isPro && isLicenseActive ) {
return;
}
const $proSectionsHeadings = $( '.wpforms-add-fields-heading[data-group="background_styles"], .wpforms-add-fields-heading[data-group="container_styles"]' );
const proSections = $( '.wpforms-builder-themes-pro-section' );
// Disable sections and show the PRO badge.
proSections.addClass( 'wpforms-builder-themes-disabled-pro' );
$proSectionsHeadings.addClass( 'wpforms-builder-themes-pro-blocked' );
// Disable clicks on blocked sections.
proSections.off( 'click' ).on( 'click', app.handleProSectionClick );
},
/**
* Disable all sections.
*
* @since 1.9.7
* @param {boolean} unblock Need to unblock status.
*/
blockAllSections( unblock = false ) {
const sections = el.$sidebar.find( '.wpforms-add-fields-buttons, .wpforms-builder-themes-sidebar-advanced' );
// Disable/Enable all sections.
if ( ! unblock ) {
sections.addClass( 'wpforms-builder-themes-disabled' );
} else {
sections.removeClass( 'wpforms-builder-themes-disabled' );
}
},
/**
* Handle the PRO section click.
*
* @since 1.9.7
*/
handleProSectionClick() {
const section = $( this ).prev( 'a' ).data( 'group' )?.replace( '_styles', '' );
if ( ! isPro ) {
app.showProModal( section, strings.pro_sections[ section ] );
return;
}
if ( ! isLicenseActive ) {
app.showLicenseModal( strings.pro_sections[ section ], strings.pro_sections[ section ], 'pro-section' );
}
},
/**
* Open the educational popup for users with no Pro license.
*
* @since 1.9.7
*
* @param {string} panel Panel slug.
* @param {string} feature Feature name.
*/
showProModal( panel, feature ) {
const type = 'pro';
const message = wpforms_education.upgrade[ type ].message_plural.replace( /%name%/g, feature );
const utmContent = {
container: 'General Container Styles',
background: 'General Background Styles',
themes: 'General Pro Themes',
};
$.alert( {
backgroundDismiss: true,
title: feature + ' ' + wpforms_education.upgrade[ type ].title_plural,
icon: 'fa fa-lock',
content: message,
boxWidth: '550px',
theme: 'modern,wpforms-education',
closeIcon: true,
onOpenBefore: function() { // eslint-disable-line object-shorthand
this.$btnc.after( '<div class="discount-note">' + wpforms_education.upgrade_bonus + '</div>' );
this.$btnc.after( wpforms_education.upgrade[ type ].doc.replace( /%25name%25/g, 'AP - ' + feature ) );
this.$body.find( '.jconfirm-content' ).addClass( 'lite-upgrade' );
},
buttons: {
confirm: {
text: wpforms_education.upgrade[ type ].button,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action: () => {
window.open( WPFormsEducation.core.getUpgradeURL( utmContent[ panel ], type ), '_blank' );
WPFormsEducation.core.upgradeModalThankYou( type );
},
},
},
} );
},
/**
* Open license modal.
*
* @since 1.9.7
*
* @param {string} feature Feature name.
* @param {string} fieldName Field name.
* @param {string} utmContent UTM content.
*/
showLicenseModal( feature, fieldName, utmContent ) {
WPFormsEducation.proCore.licenseModal( feature, fieldName, utmContent );
},
/**
* Run custom checks.
*
* @since 1.9.7
*/
runChecks() {
app.checkForClassicStyles();
if ( isPro && isLicenseActive && isModern && isFullStyles ) {
app.checkForFormFeatures();
}
app.checkForOldFP();
},
/**
* Conditionally show/hide classic styles notice and block/unblock controls.
*
* @since 1.9.7
*/
checkForClassicStyles() {
const $notice = $( '.wpforms-builder-themes-style-notice' );
const $previewNotice = $( '.wpforms-builder-themes-preview-notice' );
if ( ! isModern || ! isFullStyles ) {
app.blockAllSections();
$notice.removeClass( 'wpforms-hidden' );
$previewNotice.addClass( 'wpforms-hidden' );
}
},
/**
* Check both Lead Forms and Conversational Forms states and update the UI accordingly.
*
* @since 1.9.7
*/
checkForFormFeatures() {
const $LFSwitch = $( '#wpforms-panel-field-lead_forms-enable' );
const $CFSwitch = $( '#wpforms-panel-field-settings-conversational_forms_enable' );
const isLFEnabled = $LFSwitch.prop( 'checked' ) ?? false;
const isCFEnabled = $CFSwitch.prop( 'checked' ) ?? false;
const $LFNotice = $( '.wpforms-builder-themes-lf-notice' );
const $CFNotice = $( '.wpforms-builder-themes-cf-notice' );
const $previewNotice = $( '.wpforms-builder-themes-preview-notice' );
// Handle Lead Forms notice visibility
if ( isLFEnabled ) {
$LFNotice.removeClass( 'wpforms-hidden' );
} else {
$LFNotice.addClass( 'wpforms-hidden' );
}
// Handle Conversational Forms notice visibility
if ( isCFEnabled ) {
$CFNotice.removeClass( 'wpforms-hidden' );
} else {
$CFNotice.addClass( 'wpforms-hidden' );
}
// If either feature is enabled, hide preview and block sections
if ( isLFEnabled || isCFEnabled ) {
$previewNotice.addClass( 'wpforms-hidden' );
el.$preview.addClass( 'wpforms-hidden' );
app.blockAllSections();
} else {
// Only if both features are disabled, show preview and unblock sections
el.$preview.removeClass( 'wpforms-hidden' );
if ( isModern && isFullStyles ) {
app.blockAllSections( true );
$previewNotice.removeClass( 'wpforms-hidden' );
}
}
// Set up event handlers if they haven't been set up yet
app.setupFormFeatureEventHandlers();
},
/**
* Set up event handlers for Lead Forms and Conversational Forms switches.
*
* @since 1.9.7
*/
setupFormFeatureEventHandlers() {
// Set up notice link handlers
$( '.wpforms-builder-themes-lf-notice a' ).off( 'click', app.openLFSettings ).on( 'click', app.openLFSettings );
$( '.wpforms-builder-themes-cf-notice a' ).off( 'click', app.openCFSettings ).on( 'click', app.openCFSettings );
},
/**
* Shows the notice if the Form Pages addons version is low.
*
* @since 1.9.7
*/
checkForOldFP() {
const $FPContent = $( '#wpforms-form-pages-content-block' );
const $notice = $( '#wpforms-page-forms-fbst-notice' );
if ( $FPContent.length ) {
if ( isLowFormPagesVersion ) {
$FPContent.prepend( $notice );
$notice.removeClass( 'wpforms-hidden' );
}
}
},
/**
* Open the Lead Forms settings page.
*
* @since 1.9.7
*
* @param {Object} event Event object.
*/
openLFSettings( event ) {
app.handleClosePreviewSidebar( event );
$( 'a.wpforms-panel-sidebar-section-lead_forms' ).click();
},
/**
* Open the Conversational Forms settings page.
*
* @since 1.9.7
*
* @param {Object} event Event object.
*/
openCFSettings( event ) {
app.handleClosePreviewSidebar( event );
$( 'a.wpforms-panel-sidebar-section-conversational_forms' ).click();
},
/**
* Determine if the user is on a Mac.
*
* @return {boolean} True if the user is on a Mac.
*/
isMac() {
return navigator.userAgent.includes( 'Macintosh' );
},
};
return app;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,356 @@
/* global wpforms_builder_themes */
/**
* WPForms Form Builder Themes: Stock Photos module.
*
* @since 1.9.7
*
* @param {Object} document Document object.
* @param {Object} window Window object.
* @param {jQuery} $ jQuery object.
*
* @return {Object} Public functions and properties.
*/
// eslint-disable-next-line max-lines-per-function
export default function( document, window, $ ) { // eslint-disable-line no-unused-vars
const WPForms = window.WPForms || {};
const WPFormsBuilderThemes = WPForms.Admin.Builder.Themes || {};
/**
* Localized data aliases.
*
* @since 1.9.7
*/
const strings = wpforms_builder_themes.strings;
const routeNamespace = wpforms_builder_themes.route_namespace;
const pictureUrlPath = wpforms_builder_themes.stockPhotos?.urlPath;
/**
* Elements holder.
*
* @since 1.9.7
*
* @type {Object}
*/
const el = {};
/**
* Spinner markup.
*
* @since 1.9.7
*
* @type {string}
*/
const spinner = '<i class="wpforms-loading-spinner wpforms-loading-white wpforms-loading-inline"></i>';
/**
* Runtime state.
*
* @since 1.9.7
*
* @type {Object}
*/
const state = {};
/**
* Stock photos pictures' list.
*
* @since 1.9.7
*
* @type {Array}
*/
let pictures = wpforms_builder_themes.stockPhotos?.pictures;
/**
* Stock photos picture selector markup.
*
* @since 1.9.7
*
* @type {string}
*/
let picturesMarkup = '';
/**
* Public functions and properties.
*
* @since 1.9.7
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.7
*/
init() {
app.setup();
app.events();
},
/**
* Setup.
*
* @since 1.9.7
*/
setup() {
el.$builder = $( '#wpforms-builder' );
},
/**
* Setup.
*
* @since 1.9.7
*/
events() {
},
/**
* Open stock photos modal.
*
* @since 1.9.7
*
* @param {string} from From where the modal was triggered, `themes` or `bg-styles`.
*/
openModal( from ) {
if ( app.isPicturesAvailable() ) {
app.picturesModal();
return;
}
app.installModal( from );
},
/**
* Open stock photos install modal on a select theme.
*
* @since 1.9.7
*
* @param {string} themeSlug The theme slug.
*/
onSelectTheme( themeSlug ) {
const themesModule = WPFormsBuilderThemes.themes;
if ( app.isPicturesAvailable() ) {
return;
}
// Check only WPForms themes.
if ( ! themesModule?.isWPFormsTheme( themeSlug ) ) {
return;
}
const theme = themesModule?.getTheme( themeSlug );
const bgUrl = theme.settings?.backgroundUrl;
if ( bgUrl?.length && bgUrl !== 'url()' ) {
app.installModal( 'themes' );
}
},
/**
* Open a modal prompting to download and install the Stock Photos.
*
* @since 1.9.7
*
* @param {string} from From where the modal was triggered, `themes` or `bg-styles`.
*/
installModal( from ) {
const installStr = from === 'themes' ? strings.stockInstallTheme : strings.stockInstallBg;
$.confirm( {
title: strings.heads_up,
content: installStr + ' ' + strings.stockInstall,
icon: 'wpforms-exclamation-circle',
type: 'orange wpforms-builder-themes-modal',
buttons: {
continue: {
text: strings.continue,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
// noinspection JSUnresolvedReference
this.$$continue.prop( 'disabled', true )
.html( spinner + strings.installing );
// noinspection JSUnresolvedReference
this.$$cancel
.prop( 'disabled', true );
app.install( this, from );
return false;
},
},
cancel: {
text: strings.cancel,
keys: [ 'esc' ],
},
},
} );
},
/**
* Display the modal window with an error message.
*
* @since 1.9.7
*
* @param {string} error Error message.
*/
errorModal( error ) {
$.alert( {
title: strings.uhoh,
content: error || strings.commonError,
icon: 'fa fa-exclamation-circle',
type: 'red',
buttons: {
cancel: {
text : strings.close,
btnClass: 'btn-confirm',
keys : [ 'enter' ],
},
},
} );
},
/**
* Display the modal window with pictures.
*
* @since 1.9.7
*/
picturesModal() {
state.picturesModal = $.alert( {
title : `${ strings.picturesTitle }<p>${ strings.picturesSubTitle }</p>`,
content: app.getPictureMarkup(),
type: 'picture-selector wpforms-builder-themes-modal',
boxWidth: '800px',
closeIcon: true,
animation: 'opacity',
closeAnimation: 'opacity',
buttons: false,
onOpen() {
this.$content
.off( 'click' )
.on( 'click', '.wpforms-builder-stock-photos-picture', app.selectPicture );
},
} );
},
/**
* Install stock photos.
*
* @since 1.9.7
*
* @param {Object} modal The jQuery-confirm a modal window object.
* @param {string} from From where the modal was triggered, `themes` or `bg-styles`.
*/
install( modal, from ) {
// If a fetch is already in progress, exit the function.
if ( state.isInstalling ) {
return;
}
// Set the flag to true indicating a fetch is in progress.
state.isInstalling = true;
try {
// Fetch themes data.
wp.apiFetch( {
path: routeNamespace + 'stock-photos/install/',
method: 'POST',
cache: 'no-cache',
} ).then( ( response ) => {
if ( ! response.result ) {
app.errorModal( response.error );
return;
}
// Store the pictures' data.
pictures = response.pictures || [];
// Update the theme or open the picture selector modal.
if ( from === 'themes' ) {
WPFormsBuilderThemes.store.set( 'backgroundUrl', 'url()' );
WPFormsBuilderThemes.themes.setFormTheme( WPFormsBuilderThemes.store.get( 'wpformsTheme' ) );
} else {
app.picturesModal();
}
} ).catch( ( error ) => {
// eslint-disable-next-line no-console
console.error( error?.message );
app.errorModal( `<p>${ strings.commonError }</p><p>${ error?.message }</p>` );
} ).finally( () => {
state.isInstalling = false;
// Close the modal window.
modal.close();
} );
} catch ( error ) {
state.isInstalling = false;
// eslint-disable-next-line no-console
console.error( error );
app.errorModal( strings.commonError + '<br>' + error );
}
},
/**
* Detect whether pictures' data available.
*
* @since 1.9.7
*
* @return {boolean} True if pictures' data available, false otherwise.
*/
isPicturesAvailable() {
return Boolean( pictures?.length );
},
/**
* Generate the pictures' selector markup.
*
* @since 1.9.7
*
* @return {string} Pictures' selector markup.
*/
getPictureMarkup() {
if ( ! app.isPicturesAvailable() ) {
return '';
}
if ( picturesMarkup !== '' ) {
return picturesMarkup;
}
pictures.forEach( ( picture ) => {
const pictureUrl = pictureUrlPath + picture;
picturesMarkup += `<div class="wpforms-builder-stock-photos-picture"
data-url="${ pictureUrl }"
style="background-image: url( '${ pictureUrl }' )"
></div>`;
} );
picturesMarkup = `<div class="wpforms-builder-stock-photos-pictures-wrap">${ picturesMarkup }</div>`;
return picturesMarkup;
},
/**
* Select picture event handler.
*
* @since 1.9.7
*/
selectPicture() {
const pictureUrl = $( this ).data( 'url' );
// Update the settings.
WPFormsBuilderThemes.store.set( 'backgroundUrl', pictureUrl );
// Close the modal window.
state.picturesModal?.close();
},
};
return app;
}
@@ -0,0 +1,4 @@
export default function(e,t,s){let r=(t.WPForms||{}).Admin.Builder.Themes||{},o=wpforms_builder_themes.strings,l=wpforms_builder_themes.route_namespace,i=wpforms_builder_themes.stockPhotos?.urlPath,n={},c={},a=wpforms_builder_themes.stockPhotos?.pictures,u="",p={init(){p.setup(),p.events()},setup(){n.$builder=s("#wpforms-builder")},events(){},openModal(e){p.isPicturesAvailable()?p.picturesModal():p.installModal(e)},onSelectTheme(e){var t=r.themes;p.isPicturesAvailable()||t?.isWPFormsTheme(e)&&(t=(t?.getTheme(e)).settings?.backgroundUrl)?.length&&"url()"!==t&&p.installModal("themes")},installModal(e){var t="themes"===e?o.stockInstallTheme:o.stockInstallBg;s.confirm({title:o.heads_up,content:t+" "+o.stockInstall,icon:"wpforms-exclamation-circle",type:"orange wpforms-builder-themes-modal",buttons:{continue:{text:o.continue,btnClass:"btn-confirm",keys:["enter"],action(){return this.$$continue.prop("disabled",!0).html('<i class="wpforms-loading-spinner wpforms-loading-white wpforms-loading-inline"></i>'+o.installing),this.$$cancel.prop("disabled",!0),p.install(this,e),!1}},cancel:{text:o.cancel,keys:["esc"]}}})},errorModal(e){s.alert({title:o.uhoh,content:e||o.commonError,icon:"fa fa-exclamation-circle",type:"red",buttons:{cancel:{text:o.close,btnClass:"btn-confirm",keys:["enter"]}}})},picturesModal(){c.picturesModal=s.alert({title:`${o.picturesTitle}<p>${o.picturesSubTitle}</p>`,content:p.getPictureMarkup(),type:"picture-selector wpforms-builder-themes-modal",boxWidth:"800px",closeIcon:!0,animation:"opacity",closeAnimation:"opacity",buttons:!1,onOpen(){this.$content.off("click").on("click",".wpforms-builder-stock-photos-picture",p.selectPicture)}})},install(e,t){if(!c.isInstalling){c.isInstalling=!0;try{wp.apiFetch({path:l+"stock-photos/install/",method:"POST",cache:"no-cache"}).then(e=>{e.result?(a=e.pictures||[],"themes"===t?(r.store.set("backgroundUrl","url()"),r.themes.setFormTheme(r.store.get("wpformsTheme"))):p.picturesModal()):p.errorModal(e.error)}).catch(e=>{console.error(e?.message),p.errorModal(`<p>${o.commonError}</p><p>${e?.message}</p>`)}).finally(()=>{c.isInstalling=!1,e.close()})}catch(e){c.isInstalling=!1,console.error(e),p.errorModal(o.commonError+"<br>"+e)}}},isPicturesAvailable(){return Boolean(a?.length)},getPictureMarkup(){return p.isPicturesAvailable()?(""===u&&(a.forEach(e=>{e=i+e;u+=`<div class="wpforms-builder-stock-photos-picture"
data-url="${e}"
style="background-image: url( '${e}' )"
></div>`}),u=`<div class="wpforms-builder-stock-photos-pictures-wrap">${u}</div>`),u):""},selectPicture(){var e=s(this).data("url");r.store.set("backgroundUrl",e),c.picturesModal?.close()}};return p}
@@ -0,0 +1,908 @@
/* global wpforms_builder_themes */
/**
* WPForms Form Builder Themes: Theme module.
*
* @since 1.9.7
*
* @param {Object} document Document object.
* @param {Object} window Window object.
* @param {jQuery} $ jQuery object.
*
* @return {Object} Public functions and properties.
*/
export default function( document, window, $ ) { // eslint-disable-line max-lines-per-function
const WPForms = window.WPForms || {};
const WPFormsBuilderThemes = WPForms.Admin.Builder.Themes || {};
/**
* Localized data aliases.
*
* @since 1.9.7
*/
const { isAdmin, isPro, isLicenseActive, strings, route_namespace: routeNamespace } = wpforms_builder_themes;
/**
* Runtime state.
*
* @since 1.9.7
*
* @type {Object}
*/
const state = {};
/**
* Themes data.
*
* @since 1.9.7
*
* @type {Object}
*/
const themesData = {
wpforms: null,
custom: null,
};
/**
* Enabled themes.
*
* @since 1.9.7
*
* @type {Object}
*/
let enabledThemes = null;
/**
* Elements holder.
*
* @since 1.9.7
*
* @type {Object}
*/
const el = {};
/**
* Public functions and properties.
*
* @since 1.9.7
*/
const app = {
/**
* Start the engine.
*
* @since 1.9.7
*/
init() {
app.fetchThemesData();
app.setup();
app.events();
},
/**
* Setup.
*
* @since 1.9.7
*/
setup() {
el.$builder = $( '#wpforms-builder' );
el.$themesControl = el.$builder.find( '.wpforms-builder-themes-control' );
el.$customThemeRenamer = el.$builder.find( '#wpforms-panel-field-themes-themeName-wrap' );
el.$customThemeRemover = el.$builder.find( '#wpforms-builder-themer-remove-theme' );
el.$window = $( window );
},
/**
* Setup.
*
* @since 1.9.7
*/
events() {
el.$window.on( 'wpformsBuilderThemesDataLoaded', app.themesControlSetup );
el.$builder.on( 'wpformsSaved', app.saveCustomThemes );
},
/**
* Set up the Themes Select control.
*
* @since 1.9.7
*/
themesControlSetup() {
// Debounce custom themes update and creation.
const debouncedMaybeCreate = _.debounce( ( key ) => {
app.maybeCreateCustomTheme();
app.maybeUpdateCustomTheme( key );
}, 300 );
// Listen for all settings changes.
WPFormsBuilderThemes.store.subscribeAll( ( value, key ) => {
const allowedKeys = WPFormsBuilderThemes.common.getStyleAttributesKeys();
if ( ! allowedKeys.includes( key ) ) {
return;
}
debouncedMaybeCreate( key );
} );
// Listen for the theme name change.
WPFormsBuilderThemes.store.subscribe( 'themeName', ( value ) => {
app.changeThemeName( value );
app.updateThemesList();
} );
// Listen for the isCustomTheme setting change.
WPFormsBuilderThemes.store.subscribe( 'isCustomTheme', () => {
app.toggleCustomThemeSettings();
} );
// Check if the selected theme exists. If no, create a new one.
app.maybeCreateCustomTheme();
app.toggleCustomThemeSettings();
app.updateThemesList();
},
/**
* Update themes list.
*
* @since 1.9.7
*/
updateThemesList() {
const selectedTheme = WPFormsBuilderThemes.store.get( 'wpformsTheme' ) ?? 'default';
// Get all themes.
const html = app.getThemesListMarkup( selectedTheme );
el.$themesControl.html( html );
app.addThemesEvents();
},
/**
* Get the Themes control markup.
*
* @since 1.9.7
*
* @param {string} selectedTheme Selected theme slug.
*
* @return {string} Themes items HTML.
*/
getThemesListMarkup( selectedTheme ) {
if ( ! themesData.wpforms ) {
app.fetchThemesData();
// Return markup with an error message if themes are not available.
return `<div class="wpforms-no-themes">${ strings.themes_error }</div>`;
}
const allThemes = app.getAllThemes();
if ( ! allThemes ) {
return '';
}
const themes = Object.keys( allThemes );
let theme, firstThemeSlug;
let html = '';
let itemsHtml = '';
if ( ! app.isWPFormsTheme( selectedTheme ) ) {
firstThemeSlug = selectedTheme;
itemsHtml += app.getThemesItemMarkup( app.getTheme( firstThemeSlug ), firstThemeSlug, firstThemeSlug );
}
for ( const key in themes ) {
const slug = themes[ key ];
// Skip the first theme.
if ( firstThemeSlug && firstThemeSlug === slug ) {
continue;
}
// Ensure that all the theme settings are present.
theme = { ...allThemes.default, ...( allThemes[ slug ] || {} ) };
theme.settings = { ...allThemes.default.settings, ...( theme.settings || {} ) };
itemsHtml += app.getThemesItemMarkup( theme, slug, selectedTheme );
}
html = `<div role="radiogroup" class="wpforms-builder-themes-radio-group">
${ itemsHtml }
</div>`;
return html;
},
/**
* Get the theme item markup.
*
* @since 1.9.7
*
* @param {Object} theme Theme data.
* @param {string} slug Theme slug.
* @param {string} selectedTheme Selected theme slug.
*
* @return {string} Theme item HTML.
*/
getThemesItemMarkup( theme, slug, selectedTheme ) {
if ( ! theme ) {
return '';
}
const title = theme.name?.length > 0 ? theme.name : strings.theme_noname;
let radioClasses = 'wpforms-builder-themes-radio ';
const buttonClass = slug === selectedTheme ? 'is-active' : '';
radioClasses += app.isDisabledTheme( slug ) ? 'wpforms-builder-themes-radio-disabled' : ' wpforms-builder-themes-radio-enabled';
return `<button type="button" class="${ buttonClass }" value="${ slug }" role="radio">
<div class="wpforms-builder-themes-radio ${ radioClasses }">
<div class="wpforms-builder-themes-radio-title">${ title }</div>
</div>
<div class="wpforms-builder-themes-indicators">
<span class="component-color-indicator" title="${ strings.button_background }" style="background: ${ theme.settings.buttonBackgroundColor };" data-index="0"></span>
<span class="component-color-indicator" title="${ strings.button_text }" style="background: ${ theme.settings.buttonTextColor }" data-index="1"></span>
<span class="component-color-indicator" title="${ strings.field_label }" style="background: ${ theme.settings.labelColor };" data-index="2"></span>
<span class="component-color-indicator" title="${ strings.field_sublabel } " style="background: ${ theme.settings.labelSublabelColor };" data-index="3"></span>
<span class="component-color-indicator" title="${ strings.field_border }" style="background: ${ theme.settings.fieldBorderColor };" data-index="4"></span>
</div>
</button>`;
},
/**
* Show or hide the custom theme rename input.
*
* @since 1.9.7
*/
toggleCustomThemeSettings() {
if ( ! isAdmin ) {
return;
}
const value = WPFormsBuilderThemes.store.get( 'isCustomTheme' ) ?? '';
const shouldShow = value === 'true';
el.$customThemeRenamer.toggleClass( 'wpforms-hidden', ! shouldShow );
el.$customThemeRemover.toggleClass( 'wpforms-hidden', ! shouldShow );
},
/**
* On settings change event handler.
*
* @since 1.9.7
*/
addThemesEvents() {
const $radioButtons = el.$themesControl.find( '[role="radio"]' );
// Add event listeners to the radio buttons.
$radioButtons.off( 'click' ).on( 'click', function() {
$radioButtons.removeClass( 'is-active' );
$( this ).addClass( 'is-active' );
const selectedValue = $( this ).val();
app.selectTheme( selectedValue );
} );
// Add event listeners to the theme delete button.
el.$customThemeRemover
.off( 'click' )
.on( 'click', app.deleteThemeModal );
},
/**
* Select theme event handler.
*
* @since 1.9.7
*
* @param {string} value New attribute value.
*/
selectTheme( value ) {
if ( ! app.setFormTheme( value ) ) {
return;
}
app.onSelectThemeWithBG( value );
},
/**
* Set the form theme.
*
* @since 1.9.7
*
* @param {string} themeSlug The theme slug.
*
* @return {boolean} True on success.
*/
setFormTheme( themeSlug ) {
if ( app.maybeDisplayUpgradeModal( themeSlug ) ) {
return false;
}
const theme = app.getTheme( themeSlug );
if ( ! theme?.settings ) {
return false;
}
const attributes = Object.keys( theme.settings );
const isCustomTheme = !! themesData.custom[ themeSlug ];
// Set the theme settings.
WPFormsBuilderThemes.store.set( 'wpformsTheme', themeSlug );
WPFormsBuilderThemes.store.set( 'isCustomTheme', isCustomTheme ? 'true' : '' );
WPFormsBuilderThemes.store.set( 'themeName', isCustomTheme ? themesData.custom[ themeSlug ].name : '' );
// Clean up the settings.
const cleanSettings = {};
for ( const key in attributes ) {
const attr = attributes[ key ];
const value = theme.settings[ attr ];
cleanSettings[ attr ] = typeof value === 'string'
? value.replace( /px$/, '' )
: value;
}
// Update the theme settings.
app.updateStylesAtts( cleanSettings );
//Reinit color pickers.
WPFormsBuilderThemes.common.loadColorPickers();
return true;
},
/**
* Open stock photos install modal on the select theme.
*
* @since 1.9.7
*
* @param {string} themeSlug The theme slug.
*/
onSelectThemeWithBG( themeSlug ) {
if ( WPFormsBuilderThemes.stockPhotos.isPicturesAvailable() ) {
return;
}
// Check only WPForms themes.
if ( ! app.isWPFormsTheme( themeSlug ) ) {
return;
}
/**
* @type {Object|null}
* @property {Object|null} settings Settings.
*/
const theme = app.getTheme( themeSlug );
const bgUrl = theme.settings?.backgroundUrl;
if ( bgUrl?.length && bgUrl !== 'url()' ) {
WPFormsBuilderThemes.stockPhotos.installModal( 'themes' );
}
},
/**
* Update styles atts.
*
* @since 1.9.7
*
* @param {Object} themeSettings Theme settings.
*/
updateStylesAtts( themeSettings ) {
const allowedKeys = WPFormsBuilderThemes.common.getStyleAttributesKeys();
const validSettings = {};
for ( const key in themeSettings ) {
if ( ! allowedKeys.includes( key ) ) {
continue;
}
let value = themeSettings[ key ];
if ( key === 'backgroundUrl' && typeof value === 'string' ) {
value = app.getBackgroundUrl( value );
}
validSettings[ key ] = value;
}
// Update the settings.
if ( Object.keys( validSettings ).length ) {
Object.entries( validSettings ).forEach( ( [ key, value ] ) => {
WPFormsBuilderThemes.store.set( key, value );
} );
}
},
/**
* Extract the background URL from the string.
*
* @since 1.9.7
*
* @param {string} value Background value.
*
* @return {string} Extracted background image url.
*/
getBackgroundUrl( value ) {
const match = value.match( /^url\(\s*['"]?(.*?)['"]?\s*\)$/i );
return match?.[ 1 ] || 'url()';
},
/**
* Get all themes data.
*
* @since 1.9.7
*
* @return {Object} Themes data.
*/
getAllThemes() {
return { ...( themesData.custom || {} ), ...( themesData.wpforms || {} ) };
},
/**
* Get theme data.
*
* @since 1.9.7
*
* @param {string} slug Theme slug.
*
* @return {Object|null} Theme settings.
*/
getTheme( slug ) {
return app.getAllThemes()[ slug ] || null;
},
/**
* Get enabled themes data.
*
* @since 1.9.7
*
* @return {Object} Themes data.
*/
getEnabledThemes() {
if ( enabledThemes ) {
return enabledThemes;
}
const allThemes = app.getAllThemes();
if ( isPro && isLicenseActive ) {
return allThemes;
}
enabledThemes = Object.keys( allThemes ).reduce( ( acc, key ) => {
if ( allThemes[ key ].settings?.fieldSize && ! allThemes[ key ].disabled ) {
acc[ key ] = allThemes[ key ];
}
return acc;
}, {} );
return enabledThemes;
},
/**
* Update enabled themes.
*
* @since 1.9.7
*
* @param {string} slug Theme slug.
* @param {Object} theme Theme settings.
*/
updateEnabledThemes( slug, theme ) {
if ( ! enabledThemes ) {
return;
}
enabledThemes = {
...enabledThemes,
[ slug ]: theme,
};
},
/**
* Whether the theme is disabled.
*
* @since 1.9.7
*
* @param {string} slug Theme slug.
*
* @return {boolean} True if the theme is disabled.
*/
isDisabledTheme( slug ) {
return ! app.getEnabledThemes()?.[ slug ];
},
/**
* Whether the theme is one of the WPForms themes.
*
* @since 1.9.7
*
* @param {string} slug Theme slug.
*
* @return {boolean} True if the theme is one of the WPForms themes.
*/
isWPFormsTheme( slug ) {
return Boolean( themesData.wpforms[ slug ]?.settings );
},
/**
* Fetch themes data from Rest API.
*
* @since 1.9.7
*/
fetchThemesData() {
// If a fetch is already in progress, exit the function.
if ( state.isFetchingThemes || themesData.wpforms ) {
return;
}
// Set the flag to true indicating a fetch is in progress.
state.isFetchingThemes = true;
try {
// Fetch themes data.
wp.apiFetch( {
path: routeNamespace + 'themes/',
method: 'GET',
cache: 'no-cache',
} )
.then( ( response ) => {
themesData.wpforms = response.wpforms || {};
themesData.custom = response.custom || {};
el.$window.trigger( 'wpformsBuilderThemesDataLoaded' );
} )
.catch( ( error ) => {
// eslint-disable-next-line no-console
console.error( error?.message );
} )
.finally( () => {
state.isFetchingThemes = false;
} );
} catch ( error ) {
// eslint-disable-next-line no-console
console.error( error );
}
},
/**
* Save custom themes.
*
* @since 1.9.7
*/
saveCustomThemes() {
// Custom themes do not exist.
if ( state.isSavingThemes || ! themesData.custom || ! isAdmin ) {
return;
}
// Set the flag to true indicating a saving is in progress.
state.isSavingThemes = true;
try {
// Save themes.
wp.apiFetch( {
path: routeNamespace + 'themes/custom/',
method: 'POST',
data: { customThemes: themesData.custom },
} )
.then( ( response ) => {
if ( ! response?.result ) {
// eslint-disable-next-line no-console
console.log( response?.error );
}
} )
.catch( ( error ) => {
// eslint-disable-next-line no-console
console.error( error?.message );
} )
.finally( () => {
state.isSavingThemes = false;
} );
} catch ( error ) {
// eslint-disable-next-line no-console
console.error( error );
}
},
/**
* Get the current style attributes state.
*
* @since 1.9.7
*
* @param {Object} settings Settings.
*
* @return {Array} Current style attributes.
*/
getCurrentStyleAttributes( settings ) {
const defaultAttributes = Object.keys( themesData.wpforms.default?.settings );
const currentStyleAttributes = {};
for ( const key in defaultAttributes ) {
const attr = defaultAttributes[ key ];
currentStyleAttributes[ attr ] = WPFormsBuilderThemes.common.prepareComplexAttrValues( settings[ attr ], attr ) ?? '';
}
return currentStyleAttributes;
},
/**
* Maybe create a custom theme.
*
* @since 1.9.7
*
*
* @return {boolean} Whether the custom theme is created.
*/
maybeCreateCustomTheme() {
const settings = WPFormsBuilderThemes.getSettings();
const currentStyles = app.getCurrentStyleAttributes( settings );
const isWPFormsTheme = !! themesData.wpforms[ settings.wpformsTheme ];
const isCustomTheme = !! themesData.custom[ settings.wpformsTheme ];
// It is one of the default themes without any changes.
if (
isWPFormsTheme &&
app.getPreparedDefaultThemeSettings( themesData.wpforms[ settings.wpformsTheme ]?.settings ) === JSON.stringify( currentStyles )
) {
return false;
}
// It is a modified default theme OR unknown custom theme.
if ( isWPFormsTheme || ! isCustomTheme ) {
app.createCustomTheme( settings, currentStyles );
}
return true;
},
/**
* Prepare default theme settings for comparing.
*
* @since 1.9.7
*
* @param {Object} settings Theme properties.
*
* @return {string} Whether the custom theme is created.
*/
getPreparedDefaultThemeSettings( settings ) {
const preparedSettings = {};
Object.keys( settings ).forEach( ( key ) => {
preparedSettings[ key ] = WPFormsBuilderThemes.common.removeRgbaSpaces( settings[ key ] );
} );
return JSON.stringify( preparedSettings );
},
/**
* Create a custom theme.
*
* @since 1.9.7
*
* @param {Object} settings Style settings.
* @param {Object} currentStyles Current style settings.
*
* @return {boolean} Whether the custom theme is created.
*/
createCustomTheme( settings, currentStyles = null ) {
let counter = 0;
let themeSlug = settings.wpformsTheme;
const baseTheme = app.getTheme( settings.wpformsTheme ) || themesData.wpforms.default;
let themeName = baseTheme.name;
themesData.custom = themesData.custom || {};
// Determine the theme slug and the number of copies.
do {
counter++;
themeSlug = themeSlug + '-copy-' + counter;
} while ( themesData.custom[ themeSlug ] && counter < 10000 );
const copyStr = counter < 2 ? strings.theme_copy : strings.theme_copy + ' ' + counter;
themeName += ' (' + copyStr + ')';
// Add the new custom theme.
themesData.custom[ themeSlug ] = {
name: themeName,
settings: currentStyles || app.getCurrentStyleAttributes( settings ),
};
app.updateEnabledThemes( themeSlug, themesData.custom[ themeSlug ] );
// Update the settings with the new custom theme settings.
WPFormsBuilderThemes.store.set( 'wpformsTheme', themeSlug );
WPFormsBuilderThemes.store.set( 'isCustomTheme', 'true' );
WPFormsBuilderThemes.store.set( 'themeName', themeName );
app.updateThemesList();
return true;
},
/**
* Update custom theme.
*
* @since 1.9.7
*
* @param {string} attribute Attribute name.
* @param {string} value New attribute value.
*/
updateCustomThemeAttribute( attribute, value ) {
const settings = WPFormsBuilderThemes.getSettings();
const themeSlug = settings.wpformsTheme;
// Skip if it is one of the WPForms themes OR the attribute is not in the theme settings.
if (
themesData.wpforms[ themeSlug ] ||
(
attribute !== 'themeName' &&
! themesData.wpforms.default.settings[ attribute ]
)
) {
return;
}
// Skip if the custom theme doesn't exist in some rare cases.
if ( ! themesData.custom[ themeSlug ] ) {
return;
}
// Update the theme data.
if ( attribute === 'themeName' ) {
themesData.custom[ themeSlug ].name = value;
} else {
themesData.custom[ themeSlug ].settings = themesData.custom[ themeSlug ].settings || themesData.wpforms.default.settings;
themesData.custom[ themeSlug ].settings[ attribute ] = value;
app.maybeUpdateColorIndicator( attribute, value );
}
},
/**
* Maybe update the custom theme settings.
*
* @param {string} key Setting key.
*
* @since 1.9.7
*/
maybeUpdateCustomTheme( key ) {
const settings = WPFormsBuilderThemes.getSettings();
const isCustomTheme = settings.isCustomTheme === 'true';
if ( ! isCustomTheme ) {
return;
}
const attrValue = WPFormsBuilderThemes.common.prepareComplexAttrValues( settings[ key ], key );
app.updateCustomThemeAttribute( key, attrValue );
},
/**
* Maybe update the color indicators for the custom theme.
*
* @param {string} settingKey Setting key.
* @param {string} settingValue Setting value.
*
* @since 1.9.7
*/
maybeUpdateColorIndicator( settingKey, settingValue ) {
const colorSettingKeys = [ 'buttonBackgroundColor', 'buttonTextColor', 'labelColor', 'labelSublabelColor', 'fieldBorderColor' ];
if ( ! colorSettingKeys.includes( settingKey ) ) {
return;
}
const $indicators = el.$themesControl.find( 'button.is-active .wpforms-builder-themes-indicators' );
const indicatorIndex = colorSettingKeys.indexOf( settingKey );
const $indicator = $indicators.find( `.component-color-indicator[data-index="${ indicatorIndex }"]` );
if ( $indicator.length ) {
$indicator.css( 'background-color', settingValue );
}
},
/**
* Maybe display upgrades modal in Lite.
*
* @since 1.9.7
*
* @param {string} themeSlug The theme slug.
*
* @return {boolean} True if modal was displayed.
*/
maybeDisplayUpgradeModal( themeSlug ) {
if ( ! app.isDisabledTheme( themeSlug ) ) {
return false;
}
if ( ! isPro ) {
WPFormsBuilderThemes.common.showProModal( 'themes', strings.pro_sections.themes );
return true;
}
if ( ! isLicenseActive ) {
WPFormsBuilderThemes.common.showLicenseModal( 'themes', strings.pro_sections.themes, 'select-theme' );
return true;
}
return false;
},
/**
* Change theme name event handler.
*
* @since 1.9.7
*
* @param {string} value New attribute value.
*/
changeThemeName( value ) {
app.updateCustomThemeAttribute( 'themeName', value );
},
/**
* Delete theme event handler.
*
* @param {string} deleteThemeSlug Theme slug.
*
* @since 1.9.7
*/
deleteTheme( deleteThemeSlug ) {
// Remove theme from the theme storage.
delete themesData.custom[ deleteThemeSlug ];
},
/**
* Open the theme delete the confirmation modal window.
*
* @since 1.9.7
*
* @param {Object} e Event object.
*/
deleteThemeModal( e ) {
e.preventDefault();
const settings = WPFormsBuilderThemes.getSettings();
const selectedThemeSlug = settings.wpformsTheme;
const selectedThemeName = app.getTheme( selectedThemeSlug )?.name;
const confirm = strings.theme_delete_confirm.replace( '%1$s', `<b>${ _.escape( selectedThemeName ) }</b>` );
const content = `<p class="wpforms-theme-delete-text">${ confirm } ${ strings.theme_delete_cant_undone }</p>`;
$.confirm( {
title: strings.theme_delete_title,
content,
icon: 'wpforms-exclamation-circle',
type: 'red wpforms-builder-themes-modal',
buttons: {
confirm: {
text: strings.theme_delete_yes,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
// Delete the theme and switch to the default theme.
app.deleteTheme( selectedThemeSlug );
app.selectTheme( 'default' );
},
},
cancel: {
text: strings.cancel,
keys: [ 'esc' ],
},
},
} );
},
};
return app;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,282 @@
/* global wpforms_builder, Choices, wpf */
/**
* @param wpforms_builder.no_pages_found
*/
// noinspection ES6ConvertVarToLetConst
/**
* WPForms ChoicesJS utility methods for the Admin Builder.
*
* @since 1.7.9
*/
var WPForms = window.WPForms || {}; // eslint-disable-line no-var
WPForms.Admin = WPForms.Admin || {};
WPForms.Admin.Builder = WPForms.Admin.Builder || {};
WPForms.Admin.Builder.WPFormsChoicesJS = WPForms.Admin.Builder.WPFormsChoicesJS || ( function( document, window, $ ) {
/**
* Public functions and properties.
*
* @since 1.7.9
*
* @type {Object}
*/
const app = {
/**
* Set up the Select Page ChoicesJS instance.
*
* @since 1.7.9
*
* @param {Object} element DOM Element where to init ChoicesJS.
* @param {Object} choicesJSArgs ChoicesJS init options.
* @param {Object} ajaxArgs Object containing `action` and `nonce` to perform AJAX search.
*
* @return {Choices} ChoicesJS instance.
*/
setup( element, choicesJSArgs, ajaxArgs ) {
let $element = $( element );
let choicesJS = $element.data( 'choicesjs' );
// Destroy existing choicesJS instance.
if ( choicesJS ) {
choicesJS.destroy();
}
// Remove choicesJS elements from the DOM for cloned instances.
if ( $element.hasClass( 'choices__input' ) ) {
const $choices = $element.closest( '.choices' );
const $select = $element.detach()
.removeClass( 'choices__input' )
.data( 'choice', null )
.attr( 'data-choice', null );
$choices.replaceWith( $select );
$element = $choices.prevObject;
element = $element[ 0 ];
}
choicesJSArgs.searchEnabled = true;
choicesJSArgs.allowHTML = false; // TODO: Remove after next Choices.js release.
choicesJSArgs.searchChoices = ajaxArgs.nonce === null; // Enable searchChoices when not using AJAX.
choicesJSArgs.renderChoiceLimit = -1;
choicesJSArgs.noChoicesText = choicesJSArgs.noChoicesText || wpforms_builder.no_pages_found;
choicesJSArgs.noResultsText = choicesJSArgs.noResultsText || wpforms_builder.no_pages_found;
choicesJS = new Choices( element, choicesJSArgs );
if ( ajaxArgs.nonce === null ) {
return choicesJS;
}
$element.data( 'choicesjs', choicesJS );
app.setupEvents( $element, choicesJS, ajaxArgs );
return choicesJS;
},
/**
* Setup ChoicesJS events.
*
* @since 1.9.8.3
*
* @param {Object} $element jQuery element where to init ChoicesJS.
* @param {Object} choicesJS ChoicesJS instance.
* @param {Object} ajaxArgs Object containing `action` and `nonce` to perform AJAX search.
*/
setupEvents( $element, choicesJS, ajaxArgs ) {
const containerOuter = choicesJS.containerOuter?.element || $element.closest( '.choices' )[ 0 ];
app.setupSearchEvents( $element, choicesJS, ajaxArgs );
choicesJS.passedElement.element.addEventListener( 'change', function() {
const select = $( this ),
isMultiple = select.prop( 'multiple' );
if ( ! isMultiple ) {
return;
}
const fieldId = select.data( 'field-id' ),
fieldName = select.data( 'field-name' ),
value = choicesJS.getValue();
const selected = value.map( function( item ) {
return item.value;
} );
const $hidden = $( `#wpforms-field-${ fieldId }-${ fieldName }-select-multiple-options` );
$hidden.val( JSON.stringify( selected ) );
} );
// Add the ability to close the drop-down menu.
containerOuter?.addEventListener( 'click', function() {
if ( $( this ).hasClass( 'is-open' ) ) {
choicesJS.hideDropdown();
}
} );
// Show more button for choices after the group is toggled.
$( document )
.on( 'wpformsFieldOptionGroupToggled', function() {
wpf.showMoreButtonForChoices( containerOuter );
} )
.on( 'wpformsBeforeFieldDuplicate', function( event, id ) {
if ( $element.data( 'field-id' ) !== id ) {
return;
}
const choices = choicesJS.getValue( true );
$element.data( 'choicesjs' ).destroy();
$element.find( 'option' ).each( function( index, option ) {
if ( choices.includes( $( option ).val() ) ) {
$( option ).prop( 'selected', true );
}
} );
} )
.on( 'wpformsFieldDuplicated', function( event, id ) {
if ( $element.data( 'field-id' ) !== id ) {
return;
}
$element.data( 'choicesjs' ).init();
} );
},
/**
* Setup ChoicesJS search events.
*
* @since 1.9.8.3
*
* @param {Object} $element jQuery element where to init ChoicesJS.
* @param {Object} choicesJS ChoicesJS instance.
* @param {Object} ajaxArgs Object containing `action` and `nonce` to perform AJAX search.
*/
setupSearchEvents( $element, choicesJS, ajaxArgs ) {
const searchInput = choicesJS.input?.element || $element.nextAll( '.choices__input ' )[ 0 ];
/*
* ChoicesJS doesn't handle empty string search with it's `search` event handler,
* so we work around it by detecting empty string search with the ` keyup ` event.
*/
searchInput?.addEventListener( 'keyup', function( ev ) {
// Only capture backspace and delete keypress that results to empty string.
if (
( ev.which !== 8 && ev.which !== 46 ) ||
ev.target.value.length > 0
) {
return;
}
app.performSearch( choicesJS, '', ajaxArgs );
} );
choicesJS.passedElement?.element.addEventListener( 'search', _.debounce( function( ev ) {
// Make sure that the search term is actually changed.
if ( choicesJS.input.element.value.length === 0 ) {
return;
}
app.performSearch( choicesJS, ev.detail.value, ajaxArgs );
}, 800 ) );
},
/**
* Perform search in ChoicesJS instance.
*
* @since 1.7.9
*
* @param {Choices} choicesJS ChoicesJS instance.
* @param {string} searchTerm Search term.
* @param {Object} ajaxArgs Object containing `action` and `nonce` to perform AJAX search.
*/
performSearch( choicesJS, searchTerm, ajaxArgs ) {
if ( ! ajaxArgs.action || ! ajaxArgs.nonce ) {
return;
}
app.displayLoading( choicesJS );
const requestSearchChoices = app.ajaxSearch( ajaxArgs.action, searchTerm, ajaxArgs.nonce, choicesJS.getValue( true ) );
requestSearchChoices.done( function( response ) {
choicesJS.setChoices( response.data, 'value', 'label', true );
} );
},
/**
* Display "Loading" in the ChoicesJS instance.
*
* @since 1.7.9
*
* @param {Choices} choicesJS ChoicesJS instance.
*/
displayLoading( choicesJS ) {
choicesJS.setChoices(
[
{ value: '', label: `${ wpforms_builder.loading }...`, disabled: true },
],
'value',
'label',
true
);
},
/**
* Perform AJAX search request.
*
* @since 1.7.9
* @deprecated 1.9.4 Use `ajaxSearch` instead.
*
* @param {string} action Action to be used when doing ajax request for search.
* @param {string} searchTerm Search term.
* @param {string} nonce Nonce to be used when doing ajax request.
*
* @return {Promise} jQuery ajax call promise.
*/
ajaxSearchPages( action, searchTerm, nonce ) {
// eslint-disable-next-line no-console
console.warn( 'WPForms.Admin.Builder.WPFormsChoicesJS.ajaxSearchPages is deprecated. Use WPForms.Admin.Builder.WPFormsChoicesJS.ajaxSearch instead.' );
return app.ajaxSearch( action, searchTerm, nonce );
},
/**
* Perform AJAX search request.
*
* @since 1.9.4
*
* @param {string} action Action to be used when doing ajax request for search.
* @param {string} searchTerm Search term.
* @param {string} nonce Nonce to be used when doing ajax request.
* @param {Array} exclude Array of values to exclude from search results.
*
* @return {Promise} jQuery ajax call promise.
*/
ajaxSearch( action, searchTerm, nonce, exclude = [] ) {
const args = {
action,
search: searchTerm,
_wpnonce: nonce,
exclude,
};
return $.get(
wpforms_builder.ajax_url,
args
).fail(
function( err ) {
console.error( err ); // eslint-disable-line no-console
}
);
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
@@ -0,0 +1 @@
var WPForms=window.WPForms||{};WPForms.Admin=WPForms.Admin||{},WPForms.Admin.Builder=WPForms.Admin.Builder||{},WPForms.Admin.Builder.WPFormsChoicesJS=WPForms.Admin.Builder.WPFormsChoicesJS||((o,r)=>{let c={setup(e,n,o){let i=r(e),a=i.data("choicesjs");var t,s;return a&&a.destroy(),i.hasClass("choices__input")&&(t=i.closest(".choices"),s=i.detach().removeClass("choices__input").data("choice",null).attr("data-choice",null),t.replaceWith(s),e=(i=t.prevObject)[0]),n.searchEnabled=!0,n.allowHTML=!1,n.searchChoices=null===o.nonce,n.renderChoiceLimit=-1,n.noChoicesText=n.noChoicesText||wpforms_builder.no_pages_found,n.noResultsText=n.noResultsText||wpforms_builder.no_pages_found,a=new Choices(e,n),null!==o.nonce&&(i.data("choicesjs",a),c.setupEvents(i,a,o)),a},setupEvents(i,a,e){let n=a.containerOuter?.element||i.closest(".choices")[0];c.setupSearchEvents(i,a,e),a.passedElement.element.addEventListener("change",function(){var e,n,o=r(this);o.prop("multiple")&&(e=o.data("field-id"),o=o.data("field-name"),n=a.getValue().map(function(e){return e.value}),r(`#wpforms-field-${e}-${o}-select-multiple-options`).val(JSON.stringify(n)))}),n?.addEventListener("click",function(){r(this).hasClass("is-open")&&a.hideDropdown()}),r(o).on("wpformsFieldOptionGroupToggled",function(){wpf.showMoreButtonForChoices(n)}).on("wpformsBeforeFieldDuplicate",function(e,n){if(i.data("field-id")===n){let o=a.getValue(!0);i.data("choicesjs").destroy(),i.find("option").each(function(e,n){o.includes(r(n).val())&&r(n).prop("selected",!0)})}}).on("wpformsFieldDuplicated",function(e,n){i.data("field-id")===n&&i.data("choicesjs").init()})},setupSearchEvents(e,n,o){(n.input?.element||e.nextAll(".choices__input ")[0])?.addEventListener("keyup",function(e){8!==e.which&&46!==e.which||0<e.target.value.length||c.performSearch(n,"",o)}),n.passedElement?.element.addEventListener("search",_.debounce(function(e){0!==n.input.element.value.length&&c.performSearch(n,e.detail.value,o)},800))},performSearch(n,e,o){o.action&&o.nonce&&(c.displayLoading(n),c.ajaxSearch(o.action,e,o.nonce,n.getValue(!0)).done(function(e){n.setChoices(e.data,"value","label",!0)}))},displayLoading(e){e.setChoices([{value:"",label:wpforms_builder.loading+"...",disabled:!0}],"value","label",!0)},ajaxSearchPages(e,n,o){return console.warn("WPForms.Admin.Builder.WPFormsChoicesJS.ajaxSearchPages is deprecated. Use WPForms.Admin.Builder.WPFormsChoicesJS.ajaxSearch instead."),c.ajaxSearch(e,n,o)},ajaxSearch(e,n,o,i=[]){return r.get(wpforms_builder.ajax_url,{action:e,search:n,_wpnonce:o,exclude:i}).fail(function(e){console.error(e)})}};return c})(document,(window,jQuery));