Monday, October 11, 2010

Adobe AIR Scrollbars in Mac OSX

If you are just getting started with Adobe AIR development, it might come as a bit of a surprise to see the scrollbar implementation on OSX. They're not even close to the native WebKit implementation. Check it out:

Fugly ... I don't know where these scrollbars came from. I wonder if they're the default Flash styled scrollbars. Or if they're using the default windows scrollbars and didn't bother making specific scrollbars for mac AIR users.

Regardless, with some WebKit specific CSS declarations, you can actually manage to style the scrollbars:
::-webkit-scrollbar-button:horizontal {
background-image:;
background-repeat: repeat;
}
::-webkit-scrollbar-button:horizontal:decrement {
background-image: url(../images/chrome/scroll_arrow-left.png), url(../images/chrome/scroll_arrowbox-horizontal.png);
background-repeat: no-repeat, repeat;
background-position: 2px 2px, 0 0;
}
::-webkit-scrollbar-button:horizontal:increment {
background-image: url(../images/chrome/scroll_arrow-right.png), url(../images/chrome/scroll_arrowbox-horizontal.png);
background-repeat: no-repeat, repeat;
background-position: 2px 2px, 0 0;
}
::-webkit-scrollbar-button:vertical:decrement {
background-image: url(../images/chrome/scroll_arrow-up.png), url(../images/chrome/scroll_arrowbox-vertical.png);
background-repeat: no-repeat, repeat;
background-position: 2px 3px, 0 0;
}
::-webkit-scrollbar-button:vertical:increment {
background-image: url(../images/chrome/scroll_arrow-down.png), url(../images/chrome/scroll_arrowbox-vertical.png);
background-repeat: no-repeat, repeat;
background-position: 2px 3px, 0, 0;
}
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track-piece:horizontal {
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#F6F6F6), to(#CBCBCB), color-stop(0,#CBCBCB), color-stop(.08,#D2D2D2), color-stop(.15,#DBDBDB), color-stop(.23,#E2E2E2), color-stop(.3,#E9E9E9), color-stop(.38,#EFEFEF), color-stop(.45,#F4F4F4), color-stop(.53,#F8F8F8), color-stop(.6,#FBFBFB), color-stop(.68,#FCFCFC), color-stop(.76,#FDFDFD), color-stop(.85,#FAFAFA), color-stop(.93,#F6F6F6));
}
::-webkit-scrollbar-track-piece:vertical {
background: -webkit-gradient(linear, 100% 0%, 0% 0%, from(#F6F6F6), to(#CBCBCB), color-stop(.93,#CBCBCB), color-stop(.85,#D2D2D2), color-stop(.76,#DBDBDB), color-stop(.68,#E2E2E2), color-stop(.6,#E9E9E9), color-stop(.53,#EFEFEF), color-stop(.45,#F4F4F4), color-stop(.38,#F8F8F8), color-stop(.3,#FBFBFB), color-stop(.23,#FCFCFC), color-stop(.15,#FDFDFD), color-stop(.08,#FAFAFA), color-stop(0,#F6F6F6));
}
::-webkit-scrollbar-button:end:decrement,::-webkit-scrollbar-button:end:increment {
height: 15px;
display: block;
background-color: #CCC;
}
::-webkit-scrollbar-thumb:vertical {
background: -webkit-gradient(linear, 100% 0, 0 0, from(#2E4DBE), to(#5D5D5D), color-stop(0,#2E4DBE),color-stop(.08,#9CB5E4),color-stop(.15,#AFC6EA),color-stop(.23,#A8C0E9),color-stop(.3,#A2BEE9),color-stop(.38,#729CE4),color-stop(.45,#83AAED),color-stop(.53,#95BBFA),color-stop(.6,#A4C9FD),color-stop(.68,#B4D9FD),color-stop(.76,#BDE3FE),color-stop(.85,#B1D6FD),color-stop(.93,#5D5D5D));
border: 1px solid #666;
height: 50px;
-webkit-border-radius: 6px;
}
::-webkit-scrollbar-thumb:horizontal {
height: 50px;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#2E4DBE), to(#5D5D5D), color-stop(0,#2E4DBE),color-stop(.08,#9CB5E4),color-stop(.15,#AFC6EA),color-stop(.23,#A8C0E9),color-stop(.3,#A2BEE9),color-stop(.38,#729CE4),color-stop(.45,#83AAED),color-stop(.53,#95BBFA),color-stop(.6,#A4C9FD),color-stop(.68,#B4D9FD),color-stop(.76,#BDE3FE),color-stop(.85,#B1D6FD),color-stop(.93,#5D5D5D));
border: 1px solid #666;
-webkit-border-radius: 6px;
}
::-webkit-scrollbar-corner {
background: #EEE;
}

Here's the end result:

Not 100% perfect, but they're definitely closer to a native Mac OSX scrollbar. Might be an idea to prefix these css declarations with some kind of mac OSX identifier so Linux/Windows users don't get Mac specific scrollbars in the application.

Thanks to Vasken at Eye-Fi for helping me figure out some of the gradations and background images.

Some useful links:
http://webkit.org/blog/363/styling-scrollbars/
http://beautifulpixels.com/goodies/create-custom-webkit-scrollbar/
http://almaer.com/blog/creating-custom-scrollbars-with-css-how-css-isnt-great-for-every-task

Sunday, September 19, 2010

dojo.behavior and jquery live

My last blog post mentioned that dojo.behavior was modeled after Ben Nolan's plugin which in turn has since been replaced by jquery.live. After reading this, I made the assumption that jquery's live and dojo.behavior were analagous. Even after coding up a quick example using dojo.behavior to mirror the jquery live() example, I came to the conclusion that these two API methods offered pretty much the same functionality.

However, after a quick discussion on the #dojo IRC channel, Brian Arnold pointed out a subtle but important difference between the two APIs. Apparently, jquery's live method listens for bubbled events on the document element and then maps these events back to jquery.live declarations. dojo.behaviour is a little different. It listens for events fired from the selector on which the behavior is added in the first place. So, if I changed my previous example to look like this:
dojo.behavior.add({
'p': {
onclick: function(evt) {
dojo.place("<p>Another paragraph!</p>", dojo.query('body')[0]);
}
}
});
dojo.behavior.apply();

New paragraphs would only be added when the user clicked on the first 'Click Me' paragraph. However, with jquery, if I altered the original example to look like this:
$("p").live("click", function(){
$("body").after("<p>Another paragraph!</p>");
});

Even clicking on the 'Another paragraph' would result in new p dom nodes being added to the document.

Cheers for pointing out this difference Brian. Also - see the comments below for one other point by Brian.

One other difference between the two APIs was pointed out by Pete Higgins - dojo.behavior includes a 'found' function which operates a little like this:
var myBehavior = {
// all <span> nodes
"span" : {
// for each:
found: function(n){
console.log('this is the first time I have encountered ', n);
}
}
};
dojo.behavior.add(myBehavior);

Tuesday, September 14, 2010

dojo.connect vs. dojo.behavior

Having used dojo for the past few years, I've become very familiar with using dojo.connect to list for events being fired on objects. The API is simple, clear and concise:
var h = dojo.connect(dojo.byId("myNode"), "onclick", myInstance, "reactToNodeClick");

Plenty of documentation already exists on dojo.connect, so I'm not going to rehash it out here. Reading a recent article on dojo.behavior and couldn't help but agree that there are an enormous amount of similarities between dojo.connect and dojo.behavior. Apparently, dojo.behavior uses the dojo.connect plumbing under the covers, so I was curious to figure out why there was a separate dojo API entry-point to this exact same functionality. dojo.behavior must be offering something above and beyond dojo.connect, or at least it must expose new functionality for connecting to dom and custom events. According to comments on this article, dojo.behavior is based on Ben Nolan's plugin which seems to have been deprecated in favor of jquery's live method. Taking a look at the docs for jquery, I took a shot at converting their example to use dojo.behavior:
<!DOCTYPE html>
<html>
<head>
<style>
p {
background: yellow;
font-weight: bold;
cursor: pointer;
padding: 5px;
}
p.over {
background: #ccc;
}
span {
color: red;
}
</style>
<script type="text/javascript" src="dojotoolkit/dojo/dojo.js">
</script>
<script type="text/javascript">
dojo.require("dojo.behavior");
</script>
</head>
<body>
<p>
Click me!
</p>
<span></span>
<script>
dojo.addOnLoad(function() {
dojo.behavior.add({
'p': {
onclick: function(evt) {
dojo.place("<p>Another paragraph!</p>", dojo.query("p")[0]);
}
}
});
dojo.behavior.apply();
});
</script>
</body>
</html>

So, it seems that dojo.bevahior's main advantages over dojo.connect are:
1. behaviors are added to dom elements which have yet to be inserted into the overall document. This allows the developer to organize all the behavior for a page in one place and also allows for quickly adding behavior to dynamically added dom elements. This stackoverflow question has a pretty nice explanation of this.
2. You can add multiple behaviors to elements all in the one statement. Example:
dojo.behavior.add({
'.foo' : {
found: function(node) {
// do something with each node found
}
}, 
// shortcut for found
'.foo2' : function(node) {
// do something with the node
}
});

although, it could be argued that a combination of dojo.forEach and dojo.query would allow a developer to declare multiple dojo.connects in one statement. For example:
dojo.forEach([".foo", ".foo2"], function(item) {
dojo.query(item).connect("onclick", function(node) {
// do something with the node.
});
});

Unless I'm missing something in the dojo.behavior API I don't see a way to disconnect all the previously added behaviors. I use dojo.disconnect quite a bit when using dojo.connect to clean up after myself (phiggins article again). dojo.behavior.add doesn't return an array of handles so you cannot remove behavior once it has been added. A global suspend/resume API might be a useful addition to the dojo.behavior API to allow a developer to stop/start behaviors. It could even look like this:
dojo.behavior.suspend(selector);
dojo.behavior.suspendAll();
dojo.behavior.resume(selector)
dojo.behavior.resumeAll();

// possible usage
dojo.behavior.add({
'p': {
onclick: function(evt) {
dojo.place("<p>Another paragraph!</p>", dojo.query("p")[0]);
}
}
});
dojo.behavior.apply();
// stop all behavior on p elements that were just applied in the previous statement.
dojo.behavior.suspend('p');
// allow behaviors on p elements again
dojo.behavior.resume('p');

I dunno … that seems hackish when compared with dojo.connect/dojo.disconnect.

Even after this exportation into dojo.behavior, I'm not sure I'd ever use it over dojo.connect/disconnect. dojo.connect/dojo.disconnect gives me a level of granularity that I'm comfortable with. Even searching through the dojo source code for references to dojo.behavior, I don't really see it being used at all in internal framework code (dojo.connect/dojo.disconnect is used EVERYWHERE though).

Can you see any other uses for dojo.behavior - why would you chose to use it over dojo.connect/dojo.disconnect?

Some other useful links:
http://paulirish.com/2010/on-jquery-live/
http://forum.jquery.com/topic/jquery-live-jquery-fn-live-discussion

Monday, September 6, 2010

First impressions of gjslint

I saw a blog post from the Closure Tools Blog recently and it piqued my interest enough to give it a try. I've been running jslint on a codebase for the past 6 months or so and I've been a little frustrated with some of the 'errors' reported by it (I don't think I'm the only one). jslint is not without its warts, but it has definitely forced me to take a second look at my code and made me rethink one or two coding decisions. Some of the 'issues' my source code continuously bumps into with jslint include:

1. Not using the new operator:
Lint at line 89 character 53: Do not use 'new' for side effects.

2. Misuse of Ternary operators:
Lint at line 145 character 112: Expected an assignment or function call and instead saw an expression.
!disabled ? dojo.removeClass(el, 'disabledButton') : dojo.addClass(el, 'disabledButton');

3. Unexpected use of >> << & |
return (transferMode >> 0) & 3;

4. Use of === vs. ==
if (range.text == '') {

Anyways, I'm always interested in seeing some static analysis of my code, so I downloaded and installed gjslint. Running it is pretty simple - just point it at a directory full of .js files:
gjslint -r path/to/my/directory

Oddly, none of the issues which kept on showing up in the regular jslint checker appeared in the results of gjslint. I guess this is because gjslint is more focused on closure's code formatting rules more so than strictly correct (at least in the eyes of jslint) JavaScript?

After running gjslint on my codebase, I saw a a _lot_ of these types of errors:
Line 54, E:0200: Invalid JsDoc tag: name
Line 55, E:0200: Invalid JsDoc tag: function
Line 56, E:0200: Invalid JsDoc tag: memberOf

Apparently, the gjslint checker only allows a certain subset of certain JsDoc rules. I tried using the --nojsdoc flag in the hope that all these 'errors' would be suppressed, but no dice.

Also - 80 characters seems to be the limit that gjslint allows for JavaScript files. Would be nice if this rule could be suppressible or at least configurable. Not even sure why its a rule in the first place.

For kicks, I tried passing the --strict flag to see the output. I seem to get a lot of indentation errors:
Line 342, E:0006: (New error) Wrong indentation: expected any of {4, 16} but got 8
Line 343, E:0006: (New error) Wrong indentation: expected any of {4, 16} but got 8
Line 344, E:0006: (New error) Wrong indentation: expected any of {4, 16} but got 8

I've never been a huge fan of being overtly strict when it comes to the number of spaces which represents an indentation. I use 4 for each indent - gjslint advises you to use 2 - no big deal in my books.

Included in this release of gjslint, there's a handy tool called fixjsstyle which will help you automatically fix any styling issues in your code. One point of caution when running this tool though - make sure your code is under source control before you run it. Stepping through your code after running fixjsstyle reveals some more gjslint rules. For example, running fixjsstyle on a code base will convert all double quotes to single quotes. Wonder why that's in the style guide? Actually, it'd be great to see some explanations behind some of the rules.

Overall - I've always felt that static code analysis of code is useful to a point. It'll definitely keep you keen when it comes to coding standards and it is useful in that it'll catch any errors that any self-respecting compiler would catch, but it'll only get you so far. I've always found gathering and understanding runtime analysis of a codebase more difficult to achieve (I still have a lot to learn in this field), but more rewarding in the end.

Monday, August 30, 2010

File Promises and dojo drag and drop

When Adobe pushed AIR 2.0 out via auto update, one of the first new API features that I wanted to try to take advantage of is the File Promises API. Armed with Christian Cantrell's article, I went about integrating File Promises into my application.

Drag and drop isn't a new interaction paradigm in the application I work on. Users have always been able to drag and drop items in the application thanks to dojo's drag and drop APIs. With AIR 2.0 and File Promises though, I wanted to give the user the ability to drag files internally in the application and also out of the application and onto their desktop. To allow both internal and external drag and drop operations, I needed to override the default dojo.dnd.Avatar. Overriding the default Avatar for dojo dnd operations is really easy - the constructor for your dnd source should include the following line (basically, you need some way of telling the dnd manager to use a different function when creating the avatar):
dojo.dnd.manager().makeAvatar = dojo.hitch(this, "makeAvatar");

You'll need a makeAvatar function which returns your custom Avatar:
makeAvatar: function() {
return new my.widget.FileListAvatar(dojo.dnd.manager());
},

Once you've told the dojo dnd manager to use your custom dnd avatar, you'll need to figure out when the avatar in question is being dragged close to the edge of the application. See the _handleMouseMove function below:

dojo.declare(
"my.widget.FileListAvatar",
[dojo.dnd.Avatar],
{
_mouseOverConnects: [],

construct: function() {
// override and cancel the autoScroll function to prevent whitespace from appearing
// in the application when the user is dragging files near the border.
dojo.dnd.autoScroll = function(){};
this._mouseOverConnects.push(dojo.connect(this.manager, "onMouseMove", this, "_handleMouseMove"));
}

_handleMouseMove: function() {
var pos, viewport = dojo.window.getBox();
if(this.node) {
pos = dojo.position(this.node);
if(viewport.w <= pos.x + pos.w || pos.x <= 0
|| viewport.h <= pos.y + pos.h || pos.y + pos.h <= 0) {
// at this stage we know that the user is dragging the avatar close to the application's boundaries.
// time to make a switch from an internal dnd operation to an external dnd.
this. _handleExternalDnd();
}
}

The _handleExternalDnd is the point in the application where we start using the AIR 2.0 file promises API. In the following code, all the items in my dnd source have a store and item property which contain information on the url and file name. The url property from each dnd item is passed to a new URLFilePromise object.
_handleExternalDnd: function() {
var f, url, cb = new air.Clipboard(), promises = [], vp = dojo.window.getBox(), isDraggedOut = false;
for(var j = 0, k = this.manager.nodes.length; j < k; j++) {
f = this.manager.source.getItem(this.manager.nodes[j].id).data;
url = f.store.getValue(f.item, "Url");
if(url !== "") {
var fp = new air.URLFilePromise();
fp.request = new air.URLRequest(url);
fp.relativePath = f.store.getValue(f.item, "FileName");
promises.push(fp);
}
}
cb.setData(air.ClipboardFormats.FILE_PROMISE_LIST_FORMAT, promises);
// the next line causes the switch from internal dnd to external dnd
air.NativeDragManager.doDrag(window.htmlLoader, cb);
air.NativeApplication.nativeApplication.activeWindow.stage.addEventListener(window.runtime.flash.events.NativeDragEvent.NATIVE_DRAG_UPDATE, function(evt) {
if(!isDraggedOut && evt.localX > vp.w) {
isDraggedOut = true;
} else if (isDraggedOut && evt.localX < vp.w) {
// TODO - this is the case where the file has been dragged out of the application and the user is dragging the file back in.
// would be nice at this stage to revert to revert back to an internal dnd operation.
}
});
dojo.style(this.node, "display", "none");
},

I've left some commented code in there which might give you hints of what I'd like to do when the user drags files back into the application after initially dragging them out. I haven't figured out how to cancel a native drag and drop operation just yet - if I do, I'll be sure to update this blog post.

That's pretty much it. Once the user drags files out of the application and into their native file system browser, the urls included in the URLFilePromise object will be downloaded. Check out some of the events you can connect to if you want to give your users a progress indicator of where their files are in terms of being downloaded. One point to note when using File Promises - users are only allowed to drag files from an AIR application into the default file browser. For example, the user cannot drag files directly from an Adobe AIR application into iPhoto or Lightroom. They need to drag the files into Finder first, wait for the files to be downloaded and then drag them into the file management application of their choice.

Looking to the future, it looks like this type of functionality will start to become available natively in the browser.

Some useful links which might help with further reading:

Getting started with dojo dnd
dojocampus' dnd articles
Advanced dojo dnd tutorial
CSS Ninja article on drag and drop
CSS Ninja article how gmail's dnd works

Monday, August 23, 2010

dojo.connect Widget gotcha

I'm not too sure this small issue I ran into actually constitutes a full blog post, but here goes all the same. After reading phiggins' article on how to dispose of programatically created dijits, I started keeping track of the handles returned from dojo.connects and dojo.subscribes. I figured that these return values should be disconnected/unsubscribed just before a widget was destroyed to aid with garbage collection. Hence, a lot of my widgets started to look like this:
dojo.declare(
"my.widget.AccountCreator",
[dijit._Widget, dijit._Templated],
{
templatePath: dojo.moduleUrl("my.widget", "templates/AccountCreator.html"),
widgetsInTemplate: true,
_subscriptions: [],
_connects: [],

postCreate: function() {
this._connects.push(dojo.connect(dijit.byId("accountCreatorDialog"), "hide", this, "_handleHideDialog"));
this._subscriptions.push(dojo.subscribe("/acct/created", this, "_handleAccountCreated"));
},

destroy: function() {
// first clean up my connects and handles
dojo.forEach(this._subscriptions, dojo.unsubscribe);
dojo.forEach(this._connects, dojo.disconnect);
this._subscriptions = [];
this._connects = [];
// now go ahead and delete the rest of the widget
this.inherited(arguments);
}

Seems relatively simple, right? Being a good memory citizen and cleaning up just before the widget gets destroyed seems like a win-win situation. However, notice that my collection of dojo.connect handles is named _connects. Check out the source code for dijit._Widget (my.widget.AccountCreator inherits from dijit._Widget):
  create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){
// summary:
// Kick off the life-cycle of a widget
// params:
// Hash of initialization parameters for widget, including
// scalar values (like title, duration etc.) and functions,
// typically callbacks like onClick.
// srcNodeRef:
// If a srcNodeRef (DOM node) is specified:
// - use srcNodeRef.innerHTML as my contents
// - if this is a behavioral widget then apply behavior
// to that srcNodeRef
// - otherwise, replace srcNodeRef with my generated DOM
// tree
// description:
// Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
// etc.), some of which of you'll want to override. See http://docs.dojocampus.org/dijit/_Widget
// for a discussion of the widget creation lifecycle.
//
// Of course, adventurous developers could override create entirely, but this should
// only be done as a last resort.
// tags:
// private

// store pointer to original DOM tree
this.srcNodeRef = dojo.byId(srcNodeRef);

// For garbage collection. An array of handles returned by Widget.connect()
// Each handle returned from Widget.connect() is an array of handles from dojo.connect()
this._connects = [];

Having a property of _connects on a custom dijit which inherits from dijit._Widget landed me in trouble. When my.widget.AccountCreator's destroy method was called, the call to dojo.disconnect was throwing an odd looking error:
Result of expression '([dojo._listener, del, node_listener][listener])' [undefined] is not an object

As pointed out by both @neonstalwart and @cb1kenobi (thanks!), there's a simple solution to all this - just use the connect method in dijit._Widget.
this.connect(dijit.byId("accountCreatorDialog"), "hide", "_handleHideDialog");

dijit._Widget's connect method will also take care of automatically disconnect each dojo.connect handle when the widget is destroyed. As a result, you don't need to override the destroy method of your custom widget to clean up any lingering handles.

Have you ever stumbled into an issue like this - are there any other 'common' internal dojo variable names that you've unconsciously clobbered?

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) ? '?' : '&amp;') + 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 &amp;&amp; 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.

Monday, March 15, 2010

Javascript unit testing and code coverage

Back in the day, when I earned my crust as a Java developer, I wouldn't dare commit a new change to a code base without some unit tests. Junit and emma proved to be useful tools to make sure that any new additions to a codebase didn't adversely affect existing functionality.

Moving to developing in JavaScript - I guess I got lazy with unit tests. Not having a code coverage tool to let me know which sections of my code were being exercised by test code meant that I didn't have that 'green bar' motivator.

Then I found out about jscoverage. For an example of what this tool can produce, check out their sample reports for dojo. Getting jscoverage up and running on mac can be a bit of a pain though - here's the steps I followed:

1. There's no auto-built dmg for mac, so you have to download and install macports.

2. Once you have that up and running, open a terminal and execute:
sudo port install jscoverage

jscoverage should now be installed on your machine.

3. Check out the jscoverage documentation for examples on how to run jscoverage.

4. I use dojo's doh testing framework to run my tests. If you do too, you need to edit the runner.html file in the doh directory to include the following buton:
<h3 style="margin: 5px 5px 0px 5px; float: left;">D.O.H.: The Dojo Objective Harness</h3>
<button style="margin-top: 5px; float: left;" onclick="window.open('../../jscoverage.html');">Coverage Report</button>
<img src="small_logo.png" height="40" style="margin: 0px 5px 0px 5px; float: right;">

5. All you need to do then is point your browser at whatever file you use to run your dojo tests and you should be all set. Just click on the "Coverage Report" button once your tests have run.

Its that simple ...

Thursday, March 4, 2010

Javascript testing in Adobe AIR

I'm in the process of migrating a web application to an Adobe AIR application and I finally got around to trying to migrate some of my JavaScript unit tests to run in AIR. I'd previously written some doh tests and wanted to get them up and running in AIR. Turns out it was relatively simple. All you need to do is:

In your apache configuration file, add an entry to point to the base of your AIR project:
<VirtualHost *>
ServerName air-testing.my.domain
DocumentRoot /path/to/my/trunk/htdocs
</VirtualHost>

(The htdocs directory here should have a subdirectory called dojo which has the 4 dojo subdirectories (dojo, dijit, dojox, util)

Then, in the .html file where you previously had a <meta> redirect (lets call this runTests.html - it probably looked something like this)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en-US" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My Unit Tests</title>
<meta http-equiv="REFRESH" content="0; url=../../dojo/util/doh/runner.html?testModule=my.module.testAll&registerModulePath=mydomain,../../mydomain">
</head>
<body>
Redirecting to D.O.H runner.
</body>
</html>

... change this file to look like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en-US" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My Unit Tests</title>
<script type="text/javascript">
var addOnLoad = function() {
window.location = "http://air-testing.my.domain/dojo/util/doh/runner.html?testModule=my.module.testAll&registerModulePath=mydomain,../../mydomain"";
}
</script>
</head>
<body onload="addOnLoad();">
Redirecting to D.O.H runner.
</body>
</html>

I found it useful to have a testing application descriptor xml file in the same directory as my runTests.html test file:
<?xml version="1.0" encoding="utf-8" ?>
<application xmlns="http://ns.adobe.com/air/application/1.5">
<id>tests</id>
<filename>runTests</filename>
<name>
<text xml:lang="en_US">Run Tests</text>
</name>
<version>0.0001</version>
<description>
<text xml:lang="en_US">Run Tests</text>
</description>
<initialWindow>
<title>TESTS</title>
<content>runTests.html</content>
<systemChrome>standard</systemChrome>
<transparent>false</transparent>
<visible>true</visible>
<width>1250</width>
<height>900</height>
<x>20</x>
<y>20</y>
<minimizable>true</minimizable>
<maximizable>true</maximizable>
<minSize>850 636</minSize>
<resizable>true</resizable>
</initialWindow>
</application>

Running adl on this new test application descriptor file should kick off your doh unit tests.

Sunday, January 17, 2010

First impressions of Github

Tried out github for the first time today - bit late to the party, but better late than never.

On initial impressions, I don't immediately see the advantages of github over something like SVN/trac. Given that the tagline for Github is social coding, perhaps I'd need to be involved in a project with multiple committers to get the full github experience. Can't say that I've many complaints with svn - its a huge improvement over Rational Clearcase (which I had to use in a previous job). One definite plus of github is the ability to change code snippets through the web interface.

Regardless, http://github.com/seanoshea is where I'll be posting code snippets from now on.

Saturday, January 9, 2010

Extension to dojo dnd Selector API

I've had the good fortune of using some dojo drag and drop in a recent project and have been very impressed with the flexibility offered by the APIs. The documentation and tests have been a real help for me in getting drag and drop functionality up and running quickly in my project.

After posting a quick question to the stackoverflow forums, I tried my hand at what Eugene suggested. Here's my attempt:

dojo.provide("my.ext.SelectorMixin");

dojo.require("dojo.dnd.Selector");

dojo.declare(
"my.ext.SelectorMixin",
[dojo.dnd.Selector],
{
// summary: a mixin for the dojo.dnd.Selector class which adds functionality
// to shift the currently selected index backwards and forwards. One possible
// use for this mixin would be to allow a user select different dnd items using
// the right and left keys.

shift: function(offset, shiftKey) {
// summary: shifts the currently selected dnd item
// offset: int: the amount to bump the selection by.
// shiftKey: Boolean: whether or not this new selection happened when the user was holding
// down the shift key
var selectedNodes = this.getSelectedNodes();
if(selectedNodes && selectedNodes.length) {
// only delegate to _selectNode if at least one node is selected. If multiple nodes are selected
// assume that we go with the last selected node.
this._selectNode(this._getNodeId(selectedNodes[selectedNodes.length - 1].id, offset), shiftKey);
}
},

_selectNode: function(nodeId, shiftKey) {
// summary: selects a node based on nodeId
// nodeId: String: the id of the node to select
// shiftKey: Boolean: whether or not this new selection happened when the user was holding
// down the shift key
if(!shiftKey) {
// only clear the selection if the user was not holding down the shift key
this.selectNone();
}
this._addItemClass(dojo.byId(nodeId), "Selected");
this.selection[nodeId] = 1;
},

_getNodeId: function(nodeId, offset) {
// summary: selects a node based on nodeId
// nodeId: String: the id of the node to select
// offset: int: the number of nodes to shift the current selection by
var allNodes = this.getAllNodes(), newId = nodeId;
for(var i = 0, l = allNodes.length; i < l; i++) {
var node = allNodes[i];
if(node.id == nodeId) {
// have a match ... make sure we're not at the start or the end of the dnd set
if(!((offset == -1 && i == 0) || (i == l - 1 && offset == 1))) {
// we should be fine to go with the id the user has requested.
newId = allNodes[i + offset].id;
}
break;
}
}
// if we don't get a match, the newId defaults to the currently selected node
return newId;
}
}
);


One very simple use of this extension would be to add key handlers to dojo.doc to listen for right and left keys:

dojo.connect(dojo.doc, "onkeyup", dojo.hitch(this, function(evt) {
if(evt.keyCode == dojo.keys.RIGHT_ARROW) {
this.shift(1, evt.shiftKey);
}
if(evt.keyCode == dojo.keys.LEFT_ARROW) {
this.shift(-1, evt.shiftKey);
}
}));


I'll give a shot at providing some tests for this extension in my next post.