Porting Dojo Methods to Flash – Part 2 of 3

By on May 2, 2008 12:01 am

Adobe recently announced their new Open Screen project, which opens the licensing of the Flash Player and much more.

We’re celebrating this event with a three part series on Dojo and ActionScript and previewing some of the work by the Dojo team.

In part 1 of our series, we implemented dojo.hitch into AS. Now we can work on bringing the benefits of dojo.connect to ActionScript through our lang.connect function, which has a dependency on lang.hitch. This will be more challenging than the lang.hitch implementation, as Flash object-events work very differently from HTML object-events. We’re going to be porting the concept here more than actual Dojo code. The connection will take the source and event, and make a broadcaster and a listener. When the listener “hears” the broadcast of the event, it will trigger the callback:

FlashConnect

Let’s start by adding connect to our lang namespace:

_global.lang = {
	hitch: function(scope, method){
		...
	},
	connect: function(source:Object, event:String, target, callback):String{
		//
	}
}

As in Dojo, the source’s event is what we listen to. Unlike Dojo, where the event can be either a string or a function, we can only accept strings due to a limitation in Flash.

The data types in the previous example demonstrate what is expected in these arguments. The first two arguments must be an Object and a String, and the third and fourth are not typed, to allow for flexibility. lang.connect returns a String, which is the connection id used for removing the connection later, if desired. To use this functionality in the timeline or pre-AS2, simply remove the data typing.

The first thing we’ll do is create a closure from the target and callback. Then we need a vanilla object to use as a listener to which we’ll attach the event:

connect: function(source, event, target, callback){
	var hitched = this._hitch(target, callback);
	
	var listener = new Object();
	
	listener[event] = function(args){ //ex: listener.onRelease()
		hitched();
	}
}

Now we need our source object to tell the listener when its event has been fired. Flash components can broadcast events, but natively, Flash objects and MovieClips do not. We use the AsBroadcaster singleton to inject the object with the necessary methods (addListener, broadcastMessage, and removeListener). But we want to be able to connect to the source/event multiple times, so we only need do this once. Then we will add the listener to it:

if(!source.broadcasters) {
	source.broadcasters = {}; // a hash map of our event types
	AsBroadcaster.initialize(source)
}
source.addListener(listener);

Now we need to broadcast when our event fires. If we’ve already attached this type of event to the source, we don’t want to do it twice:

if(!source.broadcasters[event]){
	source[event] = function(){
		source.broadcastMessage(event, arguments);
	}
	source.broadcasters[event] = true;
}

Note what we have done here and how it differs from dojo.connect(). We’ve written the method directly to the source object. We’re not really listening to it. Jumping ahead a little bit, here’s how we could break this code:

	lang.connect(flashButton, "onRelease", thinger, myMethod);
	flashButton.onRelease = function(){
		trace("click me");
	}

On line 1 we successfully connected to the button’s onRelease event, but in line 2, we overwrote our connection. This is a great example of the power of dojo.connect() – using similar code, this would not overwrite the connection in a Dojo JavaScript file. The reason for this limitation in Flash is as I noted earlier, most Flash objects don’t natively broadcast events. Some do, like Mouse, Key, Select, and Flash components. If this code was written to target just those objects, we could avoid the overwrite limitation.

We can finish up our method by returning an id that references this connection. The code will create an indexed id and add the listener and the source to a hash map:

__conListeners: {},
__id: 0,
...
var id = "connect_"+this.__id++;
this.__conListeners[id] = {listener:listener, source:source};
return id;

Our final code, including the lang.disconnect method:

_global.lang = {
	__conListeners:{},
	__id:0,
	connect: function(source, event, target, callback){
		var hitched = this._hitch(target, callback);
		
		var listener = new Object();
		listener[event] = function(args){
			hitched();
		}
			
		if(!source.broadcasters) {
			source.broadcasters = {};
			AsBroadcaster.initialize(source)
		}
		
		source.addListener(listener);
		
		if(!source.broadcasters[event]){
			source[event] = function(){
				source.broadcastMessage(event, arguments);
			}
			source.broadcasters[event] = true;
		}
		
		var id = "connect_"+this.__id++;
		this.__conListeners[id] = {listener:listener, source:source};
		return id;
	}
	
	disconnect: function(id){
		var con = this.__conListeners[id];
		if(con){
			con.source.removeListener(con.listener);
			con = null;
		}
	}
}

And here is our test case:

var object = {
	doFrame: function(){
		trace("scoped onEnterFrame, 2nd connection");
	}	
};

var count = 0;
var conId = lang.connect(_root, "onEnterFrame", function(){
	trace("onEnterFrame: " + count); 
	count++;
	if(count>3){
		lang.disconnect(conId);
	}
});
lang.connect(_root, "onEnterFrame", object, "doFrame");

// outputs:
scoped onEnterFrame
onEnterFrame: 1
scoped onEnterFrame
onEnterFrame: 2
scoped onEnterFrame
testScope: dojo
onEnterFrame: 3
scoped onEnterFrame
scoped onEnterFrame
....

We’ve successfully ported hitch and connect into Flash. More can be done on our lang.connect – a little error checking can speed up development by checking that the objects and methods exist. And our implementation is not forwarding any events. Since we pass the event as a string, these could easily be checked and extended: in this case, the frame number of the timeline could be returned, a connection to a button can return it’s event type. Connecting to the Mouse object could return the x/y coordinates of the mouse in that scope.

Also without too much work, this code can be modified to work in an AS2 class file, and compiled with MTASC.

This is another 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 the final part of our series, we will explore connecting JavaScript objects to Flash objects.