Forms and Delegation
Forms
A form is a <form> with fields and a button. When the user presses the button or Enter in a field,
the submit event happens, and by default the browser sends the form and reloads the page.
On modern websites this is almost always cancelled and the data is handled in JavaScript:
const form = document.querySelector("#signup");
form.addEventListener("submit", (e) => {
e.preventDefault(); // no reload
const name = form.querySelector("#name").value.trim();
console.log("Signing up", name);
form.reset(); // clear all form fields
});
Listen for submit on the form, not click on the button: this way both the button and Enter will work.
Validating fields
Before sending, the data is checked and an error is shown next to the field:
form.addEventListener("submit", (e) => {
e.preventDefault();
const age = Number(form.querySelector("#age").value);
const error = form.querySelector(".error");
if (!Number.isInteger(age) || age < 1 || age > 120) {
error.textContent = "Age must be a whole number from 1 to 120";
return;
}
error.textContent = "";
console.log("All good");
});
trim() removes spaces at the edges — otherwise a string of only spaces would pass the "not empty" check.
Event bubbling
An event that happened on an element then bubbles up: the handlers of its parent fire, then of
its parent, and so on up to document. A click on a button inside <div id="toolbar"> is also a click on
#toolbar, and on body.
e.target is the deepest element where it all started, and e.currentTarget is the one whose
handler is running right now.
Delegation
Instead of a handler on each of a hundred buttons, you attach one — to the common parent — and in it you check which button was clicked:
document.querySelector("#menu").addEventListener("click", (e) => {
const btn = e.target.closest("button");
if (!btn) return; // the click missed the buttons
console.log("Action:", btn.dataset.action);
});
closest(selector) looks for the nearest matching element, starting with e.target itself and going
up. This matters: if there is a <span> or an icon inside the button, e.target will be that, but closest
will still find the button.
Advantages of delegation:
- one handler instead of many;
- elements added later (new to-dos in the list) work by themselves — their handler is already on the parent;
- after a
render()that recreates all theli, nothing needs to be subscribed again.
State + render + delegation
Almost all small interfaces in plain JavaScript are built like this:
let items = []; // 1. the data — the single source of truth
function render() { // 2. the page is built only from the data
// clear and create elements from items
}
list.addEventListener("click", (e) => { // 3. events change the data…
// find the element via closest, change items
render(); // …and redraw the page
});
Never keep something important "only in the HTML" (for example, whether a to-do is done — only in the element's class): after a redraw it will be lost. The data lives in the array, the HTML is its reflection.
Summary
submit+e.preventDefault()— handle a form without a reload,form.reset()— clear it.- Events bubble: a click on a button is also a click on all its parents.
- Delegation: one handler on the parent +
e.target.closest(...). - Data in variables →
render()builds the page → events change the data and callrender().