diff --git a/js/formidable.js b/js/formidable.js
index 41c018a4bd..0df9355e45 100644
--- a/js/formidable.js
+++ b/js/formidable.js
@@ -1154,6 +1154,24 @@ function frmFrontFormJS() {
return kvp.join( '&' );
}
+ /**
+ * Inserts error HTML into a field's container, tagging every inserted top-level
+ * element with a data-frm-error attribute. removeFieldError()/removeAllErrors() rely
+ * on that attribute (rather than the frm_error class) to find and remove it again,
+ * since a site's own custom field HTML template can render the [error] placeholder
+ * without a frm_error class or id, and errors that can't be found never get removed.
+ *
+ * @param {HTMLElement} container
+ * @param {string} errorHtml
+ * @return {void}
+ */
+ 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;
@@ -1177,7 +1195,7 @@ function frmFrontFormJS() {
const roleString = frm_js.include_alert_role ? 'role="alert"' : '';
errorHtml = `
${ jsErrors[ key ] }
`;
}
- container.insertAdjacentHTML( 'beforeend', errorHtml );
+ insertErrorHtml( container, errorHtml );
inputs.forEach( input => {
describedBy = input.getAttribute( 'aria-describedby' );
if ( ! describedBy ) {
@@ -1236,7 +1254,7 @@ function frmFrontFormJS() {
return;
}
- const errorMessage = container.querySelector( '.frm_error' );
+ const errorMessages = container.querySelectorAll( '.frm_error, [data-frm-error]' );
const input = container.querySelector( 'input, select, textarea' );
container.classList.remove( 'frm_blank_field', 'has-error' );
@@ -1252,10 +1270,10 @@ function frmFrontFormJS() {
}
}
- if ( errorMessage ) {
+ errorMessages.forEach( errorMessage => {
removeElementFromInputDescribedBy( errorMessage );
errorMessage.remove();
- }
+ } );
}
/**
@@ -1286,7 +1304,7 @@ function frmFrontFormJS() {
document.querySelectorAll( '.form-field' ).forEach( field => {
field.classList.remove( 'frm_blank_field', 'has-error' );
} );
- document.querySelectorAll( '.form-field .frm_error' ).forEach( el => {
+ document.querySelectorAll( '.form-field .frm_error, .form-field [data-frm-error]' ).forEach( el => {
removeElementFromInputDescribedBy( el );
el.remove();
} );
@@ -1438,7 +1456,7 @@ function frmFrontFormJS() {
return;
}
- const errors = document.querySelectorAll( '.frm_form_field .frm_error' );
+ const errors = document.querySelectorAll( '.frm_form_field .frm_error, .frm_form_field [data-frm-error]' );
if ( ! errors.length ) {
return;
}