edit
Library Type Assets in ActionScript 3.0 :: using assets created with current releases of Flash Authoring
Posted by Ali Mills
Wed, 17 May 2006 03:12:00 GMT
In the previous post Library Type Assets in ActionScript 3.0 :: using the [Embed] metatdata tag, I mentioned that an ActionScript 3.0 application can load library type assets (i.e. SWFs) created in current releases of Flash Authoring during run time. This is a big deal – a huge deal – because without this ability there wouldn’t be any way to create a MovieClip and include it in a Flex Builder 2.0 project until Blaze becomes available.
The simplest way to load a legacy SWF is with the Loader class. After it’s loaded, you can manipulate its DisplayObject properties with the Flash 9 Player’s AVM1Movie adapter class.
While this method works well for canned widgets like video and music players that don’t need to communicate with their host application, it doesn’t work well when host communication is a requirement. When this is a requirement, it’s necessary to take advantage of features available through the LocalConnection class, because the only way for ActionScript 3.0 running in the AVM2 to communicate with legacy ActionScript running in the AVM1 is over a LocalConnection .
While researching an elegant solution to the problem of how two pieces of code (AS2 and AS3) running in two separate virtual machines (AVM1 and AVM2) can communicate, I looked to the book Enterprise Integration Patterns : Designing, Building, and Deploying Messaging Solutions from the signature series of one of my favorite authors Martin Fowler. Chapter 2 of the book explores different integration styles and suggests that the Remote Procedure Invocation approach is a, “mechanism for one application to invoke a function in another application, passing the data that needs to be shared and invoking the function that tells the receiver application how to process the data.” After reading this and the rest of the section (the full Remote Procedure Invocation section can be found online at http://www.awprofessional.com/articles/article.asp?p=169483&seqNum=4), I was convinced that the Remote Procedure Invocation
approach would work and designed an AS3 stub and AS2 skeleton class. They follow:
AS3 LegacyAsStub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| package {
import flash.display.Loader;
import flash.net.LocalConnection;
public class LegacyAsStub extends Loader {
private static var AVM1_POSTFIX:String = "_AVM1";
private static var AVM2_POSTFIX:String = "_AVM2";
private var avm1Connection:LocalConnection;
private var avm2Connection:LocalConnection;
private var connectionKey:String;
public function LegacyAsStub(url:String) {
avm1Connection = new LocalConnection();
avm2Connection = new LocalConnection();
connectionKey = createConnectionKey(url);
initAvm2Connection();
load(new URLRequest(url));
}
private function createConnectionKey(url:String):String {
var splitUrl:Array = url.split("/");
return splitUrl[splitUrl.length - 1];
}
private function initAvm2Connection():void {
var avm2Key:String = connectionKey + AVM2_POSTFIX;
avm2Connection.client = this;
avm2Connection.connect(avm2Key);
}
public function invoke(methodName:String, ... args):void {
var avm1Key:String = connectionKey + AVM1_POSTFIX;
avm1Connection.send(avm1Key, "invoke", methodName, args);
}
}
}
|
AS2 LegacyAsSkeleton
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| class LegacyAsSkeleton extends MovieClip {
private static var AVM1_POSTFIX:String = "_AVM1";
private static var AVM2_POSTFIX:String = "_AVM2";
private var avm1Connection:LocalConnection;
private var avm2Connection:LocalConnection;
private var connectionKey:String;
public function LegacyAsSkeleton() {
avm1Connection = new LocalConnection();
avm2Connection = new LocalConnection();
connectionKey = createConnectionKey(_url);
initAvm1Connection();
}
private function createConnectionKey(url:String):String {
var splitUrl:Array = url.split("/");
return splitUrl[splitUrl.length - 1];
}
private function initAvm1Connection():Void {
var avm1Key:String = connectionKey + AVM1_POSTFIX;
var context:MovieClip = this;
avm1Connection.invoke = function(methodName:String, args:Array) {
context[methodName].apply(context, args);
};
avm1Connection.connect(avm1Key);
}
public function invoke(methodName:String, args:Array):Void {
var avm2Key:String = connectionKey + AVM2_POSTFIX;
if(args.length > 0) {
avm2Connection.send(avm2Key, methodName, args);
}
else {
avm2Connection.send(avm2Key, methodName);
}
}
}
|
To demonstrate how to use these classes, let’s add an animation created in Flash Professional 8 to the AutoComplete project we started in ActionScript Projects in Flex Builder 2.0 and extended in Library Type Assets in ActionScript 3.0 :: using the [Embed] metatdata tag.
First, create a new Flash document in Flash Professional 8 and save it in a folder called “swf” that’s inside you AutoComplete project folder at the same level as AutoComplete.as. Add the code:
attachMovie("RememberAnimation", "RememberAnimation", 1);
to the first frame.
Now, create a new MovieClip and name it, “RememberAnimation”. Set its linkage to “Export for ActionScript” and “Export in first frame”. Give it the identifier, “RememberAnimation” and the AS 2.0 class, “LegacyAsSkeleton”. After pressing the “OK” button and creating the “RememberAnimation” MovieClip, give it two layers with a single keyframe in the first frame of the first layer with the code:
and an animation in the second layer with as many frames as you wish.
Next, copy the LegacyAsSkeleton class code from above and save it in a file called LegacyAsSkeleton.as within the “swf” folder, and publish your FLA. A SWF called RememberAnimation.swf should have been created in the “swf” folder. If not, you can download a prebuilt FLA and SWF from here.
Close Flash and open the AutoComplete project in Flex Builder 2.0.
Modify AutoCompleteRunner.as to look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
| package {
import flash.display.Bitmap;
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.net.SharedObject;
import flash.util.describeType;
import flash.util.getClassByName;
public class AutoCompleteRunner extends Sprite {
[Embed(source="/img/Remember_up.png")] private var Remember_UpImage:Class;
[Embed(source="/img/Remember_over.png")] private var Remember_OverImage:Class;
[Embed(source="/img/Remember_down.png")] private var Remember_DownImage:Class;
private static var ANIMATION_LOCATION:String = "swf/RememberAnimation.swf";
private var animation:LegacyMovieClip;
private var savedWords:Array;
private var persistenceLayer:SharedObject;
private var autoCompleteTxt:AutoComplete;
private var rememberBtn:SimpleButton;
public var className:String;
public function AutoCompleteRunner() {
savedWords = new Array();
configureStage();
persistenceLayer = SharedObject.getLocal("AutoCompleteRunner");
configureSharedObject(persistenceLayer);
autoCompleteTxt = new AutoComplete("Start typing...");
configureAutoComplete(autoCompleteTxt);
rememberBtn = new SimpleButton();
configureButton(rememberBtn, "Remember");
rememberBtn.addEventListener(MouseEvent.CLICK, clickHandler);
addChild(rememberBtn).x = addChild(autoCompleteTxt).width;
animation = LegacyMovieClip(addChild(new LegacyMovieClip(ANIMATION_LOCATION)));
animation.x = rememberBtn.width + autoCompleteTxt.width;
}
private function configureStage():void {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
}
private function configureSharedObject(sharedObject:SharedObject):void {
if(sharedObject.data.savedWords != undefined) {
savedWords = sharedObject.data.savedWords;
}
}
private function configureAutoComplete(autoComplete:AutoComplete):void {
autoComplete.addToDictionary("Flex");
autoComplete.addToDictionary("Fireworks");
autoComplete.addToDictionary("Dreamweaver");
autoComplete.addToDictionary("Contribute");
autoComplete.addToDictionary("ColdFusion");
autoComplete.addToDictionary("Flash Paper");
autoComplete.addToDictionary("Flash");
autoComplete.addToDictionary("Breeze");
autoComplete.addToDictionary("JRun");
autoComplete.addToDictionary("Director");
autoComplete.addToDictionary("Studio");
autoComplete.addToDictionary("Captivate");
autoComplete.addToDictionary("RoboHelp");
autoComplete.addToDictionary("Authorware");
autoComplete.addToDictionary("Web Publishing System");
var len:uint = savedWords.length;
for(var i:uint; i < len; i++) {
autoComplete.addToDictionary(savedWords[i]);
}
}
private function configureButton(button:SimpleButton, name:String):void {
button.upState = createImage(name + "_UpImage");
button.overState = createImage(name + "_OverImage");
button.downState = createImage(name + "_DownImage");
button.hitTestState = createImage(name + "_UpImage");
button.useHandCursor = true;
}
private function createImage(embedLinkage:String):Bitmap {
if(className == null) {
className = describeType(this).@name.toXMLString();
}
var imgName:String = className + "_" + embedLinkage;
var imgRef:Class = getClassByName(imgName);
var img:Bitmap = new imgRef();
return img;
}
private function clickHandler(event:MouseEvent):void {
var newWord:String = autoCompleteTxt.text;
autoCompleteTxt.addToDictionary(newWord);
savedWords.push(newWord);
persistenceLayer.data.savedWords = savedWords;
persistenceLayer.flush();
animation.play();
// animation.exec("gotoAndStop", 10);
}
}
}
|
Copy the LegacyAsStub class code from above and add it as a new class called LegacyAsStub.as to the AutoComplete project, and finally add another class to the AutoComplete project called LegacyMovieClip.as with the contents:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| package {
import flash.net.URLRequest;
import flash.util.trace;
public class LegacyMovieClip extends LegacyAsStub {
public function LegacyMovieClip(url:String) {
super(url);
}
// public function onLoad():void {
// trace(">> onLoad");
// }
public function loadMovie(url:String):void {
var urlRequest:URLRequest = new URLRequest(url);
load(urlRequest);
}
public function stop():void {
invoke("stop");
}
public function play():void {
invoke("play");
}
// the rest of MovieClip's interface ...
// - draw methods should probably wrap Graphics calls
// - _x, _y, _height, and _width should be handled in AS3
// - if it makes sense to do so, pass on to AS2
}
}
|
Compile/build the AutoComplete project and after pressing the “Remember” button watch the AS2 animation play!
Notice that the onLoad() method is commented out in LegacyMovieClip.as. You can make this fire by adding the code:
private function onLoad():Void {
invoke("onLoad");
}
to a class that extends LegacyAsSkeleton.as.
The stub and skeleton above are merely the foundation for AVM1/AVM2 communication, and you can easily build on them to create more complex AVM1 objects to host within your AVM2 applications. You’re not restriced to just legacy MovieClip functionality. To take advantage of other legacy classes, simply extend the above stub and skeleton with the interface of these more complex legacy classes.