Currently I am developing a web application and the JavaScript part of it is neatly organized in an object-oriented hierarchy. For some of the functionality I needed I went for jQuery, and since I am not particularly experienced with it, I soon stumbled upon the problem of binding jQuery events to object methods. In other words, I needed to handle an event without losing my object’s context. Here is a very simple example for this situation:
MyObject = function(msg) {
this.message = msg;
this.initialize = function() {
$('#myButton').click(this.buttonClicked);
};
this.buttonClicked = function(evt) {
alert(this.message);
};
this.initialize();
};
This code won’t work, because the scope of jQuery’s .click() and .bind() methods is always that of the DOM element that triggered the event. So when myButton is clicked, instead of pointing to an instance of the MyObject class, this on line 10 will point to the DOM element #myButton and this.message will evaluate to undefined. Both .click() and .bind() methods don’t support a scope parameter. So what options do we have to run buttonClicked without losing all references to the MyObject instance?
» Storing the this reference in a local variable
The first option is to declare a local variable within the MyObject function that defines our class and assign the value of the this reference to this variable. Here is what I mean:
MyObject = function(msg) {
this.message = msg;
var me = this;
this.initialize = function() {
$('#myButton').click(this.buttonClicked);
};
this.buttonClicked = function(evt) {
alert(me.message);
};
this.initialize();
};
Although this will work, it is not a particularly nice oo practice. Additionally, you will not be able to use this trick if you are building your classes using a utility function such as Prototype’s Class.create( {...} )
» Using jQuery.proxy()
The jQuery-way of solving the issue is creating proxy function for the event handler. When you create a proxy function, you can explicitly specify the context, within which your function should be executed and the proxy will take care of it. So our code snippet would transform to this:
MyObject = function(msg) {
this.message = msg;
this.initialize = function() {
$('#myButton').bind( 'click',
jQuery.proxy( this.buttonClicked, this ) );
};
this.buttonClicked = function(evt) {
alert(this.message);
};
this.initialize();
};
The proxy is created on line 7. The method currently has two alternative signatures: jQuery.proxy( function, context ) and jQuery.proxy( context, functionName ) (where functionName is a string).
» Using a plug-in function
Alternatively, you can use a plug-in function that allows you to explicitly specify the scope of the handler. Here is an example (originally from ThinkRobot):
(function($) {
$.fn.hitch = function(ev, fn, scope) {
return this.bind(ev, function() {
return fn.apply(scope || this, Array.prototype.slice.call(arguments));
});
};
})(jQuery);
Even though both the proxy and the plug-in approaches allow you to elegantly deal with object-oriented event handling, sometimes they are not the best solution. Explicitly changing the scope of the event handler means that you can only access the event source through the event object, passed to the handler, i.e. you will have to use the evt.target property within your handler in order to find out what DOM element triggered the event in the first place. This, however, could be problematic in some cases. Consider for example the following code fragment:
MyObject = function(msg) {
this.message = msg;
this.initialize = function() {
$('#myButton').button().bind( 'click',
jQuery.proxy( this.buttonClicked, this ) );
};
this.buttonClicked = function(evt) {
alert(this.message);
};
this.initialize();
};
There is a small modification in line 6 – we are using jQuery UI to create a nicely styled button before we attach a handler to it. The .button() function takes our DOM element and structures it as a button with a title, that is explicitly defined in a span element. Additionally, one can specify icons for the button, which are also attached to the button by means of additional spans, contained in the main button span. We can end up with the following HTML structure:
Now if the user happens to click exactly on the button’s title span, this will be the value of evt.target, and not the button itself. While you can use evt.currentTarget to get the actual element that registered for event handling, don’t forget this property is not supported in Internet Explorer. This is a uncomfortable situation, which is implicitly taken care of within the jQuery .bind() method.
» Using event.data to store the object reference
The last solution might not be a very clean oo practice, but at least it offers access to both the event source and the object context at the same time. In contrast to the first method that uses a local copy of the object self-reference, this method can also be used with classes defined through Prototype’s Class.create() or objects, created with JSON notation. This method uses the optional parameter of the jQuery’s .bind() function that allows you to pass additional data along with the event object. Here is how our code fragment would look like in this case:
MyObject = function(msg) {
this.message = msg;
this.initialize = function() {
$('#myButton').button().bind( 'click',
{scope: this}, this.buttonClicked );
};
this.buttonClicked = function(evt) {
var obj = evt.data.scope;
alert(obj.message);
};
this.initialize();
};
In line 7 we pass the object’s self-reference as an event attribute, in this case named scope. We can then access this attribute in line 11 to retrieve and use our object reference once the event is actually triggered.
Tags: javascript, jquery