36. Handling Events for Multiple Elements

In This Chapter

  • Learn to efficiently react to multiple events

  • Revisit how events work for one last time

In its most basic case, an event listener deals with events fired from a single element:

Image

As you build more complicated things, the “one event handler for one element” mapping starts to show its limitation. The most common reason revolves around you creating elements dynamically using JavaScript. These elements you are creating can fire events that you may want to listen and react to, and you can have anywhere from a handful of elements that need eventing support to many MANY elements that need to have their events dealt with.

What you don’t want to do is this:

Image

You don’t want to create an event listener for each element IF the event listener is the same for all of them. The reason is because your parents told you so. The other reason is because it is inefficient. Each of these elements carries around data about the same event listener and its properties that can really start adding up the memory usage when you have a lot of content. Instead, what you want is a clean and fast way of handling events on multiple elements with minimal duplication and unnecessary things. What you want will look a little bit like this:

Image

All of this may sound a bit crazy, right? Well, in this chapter, you will learn all about how non-crazy this is and how to implement this using just a few lines of JavaScript.

Onward!

How to Do All of This

Okay—at this point, you know how simple event handling works where you have one element, one event listener, and one event handler. Despite how different the case with multiple elements may seem, by taking advantage of the disruptiveness of events, solving it is actually quite easy.

Imagine we have a case where you want to listen for the click event on any of the sibling elements whose id values are one, two, three, four, and five. Let’s complete our imagination by picturing the DOM as follows:

Image

At the very bottom, we have the elements we want to listen for events on. They all share a common parent with an element whose id value is theDude. To solve our event handling problems, let’s look at a terrible solution followed by a good solution.

A Terrible Solution

Here is what we don’t want to do. We don’t want to have five event listeners for each of these buttons:

let oneElement = document.querySelector("#one");
let twoElement = document.querySelector("#two");
let threeElement = document.querySelector("#three");
let fourElement = document.querySelector("#four");
let fiveElement = document.querySelector("#five");

oneElement.addEventListener("click", doSomething, false);
twoElement.addEventListener("click", doSomething, false);
threeElement.addEventListener("click", doSomething, false);
fourElement.addEventListener("click", doSomething, false);
fiveElement.addEventListener("click", doSomething, false);

function doSomething(e) {
  let clickedItem = e.target.id;
  console.log("Hello " + clickedItem);
}

To echo what I mentioned in the intro, the obvious reason is that you don’t want to duplicate code. The other reason is that each of these elements now has their addEventListener property set. This is not a big deal for five elements. It starts to become a big deal when you have dozens or hundreds of elements each taking up a small amount of memory. The other OTHER reason is that your number of elements, depending on how adaptive or dynamic your UI really is, can vary. Your app may add or remove elements depending on what the user is doing, so it would be difficult to keep track of all the individual event listeners that each object may or may not need. Having one overarching event handler makes this situation much more fun.

A Good Solution

The good solution for this mimics the diagram you saw much earlier where we have just one event listener. I am going to confuse you first by describing how this works. Then I’ll hopefully un-confuse you by showing the code and explaining in detail what exactly is going on. The simple and confusing solution to this is:

  1. Create a single event listener on the parent theDude element.

  2. When any of the one, two, three, four, or five elements are clicked, rely on the propagation behavior that events possess and intercept them when they hit the parent theDude element.

  3. (Optional) Stop the event propagation at the parent element just to avoid having to deal with the event obnoxiously running up and down the DOM tree.

I don’t know about you, but I’m certainly confused after having read those three steps! Let’s start to unconfuse ourselves by starting with a diagram that explains those steps more visually:

Image

The last step in our quest for complete unconfusedness is the code that translates what the diagram and the three steps represent:

let theParent = document.querySelector("#theDude");
theParent.addEventListener("click", doSomething, false);

function doSomething(e) {
  if (e.target != e.currentTarget) {
    let clickedItem = e.target.id;
    console.log("Hello " + clickedItem);
  }
  e.stopPropagation();
}

Take a moment to read and understand the code you see here. After seeing our initial goals and the diagram, we will listen for the event on the parent theDude element:

let theParent = document.querySelector("#theDude");
theParent.addEventListener("click", doSomething, false);

There is only one event listener to handle this event, and that lonely creature is called doSomething:

function doSomething(e) {
  if (e.target != e.currentTarget) {
    let clickedItem = e.target.id;
    console.log("Hello " + clickedItem);
  }
  e.stopPropagation();
}

This event listener will get called each time theDude element is clicked along with any children that get clicked as well. We only care about click events relating to the children, and the proper way to ignore clicks on this parent element is to simply avoid running any code if the element the click is from (aka the event target) is the same as the event listener target (aka theDude element):

function doSomething(e) {
  if (e.target != e.currentTarget) {                            
    let clickedItem = e.target.id;
    console.log("Hello " + clickedItem);
  }
  e.stopPropagation();
}

The target of the event is represented by e.target, and the target element the event listener is attached to is represented by e.currentTarget. By simply checking that these values not be equal, you can ensure that the event handler doesn’t react to events fired from the parent element that you don’t care about.

To stop the event’s propagation, we simply call the stopPropagation method:

function doSomething(e) {
  if (e.target != e.currentTarget) {
    let clickedItem = e.target.id;
    console.log("Hello " + clickedItem);
  }
  e.stopPropagation();                                    
}

Notice that this code is actually outside of my if statement. This is because I want the event to stop traversing the DOM under all situations once it gets overheard.

Putting It All Together

The end result of all of this code running is that you can click on any of theDude’s children and listen for the event as it propagates up:

Image

Because all of the event arguments are still unique to the element that we are interacting with (aka the source of the event), we are able to identify and special case the clicked element from inside the event handler despite the addEventListener being active only on the parent. The main thing to call out about this solution is that it satisfies the problems we set out to avoid. You only created one event listener. It doesn’t matter how many children theDude ends up having. This approach is generic enough to accommodate all of them without any extra modification to your code. This also means that you should do some strict filtering if your theDude element ends up having children besides buttons and other elements that you care about.

The Absolute Minimum

For some time, I actually proposed a solution for our Multiple Element Eventing Conundrum (MEEC as the cool kids call it!) that was inefficient but didn’t require you to duplicate many lines of code. Before many people pointed out the inefficiencies of it, I thought it was a valid solution.

The way this solution worked was by using a for loop to attach event listeners to all the children of a parent (or an array containing HTML elements). Here is what that code looked like:

let theParent = document.querySelector("#theDude");

for (let i = 0; i < theParent.children.length; i++) {
  let childElement = theParent.children[i];
  childElement.addEventListener('click', doSomething, false);
}

function doSomething(e) {
  let clickedItem = e.target.id;
  console.log("Hello " + clickedItem);
}

The end result was that this approach allowed us to listen for the click event directly on the children. The only code I wrote manually was this single event listener call that was parameterized to the appropriate child element based on where in the loop the code was in:

childElement.addEventListener('click', doSomething, false);

The reason this approach isn’t great is because each child element has an event listener associated with it. This goes back to our efficiency argument where this approach unnecessarily wastes memory.

Now, if you do have a situation where your elements are spread throughout the DOM with no nearby common parent, using this approach on an array of HTML elements is not a bad way of solving our MEEC problem.

Anyway, as you start working with larger quantities of UI elements for games, data-visualization apps, and other HTML Element-rich things, you’ll end up having to use everything you saw here at least once. I hope. If all else fails, this chapter still served an important purpose. All of the stuff about event tunneling and capturing you saw earlier clearly came in handy here. That’s something!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.196.27