Learn by reading through in order

Forms — Input Values and submit

Read fields with value and checked, respond to input and change events, cancel a submit with preventDefault, and collect fields with FormData.

You can handle clicks with addEventListener, but with a form you also need to read the values typed into its fields and check them before sending. Clicking the submit button makes the browser load a new page, so you also need a way to keep the form from being sent before you've finished checking.

This article covers value, which reads a field's value, and the submit event, which fires when a form is submitted.

Reading Entered Values — value and checked

Suppose you want to read the number entered in the quantity field of an order form (a form element that groups fields and a submit button) and calculate the total price. An input element has no closing tag and can't contain child elements or text, so reading its textContent only gives you an empty string.

Fields are input elements (their type attribute sets the kind of field) or textarea elements for multi-line text. You read a field's contents with its value (a property that gets or sets the field's current value as a string), as in quantity.value, and whether a checkbox is checked with checked. The value attribute written in the HTML is the initial value when the page opens, and typing into the field doesn't change it.

document.body.innerHTML = `<form id="order-form"><input id="quantity" type="number" value="1"><input id="gift-wrap" type="checkbox"></form>`;
const quantity = document.querySelector("#quantity");
const giftWrap = document.querySelector("#gift-wrap");

// Pretend the user typed 3 and checked the gift-wrap box
quantity.value = "3";
giftWrap.checked = true;

// value is a string even in a number field, so convert it before calculating
console.log(quantity.value + 1);              // 31
console.log(Number(quantity.value) * 1200);   // 3600
console.log(giftWrap.checked);                // true

// outerHTML still shows the value attribute written in the HTML
console.log(quantity.outerHTML);   // <input id="quantity" type="number" value="1">
The Two values of the quantity input
value attributevalue="1"written in HTMLStays at 1after typingouterHTMLshows value="1"value propertyCurrent input"3"Changes withevery keystrokeRead it withquantity.value
The assigned "3" only goes into the value property; the value attribute stays at 1. Read entered values from the value property.

The same goes for checked: checking the box doesn't add a checked attribute to the HTML. If you clear the quantity field, value becomes an empty string, and Number("") returns 0, so once you've converted it to a number, you can't tell an empty field from an entered 0.

For a car rental booking, calculate the price from the number of days and whether a child seat is needed. The days field rentalDays, which already has a value assigned, and the checkbox childSeat are already declared.

① Print the type of the days field's value.

② Convert the days to a number and print the price at 6000 yen per day.

③ If the box is checked, add 500 yen per day and print the total for the whole rental.

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

Recounting on Every Keystroke — the input and change Events

A company blog's post editor shows a character count like "13 / 40" under the title field, and it should update as the user types. If you read value only once when the page opens, characters typed afterward never show up in the count.

A field fires an input event every time a character is typed, and a change event when its value has changed and focus leaves the field. You can't type in the console, so you fire an event with the same name using dispatchEvent (a method that fires the given event on that element). You create the event to pass by specifying its name, as in new Event("input").

document.body.innerHTML = `<form id="post-form"><input id="post-title"><p id="title-count">0 / 40</p></form>`;
const postTitle = document.querySelector("#post-title");
const titleCount = document.querySelector("#title-count");

// input: recount the characters on every keystroke
postTitle.addEventListener("input", () => {
  titleCount.textContent = `${postTitle.value.length} / 40`;
});
// change: save a draft when the value has changed and focus leaves the field
postTitle.addEventListener("change", () => console.log("Draft saved"));

// Just assigning to value
postTitle.value = "Weekly report";
console.log(titleCount.textContent);   // 0 / 40

// Firing an event calls the listeners registered under the same name
postTitle.dispatchEvent(new Event("input"));
console.log(titleCount.textContent);   // 13 / 40
postTitle.dispatchEvent(new Event("change"));   // Draft saved
Assigning Alone Doesn't Update the Count
postTitle.value= "Weekly report"No input eventfiresThe listenerisn't calledStays at0 / 40dispatchEvent(…)after assigningAn input eventfiresThe listener readsvalue.lengthUpdated to13 / 40
Assigning to value doesn't fire an input event, so the count stays at 0 / 40. Firing the event with dispatchEvent calls the listener.

The input listener is called for every single character, so work that doesn't need to happen on each keystroke, like saving a draft, belongs in change, which fires when the user finishes typing. The table below shows which of the two events, input or change, each user action fires.

Actioninput Eventchange Event
Type a character in a text fieldFires on each characterDoesn't fire
Leave the field (click out or Tab)Doesn't fireFires if the value changed
Click a checkbox to toggle itFiresFires

On a checkout screen, show the address fields only when "Ship to a different address" is checked. The checkbox otherAddress and the address fields addressFields are already declared.

① Register a listener that removes the hidden class from the address fields when the box is checked.

② Before triggering a click, print the address fields' HTML.

③ Trigger a click on the checkbox, then print whether it's checked and the address fields' HTML.

JavaScript / TypeScript Editor

Run code to see output

Canceling a Submission — submit and preventDefault

A newsletter sign-up form should show an error message and block submission when the email address has no @. When the submit button is clicked, the browser sends the form's contents and navigates to a new page, so even if your listener puts an error message on the page, it disappears along with the page.

Right before a form is submitted, a submit event fires on the form element. If you call preventDefault (a method that cancels the action the browser would take after the event) in that listener, the form isn't submitted. You can check whether the cancellation took effect with the event's defaultPrevented (a property that's true if it did).

document.body.innerHTML = `<form id="newsletter-form"><input id="newsletter-email"><button>Subscribe</button><p id="email-error"></p></form>`;
const newsletterForm = document.querySelector("#newsletter-form");
const emailInput = document.querySelector("#newsletter-email");
const emailError = document.querySelector("#email-error");

// Called right before submission. Cancels it only when there's no @
newsletterForm.addEventListener("submit", (event) => {
  if (!emailInput.value.includes("@")) {
    event.preventDefault();
    emailError.textContent = "The email address has no @";
  }
});

// Instead of a real submission, create and fire a cancelable submit event
emailInput.value = "alice.example.com";
const submitEvent = new Event("submit", { cancelable: true });
newsletterForm.dispatchEvent(submitEvent);
console.log(submitEvent.defaultPrevented);   // true (preventDefault was called)
console.log(emailError.textContent);         // The email address has no @
How @ and preventDefault Change the Outcome
The submitlistener is calledNo @:alice.example.comHas @:alice@example.comNo @:alice.example.compreventDefault()and an errorpreventDefault()isn't calledpreventDefault()was forgottenNot submitted;the error staysThe browsersubmits itSubmitted; theerror is gone too
Calling preventDefault() when there's no @ keeps the error message on the page. If you forget to call it, the browser navigates away and the error message goes with it.

The left and right columns have the same input; the only difference is whether preventDefault() was called. submit fires on the form element, not the submit button, so register the listener on the form. The same listener also handles submissions made by pressing Enter in a field.

Calling click() on a Submit Button Really Submits

A button inside a form becomes a submit button if you don't specify its type, and calling click() on it without canceling actually submits the form, and you won't see any results until the page reloads. A submit fired with dispatchEvent isn't actually sent, but without { cancelable: true }, preventDefault() has no effect.

Check that a new password is long enough. passwordForm, newPassword, and passwordError are already declared.

① Register a listener that cancels the submission and sets an error message if the password is shorter than eight characters, and clears the message otherwise.

② With "cat2026", fire a cancelable submit event and print whether it was canceled.

③ With "cat2026!", create the event again, fire it, and print whether it was canceled and the error field's HTML.

JavaScript / TypeScript Editor

Run code to see output

Collecting All the Field Values at Once — FormData

Now take a shipping address form, where you want to gather the recipient's name, postal code, and whether to leave the package at the door into a single object. If you use querySelector and value for each field, every new field means another line of code to read it. When a form is submitted, each field's name attribute (the name given to its value) is sent paired with its value.

You create a FormData (an object that collects name–value pairs, using the name attributes of the fields in a form) with new FormData(form), and read a value by name with get("zip"). If you pass it to Object.fromEntries (a method that, as the reverse of Object.entries, builds an object from a list of [name, value] pairs), you get one object holding all the fields.

document.body.innerHTML = `<form id="shipping-form">
  <input id="address-search" value="Shibuya">
  <input name="recipient" value="Alice Smith">
  <input name="zip" value="150-0001">
  <input name="dropOff" type="checkbox">
  <input name="notify" type="checkbox" checked>
</form>`;
const shippingForm = document.querySelector("#shipping-form");

// Collect name–value pairs from the fields in the form
const shippingData = new FormData(shippingForm);
console.log(shippingData.get("zip"));       // 150-0001
console.log(shippingData.get("dropOff"));   // null

// Build an object from the name–value pairs
const shipping = Object.fromEntries(shippingData);
console.log(JSON.stringify(shipping));
// {"recipient":"Alice Smith","zip":"150-0001","notify":"on"}
FieldConditionValue in FormData
Address search fieldNo name attributeNot included
recipient and zipHas a name; text fieldAlice Smith and 150-0001
dropOffHas a name; unchecked boxNot included; get returns null
notifyHas a name; checked boxon (when value isn't set)

Like a Map, FormData lets you iterate over its [name, value] pairs in order, so you can pass it straight to Object.fromEntries. As long as you give new fields a name attribute, you don't need to add any lines to read them.

Printing a FormData Directly Shows {}

The exercise console converts objects to JSON strings before printing them, so if you pass a FormData directly, as in console.log(shippingData), it shows {} even though the field values have been collected. Check its contents by reading them with get, or by turning it into an object with Object.fromEntries first.

On a job site's application form, check what was entered before it's sent. The form applyForm is already declared.

① In a submit listener, cancel the submission, gather the field values into an object, and print it as a JSON string.

② In the same listener, read the value of the email opt-in field, and print "No consent" if there's no value.

③ Fire a cancelable submit event.

JavaScript / TypeScript Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1If you type three characters in a text field and then press Tab to move on, how many times do input and change fire?

Q2What does calling event.preventDefault() in a form's submit listener cancel?

Q3For an unchecked checkbox with name="dropOff", what does FormData's get("dropOff") return?