Saturday, June 25, 2011

Javascript objects and jQuery ajax callback scope issues

I was feeling good the other day when writing some Javascript objects. Defining instance values and methods on the prototype chain. In my awesome object organization effort, I decided why not add my jQuery Ajax success, error and pre-submit callback functions in an object. I also thought it would be a good idea to add the Ajax options generation into the object itself.

Here is a sample object:

function AjaxObject(){
   this.ajaxObjectState = undefined;
}

AjaxObject.prototype.callbackSuccess = function (){
   this.ajaxObjectState = "success";
};

AjaxObject.prototype.callbackError = function (){
   this.ajaxObjectState = "error";
};

AjaxObject.prototype.getAjaxOptions = function (){
   var options = {
      type: 'POST',
      error: this.callbackError,
      success: this.callbackSuccess
   };
   return options;
};

var ajaxObjectInstance = new AjaxObject();
$.ajax(url, ajaxObjectInstance.getAjaxOptions();

This blog post is about what's wrong with this and why it won't work.

The issue is that the options definition assigns a reference to the callback function itself, not the object instance of the function. In the jQuery callback scope, the "this" is that of the function of the callback, not the outer object instance that the method is contained in.

In the example above, when the success/error is called, "this.ajaxObjectState" will be undefined.

So how do you get around this? I did the following:

AjaxObject.prototype.getAjaxOptions = function (){
   var options = {
      type: 'POST',
      error: this.callbackError,
      success: this.callbackSuccess,
      callingThisReference: this
   };
   return options;
};

I added a reference to the instance object into the Ajax options as "callingThisReference". The "this" assignment in the options is the outer object instance created via "new". This is now the reference in the callback to get to the object instance variable to change, or read.

In addition, the success and error callbacks need to change a bit, which breaks the normal object organization effort in my initial approach:

AjaxObject.prototype.callbackSuccess = function (){
   this.callingThisReference.ajaxObjectState = "success";
};

AjaxObject.prototype.callbackError = function (){
   this.callingThisReference.ajaxObjectState = "error";
};

Note the "this.callingThisReference" in the callbacks. "this" is the function "this" and the "callingThisReference" is the object "this".

The addition somewhat breaks the object itself now, because it won't work outside of this fashion. The simple "new" and then calling the success or error callbacks would result in "callingThisReference" to be undefined. This can be easily fixed by checking if "callingThisReference" is defined, if not, then just use "this".

See also:


No comments:

Share on Twitter