May 27th 2008 05:20 pm
Howto: avoid binding the same eventhandler multiple times to an element event in the MS AJAX framework
The Microsoft AJAX $addHandler method is a wrapper to the browser specific implementations of the w3c standard e.g. $addHandler(element, eventName, handler).
Once you’ve bound an event handler to an element, there is no standard way to retrieve the binding information. Javascript frameworks work around this limitation by setting custom properties on the html elements to track handler bindings. As long as handlers are added and removed through the framework things will work as expected with one minor exception. If $addHandler is called twice with the same handler, when the event fires, the handler fires twice.
The Microsoft AJAX framework doesn’t check whether or not a delegate is already bound before binding it a second time, nor does it provide a means to determine what handlers are already bound. The best way to avoid this condition is by following the MS AJAX pattern for binding behaviors and elements. The pattern dictates that elements are “wrapped” in a one-to-one relationship with controls. When a control is initialized it binds element events to its own internal delegates. When other behaviors need to listen for events, they use the event handling architecture for controls and behaviors by binding against the control, not the element. Mike Ormond’s blog provides a good explanation.
Occasionally, you have the problem of adding the same handler multiple times. To work around this we need to know a little more about how the Microsoft framework tracks element event bindings.
When $addHandler is called, Microsoft AJAX adds an expando attribute to the element called “_events”. This is a hash table of the event bindings for the element. Element._events["eventName"] returns an array of delegates bound to the specified element event. The code below simply uses this internal implementation of the MS AJAX framework to avoid duplicate bindings. To use it, simply replace calls to $addHandler and $removeHandler with calls to $addHandlerIfNotDefined and $removeHandlerIfNotDefined.
$handlerDefined = function(elt, eventName, handler) {
// returns true if an eventHandler has been defined for the given element and the given event
if ( ( typeof( elt._events ) !== 'object' ) ||
( elt._events === null ) ) {
return false;
}
var cache = elt._events[eventName];
if ( !(cache instanceof Array ) ) {
return false;
} else {
for (var i=0, l = cache.length; i < l; i+=1) {
if (cache[i].handler === handler) {
return true;
}
}
}
}
$addHandlerIfNotDefined = function(elt, eventName, handler) {
// ensures the given handler is bound to the given element for the given event
if ( !$handlerDefined( elt, eventName, handler ) ) {
$addHandler(elt, eventName, handler);
}
}
$removeHandlerIfNotDefined = function(elt, eventName, handler) {
// ensures the given handler is bound to the given element for the given event
if ( $handlerDefined( elt, eventName, handler ) ) {
$removeHandler(elt, eventName, handler);
}
}
Note: this solution does not handle new instances of anonymous delegates e.g.
$addHandlerIfNotDefined(elt, “onclick”, new Function(evt){some code here});
Since a new anonymous delegate is created each time the handler is added, we would need to compare both the “state” and “code of the delegate for instance equivalence. Determining delegate object equivalence is a difficult thing, the code above compares only delegate instance equivalence.
No Comments yet »