ferro-json-ui 0.2.22

JSON-based server-driven UI schema types for Ferro
Documentation
pub(super) const SOURCE: &str = r#"
    // ── Form guards ───────────────────────────────────────────────────────

    function setupFormGuards() {
        var forms = document.querySelectorAll('[data-form-guard]');
        for (var i = 0; i < forms.length; i++) {
            initFormGuard(forms[i]);
        }
    }

    function initFormGuard(form) {
        var guardType = form.getAttribute('data-form-guard');
        if (guardType === 'number-gt-0') {
            initNumberGuard(form);
        } else if (guardType && guardType.indexOf('text-equals:') === 0) {
            var expected = guardType.slice('text-equals:'.length);
            initTextEqualsGuard(form, expected);
        }
    }

    function initTextEqualsGuard(form, expected) {
        var input = form.querySelector('input[type="text"]');
        var submitBtn = form.querySelector('button[type="submit"]');
        if (!input || !submitBtn) return;

        function check() {
            if (input.value === expected) {
                submitBtn.removeAttribute('disabled');
                submitBtn.classList.remove('opacity-50', 'cursor-not-allowed');
            } else {
                submitBtn.setAttribute('disabled', 'disabled');
                submitBtn.classList.add('opacity-50', 'cursor-not-allowed');
            }
        }

        check();
        input.addEventListener('input', check);
    }

    function initNumberGuard(form) {
        var numberInputs = form.querySelectorAll('input[type="number"]');
        var qtyInputs = form.querySelectorAll('input[data-qty-input]');
        // Merge both NodeLists
        var inputs = [];
        for (var n = 0; n < numberInputs.length; n++) inputs.push(numberInputs[n]);
        for (var q = 0; q < qtyInputs.length; q++) inputs.push(qtyInputs[q]);
        // Find the submit button — skip ProductTile +/- controls (they have data-qty-* attrs)
        var submitBtn = form.querySelector('button[type="submit"]');
        if (!submitBtn) {
            var allBtns = form.querySelectorAll('button:not([data-qty-inc]):not([data-qty-dec])');
            submitBtn = allBtns.length > 0 ? allBtns[allBtns.length - 1] : null;
        }
        if (!submitBtn || inputs.length === 0) return;

        function check() {
            var hasValue = false;
            for (var i = 0; i < inputs.length; i++) {
                if (parseFloat(inputs[i].value) > 0) {
                    hasValue = true;
                    break;
                }
            }
            if (hasValue) {
                submitBtn.removeAttribute('disabled');
                submitBtn.classList.remove('opacity-50', 'cursor-not-allowed');
            } else {
                submitBtn.setAttribute('disabled', 'disabled');
                submitBtn.classList.add('opacity-50', 'cursor-not-allowed');
            }
        }

        check(); // initial state
        for (var j = 0; j < inputs.length; j++) {
            inputs[j].addEventListener('input', check);
        }
    }
"#;