Porting Dojo Methods to Flash – Part 3 of 3

By on May 8, 2008 12:01 am

This is the final part of our three part series on porting Dojo methods to Flash.

In part one of our series, we implemented Dojo’s hitch method, and then used hitch in part two, where we made our connect method. In part three, we will be using both of these methods as we connect a JavaScript object to a Flash object.

We’ll start by setting up our test case. We’ll need an HTML page with an embedded SWF that contains a few Flash buttons and a text field, and then a text field in HTML:

FlashToJs 01

Our overall goal is to tell the SWF, “When a Flash button is clicked, trigger a function in the JavaScript environment thats sets my text field”.

Our SWF file needs to provide a channel for JavaScript to communicate with it. In Flash, import the ExternlInterface (the Flash 8+ method to communicate with its container environment) and use that to place a function in JavaScript which we’ll call connectToSwf, and link that to a function in the “this” scope called jsConnect:

// ActionScript
import flash.external.*;
ExternalInterface.addCallback("connectToSwf", this, jsConnect);

The jsConnect is quite simple. It will accept three arguments, all strings, even the object. We have no actual access to the SWF button, so we couldn’t pass a reference to it, even if we could get that reference through the ExternalInterface. So we pass a string for the scope and the event, and then use the lang.connect code that we built in Part 2 to connect to the button and its event. When this connection is made, the ExternalInterface makes a call to JS to an event that we will provide ( swfEvent.connectToJs ), passing strCallback back. swfEvent.connectToJs is hard coded in the SWF, but this could be passed in as well.

// ActionScript
function jsConnect(strSwfScope, strSwfEvent, strCallback){
	lang.connect(_root[strSwfScope], strSwfEvent, function(){
		ExternalInterface.call("swfEvent.connectToJs", strCallback);
	})
}

Okay. I cheated. This code will only work if the strSwfScope is on the root of the timeline. A more robust version might use resolveObject(strSwfScope) where that function could split a string on the dot delineator and loop through each string until the object is found.

The buttons are created and named “flashButton1” and “flashButton2”, a text field is placed for reference, and the SWF is published. To insert the SWF into the page, we’ll use dojox.av, by requiring the package, waiting for the initialization, and then placing our SWF file.

// JavaScript
dojo.require("dojox.av");

var flashMovie;
var testMovieUrl="connect.swf";
dojo.addOnLoad(function(){
	flashMovie = dojox.av.flash.place(
		dojo.byId("fmovie"), 
		{ path: testMovieUrl, width:320, height:125 }
	);
});
flashReady = function(){
	setTimeout(init, 0);
}

We used the dojox.av.flash.place method, which takes two arguments. The first is the node where you wish your SWF to be inserted. The second is an object, which takes several parameters, three of which we are using here: path, is the path to your SWF file, relative from dojo.js, or relative to the file if you are using CDN; and width/height, which is the size of our SWF. This method returns a pointer to the SWF object which we will use later. Note that a SWF can take an undetermined time to load – it’s not ready right away, especially if it’s large. Within Flash I’m calling flashReady, which tells the JS that it is loaded and ready to go – however, there still needs to be a little breathing room before the ExternalInterface is ready. Hence, the setTimeout(init, 0).

The init function is where we will build our code to connect to the Flash object. We’ll use swfEvent as the namespace. Here is the structure, followed by the two connections to the Flash buttons:

// JavaScript
init = function(){
	swfEvent = {
		_swfConnections:{},
		
		connectToJs: function(strCallback){
			//
		},
		
		connectToSwf, function(strSwfScope, strSwfMethod, scope, method){
			//
		}
	};
	
	swfEvent.connectToSwf("flashButton1", "onRelease", function(){
		dojo.byId("tf").value = "Flash Foo";
	});
	swfEvent.connectToSwf("flashButton2", "onRelease", function(){
		dojo.byId("tf").value = "Flash Bar";
	});

} 

connectToJs is the method that the Flash ExternalInterface is using for it’s return call. connectToSwf is what we will build out, and if you notice in our two connections, we need to handle anonymous functions. We can’t pass them through the ExternalInterface, so we will store them, and pass a reference instead. We’ll hitch our scope and method, and then store them in the _swfConnections object.

// JavaScript
var _hitch = dojo.hitch(scope, method);
var strMethod = "strMethod_" + new Date().getTime();
this._swfConnections[strMethod] = _hitch;

_swfConnections now contains a time-stamped reference to our hitched closure (albeit this is a dangerous way to do it if the code executes very quickly).

We now pass everything to the flashMovie pointer that dojo.av provided for us, targeting the connectToSwf function created by the ExternalInterface. All that remians is for our closure to be executed after the SWF return call to swfEvent.connectToJs. Our final code:

// JavaScript
init = function(){
	swfEvent = {
		_swfConnections:{},
		
		connectToJs: function(strCallback){
			this._swfConnections[strCallback]();
		},
		
		connectToSwf, function(strSwfScope, strSwfMethod, scope, method){
			var _hitch = dojo.hitch(scope, method);
			var strMethod = "strMethod_" + new Date().getTime();
			this._swfConnections[strMethod] = _hitch;

			flashMovie.connectToSwf(strSwfScope, strSwfMethod, strMethod);
		}
	};
	
	swfEvent.connectToSwf("flashButton1", "onRelease", function(){
		dojo.byId("tf").value = "Flash Foo";
	});
	swfEvent.connectToSwf("flashButton2", "onRelease", function(){
		dojo.byId("tf").value = "Flash Bar";
	});

} 

And the test result, after clicking the “Set to Foo” button:

FlashToJs 02

Here’s the demo

With this framework, we now can communicate between ActionScript and JavaScript as easily as we can between JavaScript and HTML!