Lecture 4 of 1418 minInteractiveFree lecture

JavaScript and events

JavaScript makes pages interactive by finding elements, changing them and reacting to events. Follow a click as it travels through the DOM — capture, target, bubble — and learn the delegation pattern used by every serious front-end codebase.

In this lecture you will

  • Select and update DOM elements with JavaScript
  • Attach event listeners and use the event object
  • Explain the capture, target and bubble phases of event propagation
  • Use event delegation and preventDefault correctly

HTML describes the page and CSS styles it. JavaScript makes it respond: a menu opens, a form validates, a list updates without reloading. In the browser, almost all JavaScript follows one pattern — find an element, listen for something to happen, then change the page.

Finding and changing elements

const counter = document.querySelector('#cart-count');   // find
const button  = document.querySelector('.add-to-cart');

button.addEventListener('click', () => {                  // listen
  const n = Number(counter.textContent) + 1;
  counter.textContent = n;                                 // change
  counter.classList.add('bump');                           // restyle
});
  • querySelector takes any CSS selector and returns the first match; querySelectorAll returns all of them.
  • textContent reads or replaces an element’s text. It is safe for user-supplied text, because it never interprets it as HTML.
  • classList.add, remove and toggle switch CSS classes — the cleanest way to change appearance from JavaScript.

Listening for events

The browser fires events for almost everything: click, input, submit, keydown, scroll, load and many more. addEventListener registers a function to run each time. The function receives an event object:

event.target
The element where the event actually happened — for example, the exact button clicked.
event.currentTarget
The element whose listener is running right now. With delegation, this is the parent.
event.preventDefault()
Cancels the browser’s default action, such as following a link or submitting a form.
event.stopPropagation()
Stops the event travelling any further through the DOM.

Follow a click through the DOM

A click on a button doesn’t only happen on the button. The event travels through the DOM in three phases. Step through them:

InteractiveCapture, target and bubble
Loading visual…
  1. A click creates an eventThe user clicks Delete. The browser creates a click event whose target is the button.
  2. Capture: down from the windowThe event travels from window down through document, body and every ancestor toward the target. Few listeners use this phase, but it exists.
  3. Target: the button’s listenersAt the target, listeners registered on the button run.
  4. Bubble: back up the treeThe event then bubbles up through li, ul, div, body and document. Any ancestor with a click listener — like the list — gets a chance to react.
  5. stopPropagationIf the li’s listener calls event.stopPropagation(), the event stops there: the list and everything above it never see it. Use sparingly — it can break other code that relies on bubbling.
  6. Event delegationOne listener on the list handles clicks for every task, including tasks added later. It checks event.target to find which button was clicked.

Event delegation in practice

const list = document.querySelector('.tasks');

list.addEventListener('click', (event) => {
  const button = event.target.closest('button[data-action="delete"]');
  if (!button) return;                       // click was somewhere else
  button.closest('li').remove();
});

closest walks up from the clicked element to find the nearest matching ancestor, so it works even when the click lands on an icon inside the button. One listener, any number of tasks.

Try both approaches on a real list. Click the icon and the text of a button and compare target with currentTarget, then add a task and try to delete it:

InteractiveOne listener or many?Click Delete, then add a task
Loading visual…

These are real listeners on real elements. With one listener per button, anything added after setup is silently dead — a common bug in pages that load content later. Delegation handles it, because the event bubbles up to the list whichever button it started on.

Forms and preventDefault

form.addEventListener('submit', async (event) => {
  event.preventDefault();                    // stay on the page
  const data = Object.fromEntries(new FormData(form));
  await saveProfile(data);
  status.textContent = 'Saved';
});

Without preventDefault, the browser would submit the form the old-fashioned way and reload the page. The async and await keywords here are the subject of the next lecture.

Handling frequent events

Some events fire far more often than you need. Typing a search term fires an input event for every keystroke; scrolling and resizing fire dozens of times a second. Sending a request to the server for each one wastes work and can return results out of order. Two small patterns fix this:

Debounce
Wait until the events stop for a moment, then run once — ideal for search-as-you-type and validating a field.
Throttle
Run at most once per interval while events keep coming — ideal for scroll and resize handlers that must stay responsive.

Type in the box, or simulate a fast typist, and count the calls:

InteractiveDebounce and throttleType, or simulate fast typing
Loading visual…

Real timers record each call. With a 300 ms wait, typing a server name fires a dozen input events, but the debounced search runs only after each pause. Throttling keeps calling at a steady rate, including once at the end, so the final value is never lost.

Key takeaways

  • querySelector finds elements; properties like textContent and classList change them.
  • addEventListener runs a function when an event happens; the event object describes what happened.
  • Events travel down to the target (capture) and back up (bubble), so ancestors can respond to clicks on their descendants.
  • Event delegation — one listener on a parent — handles many children, including ones added later.

Check your understanding

3 questions · answers explained

Q1A list of 500 tasks each has a Delete button. What is the most efficient way to handle clicks?

Thanks to bubbling, clicks on any button reach the list. One delegated listener inspects event.target — and it keeps working for tasks added later, without attaching new listeners.

Q2What does event.preventDefault() do when called in a form’s submit listener?

preventDefault cancels the browser’s built-in behaviour for that event, such as navigating or submitting. Stopping propagation is a different method, stopPropagation.

Q3In the bubble phase, in what order do listeners run for a click on a button inside li inside ul?

Bubbling starts at the target and moves outward through its ancestors — button first, then li, then ul, and so on up to the document and window.