Step 05

Forms: events that do work

Everything so far has been events for their own sake — logs and counters proving the model works. Forms are where events start earning a paycheck. A form is the platform's original interactive component: it collects values, validates them, and submits them, all before you write a line of script. Your script's job is to listen at the right moment, read what the user entered, and decide what happens next.

Learning Objectives

  • Handle submit on the form — not click on the button — and know why
  • Cancel navigation with preventDefault() and keep the page
  • Read fields by hand: value, checked radios, and selects
  • Read a whole form at once with new FormData(form)
  • Lean on constraint validation: checkValidity(), reportValidity()

Listen for submit, not click

A form submits in more ways than one: clicking the submit button, yes, but also pressing Enter in a text field. Listen for click on the button and you miss the keyboard path entirely. The submit event fires on the form for every path, which makes it the one true place to intervene:

form.addEventListener('submit', (e) => { e.preventDefault(); // stay on this page; we handle it if (!form.checkValidity()) { form.reportValidity(); // browser shows the messages out.textContent = 'Please fix the errors above.'; return; } // read the values, do the work... });

preventDefault() here cancels the browser's default action — packaging the fields and navigating to the form's action URL. That default is the right behavior for a classic server-rendered form; cancel it only because you are taking over the job.

Validation comes free. The demo form is marked novalidate so the script decides when to check — but the checking itself is still the platform's. checkValidity() asks whether the required, type="email", and friends are satisfied; reportValidity() shows the browser's own error UI. No validation library required.

Reading fields by hand

The manual approach reads each control through the DOM. Text inputs and textareas hand over value; a radio group needs a query for whichever one is :checked; a select's value is the chosen option:

const username = document.getElementById('username').value; const contact = document.querySelector('input[name="contact"]:checked')?.value || 'Not selected'; const country = document.getElementById('country').value;

The ?. matters: an untouched radio group has no checked member, and querying for one returns null. Note also what the demo does not do — echo the password back. It masks it before display. Small habit, right instinct.

Or let FormData do it

Field-by-field reading scales badly — every new control means another line of script. FormData reads the entire form in one constructor call, keyed by each control's name, exactly the way a server would receive it:

const formData = new FormData(form); for (const [key, val] of formData.entries()) { if (val instanceof File) { lines.push(`${key}: [File] name="${val.name}", size=${val.size} bytes`); } else { lines.push(`${key}: ${val}`); } }

File inputs contribute real File objects, unchecked checkboxes contribute nothing (just like a real submission), and when the network enters the story, the same object goes straight out the door: fetch(url, { method: 'POST', body: formData }).

Applied

Two small, real uses of the same machinery. First, the classic intro-course exercise done the platform way: read a number, convert on click, announce the result through a live region — and guard the empty field, because Number('') is 0, not an error.

Second, autocomplete without a library: a <datalist> gives the input native suggestions, and one input listener echoes the value as it changes. Before reaching for an autocomplete widget, check whether the platform already ships one.

Next Steps

So far you have only listened to events the browser invented. In Step 6: Custom events, you mint your own:

  • Create events with new CustomEvent(type, { detail })
  • Namespace your event names and route them through one listener
  • Build components that announce themselves instead of being polled