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
- 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
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
}
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');
- 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);
- 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);
- 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);
- 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);
- 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)
Thank you for your time invested in this article but there are two noticeable mistakes here.
First:
Second:
Ok, for reactJS users a workable solution is:
var rootElement = document.getElementById('root');
console.log(rootElement);
rootElement.addEventListener('click', rootElementClicked);
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"
Why "root" ? You can attach it to the body in react too.
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
Isn't it easier to observe with MutationObserver when the element come in?
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!
thanks for this solution :)