Forms & Validation

Form এখনো user data পাঠানোর প্রধান পথ

~35 min Intermediate 12 practice problems Live runner

1. Submit + FormData

form.addEventListener("submit", e => {
    e.preventDefault();        // stop the page reload
    const data = new FormData(form);

    // Read individual fields
    console.log(data.get("name"));
    console.log(data.get("email"));

    // Iterate everything
    for (const [k, v] of data.entries()) console.log(k, v);

    // Send to server
    fetch("/api/signup", { method: "POST", body: data });
});
FormData form-এর প্রতিটি field collect করে। সরাসরি fetch-এর body-তে পাঠানো যায় — multipart/form-data automatically সেট হয়।

2. HTML5 Constraint Attributes

<input name="email"  type="email" required>
<input name="age"    type="number" min="13" max="120" required>
<input name="name"   minlength="2" maxlength="60" required>
<input name="phone"  pattern="\+?880\d{10}" placeholder="+8801XXXXXXXXX">
<input name="url"    type="url">
<input name="agree"  type="checkbox" required>

The browser checks these on submit and refuses to send if any field fails. Pair with JS for a polished UX.

3. Programmatic Validation API

// On every form
form.checkValidity();      // boolean
form.reportValidity();     // also shows native error bubbles

// Per input
input.validity.valueMissing
input.validity.typeMismatch
input.validity.tooShort
input.validity.patternMismatch
input.validity.customError

// Set a custom error
if (passwords.value !== confirm.value) {
    confirm.setCustomValidity("Passwords don't match");
} else {
    confirm.setCustomValidity("");
}

4. Sandbox-Safe Validator

validate.js
const rules = {
    name:  v => v.length >= 2     || "name too short",
    email: v => /^\S+@\S+\.\S+$/.test(v) || "invalid email",
    phone: v => /^\+?880\d{10}$/.test(v) || "invalid BD phone",
    age:   v => (+v >= 13 && +v <= 120) || "age 13–120",
};

function validate(data) {
    const errors = {};
    for (const [k, fn] of Object.entries(rules)) {
        const r = fn(data[k] ?? "");
        if (r !== true) errors[k] = r;
    }
    return errors;
}

console.log(validate({
    name: "Arif", email: "a@x.com",
    phone: "+8801712345678", age: "22"
}));

console.log(validate({
    name: "A", email: "bad", phone: "123", age: "5"
}));

5. Accessibility

  • Always wrap inputs in <label> or use for=""/id="" linking
  • Set aria-invalid="true" on bad fields and aria-describedby pointing to the error message
  • Use autocomplete attributes (e.g. autocomplete="email") so password managers help
  • Disable the submit button while sending to prevent double-submit
  • Always re-validate on the server — never trust the browser alone
Server-side validation is mandatory Anyone can disable JS, send a custom POST, or edit your HTML. Client-side validation is a UX feature; server-side validation is a security requirement.

6. Working Form Skeleton

<form id="signup" novalidate>
    <label>Email
        <input name="email" type="email" required autocomplete="email">
        <span class="err" aria-live="polite"></span>
    </label>
    <label>Phone
        <input name="phone" pattern="\+?880\d{10}" required>
        <span class="err" aria-live="polite"></span>
    </label>
    <button type="submit">Sign up</button>
</form>

<script>
const form = document.getElementById("signup");
form.addEventListener("submit", async (e) => {
    e.preventDefault();
    let valid = true;
    form.querySelectorAll("input").forEach(input => {
        const err = input.nextElementSibling;
        if (!input.checkValidity()) {
            err.textContent = input.validationMessage;
            valid = false;
        } else err.textContent = "";
    });
    if (!valid) return;
    const res = await fetch("/api/signup",
        { method: "POST", body: new FormData(form) });
    if (res.ok) form.reset();
});
</script>

7. Glossary (শব্দকোষ)

TermMeaningবাংলায়
FormDataAuto-collects all named inputs of a form into a key/value structure.Form-এর সব named input automatically সংগ্রহ করে।
submit eventFires when the user submits a form; preventDefault to handle in JS.Form submit-এর event; JS-এ handle করতে preventDefault দিন।
Constraint validationHTML5 attributes (required, minlength, pattern) checked by the browser.HTML5 attribute দিয়ে browser-এর built-in validation।
checkValidityReturns true/false without showing UI.UI না দেখিয়ে valid কিনা চেক করে।
reportValidityLike checkValidity but also shows native error bubbles.Validity দেখায় এবং native error bubble পপ-আপ করে।
setCustomValiditySet a custom error message on an input.Custom error message নির্দিষ্ট করার API।
aria-invalidMarks an input as failing validation for screen readers.Screen reader-কে জানায় input invalid।
aria-liveAnnounces dynamic text changes (good for error messages).Dynamic text change-কে assistive tech announce করে।
novalidateDisables native validation UI so you can render your own.Browser-এর built-in UI বন্ধ — নিজের UI দেখাতে।
autocompleteHints to password managers / autofill (e.g. email).Password manager / autofill-কে hint।
মনে রাখুন: Form validation দু'জায়গায় — browser-এ (UX) এবং server-এ (security)। Client-side trust করবেন না; user JS off করতে পারে, custom POST পাঠাতে পারে। FormData + fetch সবচেয়ে পরিষ্কার pattern; aria-invalid ও aria-live দিয়ে accessibility সুনিশ্চিত করুন। সবসময় submit চলাকালীন button disable করুন — double-submit আটকাতে।

8. Practice Problems

  1. Build a runnable validator that checks an email string.
    ✨ Show Answer
    a1.js
    const isEmail = s => /^\S+@\S+\.\S+$/.test(s);
    console.log(isEmail("a@x.com"), isEmail("nope"));
  2. Validate Bangladeshi phone numbers (+8801XXXXXXXXX).
    ✨ Show Answer
    a2.js
    const isBdPhone = s => /^\+?880\d{10}$/.test(s);
    ["+8801712345678", "01712345678", "+88017"]
        .forEach(p => console.log(p, isBdPhone(p)));
  3. Convert a FormData to a plain object (sketch).
    ✨ Show Answer
    const data = Object.fromEntries(new FormData(form));
  4. Validate a strong password (≥8 chars, 1 number, 1 symbol).
    ✨ Show Answer
    a4.js
    const strong = s =>
        s.length >= 8 && /\d/.test(s) && /[!@#$%^&*()]/.test(s);
    ["hi!", "abcdef12", "abcdef1!"].forEach(p =>
        console.log(p, strong(p)));
  5. Build a "passwords match" custom validator (sketch).
    ✨ Show Answer
    function check() {
        const m = pw.value === confirm.value;
        confirm.setCustomValidity(m ? "" : "Passwords don't match");
    }
    [pw, confirm].forEach(i => i.addEventListener("input", check));
  6. Why must validation also run on the server?
    ✨ Show Answer

    Answer: The browser is fully under user control — they can disable JS, edit the HTML, send raw HTTP. Client-side validation is a UX optimisation; the server is the only trustworthy gatekeeper for invariants like uniqueness, ownership, and limits.

  7. Build an aria-live error display that updates without focus loss (sketch).
    ✨ Show Answer
    <span id="err" aria-live="polite"></span>
    
    input.addEventListener("input", () => {
        err.textContent = input.checkValidity() ? "" : input.validationMessage;
    });
  8. Disable submit while a request is in flight (sketch).
    ✨ Show Answer
    btn.disabled = true;
    try { await fetch(...); }
    finally { btn.disabled = false; }
  9. Validate a credit-card-shaped string with the Luhn algorithm (logic only).
    ✨ Show Answer
    a9.js
    const luhn = s => {
        let sum = 0, alt = false;
        for (let i = s.length - 1; i >= 0; i--) {
            let d = +s[i];
            if (alt) { d *= 2; if (d > 9) d -= 9; }
            sum += d; alt = !alt;
        }
        return sum % 10 === 0;
    };
    console.log(luhn("4539578763621486"));
  10. Why use novalidate on a form when you do JS validation?
    ✨ Show Answer

    Answer: The native error bubbles look ugly and inconsistent across browsers. Adding novalidate tells the browser to skip its own UI — you call checkValidity/reportValidity programmatically and render errors with your own styling.

  11. Build a min/max age validator that returns specific messages.
    ✨ Show Answer
    a11.js
    const ageError = a => {
        if (Number.isNaN(+a)) return "not a number";
        if (a < 13) return "too young";
        if (a > 120) return "too old";
        return null;
    };
    ["hi", 5, 22, 200].forEach(a => console.log(a, ageError(a)));
  12. In one paragraph, explain why FormData is preferable to manual input.value reads.
    ✨ Show Answer

    Answer: FormData auto-collects every named input, supports files (which JSON cannot), respects checkbox/radio semantics, and can be sent directly as the body of a fetch request — the browser handles the multipart encoding for you. Manual reads scale badly and are easy to forget when fields are added.

Summary — Module 27

Forms collect user input. Use FormData + fetch to send. Pair HTML5 attributes with JS validators for great UX, but always re-validate on the server. Tag invalid inputs with aria-invalid and explain errors via aria-live.

FormData সব field সংগ্রহ করে; fetch-এর body-তে সরাসরি পাঠান। সব validation server-এও আবার চালান — কখনোই শুধু client trust করবেন না।

Next Module → Storage — localStorage, sessionStorage, cookies।