Day 11: Events, Event Object & Delegation
✅ Today you will learn
- addEventListener and the most useful event types
- The event object: target, key, preventDefault
- Bubbling, capturing and stopPropagation
- Event delegation – one listener for many items
- Keyboard shortcuts, once and removing listeners
🏢 In the real world
A product list shows 200 sweets. A junior developer added 200 separate click listeners – then new products loaded from the server and their buttons did not work at all. The fix is event delegation: one listener on the parent that handles every current and future button. It is a classic interview question and a daily tool.
addEventListener and event types
<button id="b">Click me</button>
<input id="i" placeholder="Type here…" aria-label="Type here">
<select id="s" aria-label="Size"><option>500 g</option><option>1 kg</option></select>
<div id="box" style="margin-top:8px;padding:16px;background:#fef3c7;border-radius:8px">Hover / double-click me</div>
<pre id="log" style="height:110px;overflow:auto;background:#111827;color:#a7f3d0;padding:8px;border-radius:8px"></pre>
<script>
const log = msg => {
const box = document.querySelector('#log');
box.textContent = msg + '\n' + box.textContent;
};
document.querySelector('#b').addEventListener('click', () => log('click'));
document.querySelector('#i').addEventListener('input', e => log('input: ' + e.target.value));
document.querySelector('#s').addEventListener('change', e => log('change: ' + e.target.value));
const box = document.querySelector('#box');
box.addEventListener('mouseenter', () => log('mouseenter'));
box.addEventListener('dblclick', () => log('dblclick'));
</script>| Event | Fires when | Typical use |
|---|---|---|
| click | Mouse click, tap, Enter/Space on buttons | Buttons, cards, menus |
| input | Every keystroke / change in a field | Live search, character counters, totals |
| change | Value committed (select, checkbox, blur) | Filters, settings |
| submit | A form is submitted | Validate, then send with fetch |
| keydown | A key is pressed | Shortcuts, Esc to close |
| focus / blur | Field gains / loses focus | Show hints, validate on leave |
| scroll / resize | Page scrolls / window resizes | Sticky effects (throttle them – Day 24) |
| DOMContentLoaded | HTML parsed | Start code (not needed with defer) |
The event object
<a id="link" href="https://example.com">Go to example.com (blocked)</a>
<p>Press any key while this preview is focused…</p>
<p id="out"></p>
<script>
document.querySelector('#link').addEventListener('click', function (event) {
event.preventDefault(); // stop the default action (navigation)
document.querySelector('#out').textContent = `Link click stopped. target: <${event.target.tagName.toLowerCase()}>`;
});
document.addEventListener('keydown', e => {
document.querySelector('#out').textContent =
`key: "${e.key}" code: ${e.code} ctrl: ${e.ctrlKey} shift: ${e.shiftKey}`;
});
</script>| Property / method | Meaning |
|---|---|
| e.target | The element that was actually clicked/typed in |
| e.currentTarget | The element the listener is attached to |
| e.preventDefault() | Stop default behaviour (form submit reload, link navigation) |
| e.stopPropagation() | Stop bubbling to parents (use rarely) |
| e.key / e.code | Which key ("Enter", "Escape", "a") |
| e.ctrlKey / shiftKey / metaKey | Modifier keys for shortcuts |
Bubbling: events travel upwards
<div id="outer" style="padding:16px;background:#e0e7ff;border-radius:10px">outer div
<div id="inner" style="padding:16px;background:#c7d2fe;border-radius:10px;margin-top:6px">inner div
<button id="btn">button</button>
</div>
</div>
<p id="path"></p>
<script>
const path = [];
['#btn', '#inner', '#outer'].forEach(sel => {
document.querySelector(sel).addEventListener('click', e => {
path.push(e.currentTarget.id);
document.querySelector('#path').textContent = 'Order: ' + path.join(' → ');
});
});
document.querySelector('#btn').addEventListener('click', () => { path.length = 0; }, { capture: true });
</script>Event delegation – the professional pattern
<ul id="products"></ul>
<button id="more">Load 2 more products</button>
<p id="cart">Cart: 0 items</p>
<script>
const all = ['Kaju Katli', 'Ghewar', 'Rasmalai', 'Ladoo', 'Kalakand', 'Soan Papdi'];
let shown = 2, cartCount = 0;
const list = document.querySelector('#products');
function render() {
list.innerHTML = all.slice(0, shown).map((name, i) =>
`<li>${name} <button data-action="add" data-index="${i}">Add</button>
<button data-action="info" data-index="${i}">Info</button></li>`).join('');
}
// ONE listener on the parent handles every button – even ones added later
list.addEventListener('click', e => {
const btn = e.target.closest('button[data-action]');
if (!btn) return; // click was not on a button
const name = all[Number(btn.dataset.index)];
if (btn.dataset.action === 'add') {
cartCount++;
document.querySelector('#cart').textContent = `Cart: ${cartCount} items (last: ${name})`;
} else {
alert(`${name}: made fresh every morning`);
}
});
document.querySelector('#more').addEventListener('click', () => { shown = Math.min(all.length, shown + 2); render(); });
render();
</script>- 1Attach one listener to a stable parent (list, table, document)
- 2In the handler, find the real target with e.target.closest(selector)
- 3Return early if nothing matched
- 4Read data-* to know which item and which action
- 5Works for items rendered later – and uses less memory
Keyboard shortcuts, once and removing listeners
<button id="open">Open offer</button>
<div id="modal" hidden style="margin-top:10px;padding:16px;border:2px solid #ca8a04;border-radius:12px">🎁 10% off today! <small>(press Esc or click ✕)</small> <button id="x" aria-label="Close">✕</button></div>
<p><button id="once">Claim coupon (works once)</button> <span id="c"></span></p>
<script>
const modal = document.querySelector('#modal');
function onKey(e) { if (e.key === 'Escape') close(); }
function open() { modal.hidden = false; document.addEventListener('keydown', onKey); }
function close() { modal.hidden = true; document.removeEventListener('keydown', onKey); } // same function reference!
document.querySelector('#open').addEventListener('click', open);
document.querySelector('#x').addEventListener('click', close);
document.querySelector('#once').addEventListener('click', () => {
document.querySelector('#c').textContent = 'Coupon SWEET10 applied ✅';
}, { once: true });
</script>An anonymous arrow function cannot be removed, because writing it again creates a new function. Store it in a named function (like onKey) when you plan to remove it.
🏅 Job skills unlocked today
Practice tasks
🏋️ Practice – do it yourself
Show solution
Quick quiz
🧠 Quick Quiz – test yourself
Interview corner
💼 Interview Corner
Explain event bubbling and delegation.
After an event fires on a target it propagates up through its ancestors (bubbling). Delegation attaches one listener to a common ancestor and uses e.target/closest to decide which child triggered it – efficient for long or dynamic lists.
e.target vs e.currentTarget?
target is the innermost element where the event originated; currentTarget is the element whose listener is currently running (the one you attached to).
Summary
- el.addEventListener("click", handler) – many event types.
- e.target, e.key and e.preventDefault() are the daily helpers.
- Events bubble; delegation = one parent listener + closest().
- Use once, named handlers and removeEventListener to clean up.
Day 12: create elements and render lists from data – the data → UI pattern used by React.
