I just learned of Aaron Newton\’s Event.Delegate.js and think it is amazing. Why is it amazing?
Event delegation is a common practice where by you attach an event listener to a parent object to monitor its children rather than attach events to all the children. It’s far more efficient when you have numerous items on a page that you want to interact with.
The examples below should give you a better idea how awesome it is.
The Problem
Say you have A LOT of anchors and you want to add a click event to them. Normally you\’d do this in several ways:
// First method
var links = $$(\'a.link\');
links.each(function(link){
link.addEvent(\'click\', function(){
//Do this stuff
});
});
// Second method
$$(\'a.link\').addEvent(\'click\', function(){
//Do this stuff
});
This is all fine but it\’s not very efficient. You end up looping through all the anchors to add the event.
The Solution
With Event.Delegate you can attach an event to the parent, and have it handle everything. I\’m going to use the example from Aaron\’s post with a small addition to what I have above.
//Parent element handles the delegation, and here it\'s document.body
document.body.delegate(\'click\', \'a.link\', function(e){
//Just like addEvent, you can pass in the event and stop it.
e.stop();
alert(\'you clicked a link!\');
});
Conclusion
I really like Event Delegation. I\’m really curious how fast it is though. Time to do a couple console.profile() to log the times.
I didn\’t understand this part of his post:
Finally, you can pass in a function of your own. If your function returns true, then your method will fire. Note that the element passed to your method is not extended by MooTools yet for efficiency. You should avoid extending it yourself (by passing it through $) as this will affect performance. Instead, use the static methods on Element.
It confused me. I thought he was talking about the third argument, until he mentioned it\’s a function that returns true. Passing in a function as a test looks something like:
//My test function
var testFunc = function(target){
return target.match(\'a.link\')
}
document.body.delegate(\'click\', testFunc, function(e){
e.stop();
alert(\'you clicked a link!\');
});
Thanks for writing about this Aaron. It\’s a really great addition.
“I’m really curious how fast it is though. Time to do a couple console.profile() to log the times.”
So how much faster is it then?
The speed increase is really a trade off. You are taking the startup costs for your page and shifting the cost to only when the action is performed.
Let’s say that you have a page full of links as in the code example above and there are 100 links. You call addEvent on each of these and you run the selector ($$(‘a’)). This isn’t terribly expensive (but you may have a lot of stuff like this going on). When your page loads, this code runs and it creates a demand on the client to process your code immediately. When a user clicks a link, your method is executed immediately and quickly.
Now consider delegation. On startup, you attach an event to the parent (doc.body or something). This is one instruction and it’s nearly instantaneous. Later, a user clicks the document somewhere – anywhere. Now the delegated method fires and the plugin has to see if the user clicked a link and, if so, fire your event. To do this, it must run the same selector that the other example runs ($$(‘a’)) and then compare the element clicked with the elements in the selector *and their children*. This makes for a relatively expensive operation, but there’s two big benefits: 1) you only run this code if the user performs the behavior and 2) if the document has changed (new links are added, for instance) you don’t have to attach more events to the new elements.
So the trade off is a faster start-up that doesn’t need to be managed whenever the content changes in return for a slower event-time. Given that the user may never perform the event in question and given that we tend to put a lot of demand on the client at startup time, the benefit is usually worth it when you have a lot of objects you need to attach events to. It doesn’t make sense for attaching it to a specific dom element – for that you’d just use addEvent.
~~~~~~~~~~~~~~~~~~~~~~~~~
All that said, I actually removed this plugin temporarily. See my post here:
http://www.clientcide.com/code-releases/thoughts-on-element-delegation/
“To do this, it must run the same selector that the other example runs ($$(’a’)) and then compare the element clicked with the elements in the selector *and their children*.”
That’s a shame it has to run the $$ function again. Seems like it would be a lot faster otherwise. I wonder if there is a way to avoid this, like maybe by using the event target?
…such as:
$(document).addEvent(‘mouseover’, function(e,b){
if(e.target.nodeName==?){
//do something cool
}
} );
Running the selector on each event is the only way to ensure that the document’s current content is being used. If you cached the $$ value, you wouldn’t get any new items of that type (which is a big value in delegation). The other thing is that you have to check the children of that element for the event because the target might not be the one you’re after. If you have a link tag with a bold tag, the target is the bold tag inside the link (the bottom most element). In order to know that they clicked a link, you must test the link to see if the target is a child.
This is still better than attaching the logic to every link to the page on startup. If you have a highly dynamic page (lots of javascript) you’ll have a lot of startup logic that’s setting up search forms, links, effects, etc. You can lower that startup cost dramatically with delegation. The selector taken on its own is very fast – milliseconds – it’s when you stack them up with dozens of startup methods that the page starts to suffer.
For me, it’s not so much about the speed enhancement. I’m digging this approach cause it’s makes things simpler. Most ajax these days is about adding dynamically some content from the db, whether it’s a new comment, tag, etc. Using event delegation makes the overhead for this kind of action virtually non-existent.
I love you Aaron.
Um. Thanks Oliver.