Wednesday, August 18, 2010

Migrating to dojo 1.5

The application I work on had been running the 1.3 version of dojo for the past 18 months or so. I procrastinated with upgrading to the 1.4 version as I was busy migrating an existing web based application to an Adobe AIR app. When dojo 1.5 rolled around I had some spare cycles, so I decided to make the jump directly from 1.3 to 1.5. As with any software upgrade there were some hiccups along the way - here's the few issues I stumbled across:

1. dijit.layout.TabContainer's default controllerWidget seems to have switched from dijit.layout.TabController to dijit.layout.ScrollingTabController. This caused some issues for me as I had declared some custom CSS stylings specifically for dijit.layout.TabController which didn't apply to dijit.layout.ScrollingTabController. Pretty simple workaround for this issue though - I just needed to specify a controllerWidget property when programatically creating new dijit.layout.TabContainer widgets.

2. dojox.layout.RotatorContainer/dojox.layout.RotatorPager have been replaced by a combination of dojox.widget.AutoRotator/dojox.widget.rotator.Controller. Given that I had hacked into a lot of 'private' APIs in the 1.3 version of dojox.layout.RotatorContainer I was a little apprehensive about making the move to dojox.widget.AutoRotator. @cb1kenobi on the dojo IRC helped out a lot with the migration and my application code is a lot cleaner looking as a result.

3. dojo.deferred got a makeover in the 1.5 version of dojo. Previously, I had code which looked like this:
dojox.rpc.transportRegistry.register(
"JSONP",
function(str){ return str === "JSONP"; },
{
fire: function(r) {
var headers = [], url, def;
url = r.target + ((r.target.indexOf("?") === -1) ? '?' : '&') + r.data;
// dair.xhr.send returns a new dojo.Deferred
def = dair.xhr.send({
url: url,
method: "POST",
headers: headers,
checkHeaders: true
});
def.addCallback(this, "parseResults");
return def;
},

parseResults: function(obj) {
obj = obj.data;
var result = dojo.fromJson(obj);
if(result && result.Error) {
var errCode = new Error(result.Error.Code);
errCode.displayMsg = result.Error.Message;
return errCode;
}
return result;
},

A short explanation of the code above:

1. Make the network call (dair.xhr.send).
2. Parse the results with a dojo.fromJson call in parseResults.
3. If while parsing the results of a network call I found an Error property on the json object, treat this response as an error.
4. Returning an Error object from the parseResults method resulted in the error callback being invoked.

However, with the 1.5 version of dojo, this doesn't fly. After pestering @novemberborn on the #dojo IRC channel, he was able to point me in the right direction - I needed an extra dojo.deferred (note the promise variable is what I invoke the callback/errback on in the example below):

dojox.rpc.transportRegistry.register(
"JSONP",
function(str){ return str === "JSONP"; },
{
fire: function(r) {
var headers = [], url, def, promise = new dojo.Deferred();
url = r.target + ((r.target.indexOf("?") === -1) ? '?' : '&') + r.data;
def = dair.xhr.send({
url: url,
method: "POST",
headers: headers,
checkHeaders: true
});
def.then(dojo.hitch(this, function(obj) {
this.parseResults(obj, promise);
}));
return promise;
},

parseResults: function(obj, promise) {
obj = obj.data;
result = dojo.fromJson(obj);
if(result && result.Error) {
resErr = new Error(result.Error.Code);
resErr.displayMsg = result.Error.Message;
promise.errback(resErr);
return;
}
if(promise.fired !== 1) {
promise.callback(result);
}
},


Apart from some other styling bits and bobs, those 3 issues were the only pieces that slowed me down when doing the migration. Having migrated dojo applications from 0.43 -> 0.9 -> 1.0.2 -> 1.3 -> 1.5, I have to say that this migration ranks among the easier of all migrations. This speaks volumes for the dojo dev team - keeping true to an API is definitely changeling at times, but it makes my life a hellava lot easier. I always find that migration time is always a good time to take a second look at how you're using a toolkit. For me, I started using the .set/.get APIs instead of the .attr API. I also converted by dojo.deferred addCallback/addErrback methods to the more concise dojo.then. I also migrated some custom code to use the new dojox dnd BoundingBoxController and Selector APIs (shameless plug there). When the dust settled after the migration, I figured out that I was able to get rid of ~250 lines of custom code as I had found alternatives in the dojo toolkit - I'm always glad to get rid of code. All in all, I reckon the migration took around 4 days of work - not too bad considering the application I work on is ~11000 lines of code.

dojo 1.6 seems to be coming up pretty soon - I reckon I won't leave it another 18 months for my next dojo upgrade. http://bugs.dojotoolkit.org/ticket/8578 looks particularly interesting - I've been looking for some CI implementation for dojo doh testing and this ticket looks like it could prove very useful.

1 comment:

  1. You should keep the deferred as `var dfd = new dojo.Deferred;`, but return `dfd.promise`. That way, the caller can't resolve the deferred for you.

    Also, callback/errback have been deprecated in favor of resolve/reject.

    ReplyDelete