$addHandler : cross browser handler execution sequence
$addHandler
is an excellent method to attach event handlers to DOM elements in cross
browser scripting, defined in standard ASP.NET AJAX client library. It hides
the underlying browser specific event registration methods, and gives you the
unified way across multiple browsers. One possible problem that one can face is
that when attaching multiple handlers to same event of single DOM element, the handlers
will be invoked in different order for different browsers. The handlers will be
invoked in LIFO (handlers will be invoked in order reverse to in which they are
registered) principle in IE, while FIFO (handlers will be invoked in order in
which they are attached) principle in Firefox and Opera.
This behavior is
due to the fact that $addHandler is using attachEvent method for trident
based Internet Explorer, and addEventListener for gecko based Mozilla FireFox
and other browsers, which in turn call handlers in different sequence. Well in
most of the cases, handlers’ execution sequence does not matter, because there could
always be a sequence independent alternative implementation. However, in some
scenarios, predetermined sequence can help a lot and save a lot of time. I
faced one such scenario few days back, so looked around the way to do this.
I
was looking for the $addHandler personal version which is:
- ASP.NET Ajax
compliant ( having same parameters as original one, and returns same
Sys.UI.DomEvent
so that
can be used with standard ASP.NET Ajax code)
- Supporting
predetermined and same event handlers calling sequence across all
browsers.
- Not conflicted
with $addHandler (both the new and original can be used simultaneously on
single element’s same event without any conflict and change in behavior).
To look around what actually is happening
in $addHandler, I have looked in to MicrosoftAjax.debug, the debug file provided
with ASP.NET 2 Ajax client library to check the actual implementation of method.
You can find this file under your ASP.NET 2 Ajax Extensions installation
directory. Its path on my PC is
\Program
Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX
Extensions\vX.X.X\MicrosoftAjaxLibrary\System.Web.Extensions\X.X.X.X
Where X is ASP.NET Ajax version number, you have
installed.
I have inspected the method and came up
with my own addSeqHandler inspired by original $addHandler method, which
fulfill above requirements. Here is the code for addSeqHandler:
var addSeqHandler
= function(element, eventName, handler) {
var e =
Function._validateParams(arguments, [
{name: "element",
domElement: true},
{name: "eventName",
type: String},
{name: "handler",
type: Function}
]);
if (e) throw e;
if
(!element._seqEvents) {
element._seqEvents = {};
}
var
eventCache = element._seqEvents[eventName];
if
(!eventCache) {
element._seqEvents[eventName] = eventCache
= [];
}
if(eventCache.length==0)
$addHandler(element,
eventName,_invokeSeqHandlers);
eventCache[eventCache.length] = {
handler: handler,
browserHandler:
function(e){return handler.call(element, new
Sys.UI.DomEvent(e));}
};
}
As you see we have
registered our own handler using $addHandler and start maintaining our own
_seqEvents list of events registrations along with _events list (so called
cache according to the debug code) which is maintained by $addHandler. And so
when specific event will be fired, _invokeSeqHandlers will also be
called along with other handlers and then
_invokeSeqHandlers will
manage and call all events registered through our new method.
Here is the code of_invokeSeqHandlers
var
_invokeSeqHandlers = function(eve){
var cache =
this._seqEvents[eve.type];
for (var i = 0; i < cache.length; i++) {
cache[i].browserHandler.call(this, eve);
}
}
Please note that all methods registered
through addSeqHandler will be called in FIFO principal in all browsers. We have
introduced our own list of events, so we have to provide our own custom remove
method now. Here is the method to remove our sequential handlers:
var removeSeqHandler
= function(element, eventName, handler) {
var e =
Function._validateParams(arguments, [
{name: "element",
domElement: true},
{name: "eventName",
type: String},
{name: "handler",
type: Function}
]);
if (e) throw e;
if ((typeof(element._seqEvents) !== 'object') || (element._seqEvents == null)) throw
Error.invalidOperation(Sys.Res.eventHandlerInvalid);
var
eventCache = element._seqEvents[eventName];
if
(!(eventCache instanceof Array)) throw Error.invalidOperation(Sys.Res.eventHandlerInvalid);
for (var i = 0, l = eventCache.length; i < l; i++) {
if
(eventCache[i].handler === handler) {
break;
}
}
eventCache.splice(i, 1);
if(eventCache.length==0)
$removeHandler(element,eventName,_invokeSeqHandlers);
}
Above
methods are tested and worked fine in IE (versions 6, 7, 8), FireFox 3, Safari
3, Opera 9.
Download
code here: