18 minLesson 13 of 40
JavaScript for Web
Event Handling & Delegation
Event Handling & Delegation
Events connect user actions to your JavaScript code. Understanding event delegation — attaching one listener to a parent instead of many listeners to children — is what makes dynamic UIs performant.
Adding Event Listeners
const btn = document.querySelector("#submit");
btn.addEventListener("click", (event) => {
event.preventDefault();
console.log("Clicked!");
});
// Named function (can be removed later)
function handleClick(e) {
console.log(e.target);
}
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);
// Options: once, passive, capture
btn.addEventListener("click", handleClick, { once: true });
window.addEventListener("scroll", onScroll, { passive: true });
Event Delegation
Instead of adding listeners to every child element, add ONE to a parent. This works for dynamically added elements too:
// ❌ Adding to every item — breaks when items are added dynamically
document.querySelectorAll(".todo-item").forEach(item => {
item.addEventListener("click", handleItemClick);
});
// ✅ One listener on the parent
const list = document.querySelector(".todo-list");
list.addEventListener("click", (event) => {
// Find the closest ancestor matching the selector
const item = event.target.closest(".todo-item");
if (!item) return; // clicked on list but not an item
const id = item.dataset.id;
if (event.target.matches(".delete-btn")) deleteItem(id);
if (event.target.matches(".complete-btn")) toggleItem(id);
if (event.target.matches(".edit-btn")) editItem(id);
});
Common Event Types
// Mouse
element.addEventListener("click", handler);
element.addEventListener("dblclick", handler);
element.addEventListener("mouseenter", handler); // fires only on element
element.addEventListener("mouseleave", handler);
element.addEventListener("mouseover", handler); // fires on children too
element.addEventListener("contextmenu", (e) => {
e.preventDefault(); // custom right-click menu
});
// Keyboard
document.addEventListener("keydown", (e) => {
if (e.key === "Enter") submit();
if (e.key === "Escape") closeModal();
if (e.ctrlKey && e.key === "s") save();
if (e.key === "ArrowUp") navigateUp();
});
// Form
form.addEventListener("submit", (e) => {
e.preventDefault();
processForm(new FormData(form));
});
input.addEventListener("input", (e) => { // every keystroke
updatePreview(e.target.value);
});
input.addEventListener("change", handler); // after blur
input.addEventListener("focus", handler);
input.addEventListener("blur", handler);
// Window
window.addEventListener("resize", debounce(handleResize, 100));
window.addEventListener("scroll", throttle(handleScroll, 16), { passive: true });
document.addEventListener("DOMContentLoaded", init);
window.addEventListener("load", onFullyLoaded);
window.addEventListener("beforeunload", saveState);
Event Object
element.addEventListener("click", (event) => {
event.target; // element that triggered the event
event.currentTarget; // element the listener is on
event.type; // "click"
event.timeStamp; // when it happened
// Mouse
event.clientX; // viewport coordinates
event.pageX; // page coordinates (includes scroll)
event.button; // 0=left, 1=middle, 2=right
// Keyboard
event.key; // "Enter", "a", "ArrowUp"
event.code; // "KeyA" (physical key)
event.shiftKey;
event.ctrlKey;
event.altKey;
event.metaKey; // Cmd / Windows key
// Control
event.preventDefault(); // stop default behavior
event.stopPropagation(); // stop bubbling
});
Utility Patterns
// Debounce — wait until user stops
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const searchInput = document.querySelector("#search");
searchInput.addEventListener("input", debounce((e) => {
fetch(`/api/search?q=${e.target.value}`).then(r => r.json()).then(renderResults);
}, 300));
// Throttle — fire at most once per interval
function throttle(fn, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
window.addEventListener("scroll", throttle(updateScrollProgress, 16), { passive: true });
Keyboard Trap (Modals)
function trapFocus(modal) {
const focusable = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
modal.addEventListener("keydown", (e) => {
if (e.key !== "Tab") return;
if (e.shiftKey) {
if (document.activeElement === first) {
last.focus();
e.preventDefault();
}
} else {
if (document.activeElement === last) {
first.focus();
e.preventDefault();
}
}
});
first.focus();
}
Next lesson: Fetch API & Async JavaScript — loading data from servers without page reloads.
📱
Get Notes Free →Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises