Last active
August 11, 2025 17:00
-
-
Save Qubadi/3c12e21d4572ca9fae05670169e8a455 to your computer and use it in GitHub Desktop.
JetFormBuilder form validation with disabled submit button
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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