JavaScript Event Delegation
The ticket window has one strip. The parent is #strip. Two buttons are already on it: #red, text red, and #blue, text blue. Under the strip, #note says blank.
Event delegation means one listener on a parent handles clicks that happen on its children. You call addEventListener on the strip, not on each button. When a click lands on a button, the click travels up to the strip. event.target is the button. event.currentTarget is the strip, the element this listener sits on.
The gold ticket is not in the markup yet. The page creates #gold after the listener is stored, then appends it and clicks it. The same function still runs, because the listener sits on the parent. The note becomes gold. The page script clicks red, then blue, then gold. addEventListener ran once, so the count hands is 1. The line under the strip reads red / blue / gold / 1. These buttons are type="button". This page does not call preventDefault or stopPropagation. The plaque page did that, and it did not add a child after the listener.
One listener for every ticket
The plaque page named event and read type, target, and currentTarget on a link inside a wall. That line was click / seal / plaque / true / quiet. The coat peg stored wool and silk with no parameter. That line was wool silk / 2 / peg. This page keeps the parameter event and uses target for a different job: telling which child was clicked, including one that did not exist yet.
getElementById only hands you the strip, the red button, the blue button, and the note. Gold is born from createElement. A listener on red plus a listener on blue would miss gold unless you attached again after appendChild. One listener on the strip does not need that second attachment. Read currentTarget inside the function. After the function returns, that property is null.
What the strip records
The note starts as blank. The button labels stay red, blue, and later gold. Read the rows in order. Each snippet starts from the two-button strip, except the page row, which is the demo at the bottom. The page demo calls addEventListener once.
📊 What the strip records
| Moment | What you read | Note text | Easy to mix up |
|---|---|---|---|
| listener on the strip, click red | event.target.id |
red | reading currentTarget, which is strip |
| same click, read currentTarget | event.currentTarget.id |
strip | reading the ticket id |
| same listener, click blue | event.target.id |
blue | giving blue its own listener |
| gold created after the listener, then clicked | event.target.id |
gold | thinking a new button is silent |
| click the strip itself, guard on | the note | skip | writing the id strip |
| page script | the line | red / blue / gold / 1 | the plaque line, or a last token of 3 |
A click on a button is one event. target stays that button for every listener along the trip. This page has one listener, on the strip, so currentTarget is the strip while the function runs. The click reaches the strip because nothing calls stopPropagation. The plaque page cut that trip. This page does not.
preventDefault is for a browser default, such as a link leaving the page. These controls are buttons, not links. Skipping preventDefault is the whole story here, not a missing line.
If event.target and event.currentTarget are the same node, the click landed on the strip, not on a button. The handler writes skip and does not pretend the paper is a ticket. seen.join(" / ") only joins ids already pushed. hands is written after that join, so the last token is how many times the page called addEventListener, not how many tickets were clicked.
The ticket under the click
The listener sits on the strip. The function names the parameter event and writes event.target.id into the note. Then the page clicks red. It does not call addEventListener on red.
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
note.textContent = event.target.id;
}
strip.addEventListener("click", take);
document.getElementById("red").click();
console.log(note.textContent);
The log is red. The listener’s element is the strip. target reports the button under the click, not the parent.
Who owns the listener
Same fresh strip, same listener, same click on red. The note writes both ids, target first, then currentTarget.
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
note.textContent = event.target.id + " " + event.currentTarget.id;
}
strip.addEventListener("click", take);
document.getElementById("red").click();
console.log(note.textContent);
The log is red strip. The first word is the ticket. The second word is the element that owns this listener. A listener placed on red itself, then a click on red, would log red red. This reading uses the strip, so the two ids differ.
The ticket already beside it
Fresh strip again. One listener, still only on the strip. This time the click is blue. Blue was in the markup from the start. It still has no listener of its own.
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
note.textContent = event.target.id;
}
strip.addEventListener("click", take);
document.getElementById("blue").click();
console.log(note.textContent);
The log is blue. One addEventListener covered both buttons that were already inside the strip. Blue did not replace red, and red did not need a second call.
A ticket printed later
The listener is stored first. Then the page builds a button, sets its id to gold, sets type to button, sets the text to gold, and appends it to the strip. Only then does it click gold.
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
note.textContent = event.target.id;
}
strip.addEventListener("click", take);
let gold = document.createElement("button");
gold.id = "gold";
gold.type = "button";
gold.textContent = "gold";
strip.appendChild(gold);
gold.click();
console.log(note.textContent);
The log is gold. Gold did not exist when addEventListener ran. The click still travels from gold up to the strip, and target is gold. You do not attach a listener to gold.
A click on the paper
The strip is also a clickable node. If the click’s target is the strip, there is no ticket id to keep. The function compares target with currentTarget. When they match, the note becomes skip.
let strip = document.getElementById("strip");
let note = document.getElementById("note");
function take(event) {
if (event.target === event.currentTarget) {
note.textContent = "skip";
return;
}
note.textContent = event.target.id;
}
strip.addEventListener("click", take);
strip.click();
console.log(note.textContent);
The log is skip. Both target and currentTarget are the strip, so the guard refuses the paper. A click on red still writes red, because those two references differ. Leave the guard out and strip.click() would write the id strip into the note.
The line under the strip
The script sits at the end of the body, after the strip and the paragraphs exist. One listener is stored, and hands becomes 1. Red is clicked, then blue. Each ticket id is pushed into seen. Gold is created, appended, and clicked, so gold is pushed too. The line is written after that third click. The page never clicks the strip itself, so skip is not in the line.
<!DOCTYPE html>
<html>
<head>
<title>event-delegation — page</title>
</head>
<body>
<div id="strip">
<button id="red" type="button">red</button>
<button id="blue" type="button">blue</button>
</div>
<p id="note">blank</p>
<p id="line">…</p>
<script>
let strip = document.getElementById("strip");
let note = document.getElementById("note");
let seen = [];
let hands = 0;
function take(event) {
if (event.target === event.currentTarget) {
note.textContent = "skip";
return;
}
note.textContent = event.target.id;
seen.push(event.target.id);
}
strip.addEventListener("click", take);
hands = hands + 1;
document.getElementById("red").click();
document.getElementById("blue").click();
let gold = document.createElement("button");
gold.id = "gold";
gold.type = "button";
gold.textContent = "gold";
strip.appendChild(gold);
gold.click();
document.getElementById("line").textContent =
seen.join(" / ") + " / " + hands;
</script>
</body>
</html>
The line reads red / blue / gold / 1. The three ids are the tickets that were clicked, in order. The last token is hands, and addEventListener ran once, so that token is 1, not 3. The note ends as gold, because gold was the last click. The word blank is gone. The word skip never appears. The plaque line and the peg line are other pages.
A button that only holds text keeps event.target on the button, so id is red, blue, or gold. Put a span inside the button and the click can land on the span. The span has no id. This page does not do that.
At the window
- Put one listener on the strip and click red. The note is red. Red has no listener of its own.
- With that same listener, click blue. The note is blue.
- Create gold, append it, and click it. The note is gold. The listener was already on the strip.
- Click the strip itself. With the guard, the note is skip. Then read the page line: red / blue / gold / 1.
Misses at the window
The labels still say red, blue, and gold. The note is not the word ticket.
- A listener on red and another on blue, then surprise that gold stays silent
- Reading
event.currentTargetwhen you wanted the ticket - Reading
event.targetwhen you wanted the strip - Forgetting the guard, so
strip.click()writes strip - A
spaninside the button, sotargetis the span and the id is empty - Calling
addEventListeneragain on gold, then countinghandsas 2 - Calling
preventDefaultorstopPropagation; the plaque page did that - Expecting the handler argument to be the button element
- Reading the line as click / seal / plaque / true / quiet, or as wool silk / 2 / peg
- Reading the last token as 3 because three tickets were clicked
Keep this
- One
addEventListeneron the strip hears every button inside it - On a click of red,
event.target.idis red andevent.currentTarget.idis strip - The same listener writes blue when blue is clicked
- Gold, created after the listener, still writes gold
- A click on the strip itself, with the guard, writes skip
- The page line reads red / blue / gold / 1
🧠 Test Your Knowledge
Test Your Knowledge
Challenge yourself with this interactive quiz and see how well you understand the topic
📝 Instructions
- Read each question carefully
- Select the best answer for each question
- You can retake the quiz as many times as you want
- Your progress will be shown at the top