Over-delegating JavaScript events

Delegating event listeners in JavaScript is generally a good practice. It allows us to create only one event listener for events that might be triggered on a variety of nodes and across multiple contexts. If we're just looking to listen for any given click on a page it would be madness to create an event listener on every single node. So we wait for the event to bubble up to a higher-level node and catch the event there, creating only one event listener on one node that can handle all sorts of event sources.

But what if we go too far with event delegation? What happens then? Let's consider a contrived example: I want to open a modal to sign up for my newsletter whenever someone clicks on a link in the header nav. Here's how I might do it:

// Not recommended
var signup_modal

function show_signup_modal () {  
  signup_modal = $(modal_markup)
    .appendTo('body')
    .show()
}

$('body').on('click', '.js-menu-link', show_signup_modal)

This seems pretty standard. The problem is in the targeting on that last line there. When we let the event bubble all the way up to the body element we're allowing a couple things to happen that are not necessarily desirable. The first of which is that the event is bubbling up and running callbacks on nodes that it doesn't need to. This isn't very costly at all(Maybe a few dozen CPU cycles? Not a big deal.) but it is a bit sloppy to not scope events to only the place they need to be. The second problem is more directly related to that point: The event is firing outside of it's appropriate context and could affect the behavior of other components. If one child node triggers a click event and another far-away child node triggers a different click event, higher level nodes will receive the same type of event and there's no guaranteeing that every developer that's worked on this project has the context available to make sure they don't accidentally listen on the wrong event. So what we need to do is scope the event to a parent that we know is the nearest to the children that will be the event targets.

// Good
let signup_modal, show_signup_modal

show_signup_modal = (event) => {  
  event.stopPropagation()
  signup_modal = $(modal_markup)
    .appendTo('body')
    .show()
}

$('.menu-list').on('click', '.js-menu-link', show_signup_modal)

This way we scope the event listener to only the node that contains all of the nodes that can trigger the event. We also stop the event from propagating up because nothing higher than this node needs to know that this event is firing. This is great because now we can keep our events exactly where we need them much in the same way that we scope variables to local contexts with closures, saving CPU cycles, memory and reducing overhead on our JavaScript app.

Stay pragmatic my friends.