VS Vikesh Singh

Courses JavaScript Lesson 8

JAVASCRIPT · LESSON 8 OF 18

Events: Clicks, Forms, and Listening Properly

· 4 min read

Selecting and changing elements, from the last lesson, makes pages dynamic — but pages become interactive when they respond to the user. T

Selecting and changing elements, from the last lesson, makes pages dynamic — but pages become interactive when they respond to the user. That response mechanism is the event system: clicks, typing, form submissions, page loads, and more. Learning to listen for events properly, and avoid the sloppy habits beginners pick up from old tutorials, is the goal here.

Listening For Events With AddEventListener

The standard, modern way to listen for an event is addEventListener.

<button id="save">Save</button>
const button = document.querySelector("#save");
button.addEventListener("click", function () {
  console.log("Saved by Vikesh Singh");
});

addEventListener takes the event name ("click", with no "on" prefix) and a function to run when it fires. You'll sometimes see older code use onclick="..." directly in HTML, or button.onclick = function() {...} in JavaScript — both work, but both have a real limitation: they only let you attach one handler per element. addEventListener lets you attach as many as you want, and lets you cleanly remove a specific one later with removeEventListener, which is why it's the standard today.

The function passed to addEventListener receives an event object, which carries useful details about exactly what happened.

button.addEventListener("click", function (event) {
  console.log(event.type);
  console.log(event.target);
});

event.type tells you what kind of event fired ("click" here), and event.target tells you exactly which element triggered it — essential once one handler is listening for events from multiple elements, which is a technique called event delegation, covered below.

Handling Form Submissions

Forms deserve special care, because of one extremely common beginner mistake: not preventing the page from reloading on submit.

<form id="contact-form">
  <input type="email" id="email" required>
  <button type="submit">Send</button>
</form>
const form = document.querySelector("#contact-form");
form.addEventListener("submit", function (event) {
  event.preventDefault();
  const email = document.querySelector("#email").value;
  console.log("Submitting for:", email);
});

Without event.preventDefault(), submitting a form does the browser's default behavior — reloading the page (or navigating to the form's action URL) — which wipes out any JavaScript state and makes it look like nothing happened. Calling preventDefault() stops that default behavior so your own JavaScript can handle the submission instead, for example by sending the data with fetch (covered in a later lesson) without a full page reload.

Event Delegation For Dynamic Elements

Now, event delegation — a pattern that solves a real, common problem: handling clicks on elements that don't exist yet when the page first loads, such as list items added dynamically (exactly like the DOM lesson's example).

<ul id="post-list">
  <li data-id="1">JavaScript Basics</li>
  <li data-id="2">Advanced CSS</li>
</ul>
const list = document.querySelector("#post-list");
list.addEventListener("click", function (event) {
  if (event.target.tagName === "LI") {
    console.log("Clicked post id:", event.target.dataset.id);
  }
});

Instead of attaching a click listener to every single <li> (which would miss any <li> added later), we attach one listener to the parent <ul>. This works because of "event bubbling" — when you click a child element, the event doesn't just fire on that element; it also fires on each of its parent elements, bubbling upward. We catch it at the <ul> level and check event.target to see which specific child was actually clicked. This single pattern is how most real-world dynamic lists handle clicks efficiently, and it automatically covers new items added later with no extra code.

A practical tip on naming: dataset.id above reads a data-id="..." attribute straight off the element. Using data-* attributes to store small bits of information directly on DOM elements, and reading them through .dataset, is a clean, standard way to connect your HTML to your JavaScript logic without inventing your own system.

Keyboard Events: Keydown, Keyup, Input

It's worth knowing the difference between keydown, keyup, and keypress events too, since forms often need to react while someone is typing, not just after. keydown fires the moment a key is pressed, before the character even appears in the input; keyup fires right after the key is released, once the value has updated. For something like a live character counter under a text box, you'd listen for keyup (or the more modern "input" event, which fires on any value change including paste) rather than keydown, since you want to count the character that's actually there after the keystroke lands, not before.

A final practical habit: when attaching a listener, always ask what element should logically "own" that behavior, rather than attaching it to whatever happens to be nearby. A save button's click behavior belongs on the button; a form's overall submission behavior belongs on the form element itself, not on its submit button specifically — this distinction keeps your event-handling code organized the same way the DOM itself is organized, which pays off once a page has many interactive pieces.

A small challenge: build a small list of buttons where clicking any button logs its own text to the console, using one listener attached to their shared parent rather than one listener per button. Then add a new button dynamically with appendChild and confirm clicking it also works, without adding any new listener for it.