For most types of events, there’s a better way: Event Delegation. At the basic level it involves attaching an event delegation function to a parent element, then catching events which bubble up and passing them off to specific handlers based on the event’s target.
Before Event Delegation
Then at work I started building a UI widget with jQuery (sorry Prototype, it was already there when I joined the project; I still have a fondness for Prototype- and Ruby/Rails-style code which I’m sure I’ll pick up again when I have time to return to my own project). I needed this widget to allow inserting and deleting elements on the fly, and inserted elements to have event handlers for additional behavior.
“Hmm, I guess I can add event handlers each time I add elements. Though wouldn’t it be better if they just inherited these behaviors?”
Event Bubbling Makes Possible Event Delegation
Inheritance is one way of looking at it, but I’ll explain event bubbling before getting lost in the metaphor.
Event bubbling is the name given to the way events trigger handlers on the target element and the element’s parent, and its parent’s parent, and so on, hence “bubbling up”. At any level of an element’s ancestry the event can be examined, and by checking out the event.target property (for most browsers, IE has to be different of course) the initial or “target” element can be discerned.
Example time. Say we have some markup that looks like this:
<li><input type="checkbox" /> Watch Dark City again<li>
<li><input type="checkbox" /> Beat System Shock (the original)<li>
<li><input type="checkbox" /> Read Dawkins<li>
Fast forward to the end of the month when I’ll have finished The God Delusion and click to check off the third item in the list.
What happens is that the input element receives the click event first. After any click handlers attached there are fired, the event bubbles up to the parent element – in this case it’s the li containing the “Read Dawkins” text, and click handlers on that element get fired. Next the event continues moving up the ranks to the ul element (id=”toDoList”) and again any click handlers attached there are fired. Beginning to see how event delegation comes into play?
jQuery and Event Handlers
jQuery is a pretty good library which makes binding event handlers easy, even binding a handler function to multiple elements at once. We might add a handler to each checkbox like this:
However, what if we want to add more list items, say in response to the user typing in a text field and pressing enter? We’d have to add event handlers to each new checkbox, and can’t just reuse the line above as that would result in existing checkboxes getting multiple bindings of our clickHandler function.
How about this instead:
if (event.target.tagName.toLowerCase() == 'input' && event.target.getAttribute('type').toLowerCase() == 'checkbox')
Here we’ve attached a single handler to the root of our unordered list that can catch any click event originating on itself or any of its descendants. When an event is received it checks the target to make sure it is a checkbox, then fires checkboxClickHandler.
Benefits of Event Delegation
- Fewer handlers to attach: No need to identify individual elements and bind handlers to each of them. Saves memory too.
- Automatically cover new elements: Newly added DOM elements will bubble their events up and be handled the same as existing elements, without any extra effort.
- Greater flexibility: If we want to operate on clicks to li elements we just need to add an else if into the listClickDelegator, and code our liClickHandler function. The rest of the plumbing is already in place.
- Easy to maintain: All of our event binding and triggers happen in one place, and likely use less code than the traditional method for anything more than the basic examples I’ve included here.
Event Delegation Pitfalls
A few things to be aware of before you leap to make use of this pattern:
- Browser compatibility: Not all browsers populate event.target (IE I’m looking at you), without that information it’s difficult to know how to delegate an event. For IE event.srcElement is the way to go. If you’re using a nice library like jQuery this quirk has already been normalized for you, continue using event.target.
- Not all events bubble: Focus and blur events don’t make it up the tree, so if you want to handle those events you’ll need to bind handlers directly to the desired elements (there are some efforts to ease the pain)
- Dan Webb takes things one step further with jQuery by showing off a little $.delegate() sugar.
- Learning jQuery talks about event delegation with examples in Working with Events.