Event Delegation versus Event Handling
Spoiler: This is not a new trick and has been in use in several big projects already. Talking to some people at dconstruct I found out however that some people know about it (ppk, to be exact), and others were still quite amazed by it. So I thought, why not explain it quickly.
Check out the following nested lists. When your browser supports JavaScript, you will be able to click the links and expand and collapse nested list items.
The difference between the two of them is that the "Handlers" example is rather
slow when the list is very large while the "Delegation" example can be extended
as much as you want to without adding more overhead. The latter example is also a
lot easier to change. Try the following button to dynamically add another list
item with a nested list to each of them (this is done by changing the innerHTML property and
appending a string - much like you'd do it with an Ajax call were you to use AHAH).
You'll see that while you can expand and collapse the new nested list in the "Delegation" example, that doesn't work in the "Handlers" one.
The classic "Event Handling" example
The first example uses a classic way of event handling (Ok, really classic would be the onclick=function() method, but you get it):
YAHOO.example.toggleEventListen = {
hide:function(){
// add a CSS class to the main UL to hide all nested list items
YAHOO.util.Dom.addClass(this, 'dynamic');
// get all ULs in this one and loop over them
var uls = this.getElementsByTagName('ul');
for (var i=0; i<uls.length; i++){
// get the link above this UL and add an event listener pointing to the toggle method
var parentLink = uls[i].parentNode.getElementsByTagName('a')[0];
YAHOO.util.Event.addListener(parentLink,'click',YAHOO.example.toggleEventListen.toggle);
}
},
toggle:function(e){
// get the first nested UL and toggle its display property
var ul = this.parentNode.getElementsByTagName('ul')[0];
if(ul.style.display == 'none' || ul.style.display == ''){
ul.style.display = 'block';
} else {
ul.style.display = 'none';
}
// stop the link from being followed
YAHOO.util.Event.preventDefault(e);
}
};
This means that you loop through all the elements, and add one event handler for each of them, which can get rather slow. It also means that if you add new items, you need to set the event handlers again.
The lesser known "Event Delegation" example
The second example takes advantage of something browsers provide us with: Event Bubbling. In essence this means that whenever you do something to an element in the page (like clicking it), this element gets very excited and talkative about it - especially to its parent elements. The link clicked in the example will tell the LI it resides in, the UL it resides in and so on and so forth up until the BODY of the document. And this is what you can use to your advantage; all you need to do is to set one event handler on a main element, and use the getTarget() method of the library of your choice. Compare the event target to what you want to react to and off you go:
YAHOO.example.toggleEventDelegation = {
hide:function(){
// add a CSS class to the main UL to hide all nested list items
YAHOO.util.Dom.addClass(this,'dynamic');
// Add one event listener to the element pointing to the toggle method
YAHOO.util.Event.addListener(this,'click',YAHOO.example.toggleEventDelegation.toggle);
},
toggle:function(e){
// Check where the event came from
var origin = YAHOO.util.Event.getTarget(e);
// Compare with the right node name and test if the parent LI contains a list
if(origin.nodeName.toLowerCase()==='a' &&
origin.parentNode.getElementsByTagName('ul').length>0){
// get the first nested UL and toggle its display property
var ul = origin.parentNode.getElementsByTagName('ul')[0];
if(ul.style.display == 'none' || ul.style.display == ''){
ul.style.display = 'block';
} else {
ul.style.display = 'none';
}
// stop the link from being followed
YAHOO.util.Event.preventDefault(e);
}
}
};
The other beauty of this approach is that you can react to the same event differently according to which element reports it. You can easily extend the toggle() method to check for the LI and the UL and do other things without having to add extra handlers.
What to use when
For really small event handling efforts, the classic solution gives you more control and it is pretty easy to hand over to other developers without much explanation. Event delegation however is probably the only way to keep a large app with a lot of elements to apply handling to (or dynamically loaded elements) in check.
Comment on the blog | Digg this | Add to deli.cio.us | Technorati, Thanks to Media Temple for hosting