
Introduction to Popup Events
Despite the fact that in the end I completely used CSS for this project , it all started with the use of JavaScript and classes.
However, I had a problem. I wanted to use the so-called Pop-up Events, but I also wanted to minimize the dependencies that I would have to implement. I did not want to connect jQuery libraries for “this little test”, only to use pop-up events.
Let's take a closer look at what pop-up events are, how they work, and consider a few ways to implement them.
Consider a simple example:
Suppose there is a list of buttons. Every time I click on one of them, it should become “active”. After pressing again, the button should return to its original state.
Let's start with HTML:
I could use a standard JavaScript event handler like this:
It looks good ... But it will not work. At least not as we expect it to be.
For those who know a little functional JavaScript, the problem is obvious.
For the rest, I will briefly explain - the handler function closes to the button variable . However, this variable is one, and each iteration is overwritten.
In the first iteration, the variable refers to the first button. In the next - to the second, and so on. But, when the user clicks on the button, the cycle has already ended, and the button variable refers to the last button, which always calls the event handler for it. The mess.
What we need is a separate context for each function:
Much better! And most importantly, it works correctly. We created the createToolbarButtonHandle function , which returns an event handler. Then for each button we hang our handler.
And it looks good, and it works. Despite this, we can still make our code better.
First, we create too many handlers. For each button inside .toolbar, we create a function and bind it as an event handler. For three buttons, memory usage is negligible.
But if we have something like this:
then the computer, of course, will not explode from overflow. However, our memory usage is far from ideal. We allocate a huge amount of it, although you can do without it. Let's rewrite our code again, so as to use one function several times.
Instead of referring to the button variable to keep track of which button we clicked on, we can use the event object (the “event” object), which is passed as the first argument to each event handler.
The event object contains some data about the event. In our case, we are interested in the currentTarget field . From it we will get a link to the element that was clicked:
Excellent! We not only simplified everything to the only function that is used several times, we also made our code more readable by removing the extra generator function.
However, we can still do better.
Suppose we added a few buttons to the sheet after our code was executed. Then we would also need to add event handlers for each of them. And we would have to store a link to this handler and links from other places. It doesn't look very tempting.
Perhaps there is another approach?
Let's start by understanding how events work and how they move in our DOM.
When the user clicks on an element, an event is generated to notify the application about it. Each event travels in three stages:
Note: not all events pass the stage of interception or pop-up, some are created immediately on the element. However, this is rather an exception to the rule.
An event is generated outside the document and then sequentially moves along the DOM hierarchy to the target element. As soon as it reaches its goal, the event is selected in the same way from the DOM element.
Here is our HTML template:
When the user presses button A, the event travels this way:
Home
| #document
| Interception phase
| HTML
| BODY
| UL
| LI # li_1
| Button A <- An event occurs for the target element
| Pop Up Phase
| LI # li_1
| UL
| BODY
| HTML
v #document
Note that we can track the path that the event moves to its target element. In our case, for each button pressed, we can be sure that the event will pop up back by going through its parent - ul element. We can use this and implement pop-up events.
Pop-up events are those events that are tied to the parent element, but are executed only if they satisfy some condition.
As a specific example, take our toolbar:
Now, knowing that any click on the button will pop up through the ul.toolbar element , let's attach our event handler to it. Fortunately, we already have it:
Now we have much cleaner code, and we even got rid of loops! Note, however, that we have replaced e.currentTarget with e.target . The reason is that we handle events at a different level.
e.target - the actual purpose of the event, where it gets through the DOM, and from where it will pop up later.
e.currentTarget - the current element that handles the event. In our case, this is ul.toolbar .
At the moment, we are processing any click on every element that pops up through ul.toolbar , but our verification condition is too simple. What would happen if they had a more complex DOM that included icons and elements that were not created to be clicked on?
Oops! Now, when we click on li.separator or the icon, we add the .active class to it . At the very least, this is not good. We need a way to filter events so that we respond to the element we need.
Let's create a small helper function for this:
Our assistant does two things. Firstly, he goes around each element and his parents and check if they satisfy the condition passed in the criteria parameter . If the element satisfies, the helper adds a field to the event object, called delegateTarget , in which the element satisfying our conditions is stored. And then it calls the handler. Accordingly, if no element satisfies the condition, no handler will be called.
We can use it like this:
What the doctor ordered: one event handler attached to one element that does all the work. But does it only for the elements we need. And it responds very well to adding and removing objects from the DOM.
We briefly reviewed the basics of implementing delegation (handling of pop-up) events in pure JavaScript. This is good because we do not need to generate and attach a bunch of handlers for each element.
If I wanted to make a library out of this or use the code in development, I would add a couple of things:
A helper function to check if the object meets the criteria in a more unified and functional way. Like:
Partial use of the helper would also be nice:
Original article: Understanding Delegated JavaScript Events
(From a translator: my first, judge strictly.)
Happy coding!
However, I had a problem. I wanted to use the so-called Pop-up Events, but I also wanted to minimize the dependencies that I would have to implement. I did not want to connect jQuery libraries for “this little test”, only to use pop-up events.
Let's take a closer look at what pop-up events are, how they work, and consider a few ways to implement them.
Okay, so what's the problem?
Consider a simple example:
Suppose there is a list of buttons. Every time I click on one of them, it should become “active”. After pressing again, the button should return to its original state.
Let's start with HTML:
I could use a standard JavaScript event handler like this:
for(var i = 0; i < buttons.length; i++) {
var button = buttons[i];
button.addEventListener("click", function() {
if(!button.classList.contains("active"))
button.classList.add("active");
else
button.classList.remove("active");
});
}
It looks good ... But it will not work. At least not as we expect it to be.
Closures won
For those who know a little functional JavaScript, the problem is obvious.
For the rest, I will briefly explain - the handler function closes to the button variable . However, this variable is one, and each iteration is overwritten.
In the first iteration, the variable refers to the first button. In the next - to the second, and so on. But, when the user clicks on the button, the cycle has already ended, and the button variable refers to the last button, which always calls the event handler for it. The mess.
What we need is a separate context for each function:
var buttons = document.querySelectorAll(".toolbar button");
var createToolbarButtonHandler = function(button) {
return function() {
if(!button.classList.contains("active"))
button.classList.add("active");
else
button.classList.remove("active");
};
};
for(var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i]));
}
Much better! And most importantly, it works correctly. We created the createToolbarButtonHandle function , which returns an event handler. Then for each button we hang our handler.
So what's the problem?
And it looks good, and it works. Despite this, we can still make our code better.
First, we create too many handlers. For each button inside .toolbar, we create a function and bind it as an event handler. For three buttons, memory usage is negligible.
But if we have something like this:
then the computer, of course, will not explode from overflow. However, our memory usage is far from ideal. We allocate a huge amount of it, although you can do without it. Let's rewrite our code again, so as to use one function several times.
Instead of referring to the button variable to keep track of which button we clicked on, we can use the event object (the “event” object), which is passed as the first argument to each event handler.
The event object contains some data about the event. In our case, we are interested in the currentTarget field . From it we will get a link to the element that was clicked:
var toolbarButtonHandler = function(e) {
var button = e.currentTarget;
if(!button.classList.contains("active"))
button.classList.add("active");
else
button.classList.remove("active");
};
for(var i = 0; i < buttons.length; i++) {
button.addEventListener("click", toolbarButtonHandler);
}
Excellent! We not only simplified everything to the only function that is used several times, we also made our code more readable by removing the extra generator function.
However, we can still do better.
Suppose we added a few buttons to the sheet after our code was executed. Then we would also need to add event handlers for each of them. And we would have to store a link to this handler and links from other places. It doesn't look very tempting.
Perhaps there is another approach?
Let's start by understanding how events work and how they move in our DOM.
How do most of them work?
When the user clicks on an element, an event is generated to notify the application about it. Each event travels in three stages:
- Interception phase
- An event occurs for the target element.
- Pop-up phase
Note: not all events pass the stage of interception or pop-up, some are created immediately on the element. However, this is rather an exception to the rule.
An event is generated outside the document and then sequentially moves along the DOM hierarchy to the target element. As soon as it reaches its goal, the event is selected in the same way from the DOM element.
Here is our HTML template:
When the user presses button A, the event travels this way:
Home
| #document
| Interception phase
| HTML
| BODY
| UL
| LI # li_1
| Button A <- An event occurs for the target element
| Pop Up Phase
| LI # li_1
| UL
| BODY
| HTML
v #document
Note that we can track the path that the event moves to its target element. In our case, for each button pressed, we can be sure that the event will pop up back by going through its parent - ul element. We can use this and implement pop-up events.
Pop-up events
Pop-up events are those events that are tied to the parent element, but are executed only if they satisfy some condition.
As a specific example, take our toolbar:
ul class="toolbar">
Now, knowing that any click on the button will pop up through the ul.toolbar element , let's attach our event handler to it. Fortunately, we already have it:
var toolbar = document.querySelector(".toolbar");
toolbar.addEventListener("click", function(e) {
var button = e.target;
if(!button.classList.contains("active"))
button.classList.add("active");
else
button.classList.remove("active");
});
Now we have much cleaner code, and we even got rid of loops! Note, however, that we have replaced e.currentTarget with e.target . The reason is that we handle events at a different level.
e.target - the actual purpose of the event, where it gets through the DOM, and from where it will pop up later.
e.currentTarget - the current element that handles the event. In our case, this is ul.toolbar .
Improved Popup Events
At the moment, we are processing any click on every element that pops up through ul.toolbar , but our verification condition is too simple. What would happen if they had a more complex DOM that included icons and elements that were not created to be clicked on?
Oops! Now, when we click on li.separator or the icon, we add the .active class to it . At the very least, this is not good. We need a way to filter events so that we respond to the element we need.
Let's create a small helper function for this:
var delegate = function(criteria, listener) {
return function(e) {
var el = e.target;
do {
if (!criteria(el)) continue;
e.delegateTarget = el;
listener.apply(this, arguments);
return;
} while( (el = el.parentNode) );
};
};
Our assistant does two things. Firstly, he goes around each element and his parents and check if they satisfy the condition passed in the criteria parameter . If the element satisfies, the helper adds a field to the event object, called delegateTarget , in which the element satisfying our conditions is stored. And then it calls the handler. Accordingly, if no element satisfies the condition, no handler will be called.
We can use it like this:
var toolbar = document.querySelector(".toolbar");
var buttonsFilter = function(elem) { return elem.classList && elem.classList.contains("btn"); };
var buttonHandler = function(e) {
var button = e.delegateTarget;
if(!button.classList.contains("active"))
button.classList.add("active");
else
button.classList.remove("active");
};
toolbar.addEventListener("click", delegate(buttonsFilter, buttonHandler));
What the doctor ordered: one event handler attached to one element that does all the work. But does it only for the elements we need. And it responds very well to adding and removing objects from the DOM.
To summarize
We briefly reviewed the basics of implementing delegation (handling of pop-up) events in pure JavaScript. This is good because we do not need to generate and attach a bunch of handlers for each element.
If I wanted to make a library out of this or use the code in development, I would add a couple of things:
A helper function to check if the object meets the criteria in a more unified and functional way. Like:
var criteria = {
isElement: function(e) { return e instanceof HTMLElement; },
hasClass: function(cls) {
return function(e) {
return criteria.isElement(e) && e.classList.contains(cls);
}
}
// Больше критериев
};
Partial use of the helper would also be nice:
var partialDelgate = function(criteria) {
return function(handler) {
return delgate(criteria, handler);
}
};
Original article: Understanding Delegated JavaScript Events
(From a translator: my first, judge strictly.)
Happy coding!