From cafe85fdb5cfcb74ab085ed35450a817bbe6337a Mon Sep 17 00:00:00 2001 From: "vivi-the-going-merry[bot]" <308115520+vivi-the-going-merry[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 05:17:32 -0600 Subject: [PATCH 1/4] Show form-level AJAX errors like spam instead of submitting the page An AJAX error with no field container, such as 'spam' or 'form', left contSubmit true. The CAPTCHAs were reset and the form was submitted as a full page, so the CAPTCHA field failed first with 'The captcha is missing'. Show the error in the summary and stop. Co-Authored-By: Claude Sonnet 5.5 --- js/formidable.js | 38 +++++++++++++++++++++ js/formidable.min.js | 6 ++-- tests/cypress/e2e/Forms/ajaxSpamError.cy.js | 32 +++++++++++++++++ 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 tests/cypress/e2e/Forms/ajaxSpamError.cy.js diff --git a/js/formidable.js b/js/formidable.js index e8f8f6c8a0..fa3afd3b4d 100644 --- a/js/formidable.js +++ b/js/formidable.js @@ -1284,11 +1284,19 @@ function frmFrontFormJS() { removeAllErrors(); let $fieldCont = null; + const formErrors = {}; for ( const key in response.errors ) { const fieldContEl = object.querySelector( `#frm_field_${ key }_container` ); $fieldCont = fieldContEl ? jQuery( fieldContEl ) : jQuery(); + if ( ! fieldContEl && isFormLevelErrorKey( key ) ) { + // An error like 'spam' has no field to show on, and a page submit would only lose the message. + formErrors[ key ] = response.errors[ key ]; + contSubmit = false; + continue; + } + if ( $fieldCont.length ) { if ( ! $fieldCont.is( ':visible' ) ) { // eslint-disable-line no-jquery/no-is const inCollapsedSection = $fieldCont.closest( '.frm_toggle_container' ); // eslint-disable-line no-jquery/no-closest, formidable/no-jquery-variable-methods @@ -1340,6 +1348,7 @@ function frmFrontFormJS() { object.submit(); } else { object.insertAdjacentHTML( 'afterbegin', response.error_message ); + addFormLevelErrors( object, formErrors ); checkForErrorsAndMaybeSetFocus(); } } else if ( ! willRedirect ) { // Avoid double submission if redirecting to a page. @@ -1361,6 +1370,35 @@ function frmFrontFormJS() { postToAjaxUrl( object, data, success, error ); } + /** + * Check if an AJAX error key is for the whole form, like 'spam' or 'form', rather than a field. + * + * @param {string} key + * @return {boolean} True when the key is not a field ID. + */ + function isFormLevelErrorKey( key ) { + return ! /^\d/.test( key ); + } + + /** + * Show errors that belong to the whole form in the error summary at the top of the form. + * + * @param {HTMLElement} form + * @param {Object} formErrors + * @return {void} + */ + function addFormLevelErrors( form, formErrors ) { + const summary = form.querySelector( '[data-frm-error-summary]' ); + + if ( ! summary ) { + return; + } + + Object.keys( formErrors ).forEach( key => { + summary.insertAdjacentHTML( 'beforeend', `
${ formErrors[ key ] }
` ); + } ); + } + function postToAjaxUrl( form, data, success, error ) { let ajaxUrl = frm_js.ajax_url; const action = form.getAttribute( 'action' ); diff --git a/js/formidable.min.js b/js/formidable.min.js index 147e56fc04..ceb95c0c77 100644 --- a/js/formidable.min.js +++ b/js/formidable.min.js @@ -39,10 +39,10 @@ response=>{jQuery(document).trigger("frmBeforeFormRedirect",[object,response]);i response.replace(/^\s+|\s+$/g,"");if(response.indexOf("{")===0)response=JSON.parse(response);else response=defaultResponse}let willRedirect=false;if(response.redirect!==undefined){if(shouldTriggerEvent){triggerCustomEvent(object,"frmSubmitEvent");return}if(response.delay)setTimeout(function(){doRedirect(response)},1E3*response.delay);else doRedirect(response);willRedirect=true}if("string"===typeof response.content&&response.content!==""){if(shouldTriggerEvent){triggerCustomEvent(object,"frmSubmitEvent", {content:response.content});return}removeSubmitLoading(jQuery(object));if(frm_js.offset!=-1)frmFrontForm.scrollMsg(jQuery(object),false);const formIdInput=object.querySelector('input[name="form_id"]');const formID=formIdInput?formIdInput.value:"";response.content=response.content.replace(/ frm_pro_form /g," frm_pro_form frm_no_hide ");const replaceContent=jQuery(object).closest(".frm_forms");removeAddedScripts(replaceContent,formID);const delay=maybeSlideOut(replaceContent,response.content);setTimeout(function(){afterFormSubmittedBeforeReplace(object, response);const insertedContent=jQuery(jQuery.parseHTML(response.content));replaceContent.replaceWith(insertedContent);focusFormMessage(insertedContent);addUrlParam(response);if(typeof frmThemeOverride_frmAfterSubmit==="function"){const pageOrderInput=document.querySelector(`input[name="frm_page_order_${formID}"]`);const pageOrder=pageOrderInput?pageOrderInput.value:"";const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formReturnedInput=tempDiv.querySelector('input[name="form_id"]'); -const formReturned=formReturnedInput?formReturnedInput.value:"";frmThemeOverride_frmAfterSubmit(formReturned,pageOrder,response.content,object)}afterFormSubmitted(object,response)},delay)}else if(response.errors!==undefined&&Object.keys(response.errors).length){removeSubmitLoading(jQuery(object),"enable");let contSubmit=true;removeAllErrors();let $fieldCont=null;for(const key in response.errors){const fieldContEl=object.querySelector(`#frm_field_${key}_container`);$fieldCont=fieldContEl?jQuery(fieldContEl): -jQuery();if($fieldCont.length){if(!$fieldCont.is(":visible")){const inCollapsedSection=$fieldCont.closest(".frm_toggle_container");if(inCollapsedSection.length){let frmTrigger=inCollapsedSection.prev();if(!frmTrigger.hasClass("frm_trigger"))frmTrigger=frmTrigger.prev(".frm_trigger");frmTrigger.trigger("click")}}if($fieldCont.is(":visible")){addFieldError($fieldCont,key,response.errors);contSubmit=false}}}object.querySelectorAll(".frm-g-recaptcha, .g-recaptcha, .h-captcha").forEach(function(captchaEl){const recaptchaID= +const formReturned=formReturnedInput?formReturnedInput.value:"";frmThemeOverride_frmAfterSubmit(formReturned,pageOrder,response.content,object)}afterFormSubmitted(object,response)},delay)}else if(response.errors!==undefined&&Object.keys(response.errors).length){removeSubmitLoading(jQuery(object),"enable");let contSubmit=true;removeAllErrors();let $fieldCont=null;const formErrors={};for(const key in response.errors){const fieldContEl=object.querySelector(`#frm_field_${key}_container`);$fieldCont=fieldContEl?jQuery(fieldContEl): +jQuery();if(!fieldContEl&&isFormLevelErrorKey(key)){formErrors[key]=response.errors[key];contSubmit=false;continue}if($fieldCont.length){if(!$fieldCont.is(":visible")){const inCollapsedSection=$fieldCont.closest(".frm_toggle_container");if(inCollapsedSection.length){let frmTrigger=inCollapsedSection.prev();if(!frmTrigger.hasClass("frm_trigger"))frmTrigger=frmTrigger.prev(".frm_trigger");frmTrigger.trigger("click")}}if($fieldCont.is(":visible")){addFieldError($fieldCont,key,response.errors);contSubmit=false}}}object.querySelectorAll(".frm-g-recaptcha, .g-recaptcha, .h-captcha").forEach(function(captchaEl){const recaptchaID= captchaEl.dataset.rid;if(typeof grecaptcha!=="undefined"&&grecaptcha)if(recaptchaID)grecaptcha.reset(recaptchaID);else grecaptcha.reset();if(typeof hcaptcha!=="undefined"&&hcaptcha)hcaptcha.reset()});if(window.turnstile)object.querySelectorAll(".frm-cf-turnstile").forEach(turnstileField=>turnstileField.dataset.rid&&turnstile.reset(turnstileField.dataset.rid));jQuery(document).trigger("frmFormErrors",[object,response]);fieldsets.forEach(field=>field.classList.remove("frm_doing_ajax"));scrollToFirstField(object); -if(contSubmit)object.submit();else{object.insertAdjacentHTML("afterbegin",response.error_message);checkForErrorsAndMaybeSetFocus()}}else if(!willRedirect){showFileLoading(object);object.submit()}};const error=function(){object.querySelectorAll('input[type="submit"], input[type="button"]').forEach(button=>button.disabled=false);object.submit()};postToAjaxUrl(object,data,success,error)}function postToAjaxUrl(form,data,success,error){let ajaxUrl=frm_js.ajax_url;const action=form.getAttribute("action"); +if(contSubmit)object.submit();else{object.insertAdjacentHTML("afterbegin",response.error_message);addFormLevelErrors(object,formErrors);checkForErrorsAndMaybeSetFocus()}}else if(!willRedirect){showFileLoading(object);object.submit()}};const error=function(){object.querySelectorAll('input[type="submit"], input[type="button"]').forEach(button=>button.disabled=false);object.submit()};postToAjaxUrl(object,data,success,error)}function isFormLevelErrorKey(key){return!/^\d/.test(key)}function addFormLevelErrors(form,formErrors){const summary=form.querySelector("[data-frm-error-summary]");if(!summary)return;Object.keys(formErrors).forEach(key=>{summary.insertAdjacentHTML("beforeend",`
${formErrors[key]}
`)})}function postToAjaxUrl(form,data,success,error){let ajaxUrl=frm_js.ajax_url;const action=form.getAttribute("action"); if("string"===typeof action&&action.includes("?action=frm_forms_preview"))ajaxUrl=action.split("?action=frm_forms_preview")[0];const ajaxParams={type:"POST",url:ajaxUrl,data,success};if("function"===typeof error)ajaxParams.error=error;jQuery.ajax(ajaxParams)}function focusFormMessage(insertedContent){let message;for(const node of insertedContent.get()){if(node.nodeType!==1)continue;message=node.matches(".frm_message")?node:node.querySelector(".frm_message");if(message)break}if(message)focusInput(message)} function afterFormSubmitted(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message");if(formCompleted)jQuery(document).trigger("frmFormComplete",[object,response]);else jQuery(document).trigger("frmPageChanged",[object,response])}function afterFormSubmittedBeforeReplace(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message"); if(formCompleted)triggerCustomEvent(document,"frmFormCompleteBeforeReplace",{object,response})}function removeAddedScripts(formContainer,formID){const endReplace=document.querySelectorAll(`.frm_end_ajax_${formID}`);if(endReplace.length){formContainer.nextUntil(`.frm_end_ajax_${formID}`).remove();endReplace.forEach(el=>el.remove())}}function maybeSlideOut(oldContent,newContent){let c;let newClass="frm_slideout";if(newContent.includes(" frm_slide")){c=oldContent.children();if(newContent.includes(" frm_going_back"))newClass+= diff --git a/tests/cypress/e2e/Forms/ajaxSpamError.cy.js b/tests/cypress/e2e/Forms/ajaxSpamError.cy.js new file mode 100644 index 0000000000..2bcac687ea --- /dev/null +++ b/tests/cypress/e2e/Forms/ajaxSpamError.cy.js @@ -0,0 +1,32 @@ +describe( 'AJAX form shows the spam message', () => { + beforeEach( cy.login ); + + it( 'shows a spam error in the form instead of submitting the page again', () => { + cy.visit( '/wp-admin/admin.php?page=formidable-form-templates' ); + cy.get( '#frm-form-templates-create-form' ).click(); + cy.get( '#text' ).should( 'contain.text', 'Text' ).click(); + cy.get( 'a[href*="/wp-admin/admin.php?page=formidable&frm_action=settings&id="]' ).click(); + + cy.get( '#form_id' ).invoke( 'val' ).then( formId => { + cy.visit( `wp-admin/admin.php?page=formidable&frm_action=settings&id=${ formId }` ); + cy.get( '#frm_form_name' ).type( 'Spam error form' ); + cy.get( '#ajax_submit' ).check(); + cy.get( '#frm_submit_side_top' ).click(); + cy.get( '.frm_updated_message' ).should( 'contain.text', 'Settings Successfully Updated' ); + + cy.get( '#frm_form_key' ).invoke( 'val' ).then( formKey => { + cy.visit( `/wp-admin/admin-ajax.php?action=frm_forms_preview&form=${ formKey }` ); + + // A full page submit would reload the page and drop this marker. + cy.window().then( win => { + win.frmNoReload = true; + } ); + cy.get( 'input.frm_verify' ).invoke( 'val', 'bot' ); + cy.get( '.frm_button_submit' ).click(); + + cy.get( '.frm_error_style' ).should( 'contain.text', 'Your entry appears to be spam!' ); + cy.window().its( 'frmNoReload' ).should( 'be.true' ); + } ); + } ); + } ); +} ); From 19b75782e5910e494d489efafa5748206d4b9eb6 Mon Sep 17 00:00:00 2001 From: "vivi-the-going-merry[bot]" <308115520+vivi-the-going-merry[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 05:57:42 -0600 Subject: [PATCH 2/4] Scroll to and focus the error summary for form-level AJAX errors Co-Authored-By: Claude Sonnet 5.5 --- js/formidable.js | 10 ++++++++++ js/formidable.min.js | 2 +- tests/cypress/e2e/Forms/ajaxSpamError.cy.js | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/js/formidable.js b/js/formidable.js index fa3afd3b4d..c1059b1238 100644 --- a/js/formidable.js +++ b/js/formidable.js @@ -1349,6 +1349,16 @@ function frmFrontFormJS() { } else { object.insertAdjacentHTML( 'afterbegin', response.error_message ); addFormLevelErrors( object, formErrors ); + + if ( Object.keys( formErrors ).length ) { + const errorSummary = object.querySelector( '[data-frm-error-summary]' ); + + if ( errorSummary ) { + errorSummary.scrollIntoView( { behavior: 'smooth', block: 'center' } ); + errorSummary.focus(); + } + } + checkForErrorsAndMaybeSetFocus(); } } else if ( ! willRedirect ) { // Avoid double submission if redirecting to a page. diff --git a/js/formidable.min.js b/js/formidable.min.js index ceb95c0c77..3e63dcf4e1 100644 --- a/js/formidable.min.js +++ b/js/formidable.min.js @@ -42,7 +42,7 @@ response);const insertedContent=jQuery(jQuery.parseHTML(response.content));repla const formReturned=formReturnedInput?formReturnedInput.value:"";frmThemeOverride_frmAfterSubmit(formReturned,pageOrder,response.content,object)}afterFormSubmitted(object,response)},delay)}else if(response.errors!==undefined&&Object.keys(response.errors).length){removeSubmitLoading(jQuery(object),"enable");let contSubmit=true;removeAllErrors();let $fieldCont=null;const formErrors={};for(const key in response.errors){const fieldContEl=object.querySelector(`#frm_field_${key}_container`);$fieldCont=fieldContEl?jQuery(fieldContEl): jQuery();if(!fieldContEl&&isFormLevelErrorKey(key)){formErrors[key]=response.errors[key];contSubmit=false;continue}if($fieldCont.length){if(!$fieldCont.is(":visible")){const inCollapsedSection=$fieldCont.closest(".frm_toggle_container");if(inCollapsedSection.length){let frmTrigger=inCollapsedSection.prev();if(!frmTrigger.hasClass("frm_trigger"))frmTrigger=frmTrigger.prev(".frm_trigger");frmTrigger.trigger("click")}}if($fieldCont.is(":visible")){addFieldError($fieldCont,key,response.errors);contSubmit=false}}}object.querySelectorAll(".frm-g-recaptcha, .g-recaptcha, .h-captcha").forEach(function(captchaEl){const recaptchaID= captchaEl.dataset.rid;if(typeof grecaptcha!=="undefined"&&grecaptcha)if(recaptchaID)grecaptcha.reset(recaptchaID);else grecaptcha.reset();if(typeof hcaptcha!=="undefined"&&hcaptcha)hcaptcha.reset()});if(window.turnstile)object.querySelectorAll(".frm-cf-turnstile").forEach(turnstileField=>turnstileField.dataset.rid&&turnstile.reset(turnstileField.dataset.rid));jQuery(document).trigger("frmFormErrors",[object,response]);fieldsets.forEach(field=>field.classList.remove("frm_doing_ajax"));scrollToFirstField(object); -if(contSubmit)object.submit();else{object.insertAdjacentHTML("afterbegin",response.error_message);addFormLevelErrors(object,formErrors);checkForErrorsAndMaybeSetFocus()}}else if(!willRedirect){showFileLoading(object);object.submit()}};const error=function(){object.querySelectorAll('input[type="submit"], input[type="button"]').forEach(button=>button.disabled=false);object.submit()};postToAjaxUrl(object,data,success,error)}function isFormLevelErrorKey(key){return!/^\d/.test(key)}function addFormLevelErrors(form,formErrors){const summary=form.querySelector("[data-frm-error-summary]");if(!summary)return;Object.keys(formErrors).forEach(key=>{summary.insertAdjacentHTML("beforeend",`
${formErrors[key]}
`)})}function postToAjaxUrl(form,data,success,error){let ajaxUrl=frm_js.ajax_url;const action=form.getAttribute("action"); +if(contSubmit)object.submit();else{object.insertAdjacentHTML("afterbegin",response.error_message);addFormLevelErrors(object,formErrors);if(Object.keys(formErrors).length){const errorSummary=object.querySelector("[data-frm-error-summary]");if(errorSummary){errorSummary.scrollIntoView({behavior:"smooth",block:"center"});errorSummary.focus()}}checkForErrorsAndMaybeSetFocus()}}else if(!willRedirect){showFileLoading(object);object.submit()}};const error=function(){object.querySelectorAll('input[type="submit"], input[type="button"]').forEach(button=>button.disabled=false);object.submit()};postToAjaxUrl(object,data,success,error)}function isFormLevelErrorKey(key){return!/^\d/.test(key)}function addFormLevelErrors(form,formErrors){const summary=form.querySelector("[data-frm-error-summary]");if(!summary)return;Object.keys(formErrors).forEach(key=>{summary.insertAdjacentHTML("beforeend",`
${formErrors[key]}
`)})}function postToAjaxUrl(form,data,success,error){let ajaxUrl=frm_js.ajax_url;const action=form.getAttribute("action"); if("string"===typeof action&&action.includes("?action=frm_forms_preview"))ajaxUrl=action.split("?action=frm_forms_preview")[0];const ajaxParams={type:"POST",url:ajaxUrl,data,success};if("function"===typeof error)ajaxParams.error=error;jQuery.ajax(ajaxParams)}function focusFormMessage(insertedContent){let message;for(const node of insertedContent.get()){if(node.nodeType!==1)continue;message=node.matches(".frm_message")?node:node.querySelector(".frm_message");if(message)break}if(message)focusInput(message)} function afterFormSubmitted(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message");if(formCompleted)jQuery(document).trigger("frmFormComplete",[object,response]);else jQuery(document).trigger("frmPageChanged",[object,response])}function afterFormSubmittedBeforeReplace(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message"); if(formCompleted)triggerCustomEvent(document,"frmFormCompleteBeforeReplace",{object,response})}function removeAddedScripts(formContainer,formID){const endReplace=document.querySelectorAll(`.frm_end_ajax_${formID}`);if(endReplace.length){formContainer.nextUntil(`.frm_end_ajax_${formID}`).remove();endReplace.forEach(el=>el.remove())}}function maybeSlideOut(oldContent,newContent){let c;let newClass="frm_slideout";if(newContent.includes(" frm_slide")){c=oldContent.children();if(newContent.includes(" frm_going_back"))newClass+= diff --git a/tests/cypress/e2e/Forms/ajaxSpamError.cy.js b/tests/cypress/e2e/Forms/ajaxSpamError.cy.js index 2bcac687ea..f6edf121d0 100644 --- a/tests/cypress/e2e/Forms/ajaxSpamError.cy.js +++ b/tests/cypress/e2e/Forms/ajaxSpamError.cy.js @@ -17,6 +17,11 @@ describe( 'AJAX form shows the spam message', () => { cy.get( '#frm_form_key' ).invoke( 'val' ).then( formKey => { cy.visit( `/wp-admin/admin-ajax.php?action=frm_forms_preview&form=${ formKey }` ); + // Push the submit button below the fold so the summary lands above the viewport. + cy.get( '.frm_submit' ).then( $submit => { + $submit[ 0 ].insertAdjacentHTML( 'beforebegin', '
' ); + } ); + // A full page submit would reload the page and drop this marker. cy.window().then( win => { win.frmNoReload = true; @@ -25,6 +30,7 @@ describe( 'AJAX form shows the spam message', () => { cy.get( '.frm_button_submit' ).click(); cy.get( '.frm_error_style' ).should( 'contain.text', 'Your entry appears to be spam!' ); + cy.get( '[data-frm-error-summary]' ).should( 'be.visible' ).and( 'have.focus' ); cy.window().its( 'frmNoReload' ).should( 'be.true' ); } ); } ); From 49c4b9408d88c7dd0af7f011de9f2109096ff26c Mon Sep 17 00:00:00 2001 From: "vivi-the-going-merry[bot]" <308115520+vivi-the-going-merry[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 06:26:38 -0600 Subject: [PATCH 3/4] Use the scrollMsg helper to scroll to the form-level error summary Honors frm_js.offset and skips the scroll when it is -1, same as the other form scroll paths. scrollMsg already focuses the element. Co-Authored-By: Claude Sonnet 5.5 --- js/formidable.js | 3 +-- js/formidable.min.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/js/formidable.js b/js/formidable.js index c1059b1238..a390088d49 100644 --- a/js/formidable.js +++ b/js/formidable.js @@ -1354,8 +1354,7 @@ function frmFrontFormJS() { const errorSummary = object.querySelector( '[data-frm-error-summary]' ); if ( errorSummary ) { - errorSummary.scrollIntoView( { behavior: 'smooth', block: 'center' } ); - errorSummary.focus(); + frmFrontForm.scrollMsg( jQuery( errorSummary ), object, true ); } } diff --git a/js/formidable.min.js b/js/formidable.min.js index 3e63dcf4e1..72f4385912 100644 --- a/js/formidable.min.js +++ b/js/formidable.min.js @@ -42,7 +42,7 @@ response);const insertedContent=jQuery(jQuery.parseHTML(response.content));repla const formReturned=formReturnedInput?formReturnedInput.value:"";frmThemeOverride_frmAfterSubmit(formReturned,pageOrder,response.content,object)}afterFormSubmitted(object,response)},delay)}else if(response.errors!==undefined&&Object.keys(response.errors).length){removeSubmitLoading(jQuery(object),"enable");let contSubmit=true;removeAllErrors();let $fieldCont=null;const formErrors={};for(const key in response.errors){const fieldContEl=object.querySelector(`#frm_field_${key}_container`);$fieldCont=fieldContEl?jQuery(fieldContEl): jQuery();if(!fieldContEl&&isFormLevelErrorKey(key)){formErrors[key]=response.errors[key];contSubmit=false;continue}if($fieldCont.length){if(!$fieldCont.is(":visible")){const inCollapsedSection=$fieldCont.closest(".frm_toggle_container");if(inCollapsedSection.length){let frmTrigger=inCollapsedSection.prev();if(!frmTrigger.hasClass("frm_trigger"))frmTrigger=frmTrigger.prev(".frm_trigger");frmTrigger.trigger("click")}}if($fieldCont.is(":visible")){addFieldError($fieldCont,key,response.errors);contSubmit=false}}}object.querySelectorAll(".frm-g-recaptcha, .g-recaptcha, .h-captcha").forEach(function(captchaEl){const recaptchaID= captchaEl.dataset.rid;if(typeof grecaptcha!=="undefined"&&grecaptcha)if(recaptchaID)grecaptcha.reset(recaptchaID);else grecaptcha.reset();if(typeof hcaptcha!=="undefined"&&hcaptcha)hcaptcha.reset()});if(window.turnstile)object.querySelectorAll(".frm-cf-turnstile").forEach(turnstileField=>turnstileField.dataset.rid&&turnstile.reset(turnstileField.dataset.rid));jQuery(document).trigger("frmFormErrors",[object,response]);fieldsets.forEach(field=>field.classList.remove("frm_doing_ajax"));scrollToFirstField(object); -if(contSubmit)object.submit();else{object.insertAdjacentHTML("afterbegin",response.error_message);addFormLevelErrors(object,formErrors);if(Object.keys(formErrors).length){const errorSummary=object.querySelector("[data-frm-error-summary]");if(errorSummary){errorSummary.scrollIntoView({behavior:"smooth",block:"center"});errorSummary.focus()}}checkForErrorsAndMaybeSetFocus()}}else if(!willRedirect){showFileLoading(object);object.submit()}};const error=function(){object.querySelectorAll('input[type="submit"], input[type="button"]').forEach(button=>button.disabled=false);object.submit()};postToAjaxUrl(object,data,success,error)}function isFormLevelErrorKey(key){return!/^\d/.test(key)}function addFormLevelErrors(form,formErrors){const summary=form.querySelector("[data-frm-error-summary]");if(!summary)return;Object.keys(formErrors).forEach(key=>{summary.insertAdjacentHTML("beforeend",`
${formErrors[key]}
`)})}function postToAjaxUrl(form,data,success,error){let ajaxUrl=frm_js.ajax_url;const action=form.getAttribute("action"); +if(contSubmit)object.submit();else{object.insertAdjacentHTML("afterbegin",response.error_message);addFormLevelErrors(object,formErrors);if(Object.keys(formErrors).length){const errorSummary=object.querySelector("[data-frm-error-summary]");if(errorSummary){frmFrontForm.scrollMsg(jQuery(errorSummary),object,true)}}checkForErrorsAndMaybeSetFocus()}}else if(!willRedirect){showFileLoading(object);object.submit()}};const error=function(){object.querySelectorAll('input[type="submit"], input[type="button"]').forEach(button=>button.disabled=false);object.submit()};postToAjaxUrl(object,data,success,error)}function isFormLevelErrorKey(key){return!/^\d/.test(key)}function addFormLevelErrors(form,formErrors){const summary=form.querySelector("[data-frm-error-summary]");if(!summary)return;Object.keys(formErrors).forEach(key=>{summary.insertAdjacentHTML("beforeend",`
${formErrors[key]}
`)})}function postToAjaxUrl(form,data,success,error){let ajaxUrl=frm_js.ajax_url;const action=form.getAttribute("action"); if("string"===typeof action&&action.includes("?action=frm_forms_preview"))ajaxUrl=action.split("?action=frm_forms_preview")[0];const ajaxParams={type:"POST",url:ajaxUrl,data,success};if("function"===typeof error)ajaxParams.error=error;jQuery.ajax(ajaxParams)}function focusFormMessage(insertedContent){let message;for(const node of insertedContent.get()){if(node.nodeType!==1)continue;message=node.matches(".frm_message")?node:node.querySelector(".frm_message");if(message)break}if(message)focusInput(message)} function afterFormSubmitted(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message");if(formCompleted)jQuery(document).trigger("frmFormComplete",[object,response]);else jQuery(document).trigger("frmPageChanged",[object,response])}function afterFormSubmittedBeforeReplace(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message"); if(formCompleted)triggerCustomEvent(document,"frmFormCompleteBeforeReplace",{object,response})}function removeAddedScripts(formContainer,formID){const endReplace=document.querySelectorAll(`.frm_end_ajax_${formID}`);if(endReplace.length){formContainer.nextUntil(`.frm_end_ajax_${formID}`).remove();endReplace.forEach(el=>el.remove())}}function maybeSlideOut(oldContent,newContent){let c;let newClass="frm_slideout";if(newContent.includes(" frm_slide")){c=oldContent.children();if(newContent.includes(" frm_going_back"))newClass+= From 7cef8643692d2e6bd788ddf917520c30d1a5c59e Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Tue, 13 Oct 2026 11:39:16 -0300 Subject: [PATCH 4/4] Fix a focus issue --- js/formidable.js | 8 +++- js/formidable.min.js | 50 +++++++++++---------- tests/cypress/e2e/Forms/ajaxSpamError.cy.js | 21 +++++++++ 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/js/formidable.js b/js/formidable.js index a390088d49..c31de4f541 100644 --- a/js/formidable.js +++ b/js/formidable.js @@ -3056,7 +3056,13 @@ function frmFrontFormJS() { scrollObj = id; } - jQuery( scrollObj ).trigger( 'focus' ); + const scrollElement = scrollObj instanceof jQuery ? scrollObj.get( 0 ) : scrollObj; + if ( + ! scrollElement?.hasAttribute( 'data-frm-error-summary' ) || + getErrorConfigForForm( scrollElement.closest( '.frm-show-form' ) ).focusErrorSummary + ) { + jQuery( scrollObj ).trigger( 'focus' ); + } newPos = scrollObj.offset().top; if ( ! newPos || frm_js.offset === '-1' ) { return; diff --git a/js/formidable.min.js b/js/formidable.min.js index 72f4385912..ca5a3ddb9f 100644 --- a/js/formidable.min.js +++ b/js/formidable.min.js @@ -39,26 +39,27 @@ response=>{jQuery(document).trigger("frmBeforeFormRedirect",[object,response]);i response.replace(/^\s+|\s+$/g,"");if(response.indexOf("{")===0)response=JSON.parse(response);else response=defaultResponse}let willRedirect=false;if(response.redirect!==undefined){if(shouldTriggerEvent){triggerCustomEvent(object,"frmSubmitEvent");return}if(response.delay)setTimeout(function(){doRedirect(response)},1E3*response.delay);else doRedirect(response);willRedirect=true}if("string"===typeof response.content&&response.content!==""){if(shouldTriggerEvent){triggerCustomEvent(object,"frmSubmitEvent", {content:response.content});return}removeSubmitLoading(jQuery(object));if(frm_js.offset!=-1)frmFrontForm.scrollMsg(jQuery(object),false);const formIdInput=object.querySelector('input[name="form_id"]');const formID=formIdInput?formIdInput.value:"";response.content=response.content.replace(/ frm_pro_form /g," frm_pro_form frm_no_hide ");const replaceContent=jQuery(object).closest(".frm_forms");removeAddedScripts(replaceContent,formID);const delay=maybeSlideOut(replaceContent,response.content);setTimeout(function(){afterFormSubmittedBeforeReplace(object, response);const insertedContent=jQuery(jQuery.parseHTML(response.content));replaceContent.replaceWith(insertedContent);focusFormMessage(insertedContent);addUrlParam(response);if(typeof frmThemeOverride_frmAfterSubmit==="function"){const pageOrderInput=document.querySelector(`input[name="frm_page_order_${formID}"]`);const pageOrder=pageOrderInput?pageOrderInput.value:"";const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formReturnedInput=tempDiv.querySelector('input[name="form_id"]'); -const formReturned=formReturnedInput?formReturnedInput.value:"";frmThemeOverride_frmAfterSubmit(formReturned,pageOrder,response.content,object)}afterFormSubmitted(object,response)},delay)}else if(response.errors!==undefined&&Object.keys(response.errors).length){removeSubmitLoading(jQuery(object),"enable");let contSubmit=true;removeAllErrors();let $fieldCont=null;const formErrors={};for(const key in response.errors){const fieldContEl=object.querySelector(`#frm_field_${key}_container`);$fieldCont=fieldContEl?jQuery(fieldContEl): -jQuery();if(!fieldContEl&&isFormLevelErrorKey(key)){formErrors[key]=response.errors[key];contSubmit=false;continue}if($fieldCont.length){if(!$fieldCont.is(":visible")){const inCollapsedSection=$fieldCont.closest(".frm_toggle_container");if(inCollapsedSection.length){let frmTrigger=inCollapsedSection.prev();if(!frmTrigger.hasClass("frm_trigger"))frmTrigger=frmTrigger.prev(".frm_trigger");frmTrigger.trigger("click")}}if($fieldCont.is(":visible")){addFieldError($fieldCont,key,response.errors);contSubmit=false}}}object.querySelectorAll(".frm-g-recaptcha, .g-recaptcha, .h-captcha").forEach(function(captchaEl){const recaptchaID= -captchaEl.dataset.rid;if(typeof grecaptcha!=="undefined"&&grecaptcha)if(recaptchaID)grecaptcha.reset(recaptchaID);else grecaptcha.reset();if(typeof hcaptcha!=="undefined"&&hcaptcha)hcaptcha.reset()});if(window.turnstile)object.querySelectorAll(".frm-cf-turnstile").forEach(turnstileField=>turnstileField.dataset.rid&&turnstile.reset(turnstileField.dataset.rid));jQuery(document).trigger("frmFormErrors",[object,response]);fieldsets.forEach(field=>field.classList.remove("frm_doing_ajax"));scrollToFirstField(object); -if(contSubmit)object.submit();else{object.insertAdjacentHTML("afterbegin",response.error_message);addFormLevelErrors(object,formErrors);if(Object.keys(formErrors).length){const errorSummary=object.querySelector("[data-frm-error-summary]");if(errorSummary){frmFrontForm.scrollMsg(jQuery(errorSummary),object,true)}}checkForErrorsAndMaybeSetFocus()}}else if(!willRedirect){showFileLoading(object);object.submit()}};const error=function(){object.querySelectorAll('input[type="submit"], input[type="button"]').forEach(button=>button.disabled=false);object.submit()};postToAjaxUrl(object,data,success,error)}function isFormLevelErrorKey(key){return!/^\d/.test(key)}function addFormLevelErrors(form,formErrors){const summary=form.querySelector("[data-frm-error-summary]");if(!summary)return;Object.keys(formErrors).forEach(key=>{summary.insertAdjacentHTML("beforeend",`
${formErrors[key]}
`)})}function postToAjaxUrl(form,data,success,error){let ajaxUrl=frm_js.ajax_url;const action=form.getAttribute("action"); -if("string"===typeof action&&action.includes("?action=frm_forms_preview"))ajaxUrl=action.split("?action=frm_forms_preview")[0];const ajaxParams={type:"POST",url:ajaxUrl,data,success};if("function"===typeof error)ajaxParams.error=error;jQuery.ajax(ajaxParams)}function focusFormMessage(insertedContent){let message;for(const node of insertedContent.get()){if(node.nodeType!==1)continue;message=node.matches(".frm_message")?node:node.querySelector(".frm_message");if(message)break}if(message)focusInput(message)} -function afterFormSubmitted(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message");if(formCompleted)jQuery(document).trigger("frmFormComplete",[object,response]);else jQuery(document).trigger("frmPageChanged",[object,response])}function afterFormSubmittedBeforeReplace(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message"); -if(formCompleted)triggerCustomEvent(document,"frmFormCompleteBeforeReplace",{object,response})}function removeAddedScripts(formContainer,formID){const endReplace=document.querySelectorAll(`.frm_end_ajax_${formID}`);if(endReplace.length){formContainer.nextUntil(`.frm_end_ajax_${formID}`).remove();endReplace.forEach(el=>el.remove())}}function maybeSlideOut(oldContent,newContent){let c;let newClass="frm_slideout";if(newContent.includes(" frm_slide")){c=oldContent.children();if(newContent.includes(" frm_going_back"))newClass+= -" frm_going_back";c.removeClass("frm_going_back");c.addClass(newClass);return 300}return 0}function addUrlParam(response){let url;if(history.pushState&&response.page!==undefined){url=addQueryVar("frm_page",response.page);window.history.pushState({html:response.html},"",`?${url}`)}}function addQueryVar(key,value){key=encodeURI(key);value=encodeURI(value);const kvp=document.location.search.substr(1).split("&");let i=kvp.length;while(i--){const x=kvp[i].split("=");if(x[0]==key){x[1]=value;kvp[i]=x.join("="); -break}}if(i<0)kvp[kvp.length]=[key,value].join("=");return kvp.join("&")}function getErrorConfigForForm(formEl){const fallback={includeAlertRole:!!frm_js.include_alert_role,focusFirstError:!!frm_js.focus_first_error,focusErrorSummary:false};if(!formEl||!formEl.dataset.frmErrorConfig)return fallback;if(!formEl.frmErrorConfigCache)try{formEl.frmErrorConfigCache=JSON.parse(formEl.dataset.frmErrorConfig)}catch(e){formEl.frmErrorConfigCache=fallback}return formEl.frmErrorConfigCache}function insertErrorHtml(container, -errorHtml){const template=document.createElement("template");template.innerHTML=errorHtml;Array.from(template.content.children).forEach(el=>el.setAttribute("data-frm-error",""));container.append(template.content)}function addFieldError($fieldCont,key,jsErrors){const container=$fieldCont instanceof jQuery?$fieldCont.get(0):$fieldCont;if(!container||container.offsetParent===null)return;container.classList.add("frm_blank_field");const allInputs=container.querySelectorAll("input, select, textarea");const htmlId= -"string"===typeof jsErrors[key]&&jsErrors[key].includes("!hasClass(input,"frm_optional")||input.closest(".frm_form_field")===container);let describedBy;if(typeof frmThemeOverride_frmPlaceError==="function")frmThemeOverride_frmPlaceError(key,jsErrors);else if(""===jsErrors[key]);else{let errorHtml;if(jsErrors[key].includes("${jsErrors[key]}`}insertErrorHtml(container,errorHtml);inputs.forEach(input=>{describedBy=input.getAttribute("aria-describedby");if(!describedBy)describedBy=id;else if(!describedBy.includes(id)&&!describedBy.includes("frm_error_field_")){const {errorFirst}=input.dataset;if(errorFirst=== -"0")describedBy=`${describedBy} ${id}`;else describedBy=`${id} ${describedBy}`}input.setAttribute("aria-describedby",describedBy)})}inputs.forEach(input=>{if(["radio","checkbox"].includes(input.type)){const group=input.closest('[role="radiogroup"], [role="group"]');if(group)group.setAttribute("aria-invalid","true")}else input.setAttribute("aria-invalid","true")});jQuery(document).trigger("frmAddFieldError",[jQuery(container),key,jsErrors])}function getErrorElementId(key,input){if(isNaN(key)||!input|| -!input.id)return`frm_error_field_${key}`;return`frm_error_${input.id}`}function removeFieldError(fieldCont){const container=fieldCont instanceof jQuery?fieldCont.get(0):fieldCont;if(!container)return;const errorMessages=container.querySelectorAll(".frm_error, [data-frm-error]");const input=container.querySelector("input, select, textarea");container.classList.remove("frm_blank_field","has-error");if(input)if("true"===input.getAttribute("aria-invalid"))input.setAttribute("aria-invalid","false");else if(["radio", -"checkbox"].includes(input.type)){const group=input.closest('[role="radiogroup"], [role="group"]');if(group)group.setAttribute("aria-invalid","false")}errorMessages.forEach(errorMessage=>{removeElementFromInputDescribedBy(errorMessage);errorMessage.remove()})}function removeElementFromInputDescribedBy(el){document.querySelectorAll(`[aria-describedby*="${el.id}"]`).forEach(input=>{let ariaDescribedBy=input.getAttribute("aria-describedby").split(" ");ariaDescribedBy=ariaDescribedBy.filter(value=>{const trimmedValue= -value.trim();return trimmedValue&&trimmedValue!==el.id});if(ariaDescribedBy.length){input.setAttribute("aria-describedby",ariaDescribedBy.join(" "));return}input.removeAttribute("aria-describedby")})}function removeAllErrors(){document.querySelectorAll(".form-field").forEach(field=>{field.classList.remove("frm_blank_field","has-error")});document.querySelectorAll(".form-field .frm_error, .form-field [data-frm-error]").forEach(el=>{removeElementFromInputDescribedBy(el);el.remove()});document.querySelectorAll(".frm_error_style").forEach(error=> -error.remove())}function scrollToFirstField(object){if("function"===typeof object.get)object=object.get(0);const field=object.querySelector(".frm_blank_field");if(field)frmFrontForm.scrollMsg(jQuery(field),object,true)}function showSubmitLoading($object){showLoadingIndicator($object);disableSubmitButton($object);disableSaveDraft($object)}function showLoadingIndicator($object){if(!$object.hasClass("frm_loading_form")&&!$object.hasClass("frm_loading_prev")){addLoadingClass($object);$object.trigger("frmStartFormLoading")}} -function addLoadingClass($object){const loadingClass=isGoingToPrevPage($object)?"frm_loading_prev":"frm_loading_form";$object.addClass(loadingClass)}function isGoingToPrevPage($object){return typeof frmProForm!=="undefined"&&frmProForm.goingToPreviousPage($object)}function removeSubmitLoading(_,enable,processesRunning){if(processesRunning>0)return;document.querySelectorAll(".frm_loading_form").forEach(function(form){form.classList.remove("frm_loading_form","frm_loading_prev");jQuery(form).trigger("frmEndFormLoading"); -if(enable==="enable"){enableSubmitButton(form);enableSaveDraft(form)}})}function showFileLoading(object){const loading=document.getElementById("frm_loading");if(!loading)return;const fileInput=object.querySelector("input[type=file]");const fileval=fileInput?fileInput.value:"";if(fileval!=="")setTimeout(function(){jQuery(loading).fadeIn("slow")},2E3)}function confirmClick(){const message=this.dataset.frmconfirm;return confirm(message)}function onHoneypotFieldChange(){const css=window.getComputedStyle(this).boxShadow; -if(css?.match(/inset/))this.remove()}function changeFocusWhenClickComboFieldLabel(){let label;const comboInputsContainer=document.querySelectorAll(".frm_combo_inputs_container");comboInputsContainer.forEach(function(inputsContainer){if(!inputsContainer.closest(".frm_form_field"))return;label=inputsContainer.closest(".frm_form_field").querySelector(".frm_primary_label");if(!label)return;label.addEventListener("click",function(){inputsContainer.querySelector(".frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea").focus()})})} +const formReturned=formReturnedInput?formReturnedInput.value:"";frmThemeOverride_frmAfterSubmit(formReturned,pageOrder,response.content,object)}afterFormSubmitted(object,response)},delay)}else if(response.errors!==undefined&&Object.keys(response.errors).length){removeSubmitLoading(jQuery(object),"enable");let contSubmit=true;removeAllErrors();let $fieldCont=null;const formErrors={};for(const key in response.errors){const fieldContEl=object.querySelector(`#frm_field_${key}_container`);$fieldCont=fieldContEl? +jQuery(fieldContEl):jQuery();if(!fieldContEl&&isFormLevelErrorKey(key)){formErrors[key]=response.errors[key];contSubmit=false;continue}if($fieldCont.length){if(!$fieldCont.is(":visible")){const inCollapsedSection=$fieldCont.closest(".frm_toggle_container");if(inCollapsedSection.length){let frmTrigger=inCollapsedSection.prev();if(!frmTrigger.hasClass("frm_trigger"))frmTrigger=frmTrigger.prev(".frm_trigger");frmTrigger.trigger("click")}}if($fieldCont.is(":visible")){addFieldError($fieldCont,key,response.errors); +contSubmit=false}}}object.querySelectorAll(".frm-g-recaptcha, .g-recaptcha, .h-captcha").forEach(function(captchaEl){const recaptchaID=captchaEl.dataset.rid;if(typeof grecaptcha!=="undefined"&&grecaptcha)if(recaptchaID)grecaptcha.reset(recaptchaID);else grecaptcha.reset();if(typeof hcaptcha!=="undefined"&&hcaptcha)hcaptcha.reset()});if(window.turnstile)object.querySelectorAll(".frm-cf-turnstile").forEach(turnstileField=>turnstileField.dataset.rid&&turnstile.reset(turnstileField.dataset.rid));jQuery(document).trigger("frmFormErrors", +[object,response]);fieldsets.forEach(field=>field.classList.remove("frm_doing_ajax"));scrollToFirstField(object);if(contSubmit)object.submit();else{object.insertAdjacentHTML("afterbegin",response.error_message);addFormLevelErrors(object,formErrors);if(Object.keys(formErrors).length){const errorSummary=object.querySelector("[data-frm-error-summary]");if(errorSummary)frmFrontForm.scrollMsg(jQuery(errorSummary),object,true)}checkForErrorsAndMaybeSetFocus()}}else if(!willRedirect){showFileLoading(object); +object.submit()}};const error=function(){object.querySelectorAll('input[type="submit"], input[type="button"]').forEach(button=>button.disabled=false);object.submit()};postToAjaxUrl(object,data,success,error)}function isFormLevelErrorKey(key){return!/^\d/.test(key)}function addFormLevelErrors(form,formErrors){const summary=form.querySelector("[data-frm-error-summary]");if(!summary)return;Object.keys(formErrors).forEach(key=>{summary.insertAdjacentHTML("beforeend",`
${formErrors[key]}
`)})} +function postToAjaxUrl(form,data,success,error){let ajaxUrl=frm_js.ajax_url;const action=form.getAttribute("action");if("string"===typeof action&&action.includes("?action=frm_forms_preview"))ajaxUrl=action.split("?action=frm_forms_preview")[0];const ajaxParams={type:"POST",url:ajaxUrl,data,success};if("function"===typeof error)ajaxParams.error=error;jQuery.ajax(ajaxParams)}function focusFormMessage(insertedContent){let message;for(const node of insertedContent.get()){if(node.nodeType!==1)continue; +message=node.matches(".frm_message")?node:node.querySelector(".frm_message");if(message)break}if(message)focusInput(message)}function afterFormSubmitted(object,response){const tempDiv=document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message");if(formCompleted)jQuery(document).trigger("frmFormComplete",[object,response]);else jQuery(document).trigger("frmPageChanged",[object,response])}function afterFormSubmittedBeforeReplace(object,response){const tempDiv= +document.createElement("div");tempDiv.innerHTML=response.content;const formCompleted=tempDiv.querySelector(".frm_message");if(formCompleted)triggerCustomEvent(document,"frmFormCompleteBeforeReplace",{object,response})}function removeAddedScripts(formContainer,formID){const endReplace=document.querySelectorAll(`.frm_end_ajax_${formID}`);if(endReplace.length){formContainer.nextUntil(`.frm_end_ajax_${formID}`).remove();endReplace.forEach(el=>el.remove())}}function maybeSlideOut(oldContent,newContent){let c; +let newClass="frm_slideout";if(newContent.includes(" frm_slide")){c=oldContent.children();if(newContent.includes(" frm_going_back"))newClass+=" frm_going_back";c.removeClass("frm_going_back");c.addClass(newClass);return 300}return 0}function addUrlParam(response){let url;if(history.pushState&&response.page!==undefined){url=addQueryVar("frm_page",response.page);window.history.pushState({html:response.html},"",`?${url}`)}}function addQueryVar(key,value){key=encodeURI(key);value=encodeURI(value);const kvp= +document.location.search.substr(1).split("&");let i=kvp.length;while(i--){const x=kvp[i].split("=");if(x[0]==key){x[1]=value;kvp[i]=x.join("=");break}}if(i<0)kvp[kvp.length]=[key,value].join("=");return kvp.join("&")}function getErrorConfigForForm(formEl){const fallback={includeAlertRole:!!frm_js.include_alert_role,focusFirstError:!!frm_js.focus_first_error,focusErrorSummary:false};if(!formEl||!formEl.dataset.frmErrorConfig)return fallback;if(!formEl.frmErrorConfigCache)try{formEl.frmErrorConfigCache= +JSON.parse(formEl.dataset.frmErrorConfig)}catch(e){formEl.frmErrorConfigCache=fallback}return formEl.frmErrorConfigCache}function insertErrorHtml(container,errorHtml){const template=document.createElement("template");template.innerHTML=errorHtml;Array.from(template.content.children).forEach(el=>el.setAttribute("data-frm-error",""));container.append(template.content)}function addFieldError($fieldCont,key,jsErrors){const container=$fieldCont instanceof jQuery?$fieldCont.get(0):$fieldCont;if(!container|| +container.offsetParent===null)return;container.classList.add("frm_blank_field");const allInputs=container.querySelectorAll("input, select, textarea");const htmlId="string"===typeof jsErrors[key]&&jsErrors[key].includes("!hasClass(input,"frm_optional")||input.closest(".frm_form_field")===container);let describedBy;if(typeof frmThemeOverride_frmPlaceError=== +"function")frmThemeOverride_frmPlaceError(key,jsErrors);else if(""===jsErrors[key]);else{let errorHtml;if(jsErrors[key].includes("${jsErrors[key]}`}insertErrorHtml(container,errorHtml);inputs.forEach(input=>{describedBy=input.getAttribute("aria-describedby");if(!describedBy)describedBy= +id;else if(!describedBy.includes(id)&&!describedBy.includes("frm_error_field_")){const {errorFirst}=input.dataset;if(errorFirst==="0")describedBy=`${describedBy} ${id}`;else describedBy=`${id} ${describedBy}`}input.setAttribute("aria-describedby",describedBy)})}inputs.forEach(input=>{if(["radio","checkbox"].includes(input.type)){const group=input.closest('[role="radiogroup"], [role="group"]');if(group)group.setAttribute("aria-invalid","true")}else input.setAttribute("aria-invalid","true")});jQuery(document).trigger("frmAddFieldError", +[jQuery(container),key,jsErrors])}function getErrorElementId(key,input){if(isNaN(key)||!input||!input.id)return`frm_error_field_${key}`;return`frm_error_${input.id}`}function removeFieldError(fieldCont){const container=fieldCont instanceof jQuery?fieldCont.get(0):fieldCont;if(!container)return;const errorMessages=container.querySelectorAll(".frm_error, [data-frm-error]");const input=container.querySelector("input, select, textarea");container.classList.remove("frm_blank_field","has-error");if(input)if("true"=== +input.getAttribute("aria-invalid"))input.setAttribute("aria-invalid","false");else if(["radio","checkbox"].includes(input.type)){const group=input.closest('[role="radiogroup"], [role="group"]');if(group)group.setAttribute("aria-invalid","false")}errorMessages.forEach(errorMessage=>{removeElementFromInputDescribedBy(errorMessage);errorMessage.remove()})}function removeElementFromInputDescribedBy(el){document.querySelectorAll(`[aria-describedby*="${el.id}"]`).forEach(input=>{let ariaDescribedBy=input.getAttribute("aria-describedby").split(" "); +ariaDescribedBy=ariaDescribedBy.filter(value=>{const trimmedValue=value.trim();return trimmedValue&&trimmedValue!==el.id});if(ariaDescribedBy.length){input.setAttribute("aria-describedby",ariaDescribedBy.join(" "));return}input.removeAttribute("aria-describedby")})}function removeAllErrors(){document.querySelectorAll(".form-field").forEach(field=>{field.classList.remove("frm_blank_field","has-error")});document.querySelectorAll(".form-field .frm_error, .form-field [data-frm-error]").forEach(el=>{removeElementFromInputDescribedBy(el); +el.remove()});document.querySelectorAll(".frm_error_style").forEach(error=>error.remove())}function scrollToFirstField(object){if("function"===typeof object.get)object=object.get(0);const field=object.querySelector(".frm_blank_field");if(field)frmFrontForm.scrollMsg(jQuery(field),object,true)}function showSubmitLoading($object){showLoadingIndicator($object);disableSubmitButton($object);disableSaveDraft($object)}function showLoadingIndicator($object){if(!$object.hasClass("frm_loading_form")&&!$object.hasClass("frm_loading_prev")){addLoadingClass($object); +$object.trigger("frmStartFormLoading")}}function addLoadingClass($object){const loadingClass=isGoingToPrevPage($object)?"frm_loading_prev":"frm_loading_form";$object.addClass(loadingClass)}function isGoingToPrevPage($object){return typeof frmProForm!=="undefined"&&frmProForm.goingToPreviousPage($object)}function removeSubmitLoading(_,enable,processesRunning){if(processesRunning>0)return;document.querySelectorAll(".frm_loading_form").forEach(function(form){form.classList.remove("frm_loading_form", +"frm_loading_prev");jQuery(form).trigger("frmEndFormLoading");if(enable==="enable"){enableSubmitButton(form);enableSaveDraft(form)}})}function showFileLoading(object){const loading=document.getElementById("frm_loading");if(!loading)return;const fileInput=object.querySelector("input[type=file]");const fileval=fileInput?fileInput.value:"";if(fileval!=="")setTimeout(function(){jQuery(loading).fadeIn("slow")},2E3)}function confirmClick(){const message=this.dataset.frmconfirm;return confirm(message)}function onHoneypotFieldChange(){const css= +window.getComputedStyle(this).boxShadow;if(css?.match(/inset/))this.remove()}function changeFocusWhenClickComboFieldLabel(){let label;const comboInputsContainer=document.querySelectorAll(".frm_combo_inputs_container");comboInputsContainer.forEach(function(inputsContainer){if(!inputsContainer.closest(".frm_form_field"))return;label=inputsContainer.closest(".frm_form_field").querySelector(".frm_primary_label");if(!label)return;label.addEventListener("click",function(){inputsContainer.querySelector(".frm_form_field:first-child input, .frm_form_field:first-child select, .frm_form_field:first-child textarea").focus()})})} function maybeFocusOnComboSubField(element){if("FIELDSET"!==element.nodeName)return false;if(!element.querySelector(".frm_combo_inputs_container"))return false;const comboSubfield=element.querySelector('[aria-invalid="true"]');if(comboSubfield){focusInput(comboSubfield);return true}return false}function checkForErrorsAndMaybeSetFocus(){const errors=document.querySelectorAll(".frm_form_field .frm_error, .frm_form_field [data-frm-error]");if(!errors.length)return;const formContainer=errors[0].closest(".frm-show-form"); const config=getErrorConfigForForm(formContainer);if(config.focusErrorSummary){const summary=formContainer?formContainer.querySelector("[data-frm-error-summary]"):null;if(summary){summary.focus();return}}if(!config.focusFirstError&&!config.focusErrorSummary)return;let element=errors[0];let timeoutCallback;do{element=element.previousSibling;if(!element)break;if(["input","select","textarea"].includes(element.nodeName.toLowerCase())){focusInput(element);break}if(maybeFocusOnComboSubField(element))break; if(element.classList!==undefined){if(element.classList.contains("html-active"))timeoutCallback=function(){const textarea=element.querySelector("textarea");if(null!==textarea)textarea.focus()};else if(element.classList.contains("tmce-active"))timeoutCallback=function(){tinyMCE.activeEditor.focus()};else if(element.classList.contains("frm_opt_container")){const firstInput=element.querySelector("input");if(firstInput){focusInput(firstInput);break}}if("function"===typeof timeoutCallback){setTimeout(timeoutCallback, @@ -96,10 +97,11 @@ document.createElement("input");antispamInput.type="hidden";antispamInput.name=" !!input.value).length;if(hasFileFields<1){const actionInput=object.querySelector('input[name="frm_action"]');const action=actionInput?actionInput.value:"";frmFrontForm.checkFormErrors(object,action)}else object.submit()}else object.submit()},validateFormSubmit(object){const form=object instanceof jQuery?object.get(0):object;if(typeof tinyMCE!=="undefined"&&form?.querySelector(".wp-editor-wrap"))tinyMCE.triggerSave();jsErrors=[];if(shouldJSValidate(object)){frmFrontForm.getAjaxFormErrors(object);if(Object.keys(jsErrors).length)frmFrontForm.addAjaxFormErrors(object)}return jsErrors}, getAjaxFormErrors(object){let customErrors;let key;const form=object instanceof jQuery?object.get(0):object;jsErrors=validateForm(object);if(typeof frmThemeOverride_jsErrors==="function"){const actionInput=form?form.querySelector('input[name="frm_action"]'):null;const action=actionInput?actionInput.value:"";customErrors=frmThemeOverride_jsErrors(action,object);if(Object.keys(customErrors).length)for(key in customErrors)jsErrors[key]=customErrors[key]}triggerCustomEvent(document,"frm_get_ajax_form_errors", {formEl:object,errors:jsErrors});return jsErrors},addAjaxFormErrors(object){let key;const form=object instanceof jQuery?object.get(0):object;removeAllErrors();for(key in jsErrors){const fieldCont=form?getFieldContainerForErrorKey(form,key):null;if(fieldCont)addFieldError(fieldCont,key,jsErrors);else delete jsErrors[key]}scrollToFirstField(object);checkForErrorsAndMaybeSetFocus()},checkFormErrors:getFormErrors,checkRequiredField,showSubmitLoading,removeSubmitLoading,scrollToID(id){const object=jQuery(document.getElementById(id)); -frmFrontForm.scrollMsg(object,false)},scrollMsg(id,object,animate){let newPos;let screenTop;let screenBottom;let scrollObj="";if(object===undefined){scrollObj=jQuery(document.getElementById(`frm_form_${id}_container`));if(scrollObj.length<1)return}else if(typeof id==="string"){const formEl=object instanceof jQuery?object.get(0):object;const fieldEl=formEl?formEl.querySelector(`#frm_field_${id}_container`):null;scrollObj=fieldEl?jQuery(fieldEl):jQuery()}else scrollObj=id;jQuery(scrollObj).trigger("focus"); -newPos=scrollObj.offset().top;if(!newPos||frm_js.offset==="-1")return;newPos=newPos-frm_js.offset;const docMarginTop=getComputedStyle(document.documentElement).marginTop;const bodyMarginTop=getComputedStyle(document.body).marginTop;if(docMarginTop||bodyMarginTop)newPos=newPos-parseInt(docMarginTop)-parseInt(bodyMarginTop);if(newPos&&window.innerHeight){screenTop=document.documentElement.scrollTop||document.body.scrollTop;screenBottom=screenTop+window.innerHeight;if(newPos>screenBottom||newPos/g, -">").replace(/"/g,""").replace(/'/g,"'")},triggerCustomEvent,documentOn}}window.frmFrontForm=frmFrontFormJS();jQuery(document).ready(function(){frmFrontForm.init()});function frmRecaptcha(){frmCaptcha(".frm-g-recaptcha")}function frmHcaptcha(){frmCaptcha(".h-captcha")}function frmTurnstile(){frmCaptcha(".frm-cf-turnstile")} +frmFrontForm.scrollMsg(object,false)},scrollMsg(id,object,animate){let newPos;let screenTop;let screenBottom;let scrollObj="";if(object===undefined){scrollObj=jQuery(document.getElementById(`frm_form_${id}_container`));if(scrollObj.length<1)return}else if(typeof id==="string"){const formEl=object instanceof jQuery?object.get(0):object;const fieldEl=formEl?formEl.querySelector(`#frm_field_${id}_container`):null;scrollObj=fieldEl?jQuery(fieldEl):jQuery()}else scrollObj=id;const scrollElement=scrollObj instanceof +jQuery?scrollObj.get(0):scrollObj;if(!scrollElement?.hasAttribute("data-frm-error-summary")||getErrorConfigForForm(scrollElement.closest(".frm-show-form")).focusErrorSummary)jQuery(scrollObj).trigger("focus");newPos=scrollObj.offset().top;if(!newPos||frm_js.offset==="-1")return;newPos=newPos-frm_js.offset;const docMarginTop=getComputedStyle(document.documentElement).marginTop;const bodyMarginTop=getComputedStyle(document.body).marginTop;if(docMarginTop||bodyMarginTop)newPos=newPos-parseInt(docMarginTop)- +parseInt(bodyMarginTop);if(newPos&&window.innerHeight){screenTop=document.documentElement.scrollTop||document.body.scrollTop;screenBottom=screenTop+window.innerHeight;if(newPos>screenBottom||newPos/g,">").replace(/"/g,""").replace(/'/g,"'")},triggerCustomEvent,documentOn}}window.frmFrontForm=frmFrontFormJS();jQuery(document).ready(function(){frmFrontForm.init()});function frmRecaptcha(){frmCaptcha(".frm-g-recaptcha")} +function frmHcaptcha(){frmCaptcha(".h-captcha")}function frmTurnstile(){frmCaptcha(".frm-cf-turnstile")} function frmCaptcha(captchaSelector){if(".h-captcha"===captchaSelector){const captchaLabels=document.querySelectorAll('label[for="h-captcha-response"]');if(captchaLabels.length)captchaLabels.forEach(label=>{const captchaResponse=label.closest("form")?.querySelector('[name="h-captcha-response"]');if(captchaResponse)label.htmlFor=captchaResponse.id});return}let c;const captchas=document.querySelectorAll(captchaSelector);const cl=captchas.length;for(c=0;c { win.frmNoReload = true; } ); cy.get( 'input.frm_verify' ).invoke( 'val', 'bot' ); + cy.intercept( 'POST', '**/admin-ajax.php' ).as( 'submitForm' ); cy.get( '.frm_button_submit' ).click(); + cy.wait( '@submitForm' ); cy.get( '.frm_error_style' ).should( 'contain.text', 'Your entry appears to be spam!' ); cy.get( '[data-frm-error-summary]' ).should( 'be.visible' ).and( 'have.focus' ); cy.window().its( 'frmNoReload' ).should( 'be.true' ); + + // Keep scrolling to the summary when automatic error focus is disabled. + cy.get( '.frm-show-form' ).then( $form => { + const [ form ] = $form; + const config = JSON.parse( form.dataset.frmErrorConfig ); + config.focusErrorSummary = false; + config.focusFirstError = false; + form.dataset.frmErrorConfig = JSON.stringify( config ); + delete form.frmErrorConfigCache; + } ); + cy.get( '.frm_button_submit' ).focus().click(); + cy.wait( '@submitForm' ); + cy.get( '[data-frm-error-summary]' ).should( 'not.have.focus' ).and( 'contain.text', 'Your entry appears to be spam!' ); + cy.get( '[data-frm-error-summary]' ).should( $summary => { + const { top } = $summary[ 0 ].getBoundingClientRect(); + expect( top ).to.be.at.least( 0 ); + expect( top ).to.be.lessThan( $summary[ 0 ].ownerDocument.defaultView.innerHeight ); + } ); + cy.window().its( 'frmNoReload' ).should( 'be.true' ); } ); } ); } );