Event delegation is especially useful in effects like dropdown menus, where lots of events on links may take place that can easily be handled at the root level (an
<ul> in this case).
There used to be one problem, though: although event delegation works fine for the mouse events, it does not work for the focus and blur events we need to make dropdown menus keyboard-accessible.
For all I know they're already aware of this technique; but it was new to me so I publish it anyway.
For those unaware of it, let's briefly discuss event delegation. As context I'll use a dropdown menu.
When a user uses a mouse to steer through a dropdown menu, you want to capture all mouseover and mouseout events in order to see whether the latest user action should open or close a menu. (You also have to carefully distinguish between useful and useless events because Firefox, Safari and Opera still don't support the mouseenter and mouseleave events, but that's another story.)
The mouseover and mouseout events bubble up; that is, when a mouseover event on a link fires, the event "climbs" the DOM tree to see if any mouseover event handlers are defined on the ancestor nodes of the link. It first checks the link itself, then goes on to the containing
<li>, followed by the
<ol>, and all the way up to document or window level.
That means that it's perfectly possible to define your generic onmouseover and onmouseout event handlers on the
<ol> that forms the root element of the dropdown menu. When these events take place lower in the tree, event bubbling makes sure they eventually end up at the root element and can be handled there.
<ol id="dropdown"> <li><a href="#">List item 1</a> <ol> <li><a href="#">List item 1.1</a></li> <li><a href="#">List item 1.2</a></li> <li><a href="#">List item 1.3</a></li> </ol> </li> [etc.] </ol> $('dropdown').onmouseover = handleMouseOver; $('dropdown').onmouseout = handleMouseOut;
The great advantage of this trick is that you don't need to set two event handlers on every link, which may save some browser memory.
This works fine. However, as soon as you want to make your dropdown keyboard-accessible, too, you run into a problem.
In theory, making a dropdown keyboard-accessible is easy: you just define event handlers for the focus and blur events, too. The problem is that these events do not bubble up. A focus or blur event on a link fires only on the link itself, and not on any ancestor element of the link.
This is an ancient rule. A few events, most motably focus, blur, and change, do not bubble up the document tree. The exact reasons for this have been lost in the mist of history, but part of the cause is that these events just don't make sense on some elements. The user cannot focus on or change a random paragraph in any way, and therefore these events are just not available on these HTML elements. In addition, they do not bubble up.
Take the following situation:
<p id="testParagraph"> Some text. <input id="testInput" /> </p> $('testParagraph').onfocus = handleEventPar; $('testInput').onfocus = handleEventInput;
When the user focuses on the input field, the
handleEventInput function is executed. However, the event does not bubble up, so
handleEventPar is not executed. In addition, it's not possible to focus on the paragraph (except when it has a
tabindex attribute), so
handleEventPar is never executed.
Except when you use event capturing.
Event capturing is the reverse of event bubbling. When an event bubbles up it starts at the event target and then moves up the DOM tree. When an event is captured, it starts at the top of the DOM tree (usually the window or document object) and then moves down to the event target.
One of the most curious conclusions of my event research is that when you define event handlers in the capturing phase the browser executes any and all event handlers set on ancestors of the event target whether the given event makes sense on these elements or not.
So let's take the same example as above, but now with
addEventListener in the capturing phase. (You define it on the capturing phase by setting the last argument to
<p id="testParagraph"> Some text. <input id="testInput" /> </p>
$('testParagraph').onfocus = handleEventPar; $('testInput').onfocus = handleEventInput;$('testParagraph').addEventListener('focus',handleEventPar,true); $('testInput').addEventListener('focus',handleEventInput,true);
Now if the user focuses on the input, the event starts at the window or document level and then moves down to the input. On the way it encounters the onfocus event handler on the paragraph and executes it, even though the event does not make sense on a paragraph.
As a result,
handleEventPar is executed, and then
Unfortunately IE does not support event capturing. However, it supports the focusin and focusout events that, contrary to focus and blur, do bubble up. If we use these events to get IE on board, we're ready to sail.
Opera (9.5b) incorrectly fires all focus and blur events twice.
Therefore the conclusion must be that onfocus and onblur event handlers may be delegated just as onmouseover and onmouseout are by registering them in the capturing phase. In order to properly delegate all events for a keyboard-accessible dropdown menu you do:
<ol id="dropdown"> <li><a href="#">List item 1</a> <ol> <li><a href="#">List item 1.1</a></li> <li><a href="#">List item 1.2</a></li> <li><a href="#">List item 1.3</a></li> </ol> </li> [etc.] </ol> $('dropdown').onmouseover = handleMouseOver; $('dropdown').onmouseout = handleMouseOut; $('dropdown').onfocusin = handleMouseOver; $('dropdown').onfocusout = handleMouseOut; $('dropdown').addEventListener('focus',handleMouseOver,true); $('dropdown').addEventListener('blur',handleMouseOut,true);
Now you've succesfully delegated the focus and blur events.
The fact that the capturing phase of the event works different from the bubbling phase raises a few questions, which I might answer at a later date:
(Note: In the case of focus and blur, it might make sense for the events not to bubble up. After all, if a user focuses on a link or form field, you don't want any generic window.onfocus to fire, because on the window that event has a totally different function: it fires when the user focuses on the entire browser window.)
I’ll be around at the following conferences:
Comments are closed.