Event Delegation for MooTools
I just posted a long post about a bunch of new plugins I released today. Lots of good stuff in there – pointy tips, a form validator extension, a tips extension, but I want to take a minute to talk about Event.Delegate.js.
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. For instance, let’s say you want to monitor all the links on a page. Using MooTool’s addEvent method you could loop through all the link as and attach an event to each:
$$('a').each(function(link) {
link.addEvent('click', function(){
alert('you clicked a link!');
});
});
This would take a while with a page full of links.
Delegation
With delegation we attach the event to the parent of a group of elements and then limit the behavior when the target of the event matches our criteria. In this case, we’d attach a click event to the document.body, so our code would run very quickly on startup (instead of looping over all the links on the page). Then, when the user clicks on that parent element, we check if the target of the event (i.e. the thing they clicked) matches our conditions to fire the element.
Here’s what that looks like:
Update: I have a new version of delegation posted.
Update This syntax has changed slightly since I first posted. I’ve updated these code examples to the new syntax.
$(document.body).delegate('a', 'click', function(){
alert('you clicked a link!');
});
Element.deletate takes three arguments: an event type, a test, and the method to execute when the test passes. The easiest way to specify the test is to pass in a selector. If the element target that fired the event matches the selector, then your method will fire. Alternately, you can pass in an array of elements and, if the element that fired the event is in that array, your method will fire. 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. The default method (that checks the element against a selector) looks like this:
function(target) {
//note that target has not been extended by MooTools
return Element.match(target, selector);
};
Removing Delegations
You can remove all the events on the parent element with removeEvents, but this will remove events other than delegated ones. I.e. if you had a click event on document.body in addition to a delegation (like the first delegation example above), you’d remove both like so:
$(document.body).removeEvents('click');
//no more click events on the body
If you executed the code above, our delegated click event from the previous example will be removed (but so would any other click events on the body).
You can remove a specific delegation with unDelegate but you must pass in a pointer to the specific function you added to begin with.
var monitor = function(){ alert('you clicked a link!')};
$(document.body).delegate('a', 'click', monitor);
//link clicks are delegated to document.body
//...now we remove the delegation:
$(document.body).unDelegate('click', monitor);
Careful with things like binding. If you attach a method this way:
var monitor = function(){ alert('you clicked a link!')};
//here we bind something to monitor:
$(document.body).delegate('a', 'click', monitor.bind(this));
There’s no way to remove this delegation. When we call .bind (or pass or any other Function method), we create a new function and that’s what gets attached. Be sure to keep a reference to the attached function if you want to remove it later, like so:
var monitor = function(){ alert('you clicked a link!')};
//here we bind something to monitor:
var boundMonitor = monitor.bind(this);
$(document.body).delegate('a', 'click', boundMonitor);
//...now we remove the delegation:
$(document.body).unDelegate('click', boundMonitor);
Using event delegation can vastly improve the performance of your application when you are monitoring a lot of DOM elements. You can see it in action in the tutorial.
Follow @clientcide on twitter to get notified of new posts.
To follow me personally on twitter, follow @anutron.
December 23rd, 2008 at 5:43 pm
Just wanted to point out that Mootools kind of does this for you. Or well, it doesn’t delegate probably in the same way you do. I haven’t popped the hood yet.
But you’re example can be done without looping through each element:
$$('a').addEvent('click', function() {console.log('poop');
});
The addEvent function also works for element collections and will add the event to every element in the collection.
December 23rd, 2008 at 6:50 pm
That’s not really accurate Sean. If you call a method on what $$ returns (an array) it basically handles the looping for you. So your example above in your comment is the same thing as my first example. You’re still looping over all the anchors in the document and adding an event to each. Further, if you were to chain this you would actually loop through it more than once. I.e. this is bad:
$$('a').addEvent(...).setStyle(...)The above code would loop through all the links twice.
Further, this doesn’t accomplish the same goals. You aren’t delegating the event to a parent but rather attaching an event to each item, which is precisely what delegation helps you avoid.
December 29th, 2008 at 11:37 pm
[...] this functionality into MooTools 1.3 but I assume they will. I strongly suggest MooTools developers read Aaron’s article at Clientcide. I also recommend this article for jQuery users as John Resig recently boasted of this addition to [...]