Disposing handlers in ASP.NET AJAX: addHandler vs. addHandlers


Share

I ran into an issue while working on an ASP.NET AJAX Extender for the AJAX Control Toolkit that caused me a bit of a headache, so I thought I’d share my solution to it here.

The problem comes from the fact that the Sys.UI.DomEvent class provides a few methods to make it easy to add cross-browser event handlers to your javascript classes: addHandler, addHandlers, removeHandler, and clearHandlers, all of which can be accessed through the $hortcut key, thus these methods can be expressed via $addHandler, et al.

When you add a batch of handlers as a group, you have the option of specifying the handler owner in the last parameter. What this means is that when the event is raised, it is raised in the context of the object you specified rather than the context of the element you added the handler to. In the example below, when the event is raised we will have access to all of the properties and functions within the custom object we’re building, “this”, because our context is “this”, not the “element” we’re attaching the event to.

function someMethod()
{
    $addHandlers($get("element"), {"click":_clickHandler, "keyDown":_keyDownHandler}, this)

}

So when the _clickHandler is called during the click event, if you were to access a private variable that belonged to whatever the ‘this’ class happened to be, you would find the method and your code would rock. It would not check the event’s element for someVariable, because someVariable belongs to our control class.

_clickHandler : function(e)
{
    if(this._someVariable)
    {
        // do something awesome

    }

When you use $addHandler, however, you don’t get to specify a context. That means that if the element object specified above doesn’t have _someVariable attached to it, it will evaluate to “undefined”. You have lost the context for your event! Depending on your goals, this can stop your control development cold.

So far we don’t have any issues, we can use $addHandlers for our contextual events and $addHandler when we only care about the event’s parent object. The problem crops up when we need to clean up after ourselves and dispose our handlers to avoid memory leaks (the browser isn’t going to do this for us).

We can use $clearHandlers, but that’s not very responsible of us since we could be clearing another developer’s events from the element (it clears all of them, not just the ones we added). If we try to use $removeHandler to remove handlers introduced by the plural $addHandlers, we’ll get an exception because the handlers were not created using the singular $addHandler method specifically. But if we use $addHandler, we don’t get context! If this situation applies to you, you’ll quickly see how frustrating it can be.

The way around this problem is to manually create the context handlers we need as an array, and then remove only those handlers when disposing using a helper method. It’s tedious but necessary to avoid exceptions or memory leaks when creating controls that can’t make use of $clearHandlers.

// How the delegates get added
this._delegates =
{
    click: Function.createDelegate(this, _keyDownHandler),
    keydown: Function.createDelegate(this, _clickHandler);
}
$addHandlers(this._element, this._delegates);
 
// How they get removed
this._removeHandlers(this._element, this._delegates);
 
// The helper method that does the work
_removeHandlers : function(element, events)
{
    for (var evt in events)
    {
        $removeHandler(element, name, events[evt]);
    }
}

It’s not completely clear to me why calling $removeHandler in a helper method, rather than on its own with the event previously added by a plural $addHandlers method, will avoid the framework exception, but there you have it; no memory leaks, and no errors thrown on dispose. It may be due to how $addHandlers assigns the context object that makes it unrecognizable from a context free event via $addHandler, whereas we “hide” this work when we manually create the delegates ourselves.

The AJAX Control Toolkit has this method already, in $common.removeHandlers, if you’re developing on that foundation.

Kick It on DotNetKicks.com
blog comments powered by Disqus