Handling events for many elements tutorial: is this always the best approach?

I would like to share my thoughts (and very little experience), as a junior web developer, on Kirupa’s very interesting tutorial “Handling events for many elements”. I tried using it in a project I am currently working on (as a student enrolled in a JavaScript/React online course). I’m basically building an image slider that opens when you click any of the images in a gallery. Following Kirupa’s idea, I added an event listener to the closest common ancestor of my image elements (or the div that serves as a container for my images rather), which happens to be the div that contains my entire image gallery. So I have another div element between that ancestor and my “div.image” elements. Therefore, I end up with intermediary elements which also have that event listener on them when they absolutely do not need it. It even creates a bug when I click slightly below an image in the gallery because I am targeting an element that should not be listening to the click event. So I was wondering whether Kirupa’s solution is adequate in case there are other descendents in between the common ancestor and the elements we want to add the event listeners to. I’d be interested to have other developers’ view on this. As I mentioned above, I am a JavaScript beginner, so any advice from more experienced developers would be much appreciated. :smiley:

Below is the code that deals with listening and reacting to the click event:

// Handle click event on media elements
const handleClickOnMedia = ($event) => {
  let clickedItemParentId;
  if ($event.target !== $event.currentTarget) {
  clickedItemParentId = $event.target.parentElement.id;
  openLightbox(clickedItemParentId);
  }
  $event.stopPropagation();

  return clickedItemParentId;
};

// We target the nearest common ancestor of all media elements
const mediaCommonAncestor = document.querySelector('.photographer__portfolio-images');
// We can then add an event listener on that element
// instead of adding multiple event listeners on all media elements
mediaCommonAncestor.addEventListener('click', handleClickOnMedia);

And this is the structure of my HTML:
Screen Shot 2022-06-10 at 18.00.09

What your getting is pointer events from the parent element.

There’s 2 ways to deal with it and its good practice to use both.

Pointer events: none

  • set all elements in your CSS as *{pointer-events: none}
  • create a CSS selector that targets every element that you want click events from
    • e.g .clickable{pointer-events: auto}
    • or button, input, select{pointer-events: auto}

Default return function

  • set your events as a data-set attribute to the DOM node e.g.
    • <div data-event = “openLightbox” class = "clickable" >Image</div>
  • access the event like e.target.dataset.event ("openLightbox")
  • run/ access the desired function as a property of an object (openLightbox is now the object method… events.openLightbox )
  • have a fallback function (no_event) for when you have forgotten to add an attribute (data-event = "open_lightbox") to the element as above

e.g.

const events = {
    no_event: function(){return console.log("no event attached")},
    openLightbox: function(e){
      parent_id = e.target.parentElement.id;
 //run some code
    }
}
const listenHandler = function(e){
  if(e.target === e.currentTarget) return
  let run = events[e.target.dataset.event] || events["no_event"];
e.stopPropogation()
  return run(e)
}

FYI you can just add an eventListener() and an event object (all your event functions in one object) for an entire page IF there’s no shadow DOM.

And also… you don’t have to use div all over your page.

There are elements that are semantic e.g. <nav> <a> <button> but <div> and <span> mean nothing to the browser without e.g. role = "navigation".

If you have an undefined customElement e.g.<portfolio-carousel> (not registered with e.g customElements.define("portfolio-carousel", portf_carousel)… then the browser will treat it as a <div> by default…

something like this is way more readable than <div> soup:

<portfolio-carousel>
    <image-card>
        <frame-16-9>
            <img src="" alt="">
        </frame-16-9>
    </image-card>
    <image-card>
        <frame-16-9>
            <img src="" alt="">
        </frame-16-9>
    </image-card>
</portfolio-carousel>

You can also use more CSS selectors e.g
frame-16-9 { aspect-ratio: 16 / 9;}

instead of class soup like Tail Wind…

Is it just me or does the words “tail wind” immediately bring to mind a fart… :laughing:

I hope this helps :slightly_smiling_face:

2 Likes

Also having all your event functions in an object makes it super easy to add a finite state machine

Finite state machines are the most robust way to handle state… The Mars rover used nested FSM (hierarchical state machines)…

Xstate is the well known one for JS…

1 Like

Thanks a lot for your very detailed answer! Lots of new stuff for me here. The only thing I knew about was the div soup you’re supposed to avoid, which I do as best I can. I just feel that once you’ve used all the semantic tags you’re supposed to and find yourself deep inside your HTML structure there’s not much you can do about it. I had no clue you could create your own custom HTML tags. I mean, really, what have they been teaching me?!

I had never thought of Tailwind as a potential gas hazard but now that you mention it… :smile:

Ok I’m off to put all the new stuff to the practice. I’ll let you know how it worked out. :wink:

2 Likes

And thanks for sharing the blog post about hierarchical state machines. More new stuff for me to reflect on… :smiley:

1 Like