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
2 changes: 1 addition & 1 deletion js/formidable_admin.js

Large diffs are not rendered by default.

23 changes: 22 additions & 1 deletion js/src/admin/admin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
/* exported frm_add_logic_row, frm_remove_tag, frm_show_div, frmCheckAll, frmCheckAllLevel */

/**
* Internal dependencies
*/
const { validateField } = require( './settings/validateField' );
const { getRangeSettingsDefaults, validateNumberRangeSetting, validateStepSetting, validateRangeSettings } = require( './settings/validateRangeSettings' );

window.FrmFormsConnect = window.FrmFormsConnect || ( function( document, window, $ ) {
const el = {
messageBox: null,
Expand Down Expand Up @@ -8793,7 +8799,9 @@ window.frmAdminBuildJS = function() {
* @return {void}
*/
function handleBuilderChangeEvent( event ) {
maybeShowSaveAndReloadModal( event.target );
const target = event.target;
maybeShowSaveAndReloadModal( target );
validateRangeSettings( target );
}

/**
Expand Down Expand Up @@ -11487,6 +11495,19 @@ window.frmAdminBuildJS = function() {
}
},

/**
* @since x.x
*/
settings: {
validate: {
validateField,
getRangeSettingsDefaults,
validateNumberRangeSetting,
validateStepSetting,
validateRangeSettings,
},
},

applyZebraStriping,
initModal,
infoModal,
Expand Down
30 changes: 30 additions & 0 deletions js/src/admin/settings/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Gets the field ID from the single settings element or the closest single settings element to the field.
*
* @since x.x
*
* @param {HTMLElement} singleSettings The single settings element.
* @param {HTMLElement} field The field element to get field ID from.
*
* @return {string|undefined} The field ID or undefined if not found.
*/
export const getFieldId = ( singleSettings = null, field = null ) =>
singleSettings ? singleSettings.dataset.fid : field?.closest( '.frm-single-settings' )?.dataset.fid;

/**
* Gets the field type from the single settings element.
*
* @since x.x
*
* @param {HTMLElement} singleSettings The single settings element.
* @param {HTMLElement} field The field element.
*
* @return {string|undefined} The field type or undefined if not found.
*/
export const getFieldType = ( singleSettings = null, field = null ) => {
if ( ! singleSettings ) {
singleSettings = field?.closest( '.frm-single-settings' );
}

return singleSettings?.className.match( /frm-type-(\w+)/ )?.[ 1 ];
};
44 changes: 44 additions & 0 deletions js/src/admin/settings/validateField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Runs validation and handles UI feedback.
*
* @since x.x
*
* @param {HTMLElement} field The field element being validated.
* @param {Function} getError Function that returns error message or empty string.
*
* @return {string} The error message or empty string.
*/
export function validateField( field, getError ) {
const errorMessage = getError();
if ( errorMessage ) {
frmAdminBuild.infoModal( errorMessage );
field.classList.add( 'frm_invalid_field' );
focusFieldOnModalDismiss( field );
} else {
field.classList.remove( 'frm_invalid_field' );
}

return errorMessage;
}
Comment thread
shervElmi marked this conversation as resolved.

/**
* Returns focus to the invalid field once the info modal is dismissed.
*
* @since x.x
*
* @param {HTMLElement} field The invalid field element.
*
* @return {void}
*/
function focusFieldOnModalDismiss( field ) {
const dismissers = document.querySelectorAll(
'#frm_info_modal .dismiss, #frm_info_modal #frm-info-click, .ui-widget-overlay.ui-front'
);

function onModalClose() {
setTimeout( () => field.focus(), 0 );
dismissers.forEach( el => el.removeEventListener( 'click', onModalClose ) );
}

dismissers.forEach( el => el.addEventListener( 'click', onModalClose ) );
}
158 changes: 158 additions & 0 deletions js/src/admin/settings/validateRangeSettings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { validateField } from './validateField';
import { getFieldId, getFieldType } from './utils';

/**
* Gets the default values for range settings validation.
*
* @since x.x
*
* @param {HTMLElement} singleSettings The single settings element.
*
* @return {Object} The defaults object with maxNum, minNum, and step.
*/
export function getRangeSettingsDefaults( singleSettings ) {
const fieldType = getFieldType( singleSettings ) || 'number';
const defaultSettings = {
maxNum: 9999999,
minNum: 0,
step: 1
};

/**
* Filters the default values for range settings validation.
*
* @since x.x
*
* @param {Object} defaultSettings The default settings.
* @param {Object} context Additional context.
* @param {HTMLElement} context.singleSettings The single settings element.
* @param {string} context.fieldType The field type.
*
* @return {Object} The filtered default settings.
*/
return wp.hooks.applyFilters( 'frm_range_settings_defaults', defaultSettings, { singleSettings, fieldType } );
Comment thread
shervElmi marked this conversation as resolved.
}

/**
* Validates number range setting.
*
* @since x.x
*
* @param {HTMLElement} field The field element being validated.
*/
export function validateNumberRangeSetting( field ) {
if ( ! field.closest( '.frm-number-range' ) ) {
return;
}

const singleSettings = field.closest( '.frm-single-settings' );
const fieldId = getFieldId( singleSettings );
if ( ! fieldId ) {
return;
}

const minValueInput = document.querySelector( `[name="field_options[minnum_${ fieldId }]"]` );
if ( ! minValueInput ) {
return;
}

const maxValueInput = document.querySelector( `[name="field_options[maxnum_${ fieldId }]"]` );
if ( ! maxValueInput ) {
return;
}

return validateField( field, () => {
const { minNum, maxNum } = getRangeSettingsDefaults( singleSettings );

return parseFloat( minValueInput.value || minNum ) >= parseFloat( maxValueInput.value || maxNum )
? __( 'Minimum value cannot be greater than or equal to maximum value.', 'formidable' )
: '';
} );
Comment on lines +72 to +78
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Function 'validateNumberRangeSetting' expected no return value


Any code paths that do not have explicit returns will return undefined. It is recommended to replace any implicit dead-ends that return undefined with a return null statement.

}

/**
* Validates step setting.
*
* @since x.x
*
* @param {HTMLElement} field The field element being validated.
*/
export function validateStepSetting( field ) {
if ( ! field.closest( '.frm-step' ) ) {
return;
}

const singleSettings = field.closest( '.frm-single-settings' );
const fieldId = getFieldId( singleSettings );
if ( ! fieldId ) {
return;
}

const stepInput = document.querySelector( `[name="field_options[step_${ fieldId }]"]` );
if ( ! stepInput ) {
return;
}

return validateField( field, () => {
const { step, minNum, maxNum } = getRangeSettingsDefaults( singleSettings );
const stepInputValue = parseFloat( stepInput.value || step );
if ( stepInputValue <= 0 ) {
return __( 'Step value must be greater than 0.', 'formidable' );
}

const maxValueInput = document.querySelector( `[name="field_options[maxnum_${ fieldId }]"]` );
if ( ! maxValueInput ) {
return '';
}

const minValueInput = document.querySelector( `[name="field_options[minnum_${ fieldId }]"]` );
const minValue = parseFloat( minValueInput?.value || minNum );
const maxValue = parseFloat( maxValueInput.value || maxNum );

return stepInputValue > maxValue - minValue
? __( 'Step value cannot be greater than the difference between the minimum and maximum values.', 'formidable' )
: '';
} );
Comment on lines +104 to +123
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Function 'validateStepSetting' expected no return value


Any code paths that do not have explicit returns will return undefined. It is recommended to replace any implicit dead-ends that return undefined with a return null statement.

Comment on lines +104 to +123
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Function 'validateStepSetting' expected no return value


Any code paths that do not have explicit returns will return undefined. It is recommended to replace any implicit dead-ends that return undefined with a return null statement.

}

/**
* Validates all range related settings for a field and lets add-ons extend the validation.
*
* This is the single entry point used by the builder change handler. It runs
* the number range and step checks, then exposes the `frm_validate_range_settings`
* filter so add-ons (like Pro) can contribute additional checks (e.g. gap range)
* without the core needing to know anything about them.
*
* @since x.x
*
* @param {HTMLElement} field The field element being validated.
*
* @return {string} The error message, or empty string when valid.
*/
export function validateRangeSettings( field ) {
let errorMessage = validateNumberRangeSetting( field );
if ( ! errorMessage ) {
errorMessage = validateStepSetting( field );
}

/**
* Filters the range settings validation result so add-ons can add their own checks.
*
* @since x.x
*
* @param {string} errorMessage The current error message, or empty string when valid.
* @param {Object} context Additional context.
* @param {HTMLElement} context.field The field element being validated.
*
* @return {string} The (possibly updated) error message.
*/
return wp.hooks.applyFilters( 'frm_validate_range_settings', errorMessage || '', { field } );
}
Loading