How to addEventListener to future DOM elements?
Asked Answered
A

3

16

File partial.html looks like this: <button id="test">Hi I am from a partial!</button>

Partial.html is dynamically included on the page, using XMLHttpRequest:

var oReq = new XMLHttpRequest();
oReq.open('get', 'partial.html', true);
oReq.send();
oReq.onload = function() {
  document.querySelector('#pageArea').innerHTML = this.response;
};

How can I add an event listener that will apply to future exisiting #test without doing it after it's content has been loaded and inserted into #pageArea?

(No jQuery solutions, please!)

Assistance answered 2/12, 2013 at 14:42 Comment(1)
You may need to parse HTML before browser does it. Why you need to do it before it has been loaded and inserted in DOM structure?Oxeyed
E
22

Events like click bubble, so you attach the event handler to the closest non-dynamic parent, and inside the event handler you check if it was the button being clicked by seeing if it was the event's target :

var parent = document.getElementById('pageArea');

if (parent.addEventListener) {
    parent.addEventListener('click', handler, false);
}else if (parent.attachEvent) {
    parent.attachEvent('onclick', handler);
}

function handler(e) {
    if (e.target.id == 'test') {
         // the button was clicked
    }
}

FIDDLE

Elevon answered 2/12, 2013 at 14:45 Comment(3)
Will this approach have any impact on performance? Is it not better to attach the eventListener directly to the element?Assistance
@Assistance - you can't attach an event handler to something that isn't there, that's why you have to delegate.Elevon
Always wondered how you might approach this in plain js (use jQuery .on when I need this). Thanks for this!Organelle
S
3

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() and off() helper functions:

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:

const _ev = (type, name, delegator, ...arg) => {
  if (typeof delegator === "string") return document.querySelectorAll(delegator).forEach(el => _ev(type, name, el, ...arg));
  if (typeof arg[0] === "string") delegator[type](name, ev => ev.target.closest(arg[0]) && arg[1](ev), arg[2]);
  else delegator[type](name, arg[0], arg[1]);
};
const on = (...arg) => _ev("addEventListener", ...arg);
const off = (...arg) => _ev("removeEventListener", ...arg);


// Use like:

on("click", "#staticParent", ".dynamicChild", (ev) => {
  console.log(ev.currentTarget, ev.target);
});

on("click", ".test", (ev) => {
  console.log(ev.currentTarget);
}, { once: true });
body * { padding: 0.5rem; background: #0002; margin: 0.2rEm; }
<div id="staticParent"> Parent
  <div class="dynamicChild">Existent or dynamic Child (Clickable)</div>
</div>

<div class="test">Test 1 (I'm only clickable once)</div>
Succinct answered 9/4, 2022 at 15:32 Comment(0)
O
-1

Roko's point about needing to check event.target is a good one. But the _ev function from that example doesn't actually seem to properly filter child nodes from the event.target (at least in my testing). To avoid the event.target problem mentioned I had to modify that example to explicitly check the target, like:

on("click", "#staticParent", ".dynamicChild", (ev) => {
  if (ev.target.closest(".dynamicChild")){
    // whatever you're trying have dynamic child do
  }
});

Otherwise, a child node within .dynamicChild will still overtake the event target.

Outthink answered 15/3, 2024 at 18:25 Comment(2)
That's already stated in my answer. Twice. But yes, this more explicitly proves what's already underlined. If an improvement was necessary you could've point that out in a single comment.Succinct
I was just pointing out that your answer explained the common pitfal, showed how to fix the common pitfall, but then provided a great example solution to the original question (of registering event listeners for future DOM elements) that is still susceptible to the event target problem. Can't really post multi-line code examples in a comment, which is why I posted an "answer" because it was easier to show than to try and explain in a comment. Not trying to ruffle feathers - just trying to help future people who stumble upon this answer understand what they're looking at.Outthink

© 2022 - 2025 — McMap. All rights reserved.