Bajwa's Blog

ASP.NET Web 2.0, A Simple Talk by ALI H. Bajwa
$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:

 

 

 

More Posts