LEARN TO CODE | JAVASCRIPT | 03 INTERACTIVE PAGES

03.3 Forms and input validation

Read form values, reject missing or unsuitable input and produce an honest preview without navigation or a false claim that data was saved.

01 | A FORM HAS A PURPOSE

A club needs a simple ticket-cost preview. The visitor enters a practice name, email address and number of tickets, then the page calculates a total.

This project is a browser-only preview . It does not reserve places, send email, charge money or save personal data. The output will say Preview only so the visitor understands what happened.

Validation checks whether input meets the rules for a task. It cannot prove that a visitor is honest or that an email address belongs to them.

In VS Code, create index.html, script.js and, when shown, style.css in one practice folder. Save changes, open the HTML page in your browser and inspect both the page and its Console. Replace the previous example unless a task explicitly extends it.

02 | STATE THE RULES FIRST

A small, testable form specification
Field Rule
Practice name 1 to 40 characters after trimming surrounding spaces
Practice email Non-blank, at most 254 characters, acceptable to the browser email constraint
Quantity Whole number from 1 to 6
Output Quantity × 6 credits, shown as a preview

Our JavaScript length checks count UTF-16 code units, as ordinary string.length does, not necessarily user-perceived characters. A product handling all languages needs a deliberate text-length policy. This example keeps the rule explicit.

The email input checks a browser-supported address shape. It does not send a verification message or establish mailbox ownership.

03 | USE THE FORM’S SUBMIT EVENT

Listen to submit on the form, rather than only click on its button. Submission may also be triggered by Enter in a field or another supported browser interaction.

form.addEventListener("submit", (event) => {
  event.preventDefault();
  // Read, validate, then display the local preview.
});

This fragment assumes form is the selected form element. A normal form submission may navigate away or send data. preventDefault cancels that default action for this event, allowing our local preview to stay on the same page.

It does not automatically validate, save or secure anything. It also does not stop event propagation. Those are separate behaviours.

04 | HTML CONSTRAINTS HELP THE VISITOR

<input id="quantity" type="number" min="1" max="6" step="1" required>
<input id="email" type="email" required>

required rejects an empty field. min and max constrain the numeric range. step=1 requires a whole-number step from the permitted base. type=email enables the browser’s email-format check.

The complete form uses novalidate so automatic interactive validation does not prevent our submit handler from running first. The handler then explicitly calls reportValidity after its own checks. novalidate does not remove the constraints; it changes when the browser automatically uses them.

reportValidity returns whether the constraints are satisfied and can show the browser’s field guidance. checkValidity checks without the same interactive report.

05 | READ TEXT BEFORE CONVERTING

An input’s value is text even when the control is type=number. A blank string converted with Number becomes 0, so check blank input separately before conversion.

const quantityText = "3";
const quantity = Number(quantityText);
console.log(Number.isInteger(quantity));
console.log(quantity >= 1 && quantity <= 6);

Number.isInteger rejects fractional numbers, NaN and infinities. The range checks reject zero, negatives and too-large quantities. parseInt alone would be unsuitable as validation because it can accept only the initial integer part of text such as "3.5".

trim removes surrounding whitespace from a string. A name made only of spaces should fail the non-blank requirement even though a basic required text field can regard those spaces as input.

06 | RETURN A RESULT FROM VALIDATION

readBooking in the complete program returns an object. A failure has ok:false and a message; a success has ok:true and the cleaned fields. This gives the caller a clear decision rather than mixing calculation, display and validation in one long block.

It checks names, email length and numeric quantity. The browser’s reportValidity supplies the additional email-format and control constraints. readBooking by itself is therefore not a complete email validator.

Returning immediately on failure prevents the later cost calculation from using unsuitable data. The form is left populated so the visitor can correct it.

07 | COMPLETE COST-PREVIEW PROGRAM

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>A workshop cost preview</title>
  <link rel="stylesheet" href="style.css">
  <script src="script.js" defer></script>
</head>
<body>
  <main>
    <h1>A workshop cost preview</h1>
<p>Local practice only. This preview does not reserve places, send email or save personal data. Use fictional details.</p>
<form id="booking-form" novalidate>
  <p><label for="booking-name">Practice name : </label> <input id="booking-name" name="name" required maxlength="40" autocomplete="off" aria-describedby="booking-name-note"></p>
  <p id="booking-name-note">Enter 1 to 40 characters after trimming surrounding spaces.</p>
  <p><label for="booking-email">Practice email : </label> <input id="booking-email" name="email" type="email" required maxlength="254" autocomplete="off"></p>
  <p><label for="booking-quantity">Tickets, 1 to 6 : </label> <input id="booking-quantity" name="quantity" type="number" min="1" max="6" step="1" required value="1"></p>
  <button type="submit">Preview cost</button>
</form>
<p id="booking-result" role="status" aria-live="polite">No preview yet.</p>
  </main>
</body>
</html>

script.js

const form = document.querySelector("#booking-form");
const nameInput = document.querySelector("#booking-name");
const emailInput = document.querySelector("#booking-email");
const quantityInput = document.querySelector("#booking-quantity");
const result = document.querySelector("#booking-result");
const priceInCredits = 6;

function readBooking(nameText, emailText, quantityText) {
  const name = nameText.trim();
  const email = emailText.trim();
  if (name.length < 1 || name.length > 40) {
    return { ok: false, message: "Enter a name of 1 to 40 characters." };
  }
  if (email === "" || email.length > 254) {
    return { ok: false, message: "Enter a practice email address up to 254 characters." };
  }
  if (quantityText.trim() === "") {
    return { ok: false, message: "Enter a ticket quantity." };
  }
  const quantity = Number(quantityText);
  if (!Number.isInteger(quantity) || quantity < 1 || quantity > 6) {
    return { ok: false, message: "Choose a whole number of tickets from 1 to 6." };
  }
  return { ok: true, name, email, quantity };
}

form.addEventListener("submit", (event) => {
  event.preventDefault();
  result.textContent = "Checking this preview...";
  const booking = readBooking(nameInput.value, emailInput.value, quantityInput.value);
  if (!booking.ok) {
    result.textContent = booking.message;
    return;
  }
  // Use the browser's email/required/range rules as well as our own checks.
  if (!form.reportValidity()) {
    result.textContent = "Check the highlighted field. No preview was created.";
    return;
  }
  const total = booking.quantity * priceInCredits;
  result.textContent = `${booking.name}: ${booking.quantity} ticket(s) cost ${total} credits. Preview only.`;
});

08 | TRY THE FORM

Local practice only. This preview does not reserve places, send email or save personal data. Use fictional details.

Enter 1 to 40 characters after trimming surrounding spaces.

No preview yet.

The preview stays on this page and uses textContent for the visitor’s name. There is no network request or storage operation in this program.

TRY IT YOURSELF

Use a fictional email such as alex@example.com and compare quantities 1, 6 and 7. Then try a blank or spaces-only name.

Reveal guidance

1 costs 6 credits and 6 costs 36 credits. 7 is rejected, as is a blank or spaces-only name. An invalid attempt replaces any earlier success text so an outdated preview does not look current.

09 | DISPLAY INPUT AS TEXT

Enter <b>Alex</b> as the practice name. The preview should display those characters, without creating a bold element. textContent is used deliberately for user-controlled text.

Do not interpolate visitor input into innerHTML to make the preview look formatted. Create separate elements if needed and put user text into their textContent.

Validation and output handling solve different problems. A valid name can still contain characters that have meaning in HTML; accepting a name does not authorise parsing it as markup.

10 | CLIENT CHECKS ARE NOT SERVER PROTECTION

The visitor controls their browser and can bypass or alter JavaScript. Client-side validation improves feedback and avoids mistakes, but a real backend must independently validate and authorise received data.

For a real booking system, the server must check availability when committing a booking, handle concurrent requests and return the actual result. A successful local multiplication proves none of those things.

This distinction lets you describe the project honestly: a responsive cost preview with validation, rather than a reservation service.

11 | RECORD A USEFUL TEST SET

Test both boundaries and recovery
Test Expected
Name spaces only Reject with a name message
Malformed email Browser email constraint rejects
Quantity blank / 0 / 7 / 1.5 Reject; no successful preview
Quantity 1 / 6 Preview 6 / 36 credits
Name containing angle brackets Literal text; no injected markup
Invalid attempt after valid preview Replace old result with current guidance

TRY IT YOURSELF

Add a fixed 2-credit fee. Which parts of the program and test evidence need to change?

Reveal guidance

Change the total calculation to quantity * priceInCredits + 2 and make the fee visible in the specification/output. Boundary totals become 8 and 38. Input rules remain the same unless the requirements also change.

KEEP LEARNING

Previous page 03.4 Browser storage and JSON

Download Section 03 example programs

Further reading: The DOM · Event listeners · Form validation · Browser storage