Skip to content

Instantly share code, notes, and snippets.

@Qubadi
Last active August 11, 2025 17:00
Show Gist options
  • Save Qubadi/3c12e21d4572ca9fae05670169e8a455 to your computer and use it in GitHub Desktop.
Save Qubadi/3c12e21d4572ca9fae05670169e8a455 to your computer and use it in GitHub Desktop.
JetFormBuilder form validation with disabled submit button
UPDATED: 11.08.2025
It checks to see if a JetFormBuilder form has all required fields filled.
If any of them are missing, the submit button is disabled, and this prevents their submission.
The button activates again when all fields are complete.
Copy the following PHP code and create a PHP snippet using your snippet plugins.
Paste the code into the plugin and save it.
________________________________________
add_action('wp_footer', function () { ?>
<script>
(function () {
'use strict';
// -------- helpers --------
const qsa = (root, sel) => Array.from(root.querySelectorAll(sel));
const isVisible = el => !!(el && (el.offsetParent || el.getClientRects().length));
// Revalidation scheduler (prevents hammering)
function makeScheduler(fn) {
let queued = false;
return function schedule() {
if (queued) return;
queued = true;
requestAnimationFrame(() => { queued = false; fn(); });
};
}
function attachToForm(form) {
if (!form || form.dataset.jfbGuardAttached) return;
form.dataset.jfbGuardAttached = '1';
let internalUpdate = false; // guard so we ignore our own DOM mutations
const scheduleRevalidate = makeScheduler(revalidate);
const findSubmitButtons = () =>
qsa(form, 'button[type="submit"], input[type="submit"], .jet-form-builder__submit');
const findPages = () =>
qsa(form, '.jet-form-builder-page, .jet-form-builder__page, [data-jfb-page]');
const currentPage = () => {
const pages = findPages().filter(isVisible);
return pages[0] || form;
};
const findNextButtonsOn = (container) =>
qsa(container, '.jet-form-builder__next-page, [data-jfb-action="next"], [data-action="next"], .jet-form-builder-next')
.filter(isVisible);
const pageIsValid = (container) => {
const fields = qsa(container, '[required]').filter(el =>
!el.disabled && isVisible(el) && el.type !== 'hidden'
);
// HTML5 validity includes radio/checkbox groups properly
return fields.every(el => el.checkValidity());
};
function setBtnState(btn, enabled) {
// Only change if state actually differs (avoids needless mutations)
const shouldDisable = !enabled;
if (btn.disabled !== shouldDisable) btn.disabled = shouldDisable;
const hasInactive = btn.classList.contains('inactive');
if (hasInactive === enabled) {
// toggle only when needed
if (enabled) btn.classList.remove('inactive');
else btn.classList.add('inactive');
}
const aria = String(!enabled);
if (btn.getAttribute('aria-disabled') !== aria) {
btn.setAttribute('aria-disabled', aria);
}
}
function updateNextState() {
const page = currentPage();
const ok = pageIsValid(page);
const nextButtons = findNextButtonsOn(page);
nextButtons.forEach(btn => setBtnState(btn, ok));
}
function updateSubmitState() {
const submitButtons = findSubmitButtons();
if (!submitButtons.length) return;
const ok = form.checkValidity();
submitButtons.forEach(btn => {
const visible = isVisible(btn);
setBtnState(btn, visible && ok);
});
}
function revalidate() {
internalUpdate = true;
try {
updateNextState();
updateSubmitState();
} finally {
internalUpdate = false;
}
}
// Listen to input/change bubbling inside the form
form.addEventListener('input', scheduleRevalidate, true);
form.addEventListener('change', scheduleRevalidate, true);
// Observe DOM changes but ignore those we cause ourselves
const mo = new MutationObserver((mutations) => {
if (internalUpdate) return; // ignore our own writes
// Only react to meaningful changes (visibility/structure)
const meaningful = mutations.some(m => (
m.type === 'childList' ||
(m.type === 'attributes' && (
m.attributeName === 'class' ||
m.attributeName === 'style' ||
m.attributeName === 'hidden' ||
m.attributeName === 'aria-hidden'
))
));
if (meaningful) scheduleRevalidate();
});
mo.observe(form, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'style', 'hidden', 'aria-hidden']
});
// First run
scheduleRevalidate();
}
function scanForForms() {
const candidates = qsa(document,
'form.jet-form-builder, .jet-form-builder form, form[data-form-type="jet-form-builder"]'
);
candidates.forEach(attachToForm);
}
// Initial + future (e.g., JetPopup)
document.addEventListener('DOMContentLoaded', scanForForms);
const globalMO = new MutationObserver(scanForForms);
globalMO.observe(document.documentElement || document.body, { childList: true, subtree: true });
})();
</script>
<style>
/* Optional visual hint; keeps your existing styling intact */
.jet-form-builder .inactive,
.jet-form-builder__submit.inactive,
.jet-form-builder__next-page.inactive {
cursor: not-allowed;
opacity: 0.6;
pointer-events: none;
}
</style>
<?php });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment