Porting Dojo Methods to Flash – Part 1 of 3

By on May 1, 2008 8:57 am

There’s been some breaking news from Adobe, announcing their Open Screen project. As of today, they are opening the licensing of the Flash Player, FLV/F4V video, publishing the AMF protocol and device APIs for the player, and more.

This is great news for businesses, developers, and the Open Web in general. No longer does open source ActionScript code need to do workarounds and leave things out in an effort to make it “as open as reasonably possible”.

In celebration of this announcement, we’re beginning our three part series on ActionScript code, and preview some of the Dojo team’s efforts in this area.

Flash ActionScript is a powerful and mature language, but any language can use some extra syntactic sugar to either make it easier to work with, quicker to write, or make it look more like a language you are used to. We’ll explore some techniques to bring Dojo-like syntax to ActionScript.

Our goal today will be to port dojo.hitch, which is useful for creating a closure that maintains a method in a given context. The most common example for using hitch would be a setTimeout:

setTimeout( dojo.hitch(this, function(){
	this.initialize();
	this.build();
	this.display();
}), 500);

hitch() is also very useful in callbacks: instead of passing an object and a method to an asynchronous function to use as a callback when it’s ready, you could pass it one hitch function that keeps “this” in context.

AS2 is inefficient at handling closures, and the typical workaround is to use the mx.utils.Delegate class. We’ll avoid Delegate altogether. To keep things simple, we’ll use global objects that can be created on a Flash IDE timeline, as opposed to getting into AS class files.

First we’ll create a global namespace our code, and insert our hitch method. We’re going to use lang (Note that from here on, these examples are ActionScript):

_global.lang = {
	hitch: function(scope, method){
		//
	}
}

Because dojo.hitch() uses standard methods and no browser-specific funniness, this port will be very straightforward. We’re going to simplify it a bit, removing some argument massaging and error checking.

Our first two arguments are scope and method. We need to check that both were passed, by verifying method is not null. Otherwise we assume that this was an anonymous function, and we give it a global scope:

if(!method){
	method = scope;
	scope = _global;
}

Next, we’ll check for additional parameters besides our scope and method. If so, we’ll take the arguments object and remove the first two slots; those being the scope and method. In other words, if we are passed: [ obj, initialize, true, false, 99 ], we need "true, false, 99" to be our actual arguments. We remove the first two slots by using shift() twice:

var args;
if(arguments.length>2){
	args = arguments;
	args.shift();
	args.shift();
}

Notice that in Flash we didn’t have to jump through hoops to edit the arguments object. In JavaScript, the arguments object is “arguably” broken, and we have to steal array methods from the Array object before would could apply our shift method:

args = Array.prototype.slice.call(arguments);

Now that we have our “args”, we’re ready to return our closure. We just check that the method is a string or a function and return it accordingly. Here is the final result of our code:

_global.lang = {
	hitch: function(scope, method){
		if(!method){
			method = scope;
			scope = _global;
		}
		var args;
		if(arguments.length>2){
			args = arguments;
			args.shift();
			args.shift();
		}
		if(typeof(method) == "string"){
			return  function(){ scope[method].apply(scope, args) };
		}
		return function(){ method.apply(scope, args) };
	}
}

We’ll create a simple object and test that our closures work and our arguments are properly passed:

var object = {
	id:"testObject",
	testScope: function(){
		setTimeout(lang.hitch(this, function(){
			trace("testScope: " + this.id);			 
		}), 200);
	},
	sub: function(){
		trace("testArgs:" + arguments);
	},
	testArgs: function(){
		(lang.hitch(this, "sub", 999, 333, 222))();
	}
	
};
object.testArgs();
object.testScope();

// output: 
testArgs:999,333,222
testScope: testObject

Our tests are successful and show a few different ways that hitch can be used. Without too much work, this code can be modified to work in an AS2 class file, and compiled with MTASC.

This is just one example of how Dojo is not limited to JavaScript nor even a browser, and how you can port your favorite Dojo functionality to other languages. In part two of our series, we will implement dojo.connect in ActionScript.

Comments