Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions classes/controllers/FrmFieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ public static function load_field() {
wp_die();
}

$_GET['page'] = 'formidable';
$fields = self::get_builder_fields_by_id( $form_id );
$values = array(
$_GET['page'] = 'formidable';
$fields = self::get_builder_fields_by_id( $form_id );
$values = array(
'id' => $form_id,
'doing_ajax' => true,
);
$field_html = array();
$field_html = array();
$defer_settings = FrmAppHelper::get_post_param( 'defer_settings', 0, 'absint' );

foreach ( $field_ids as $field_id ) {
if ( ! isset( $fields[ $field_id ] ) ) {
Expand All @@ -47,6 +48,16 @@ public static function load_field() {
}

$field = $fields[ $field_id ];
unset( $values['deferred_settings'] );

// Specialized and add-on fields keep their existing AJAX initialization contract.
if ( $defer_settings && in_array(
$field->type,
array( 'text', 'textarea', 'email', 'url', 'password', 'number', 'phone', 'date', 'time', 'checkbox', 'radio', 'select', 'hidden', 'html' ),
true
) ) {
$values['deferred_settings'] = (object) array( 'html' => '' );
}

ob_start();
self::load_single_field( $field, $values );
Expand All @@ -57,6 +68,13 @@ public static function load_field() {
'type' => $field->type,
'html' => ob_get_clean(),
);

if ( ! isset( $values['deferred_settings'] ) ) {
continue;
}

$field_html[ $field_id ]['settingsHtml'] = $values['deferred_settings']->html;
$field_html[ $field_id ]['settingsMeta'] = $values['deferred_settings']->meta;
}//end foreach

// admin_footer never fires here, so the deferred tooltip text rides along with the html.
Expand Down
16 changes: 16 additions & 0 deletions classes/views/frm-forms/add_field.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,24 @@
<?php
}//end if

if ( isset( $values['deferred_settings'] ) ) {
ob_start();
}

FrmFieldsController::load_single_field_settings( compact( 'field', 'field_obj', 'values', 'display' ) );

if ( isset( $values['deferred_settings'] ) ) {
$values['deferred_settings']->html = ob_get_clean();
$values['deferred_settings']->meta = array(
'name' => $field['name'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

'type' => $field['type'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

'key' => $field['field_key'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

'order' => $field['field_order'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

'classes' => $display['css'] ? $field['classes'] : null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $display might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $display might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $display might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $field might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $display might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

'align' => $field['align'] ?? null,
);
}

if ( 'divider' === $field['type'] ) {
?>
</div>
Expand Down
2 changes: 1 addition & 1 deletion js/formidable_admin.js

Large diffs are not rendered by default.

79 changes: 71 additions & 8 deletions js/src/admin/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { initShowBoxIconSwap } = require( './showBoxIconSwap' );
const { hydrateBuilderSelect, hydrateBuilderSelectsIn } = require( './sharedSelectOptions' );
const { processFieldLoadBatch } = require( './fieldLoadBatch' );
const { hydrateFieldPlaceholders } = require( './fieldPlaceholders' );
const { addFieldSettingsMetadata, cacheFieldSettings, forgetFieldSettings, materializeFieldSettings, materializeAllFieldSettings } = require( './deferredFieldSettings' );

// Footer scripts can restore placeholders before add-ons inspect the builder's fields.
hydrateFieldPlaceholders();
Expand Down Expand Up @@ -2019,13 +2020,14 @@ window.frmAdminBuildJS = function() {
}

moveFieldSettings( document.getElementById( `frm-single-settings-${ fieldId }` ) );
const layoutClassesInput = document.getElementById( `frm_classes_${ fieldId }` );
let layoutClassesInput = document.getElementById( `frm_classes_${ fieldId }` );

if ( ! layoutClassesInput ) {
// not every field type has a layout class input.
return;
}

const previousClasses = layoutClassesInput.value;
if ( false === activeLayoutClass ) {
if ( '' !== currentClassToAdd ) {
layoutClassesInput.value = layoutClassesInput.value.concat( ` ${ currentClassToAdd }` );
Expand All @@ -2045,6 +2047,13 @@ window.frmAdminBuildJS = function() {
layoutClassesInput.value = layoutClassesInput.value.concat( ' frm_first' );
}

if ( layoutClassesInput.closest( '.frm-deferred-settings-meta' ) ) {
if ( previousClasses === layoutClassesInput.value ) {
return;
}
moveFieldSettings( ensureFieldSettings( fieldId ) );
layoutClassesInput = document.getElementById( `frm_classes_${ fieldId }` );
}
jQuery( layoutClassesInput ).trigger( 'change' );
};
}
Expand Down Expand Up @@ -2778,6 +2787,7 @@ window.frmAdminBuildJS = function() {
// Only acknowledge received definitions so concurrent batches remain independent.
known_tooltips: Object.keys( frm_admin_js.tooltips || {} ).join( ',' ),
known_select_options: Object.keys( frm_admin_js.selectOptions || {} ).join( ',' ),
defer_settings: 1,
nonce: frmGlobal.nonce
},
success: async response => {
Expand Down Expand Up @@ -2880,20 +2890,60 @@ window.frmAdminBuildJS = function() {
placeholderSpinnerObserver?.unobserve( oldField );
dragDropObserver.unobserve( oldField );
dragDropAttachers.delete( oldField );
cacheFieldSettings( key, field.settingsHtml );
jQuery( oldField ).replaceWith( field.html );

const element = document.getElementById( `frm_field_id_${ key }` );
if ( ! element ) {
return null;
}
element.querySelectorAll( '[data-toggle]' ).forEach( toggle => toggle.setAttribute( 'data-bs-toggle', toggle.getAttribute( 'data-toggle' ) ) );
element.querySelectorAll( '.frm-dropdown-menu' ).forEach( dropdownMenu => dropdownMenu.classList.add( 'dropdown-menu' ) );
element.querySelectorAll( '[data-tip-key]' ).forEach( resolveDeferredTooltip );
if ( field.settingsMeta ) {
addFieldSettingsMetadata( element, key, field.settingsMeta );
}
prepareLoadedFieldMarkup( element );
setupSortable( `#frm_field_id_${ key }.edit_field_type_divider ul.frm_sorting` );
lazyMakeDraggable( element );
return { id: key, type: field.type, element };
}

/**
* Prepare deferred settings before selection or another interaction reads their inputs.
*
* @since x.x
* @param {string} fieldId Numeric field ID.
* @return {HTMLElement|null} The field settings panel, if the field has loaded.
*/
function ensureFieldSettings( fieldId ) {
return materializeFieldSettings( fieldId, prepareLoadedFieldMarkup );
}

/**
* Resolve shared markup attributes for a field preview or its newly inserted settings.
*
* @since x.x
* @param {HTMLElement} element The inserted preview or settings panel.
* @return {void}
*/
function prepareLoadedFieldMarkup( element ) {
element.querySelectorAll( '[data-toggle]' ).forEach( toggle => toggle.setAttribute( 'data-bs-toggle', toggle.getAttribute( 'data-toggle' ) ) );
element.querySelectorAll( '.frm-dropdown-menu' ).forEach( dropdownMenu => dropdownMenu.classList.add( 'dropdown-menu' ) );
element.querySelectorAll( '[data-tip-key]' ).forEach( resolveDeferredTooltip );
}

/**
* Prepare settings before delegated preview handlers read or modify them.
*
* @since x.x
* @param {Event} event The preview interaction.
* @return {void}
*/
function prepareInteractedFieldSettings( event ) {
const field = event.target.closest( '#frm-show-fields li.form-field' );
if ( field ) {
ensureFieldSettings( field.dataset.fid );
}
}

/**
* Initialize only the fields inserted in this slice before yielding to user input.
*
Expand Down Expand Up @@ -3229,7 +3279,7 @@ window.frmAdminBuildJS = function() {
}

function maybeDuplicateUnsavedSettings( originalFieldId, newFieldHtml ) {
const originalSettings = document.getElementById( `frm-single-settings-${ originalFieldId }` );
const originalSettings = ensureFieldSettings( originalFieldId );
if ( ! originalSettings ) {
return;
}
Expand Down Expand Up @@ -3845,6 +3895,7 @@ window.frmAdminBuildJS = function() {
}

function hasExcludedOption( field, excludedOpts ) {
ensureFieldSettings( field.fieldId );
let hasOption = false;
for ( let i = 0; i < excludedOpts.length; i++ ) {
const inputs = document.getElementsByName( getFieldOptionInputName( excludedOpts[ i ], field.fieldId ) );
Expand Down Expand Up @@ -4820,6 +4871,7 @@ window.frmAdminBuildJS = function() {
* Delete a field option.
*/
function deleteFieldOption() {
materializeAllFieldSettings( prepareLoadedFieldMarkup );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, verified live: deleting a single option on a loaded field runs materializeAllFieldSettings() and inserts every pending panel. On the 37-field test form that took the settings count from 13 to 39 (about 160 ms). On a 150-field form most of the deferral is lost the first time anyone edits or deletes a choice. The same call is added at line 6437 and in adjustConditionalLogicOptionOrders (line 7132).

Only fields that have logic rows pointing at the changed field need their panel. A cheap guard would be to skip the call unless some pending panel could contain a .frm_logic_row, or to track which field IDs have logic rows in the preview metadata.

const parentLi = this.parentNode;
const parentUl = parentLi.parentNode;

Expand Down Expand Up @@ -6071,6 +6123,7 @@ window.frmAdminBuildJS = function() {
nonce: frmGlobal.nonce
},
success() {
forgetFieldSettings( fieldId );
const $thisField = jQuery( document.getElementById( `frm_field_id_${ fieldId }` ) );
const settings = jQuery( `#frm-single-settings-${ fieldId }` );

Expand Down Expand Up @@ -6381,6 +6434,7 @@ window.frmAdminBuildJS = function() {
optionMap[ fieldId ][ originalValue ].value = newValue;
}

materializeAllFieldSettings( prepareLoadedFieldMarkup );
const fieldIds = [];
const rows = builderPage.querySelectorAll( '.frm_logic_row' );
const rowLength = rows.length;
Expand Down Expand Up @@ -7075,6 +7129,7 @@ window.frmAdminBuildJS = function() {
}

function adjustConditionalLogicOptionOrders( fieldId, type ) {
materializeAllFieldSettings( prepareLoadedFieldMarkup );
const rows = builderPage.querySelectorAll( '.frm_logic_row' );
const rowLength = rows.length;

Expand Down Expand Up @@ -7132,6 +7187,7 @@ window.frmAdminBuildJS = function() {
}

function getFieldOptions( fieldId ) {
ensureFieldSettings( fieldId );
const options = [];
const optsContainer = document.getElementById( `frm_field_${ fieldId }_opts` );

Expand Down Expand Up @@ -7206,6 +7262,7 @@ window.frmAdminBuildJS = function() {
return;
}
const { sourceID } = atts;
ensureFieldSettings( sourceID );
const { placeholder } = atts;
const isProduct = isProductField( sourceID );
const showOther = atts.other;
Expand Down Expand Up @@ -7260,6 +7317,7 @@ window.frmAdminBuildJS = function() {
* @param {boolean} showValueAsLabel Whether to show the value as label for empty labels.
*/
function getMultipleOpts( fieldId, showValueAsLabel = false ) {
ensureFieldSettings( fieldId );
let i;
let saved;
let labelName;
Expand Down Expand Up @@ -7632,12 +7690,12 @@ window.frmAdminBuildJS = function() {
field = self.getFieldOrderInputById( fieldId, fields[ i ] );

// get current field order, make sure we don't get the "field" reference as the "field" value will get updated later.
currentOrder = field ? Object.assign( {}, field.value )[ 0 ] : null;
currentOrder = field ? Number( field.value ) : null;
newOrder = i + 1;

if ( currentOrder != newOrder && null !== currentOrder ) {
field.value = newOrder;
singleField = fields[ i ].querySelector( `#frm-single-settings-${ fieldId }` );
singleField = ensureFieldSettings( fieldId );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, source-read (I could not drive a drag live): ensureFieldSettings( fieldId ) here runs for every field whose index shifted. Dropping or inserting a field near the top of a long form shifts almost every field after it, so one drag materializes all of them in one synchronous loop. Only the order input has to reach the save form, and the metadata field_order_<id> input already carries it. Moving that input into the form, and leaving the panel deferred, would keep reorder cheap.


// add field that needs to be moved to "updateFieldOrder.prototype.fieldSettingsForm"
moveFieldsClass.append( singleField );
Expand Down Expand Up @@ -8746,7 +8804,10 @@ window.frmAdminBuildJS = function() {
allFieldSettings[ i ].classList.add( 'frm_hidden' );
}

const singleField = document.getElementById( `frm-single-settings-${ fieldId }` );
const singleField = ensureFieldSettings( fieldId );
if ( ! singleField ) {
return;
}
hydrateBuilderSelectsIn( singleField );
moveFieldSettings( singleField );

Expand Down Expand Up @@ -11859,6 +11920,8 @@ window.frmAdminBuildJS = function() {

buildInit() {
hydrateFieldPlaceholders();
document.addEventListener( 'click', prepareInteractedFieldSettings, true );
document.addEventListener( 'focusin', prepareInteractedFieldSettings, true );

document.addEventListener( 'focusin', event => {
if ( event.target.matches( 'select[data-frm-options]' ) ) {
Expand Down
Loading
Loading