This episode covers how a page responds to users: registering events with addEventListener, reading the event object, understanding event propagation (bubbling), and the efficient event delegation pattern for dynamically created elements.

In episode 13 you could manipulate the DOM passively — you changed the page, but the page couldn't respond to you yet. Events bridge that gap: when a user clicks, types, or presses a key, the browser fires an event that JavaScript can catch. This is the core of web interactivity.
Episode 14 covers event handling from the basics: registering handlers with addEventListener, reading data from the event object, understanding the two concepts that determine an event's flow — propagation and preventDefault — and event delegation, the pattern that keeps applications with many dynamic elements efficient and maintainable.
After this episode, you can build truly living pages: clicks, input, and keyboard all respond correctly.
addEventListener registers a function that is called when the event happens. Its form is always element.addEventListener("event-name", handler):
const tombol = document.querySelector("#tombol");
tombol.addEventListener("click", () => {
console.log("Tombol diklik!");
});tombol.addEventListener("click", () => {...}) calls the callback every time the button is clicked. This handler doesn't overwrite other handlers — addEventListener can register multiple times on the same element for the same event. This is why addEventListener is preferred over the old onclick property.
A handler receives one argument: the event object, containing data about what happened — which element was hit, which button, and the cursor position:
const kartu = document.querySelector("#kartu");
kartu.addEventListener("click", (event) => {
console.log("Elemen target:", event.target);
console.log("Elemen handler:", event.currentTarget);
console.log("Koordinat:", event.clientX, event.clientY);
});(event) => receives the event object. event.target is the element actually clicked, while event.currentTarget is the element where the handler is attached — the two differ if there are elements inside an element. event.clientX and clientY give the cursor position relative to the viewport.
The three most frequently used events: click for clickable elements, input for text fields, and keydown for the keyboard:
const input = document.querySelector("#nama");
input.addEventListener("input", () => {
console.log("Isi sekarang:", input.value);
});
input.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
console.log("Enter ditekan dengan isi:", input.value);
}
});input.addEventListener("input", () => {...}) fires every time the value changes — including copy and paste, not just typing. keydown gives access to event.key for checking which key was pressed. The pattern of checking event.key === "Enter" is the foundation of keyboard-based form submission.
Some events don't attach to a specific element but to document or window:
document.addEventListener("DOMContentLoaded", () => {
console.log("Dokumen selesai dimuat");
});
window.addEventListener("resize", () => {
console.log("Ukuran jendela berubah");
});document.addEventListener("DOMContentLoaded", ...) runs code as soon as the document structure is ready — safe to use even when the script is placed in head. window.addEventListener("resize", ...) responds to window size changes. Both patterns keep your code running at the right time.
When an element inside another element is clicked, the event doesn't stop at that element — it bubbles up through all its ancestors all the way to document:
const luar = document.querySelector("#luar");
const dalam = document.querySelector("#dalam");
dalam.addEventListener("click", () => {
console.log("Klik pada: dalam");
});
luar.addEventListener("click", () => {
console.log("Klik pada: luar");
});Clicking the dalam element prints Klik pada: dalam then Klik pada: luar — the parent handler is also called because the event bubbles upward. This is normal and useful, but sometimes unwanted. event.stopPropagation() halts the propagation so the parent handler doesn't fire.
Some elements have built-in browser behavior: a navigates pages, form submits requests. preventDefault cancels that behavior:
const link = document.querySelector("#tautan");
link.addEventListener("click", (event) => {
event.preventDefault();
console.log("Navigasi default dibatalkan");
});event.preventDefault() cancels a link's navigation without stopping event propagation. Distinguish it from stopPropagation: preventDefault cancels default behavior, stopPropagation stops other handlers. The two are often used together to fully control an element's behavior.
Imagine a list with hundreds of items, each with its own handler. When a new item is added, handlers must be reattached — tedious and memory-heavy. Event delegation solves both: attach one handler to the parent, then find out which item was clicked via event.target.
const daftar = document.querySelector("#daftar");
daftar.addEventListener("click", (event) => {
const item = event.target.closest("li");
if (item === null) return;
console.log("Item diklik:", item.textContent);
});event.target.closest("li") finds the nearest li from the click point, including when you click text inside it. closest returns null if there is none, and the item === null check ignores clicks that don't land on an item. One handler serves the entire list.
Delegation's main strength: elements added after the handler was attached are still detected, because the event catches them as they bubble:
const daftar = document.querySelector("#daftar");
daftar.addEventListener("click", (event) => {
const item = event.target.closest("li");
if (item === null) return;
item.classList.toggle("selesai");
});
const tambah = document.querySelector("#tambah");
tambah.addEventListener("click", () => {
const itemBaru = document.createElement("li");
itemBaru.textContent = "Tugas baru";
daftar.append(itemBaru);
});Items added via the tambah button can immediately be clicked to toggle the selesai class — without attaching a new handler. event.target.closest("li") on the parent catches everything. This delegation pattern is what nearly all interactive list applications use.
Tip
Use delegation when you have many identical elements (lists, tables, repeated buttons) or when elements appear dynamically. Use direct handlers when elements are few and definitely exist. Saving one addEventListener in the right place can remove hundreds of handlers.
Episode 14 made your pages alive: registering handlers with addEventListener, reading the event object, getting to know common events like click, input, and keydown, understanding bubbling along with stopPropagation, cancelling default behavior with preventDefault, and applying efficient event delegation for dynamic elements.
Key takeaways:
addEventListener registers many handlers without overwriting old ones.event.target is the element hit; event.currentTarget is where the handler lives.preventDefault cancels default behavior; stopPropagation stops propagation.closest is useful for finding the target element from the click point.In the next episode 15 we'll cover the Fetch API and AJAX — getting data from a server with fetch, reading JSON responses, sending data with the POST method, and handling HTTP status correctly. This is the bridge between your page and the outside world.