diff --git a/js/formidable.js b/js/formidable.js index e8f8f6c8a0..c31de4f541 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,16 @@ function frmFrontFormJS() { 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 ) { // Avoid double submission if redirecting to a page. @@ -1361,6 +1379,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' ); @@ -3009,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 147e56fc04..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;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= -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("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 { + 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 }` ); + + // 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; + } ); + 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' ); + } ); + } ); + } ); +} );