Tuesday 30 June 2009

The Importance of 'this' Scope Management Through Javascript Event Handlers

During the development of vBulletin 3.x, much of our Javascript code started to take on a far more object-oriented style. With more widespread use of client-side scripting for features that would appear multiple times on a page, such as popup-menus vBMenu and collapsible elements vBCollapse, having encapsulated code made development much easier.

With one exception - event handling.

Let's take some very simple code to illustrate the problem. The purpose of this code is to force all links to be diverted through a different script. It's overkill, but it illustrates the problem.
function linkRedirect(link_element)
{
this.redirect_base = "http://example.com/redirect?url=";
this.link_element = link_element;
this.link_element.addEventListener("click",
this.handleClick, false);
}

linkRedirect.prototype.handleClick = function(e)
{
e.preventDefault();

// The following will not work
window.location = this.redirect_base
+ escape(this.link_element.getAttribute("href"));
}
In order to get around this, one can use a closure and refer to it instead of this in the event handler callback, like this:

function linkRedirect(link_element)
{
this.redirect_base = "http://example.com/redirect?url=";
this.link_element = link_element;

var closure = this;
this.link_element.addEventListener("click", function(e)
{
e.preventDefault();
closure.execRedirect();
}
, false);
}

linkRedirect.prototype.execRedirect = function()
{
window.location = this.redirect_base
+ escape(this.link_element.getAttribute("href"));
}
Unfortunately, closures can be a great source of memory leaks, especially in Internet Explorer, and are best avoided if possible.

Mid-way through the development of vBulletin, we came across the Yahoo! User Interface (YUI) library, which wraps all this stuff into one beautiful function, which allows this to be passed as a parameter to the callback:
function linkRedirect(link_element)
{
this.redirect_base = "http://localhost/~kier/jq/redirect.php?url=";
this.link_element = link_element;

YAHOO.util.Event.on(this.link_element, "click",
this.execRedirect, this, true);

}

linkRedirect.prototype.execRedirect = function(e)
{
e.preventDefault();
window.location = this.redirect_base
+ escape(this.link_element.getAttribute("href"));
}
Isn't that fantastic?

So why am I writing about this? Well, I've been playing with jQuery and found to my horror that it does not have this scope management system by default, requiring a revertion to the bad old days of closures and helper functions... ugh.

Today, I'm going to attempt to extend jQuery to be able to pass scope parameters in the same way that YUI does. Good luck me...

2 comments:

  1. ... and after a couple of hours of investigation, I found that the bleeding-edge version of jQuery in SVN does indeed have scope handling through .bind().

    $(el).bind("click", this.myMethod, this);

    Hooray!

    ReplyDelete