59

Is possible to add event listener (Javascript) to all dynamically generated elements? I'm not the owner of the page, so I cannot add a listener in a static way.

For all the elements created when the page loaded I use:

doc.body.addEventListener('click', function(e){
//my code
},true);

I need a method to call this code when new elements appear on the page, but I cannot use jQuery (delegate, on, etc cannot work in my project). How can I do this?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
jenjis
  • 1,077
  • 4
  • 18
  • 30

8 Answers8

88

It sounds like you need to pursue a delegation strategy without falling back to a library. I've posted some sample code in a Fiddle here: http://jsfiddle.net/founddrama/ggMUn/

The gist of it is to use the target on the event object to look for the elements you're interested in, and respond accordingly. Something like:

document.querySelector('body').addEventListener('click', function(event) {
  if (event.target.tagName.toLowerCase() === 'li') {
    // do your action on your 'li' or whatever it is you're listening for
  }
});

CAVEATS! The example Fiddle only includes code for the standards-compliant browsers (i.e., IE9+, and pretty much every version of everyone else) If you need to support "old IE's" attachEvent, then you'll want to also provide your own custom wrapper around the proper native functions. (There are lots of good discussions out there about this; I like the solution Nicholas Zakas provides in his book Professional JavaScript for Web Developers.)

founddrama
  • 2,391
  • 1
  • 22
  • 31
  • 2
    @JianWeihang Can you clarify your question? You wouldn't want to apply the element directly to the `body` element (e.g., as an `onclick` attribute) or else run the risk of blowing away listeners that are already there. An event delegation strategy is far more safe: less likely to interfere with or clobber other listeners on the element, and less likely to hog memory with per-element listeners. – founddrama May 29 '15 at 12:53
  • Great answer, I was wondering isn't just `document.body === document.querySelector('body')` ? That's why, you always attach a click handler to the only one body element in both cases. – drinchev Feb 20 '16 at 11:34
  • 3
    @drinchev Yes, those are both equivalent. When giving examples, I prefer to use the `document.querySelector()` version because then it's easier to generalize -- i.e., not every element can be accessed from the `document` as a property. – founddrama Feb 28 '16 at 12:46
  • If my event is `focus`, it seem don't work. How should I do? – ColorWin Nov 29 '16 at 06:41
  • @ColorWin You need to use [event capturing](https://stackoverflow.com/q/15882045/4642212). – Sebastian Simon Aug 10 '18 at 16:20
18

Depends on how you add new elements.

If you add using createElement, you can try this:

var btn = document.createElement("button");
btn.addEventListener('click', masterEventHandler, false);
document.body.appendChild(btn);

Then you can use masterEventHandler() to handle all clicks.

ATOzTOA
  • 34,814
  • 22
  • 96
  • 117
  • 1
    I'm not the owner of the page which adds elements, so I do not know the way in which the elements are inserted. – jenjis Jan 10 '13 at 15:10
  • The problem I see with this answer, is that the event listeners will still be retained in memory when/if the underlying element is removed. This is because a reference to `btn` still exists. In this case, the chosen answer is the better answer. [See here](https://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory) for more information. – Crayons Aug 22 '21 at 17:25
3

An obscure problem worth noting here may also be this fact I just discovered:

If an element has z-index set to -1 or smaller, you may think the listener is not being bound, when in fact it is, but the browser thinks you are clicking on a higher z-index element.

The problem, in this case, is not that the listener isn't bound, but instead it isn't able to get the focus, because something else (e.g., perhaps a hidden element) is on top of your element, and that what get's the focus instead (meaning: the event is not being triggered). Fortunately, you can detect this easily enough by right-clicking the element, selecting 'inspect' and checking to see if what you clicked on is what is being "inspected".

I am using Chrome, and I don't know if other browsers are so affected. But, it was hard to find because functionally, it resembles in most ways the problem with the listener not being bound. I fixed it by removing from CSS the line: z-index:-1;

Dorian Mazur
  • 514
  • 2
  • 12
elliott954
  • 59
  • 3
3

When you have to support only "modern" web browsers (not Microsoft Internet Explorer), mutation observers are the right tool for this task:

new MutationObserver(function(mutationsList, observer) {
    for(const mutation of mutationsList) {
        if (mutation.type === 'childList') {
            // put your own source code here
        }
    }
}).observe(document.body, {childList: true, subtree: true});
gouessej
  • 3,640
  • 3
  • 33
  • 67
2

I have created a small function to add dynamic event listeners, similar to jQuery.on().

It uses the same idea as the accepted answer, only that it uses the Element.matches() method to check if the target matches the given selector string.

addDynamicEventListener(document.body, 'click', '.myClass, li', function (e) {
    console.log('Clicked', e.target.innerText);
});

You can get if from github.

XCS
  • 27,244
  • 26
  • 101
  • 151
  • 1
    Finally this worked for me. I had to add events to dynamically generated woocommerce messages. Tried everything and this is the only solution that has worked. Thanks a lot!! – Dorji Tshering Jul 26 '22 at 06:53
0

Delegating the anonymous task to dynamically created HTML elements with event.target.classList.contains('someClass')

  1. returns true or false
  2. eg.
let myEvnt = document.createElement('span');
myEvnt.setAttribute('class', 'someClass');
myEvnt.addEventListener('click', e => {
  if(event.target.classList.contains('someClass')){ 
    console.log(${event.currentTarget.classList})
}})
  1. Reference: https://gomakethings.com/attaching-multiple-elements-to-a-single-event-listener-in-vanilla-js/
  2. Good Read: https://eloquentjavascript.net/15_event.html#h_NEhx0cDpml
  3. MDN : https://developer.mozilla.org/en-US/docs/Web/API/Event/Comparison_of_Event_Targets
  4. Insertion Points:
Gruber
  • 2,196
  • 5
  • 28
  • 50
-1

You might wanna take a look at this library: https://github.com/Financial-Times/ftdomdelegate which is 1,8K gzipped

It is made for binding to events on all target elements matching the given selector, irrespective of whether anything exists in the DOM at registration time or not.

You need to import the script and then instantiate it like that:

  var delegate = new Delegate(document.body);
  delegate.on('click', 'button', handleButtonClicks);

  // Listen to all touch move
  // events that reach the body
  delegate.on('touchmove', handleTouchMove);

});


Nasia Makrygianni
  • 761
  • 1
  • 11
  • 19
-15

Use classList property to bind more than one class at a time

var container = document.getElementById("table");
container.classList.add("row", "oddrow", "firstrow");
Shubh
  • 6,693
  • 9
  • 48
  • 83