Skip to content

Sponsor

Sponsor

What Is the Forms Module?

Quick Start (30 seconds)

html
<!-- Your HTML form -->
<form id="loginForm">
  <input name="email" type="email" required />
  <input name="password" type="password" required />
  <button type="submit">Login</button>
</form>
javascript
// Read all values instantly
const data = Forms.loginForm.values;
// → { email: 'user@example.com', password: 'secret' }

// Validate before submitting
const { isValid, errors } = Forms.loginForm.validate();

// Submit to your server
await Forms.loginForm.submitData({
  url: '/api/login',
  onSuccess: (result) => console.log('Logged in!', result)
});

That's it. No manual querySelector. No FormData boilerplate. No custom fetch setup. The Forms module does it all.


What Is the Forms Module?

Forms is a smart, proxy-based system that gives every <form> element on your page a rich API for reading values, validating fields, serializing data, and submitting to a server — all through a simple, consistent interface.

Think of it as giving your plain HTML forms superpowers — they can now read and write their own data, validate themselves, and even talk to your server, without any extra setup.


Why Does This Exist?

Working with Forms in Plain JavaScript

Without the Forms module, handling even a basic form requires a lot of repetitive work:

javascript
// Get values — manual and tedious
const form = document.getElementById('loginForm');
const email    = form.querySelector('[name="email"]').value;
const password = form.querySelector('[name="password"]').value;
const remember = form.querySelector('[name="remember"]').checked;

// Validate — you write all the logic yourself
let errors = {};
if (!email || !email.includes('@')) errors.email = 'Invalid email';
if (!password || password.length < 6) errors.password = 'Too short';

// Show errors — more manual DOM work
if (errors.email) {
  const field = form.querySelector('[name="email"]');
  field.classList.add('invalid');
  // inject an error element...
}

// Submit — wire up fetch yourself
if (Object.keys(errors).length === 0) {
  fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password, remember })
  })
  .then(res => res.json())
  .then(data => { /* handle */ })
  .catch(err => { /* handle */ });
}

Problems: ❌ Reading each field individually is tedious and error-prone ❌ Validation logic must be written from scratch every time ❌ Showing/clearing error messages requires manual DOM manipulation ❌ Fetch boilerplate repeats on every form in your project ❌ Forgetting one field means a bug that's hard to catch

The Forms Module Way

javascript
// Read all values in one go
const { isValid, errors } = Forms.loginForm.validate({
  email:    { required: true, email: true },
  password: { required: true, minLength: 6 }
});

if (isValid) {
  await Forms.loginForm.submitData({
    url: '/api/login',
    onSuccess: (result) => console.log('Done!', result),
    onError:   (error)  => console.error('Failed:', error)
  });
}

Benefits: ✅ All field values in one object — no manual querySelector ✅ Built-in validation with HTML5 + custom rules ✅ Error messages appear and clear automatically ✅ Fetch is handled for you — just pass a URL ✅ Works the same way for every form in your project


Mental Model: The Smart Form Assistant

Think of the Forms module like a dedicated assistant sitting at a front desk, managing every form in your office building.

Without Forms (you doing everything manually):
├── Walk to desk 1, ask for the name field value
├── Walk to desk 2, ask for the email field value
├── Check each value yourself
├── Write the error note and tape it to each desk
├── Package everything up and mail it
└── Handle the response yourself

With Forms (the smart assistant handles it):
├── "Hey Forms.contactForm — give me all the values"
│    → { name: 'Alice', email: 'alice@example.com' }
├── "Validate against these rules"
│    → { isValid: true, errors: {} }
└── "Submit to /api/contact"
     → handles fetch, errors, and responses for you

You describe what you want. The Forms module handles how to do it.


How Does It Work?

The Forms object is a JavaScript Proxy. When you write Forms.loginForm, it intercepts that property access and:

Forms.loginForm

Proxy intercepts "loginForm"

Looks up document.getElementById("loginForm")

Finds a <form> element — enhances it

Returns the enhanced form with full API

You call .values, .validate(), .submitData()

The enhancement pipeline runs once per form and adds all the methods:

Raw <form> element

Step 1: Core .update() from EnhancedUpdateUtility

Step 2: .values, .validate(), .reset(), .serialize(), .submitData()

Step 3: Form-aware .update() wrapper (handles values/validate/reset/submit keys)

Step 4: External enhancers (from 02_dh-form-enhance.js or your own plugins)

Fully enhanced <form> element ✨

The result is cached in a Map, so the second time you access Forms.loginForm, it returns the already-enhanced element instantly.


What Can the Forms Module Do?

Here's the complete list of capabilities added to every form:

FeatureHow to use
Read all valuesform.values
Write all valuesform.values = { name: 'Alice' }
Validate fieldsform.validate(rules)
Clear validationform.clearValidation()
Read a single fieldform.getField('email')
Write a single fieldform.setField('email', 'alice@example.com')
Serialize dataform.serialize('json')
Submit via fetchform.submitData(options)
Reset the formform.reset()
Update anythingform.update({ values, validate, text, style, ... })

Accessing a Form

The Forms global object is your entry point. Access any form by its id:

javascript
// HTML: <form id="myForm">...</form>

const form = Forms.myForm;     // get the enhanced form element
form.values;                    // read all values

You can also check if a form exists before using it:

javascript
if ('myForm' in Forms) {
  console.log('Form found:', Forms.myForm);
}

Global Utilities

Beyond per-form access, Forms also has global helpers:

javascript
// Get all forms on the page (enhanced)
const allForms = Forms.getAllForms();

// Validate every form at once
const results = Forms.validateAll();

// Reset every form
Forms.resetAll();

// Check cache stats
console.log(Forms.stats());
// → { hits: 5, misses: 2, cacheSize: 3, hitRate: 0.71 }

The Complete Forms Namespace

Forms
├── Forms.formId          → access any form by its HTML id
├── Forms.getAllForms()   → array of all enhanced forms
├── Forms.validateAll()  → validate every form at once
├── Forms.resetAll()     → reset every form at once
├── Forms.stats()        → cache hit/miss statistics
├── Forms.clear()        → clear the internal cache
├── Forms.configure()    → update module options
└── Forms.addEnhancer()  → register a plugin function

Per-form API (available on every Forms.formId):
├── .values              → getter/setter for all field data
├── .validate(rules)     → run validation
├── .clearValidation()   → remove error state
├── .getField(name)      → get a single field element
├── .setField(name, val) → set a single field value
├── .serialize(format)   → 'object' | 'json' | 'formdata' | 'urlencoded'
├── .submitData(options) → async fetch submission
├── .reset(options)      → reset + clear validation
└── .update(object)      → form-aware update method

Key Principle

"Access a form by its ID, then call methods on it — just like you would on any well-designed JavaScript object."

Forms.loginForm is not magic — it's a Proxy doing smart DOM lookups and caching. Once you have the form, everything else is a straightforward method call.


What's Next?

Now that you understand what the Forms module is and how it's structured:

  • Reading and Writing Values — using .values getter and setter
  • Validation — built-in rules and custom validators
  • Serialization — converting form data to JSON, FormData, etc.
  • Submission — async fetch with lifecycle hooks
  • The Enhanced Pipeline — advanced submission with loading states and retries