Stage 2: Frontend Fundamentals

JavaScript · Lesson 07

Events

addEventListener, the event object, form events and event delegation.

JavaScript2 min readFree lesson

An event is the browser telling your code that something happened: a click, a key press, a form submission, the page finishing loading. Listening for events is what turns a static document into an interface.

Learning objectives

  • Attach handlers with addEventListener.
  • Read the event object and stop default browser behaviour.
  • Understand bubbling, and use delegation instead of one listener per element.
  • Handle form, keyboard and input events correctly.

1. addEventListener

const button = document.querySelector("#save");

button.addEventListener("click", (event) => {
  console.log("clicked", event.target);
});

event.target is the element that fired the event; event.currentTarget is the element the listener is attached to. They differ when events bubble.

2. Forms: preventDefault

A <form> submits to the server and reloads the page by default. Single-page apps intercept it:

const form = document.querySelector("#signup");

form.addEventListener("submit", (event) => {
  event.preventDefault();

  const data = new FormData(form);
  const email = String(data.get("email") ?? "").trim();

  if (!email.includes("@")) {
    document.querySelector("#error").textContent = "Enter a valid email.";
    return;
  }

  console.log({ email });
});

Always call event.preventDefault() before any await; after an await the event may already have been handled by the browser.

3. Input and keyboard events

const search = document.querySelector("#search");

search.addEventListener("input", (event) => {
  console.log(event.target.value); // fires on every keystroke
});

document.addEventListener("keydown", (event) => {
  if (event.key === "Escape") closeDialog();
  if (event.key === "Enter" && event.ctrlKey) submit();
});

Use input for live values. change only fires when the field loses focus, which feels broken in a search box.

4. Bubbling and delegation

Events bubble from the target up through its ancestors. That means one listener on a list can handle clicks on every item:

const list = document.querySelector("#tasks");

list.addEventListener("click", (event) => {
  const button = event.target.closest("button[data-action]");
  if (!button) return;

  const item = button.closest("li");
  if (button.dataset.action === "delete") item.remove();
  if (button.dataset.action === "done") item.classList.toggle("done");
});

This works for items added after the listener was attached — which is exactly why delegation is the standard pattern for dynamic lists.

5. Removing listeners and cleanup

function onScroll() { /* ... */ }

window.addEventListener("scroll", onScroll);
// later
window.removeEventListener("scroll", onScroll);

The function reference must be identical. removeEventListener("scroll", () => {}) does nothing.

Common mistakes

  • Forgetting preventDefault in a submit handler.
  • Attaching a listener inside a loop to every item instead of using delegation.
  • Reading event.target.value when the target is a child element — use event.currentTarget.value.

Practice

Write the character counter and the delegated to-do list described below. Both are small, and both patterns appear in every real application.

Assignment

Assignment 07 — Events in practice

Build one page with two independent widgets.

1. Character counter

  • A <textarea> with a 200-character limit.
  • A live counter under it: 42 / 200.
  • The counter turns red when fewer than 20 characters remain.
  • The submit button is disabled while the field is empty or over the limit.

Use the input event, not change.

2. Delegated to-do list

  • An input and an Add button that append a new <li>.
  • Each <li> contains the task text plus a Done and a Delete button.
  • One click listener on the <ul> handles both buttons using event.target.closest("button[data-action]").
  • Done toggles a done class; Delete removes the <li>.

Constraints

  • Do not attach a listener to each new <li>.
  • Do not use inline onclick attributes.
  • Use textContent for the task text (never innerHTML).

Done when

  • Adding a task after the page has loaded still works with the same single listener.
  • Pressing Enter in the input adds the task as well as clicking the button.

Learn this lesson with a mentor

Work through the Build Your Homepage series on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now