DEV Community

Cover image for Adding Event Listeners to the future DOM elements using Event Bubbling
Akhil sai
Akhil sai

Posted on

Adding Event Listeners to the future DOM elements using Event Bubbling

Brief Intro :

Before I get into details, let me answer the primary questions so that you need not waste time reading the whole article

  • Who is this for?
    • Any native javascript developer who has beginner knowledge of DOM API
  • What will I learn?
    • You will be learning how to handle the use cases where you have to add an event listener to an element that might be added to UI in the future workflow. In short, you will be learning Event Delegation

Problem Statement :

It all started when I was developing a simple MVC to-do app using vanilla javascript. I came across a requirement of listening to the click event of the task item that is going to be added to the UI when the user clicks on 'Add Task Button'.

Then I started to wonder, how can one add the event listeners to the elements that will be added in the future workflow?

Generally, one can add an event listener to an element that already exists in the DOM in the following way

 let taskDOMElement = document.querySelector('#task');
 taskDOMElement.addEventListener('click',clickHandler,false);
 function clickHandler()
 {
    //handle the event
 }
Enter fullscreen mode Exit fullscreen mode

But in order to add the event listeners to the elements that are going to be appended to DOM in the future, we have to use the concept of Event Bubbling where the events bubble from the target Element to the parent Element invoking their respective event handlers

Solution :

  • First, we have to figure out the nearest non-dynamic element in our DOM that will not be changed in our workflow(in my case it is body element)
let rootElement = document.querySelector('body');
Enter fullscreen mode Exit fullscreen mode
  • Then attach the same event listener to that element you want to listen from the future element(in my case, it is click event)
let rootElement = document.querySelector('body');
rootElement.addEventListener('click',function(event){},true);
Enter fullscreen mode Exit fullscreen mode
  • Now check if the event target matches your selector(in my case its a li tag)
let rootElement = document.querySelector('body');
rootElement.addEventListener('click',function(event){
let targetElement = event.target
let selector = 'li';
if(targetElement.match(selector) {
//logic for handling the click event of li tag
 }
},true);
Enter fullscreen mode Exit fullscreen mode
  • But to be more precise , we have to traverse from the target to the root Element for capturing the event
rootElement.addEventListener('click',function(event){
let targetElement = event.target
let selector = 'li';
while(targetElement != null) {
if(targetElement.match(selector) {
   //logic for handling the click event of li tag
   return;
  }
  targetElement = targetElement.parentElement;
 }
},true);
Enter fullscreen mode Exit fullscreen mode
  • So after few touchups,here is the generalised function to handle the event listeners of future elements
addCustomEventListener: function (selector, event, handler) {
        let rootElement = document.querySelector('body');
        //since the root element is set to be body for our current dealings
        rootElement.addEventListener(event, function (evt) {
                var targetElement = evt.target;
                while (targetElement != null) {
                    if (targetElement.matches(selector)) {
                        handler(evt);
                        return;
                    }
                    targetElement = targetElement.parentElement;
                }
            },
            true
        );
    }

//adding the Event Listeners to all the li tasks
addCustomEventListener('li#task','click',taskClickHandler);

Enter fullscreen mode Exit fullscreen mode
  • So, this is how you can attach event listeners to the future elements i.e., elements that are added dynamically in future

Links

Credits

Cover Image by Alejandro Alvarez

Conclusion:

I hope you find this article useful and helpful at some point in time. I would love to hear your feedback on the article and discuss more on this

Top comments (7)

Collapse
 
safwatfathi profile image
Safwat Fathi

Thank you for your time invested in this article but there are two noticeable mistakes here.

First:

  • adding true as third argument to event listener means that we are using event capturing approach which means the event starts from root element to target element (top to bottom) not from as you said from target to root (bottom to top).

Second:

  • you can achieve the same result without using event capturing, simply remove the third argument of the event listener function and the event bubbling behavior will be implemented.
Collapse
 
sunbeamrapier profile image
SunbeamRapier

Ok, for reactJS users a workable solution is:

var rootElement = document.getElementById('root');
console.log(rootElement);
rootElement.addEventListener('click', rootElementClicked);

console.log('event listener added to root element');

function rootElementClicked(event) {
  event.preventDefault();
  const { name, value } = event.target;

  console.log("Root element clicked with [" + event.target.className, event.target.id);
}
Enter fullscreen mode Exit fullscreen mode

So, the event-listener is app-wide, so a click anywhere will call the event-handler function. Then in the code for that function, the element class and ID will tell you what was clicked.

Rapier...

Note the event.preventDefault(); line - it prevents a refresh of the web page, otherwise the target class & ID are returned as "undefined"

Collapse
 
ivan_jrmc profile image
Ivan Jeremic

Why "root" ? You can attach it to the body in react too.

Collapse
 
sunbeamrapier profile image
SunbeamRapier

Hi. Thank you for taking the trouble to post this solution. However, I am finding your example somewhat difficult to follow. It would have been very helpful to see a worked example showing the html code and the related eventhandler code so that I could understand exactly what you are working with and translate your example for my website layout. I have never used vanilla JS, coming to javascript via reactJS which is, of course, quite different in many respects...
Also, I found it difficult to determine which terms in your code are javascript terms and which need to be replaced by me with element names, IDs or classes. Again, a fully-worked example would have been very useful here.

I haven't seen the notation 'li#task' before and I and if it is to be replaced by something meaningful in my environment, I am not sure what to replace it with? My event function name?

Also, in the while loop, I am not really sure why you are navigating up the parent elements
In my case, I have a navbar object (flex-container) with individual tiles with id: "linkContainer" and it is to these tiles that I want to add the event handlers, but they are all siblings. I think, anyway, I need to be navigating down if I am starting at the root element. If I start at the root I need to find the flex container first (which is a child of the root element I imagine, and then I will need to find the linkContainer children elements of the flex container. Again, without being able to see the HTML structure you are working with, it is hard to envisage what is happening here.

With regard to the statement (targetElement.matches(linkcontainer)) { It is not clear whether I add a text string with the element.id, as I have done, or just the ID name without quotes? Again a fully-worked example would make that clear.

This is my HTML structure. If you have some suggestions which would help me apply your code to this I would be very thankful.


Home
About
Technology
Marketing
Support
Modules

It is the linkcontainer Tile1 ...Tile6 elements above which require the event handlers.

ReactDOM.render(


,
document.getElementById('root')

Cheers from sunny Sydney

Rapier

Collapse
 
ivan_jrmc profile image
Ivan Jeremic

Isn't it easier to observe with MutationObserver when the element come in?

Collapse
 
d4phy profile image
Dafina David

Thank you for this simple solution! This works very well, but only in Mozilla. Do you know if it is cross-platform, please?
Thank you!

Collapse
 
pkubik11 profile image
Peter Kubik

thanks for this solution :)