Don't just use Event.target
!
As wrongly suggested by many other answers I found online, an Event.target might well be an undesired child element!
Consider for example: <button class="dynamic" type="button">CLICK <i>ME!</i></button>
if you click on the "ME!" part of the button — your logic will fail miserably.
Always use Event.target
in combination with .closest()
document.querySelector("#staticParent").addEventListener("click", evt => {
if (event.target.closest(".dynamic")) {
// The desired button (or its child) was clicked!
}
});
Creating your own on()
helper function:
For dynamically created elements on whom you want to assign a click event before they are inserted in the DOM — assign the event to the parent delegator, and than query the Event Event.target.closest(selector)
matches the desired dynamic child selector:
// On handler:
const on = (evtName, delegator, ...args) => {
if (typeof delegator === "string") {
delegator = document.querySelector(delegator);
}
if (typeof args[0] === "string") {
delegator.addEventListener(evtName, (evt) => {
if (evt.target.closest(args[0])) {
args[1](evt);
}
});
} else {
delegator.addEventListener(evtName, ...args);
}
};
// Use like:
// Dynamic child elements:
on("click", document.querySelector("#staticParent"), ".dynamicChild", (evt) => {
console.log(evt.target); // The dynamic child
console.log(evt.currentTarget); // The static parent delegator
});
// Static elements:
on("click", document.querySelector("#staticParent"), (evt) => {
console.log(evt.currentTarget); // The static parent
});
Here's a more compressed version of the above with some other useful Utility functions and some more nifty examples:
// DOM utility functions:
const el = (sel, parent) => (parent || document).querySelector(sel);
const els = (sel, parent) => (parent || document).querySelectorAll(sel);
// Utility functions:
const isStr = (str) => typeof str === 'string' || str instanceof String;
// On handler:
const on = (evtName, delegator, ...a) =>
(isStr(delegator) ? el(delegator) : delegator)
.addEventListener(evtName, ...isStr(a[0]) ? [e => e.target.closest(a[0]) && a[1](e), a[2]] : a);
The above snippets can be used like i.e:
// Add event to dynamic or existing child elements of a single static parent
on("click", "#delegatorParent", ".dynamicTarget", (evt) => {
console.log(evt.target); // The dynamic child
console.log(evt.currentTarget); // The static parent (delegator)
});
// Add event to dynamic or existing child elements of multiple static parents
els(".delegatorParent").forEach(elParent => {
on("click", elParent, ".dynamicTarget", (evt) => {
console.log(evt.target); // The dynamic child
console.log(evt.currentTarget); // The static parent (delegator)
});
});
It can be also used on static (existing) elements:
on("click", "#target", (evt) => {
console.log(evt.currentTarget); // The static element
});
on("click", "#target", (evt) => {
console.log(evt.currentTarget); // The static element
}, { once: true });
// Add event to multiple Elements:
els(".items").forEach(elItem => {
on("click", elItem, (evt) => {
console.log(evt.currentTarget); // One of the static elements
});
});
Here's the same on()
function from above, written in a more readable format without any dependency: