Question

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!)

Was it helpful?

Solution

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

OTHER TIPS

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:

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top