Basic event delegation in Prototype
Posted by Pat Nakajima on February 13, 2008 in Javascript.Event delegation is made possible by the fact that certain DOM events “bubble” up through their ancestors. So if we listen for
onclick events in the body, we’ll should hear every one that occurs throughout the page. And if we want to assign certain behaviors to certain elements’ onclick event, we just have to check to see if the target element of the event matches the group of elements to which we want to assign a particular behavior. Make sense? Not so much? Here’s some code:
// Introduces Event delegation (http://icant.co.uk/sandbox/eventdelegation)
Object.extend(Event, {
delegate: function(element, eventName, targetSelector, handler) {
var element = $(element);
function selectorMatch(element) {
return element.match(targetSelector);
}
function validateTarget(origin) {
if ( origin.match(targetSelector) ) { return origin; }
var ancestors = origin.ancestors();
return ancestors.find(selectorMatch);
}
function createDelegation(_delegatedEvent) {
var rawOrigin = _delegatedEvent.element();
var origin = validateTarget(rawOrigin);
if ( origin != null && (typeof handler == 'function') ){
_delegatedEvent.element = function() { return origin; }
return handler(_delegatedEvent);
}
};
element.observe(eventName, createDelegation);
return element;
},
delegators: function(element, eventName, rules) {
var element = $(element);
function delegateRule(rule) {
element.delegate(eventName, rule.key, rule.value)
}
$H(rules).each(delegateRule)
return element;
}
})
Element.addMethods({
delegate: Event.delegate,
delegators: Event.delegators
})
Object.extend(document, {
delegate: Event.delegate,
delegators: Event.delegators
})
With that snippet, we have two new methods available to our page elements: delegate and delegators (this could definitely be refactored into a cleaner implementation, but for the sake of illustration, it’s pretty good). So now if we had a div with some links in it, we could delegate behaviors within that div like so:
ElementBehaviors = {
// I create an alert message out of my target element's innerHTML
alertify: function(event) {
var element = event.element();
alert(element.innerHTML);
event.stop();
},
// I remove my target element
removify: function(event) {
var element = event.element();
element.remove();
event.stop();
}
}
$('div_id').delegators('click', {
'.alert': ElementBehaviors.alertify,
'.remove': ElementBehaviors.removify
})
Now, links within $('div_id') with the class name .alert will have their innerHTML alerted when clicked, and elements with the class name remove will just be removed when clicked, even if they are added to the page dynamically. No reloading or reassignmening of handlers is necessary.
So that’s great. But what if you want to event delegation for events that don’t bubble, such as form submissions?
Simulating event bubbling with Prototype’s custom events.
To simulate event bubbling, we have to resort to listening for bubbling events that can cause non-bubbling events. By checking the circumstances surrounding these “trigger” events, we can determine whether or not to fire a custom event. Here’s some code:var Bubbler = {
// Checks to see whether or not this element will submit
// a form if the Enter key is pressed within it.
submittableInput: function(element) {
var element = $(element);
return ( element.match('input[type=text]') || element.match('input[type=password]') )
},
// Checks to see whether or not this element will submit a
// form if clicked.
submitButton: function(element) {
var element = $(element);
return ( element.match('input[type=submit]') || element.match('input[type=image]') )
},
Behaviors: {
// Fires the 'form:submitted' custom event if the Enter key was
// pressed while the cursor was within a input that would submit
// a form.
keypress: function(event) {
if ( event.keyCode == 13 ) {
var element = event.element();
if ( Bubbler.submittableInput(element) ){
element.form.fire('form:submitted', { 'originalEvent': event });
}
}
},
// Fires the 'form:submitted' custom event if an element that
// would submit the form was clicked.
click: function(event) {
var element = event.element();
if ( Bubbler.submitButton(element) ) {
element.form.fire('form:submitted', { 'originalEvent': event });
}
}
}
}
// Always remaining vigilant.
Event.observe(document, 'keypress', Bubbler.Behaviors.keypress)
Event.observe(document, 'click', Bubbler.Behaviors.click)
The above code continually listens to all keypress and onclick events in the entire document. When one of them matches the conditions required to submit a form, it’s smart enough to find that particular form element, and fires the ‘form:submitted’ custom event from it. Custom events bubble up through the DOM.
var FormBehaviors = {
// Takes a custom event, submit's the event's target (a form)
// via AJAX and stops the trigger event if it exists.
remotify: function(event) {
var element = event.element();
element.request();
if ( event.memo['originalEvent'] != null )
event.memo['originalEvent'].stop()
event.stop();
}
}
document.delegate('form:submitted', '.remotify', FormBehaviors.remotify)
So now, any form with the class name “remotify” will be submitted via AJAX, again including those added dynamically. And again, no handler refreshes or reassignments were necessary.
If you haven’t played with event delegation yet, give it a try. And if you’re way better than me at Javascript, and can point out some better ways for what I’ve described above, please do share in the comments.

Subscribe to devthatweb!
peoplewhocommented
I still don’t like the delegate vs. delegators thing, I would go for a single Event.delegate.
It’s just an aesthetic thing, but that’s some place I love to waste time on ;)
Other than that, I’ve been playing with kangax’s attempt at this, and I’ve been wondering if you really need to pass the element, as the API gets a little nicer when you only pass the event and the selector(s). And scoping would be achieved by just adding the #id in front of the selector anyway.
We should probably try a complex DOM and benchmark against it :)
Nicolás SanguinettiI’m coming to the same conclusion as you regarding the delegate vs. delegators syntax, though I don’t mind having the option to use either. And there’s no need to pass the element if you call the
delegatorsordelegatemethods on the element itself.I took a look at kangax’s implementation, and while it’s a good deal less readable than mine, it covers many more bases (especially the fact that it is able to unregister behaviors). I’d definitely be interested in seeing some benchmark comparisons between his method and mine.
Pat NakajimaNice work Pat, many people on the delegation wagon, I was just caught by Greg Reimer post about the same thing.
The difference, maybe, that you still need an “onload” event, or at least wait for the body to exist to be able to attach events this way.
Were you planning to also fix the “focus/blur” non bubbling events ?
Diego PeriniSorry, but comments are closed on this blog after 30 days.