Home >

About Advanced Flash Tactics
Advanced Flash Tactics or AFTs are techniques that come from deep within the Flash Art Of War, the oldest Flash military treatise in the world. Each AFT is designed to be quickly digested, usually only taking a few minutes to get up and running, and contains valuable information you can directly apply to your next Flash campaign. In this AFT I will go over - Dynamically Creating Classes From XML.
This is a great technique that I have been using for a very long time in AS 3. The basic ideas is once you know the string name of a class plus its full package path you can easily create it at run time. Why would you want to do something like this? Two scenarios that come to mind are that you have classes you load up externally from another swf or you have all the classes compiled but you want to change them in an XML config without having to recompile. This is also a great technique when you have sites powered by a CMS. Imagine being able to put the class to use for a section in the CMS and allowing the content administrator to be able to pick from a set list of "template" classes. So now that we have a few examples of where to use this, lets take a look at how easy this is to set up.
Step 1: Create A Test Class
The first thing we need to do is make a test class for us to use. Create the following class in your project in a com.flashartofwar.example package.
package com.flashartofwar.example {
import flash.display.Sprite;
/**
* @author jessefreeman
*/
public class RedBox extends Sprite {
protected var _width:Number = 0;
protected var _height:Number = 0;
/**
* We override the public setter for width so we can redraw
* the box when the width is changed.
*/
override public function set width(value : Number) : void {
_width = value;
trace("Width", super.width);
redraw();
}
override public function get width() : Number {
return _width;
}
/**
* We override the public setter for width so we can redraw
* the box when the width is changed.
*/
override public function set height(value : Number) : void {
_height = value;
redraw();
}
override public function get height() : Number {
return _height;
}
public function RedBox() {
width = 100;
height = 100;
redraw();
}
/**
* This simply clears the graphics and redraws the box based
* on the new width and height.
*/
public function redraw():void
{
graphics.clear();
graphics.beginFill(0xFF0000);
graphics.drawRect(0, 0, width, height);
graphics.endFill();
}
}
}
As you can see this basic class extends Sprite, adds in some custom logic for managing width/height, and drawing a shape. I wouldn't focus that much on this class, it is just a basic example, any class can work here really.
Step 2: Setup a Doc Class
Now we need a Document Class to test that our RedBox class works.
package {
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import com.flashartofwar.example.RedBox;
import flash.display.Sprite;
/**
* @author jessefreeman
*/
public class DynamicClassDemo extends Sprite {
public function DynamicClassDemo() {
configureStage();
createRedBox();
}
private function configureStage() : void {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
}
private function createRedBox() : void {
var redBox:RedBox = new RedBox();
redBox.width = 100;
redBox.height = 100;
redBox.x = redBox.y = 10;
addChild(redBox);
}
}
}
Again, nothing special here. I configure the stage and set up a method to create the RedBox. If you run this you should see a 100x100 px red box. Up until this step everything should look really standard.
Step 3: Using getDefinitionByName
So now we are going to try our first attempt at dynamically creating the RedBox class. We are going to use a package method call getDefinitionByName to lookup a class by it's full package and class name. Replace createRedBox with the following code:
private function createRedBox() : void {
var tempClass : Class = getDefinitionByName("com.flashartofwar.example.RedBox") as Class;
var redBox:RedBox = new tempClass();
redBox.width = 100;
redBox.height = 100;
redBox.x = redBox.y = 10;
addChild(redBox);
}
You will also have to import the follow:
import flash.utils.getDefinitionByName;
Ok so notice how we simply replaced new RedBox() with new tempClass() and on the line before that we use getDefinitionByName? So here is what is happening. We make a "holder" variable of type Class then use the getDefinitionByName method to return an instance of the com.flashartofwar.example.RedBox class. Once we have a reference to the class itself we can simply call new on it. Neat trick right? Well this is no different then if we said var tempClass : Class = com.flashartofwar.example.RedBox except we simply rely on AS 3's own internal class lookup system to give us the correct class. If you run this you should see the same red box exactly like we had in step 2.
Lets move over to creating this class from XML. You'll be surprised at how easily it is.
Step 4: Creating RedBox From XML
So now we are going to do the same thing we did in step 3 but pull the class name out of some XML. To make things interesting I have changed a few things in this example. Why don't you replace createRedBox again with the following code:
private function createRedBox() : void {
// This is some sample XML data, we can just as easily
// loda this at runtime instead of compiling it in.
var xmlData:XML = <boxes base-package="com.flashartofwar.example">
<box name="box1" class="RedBox" x="10" y="10" width="100" height="100"/>
<box name="box2" class="RedBox" x="120" y="10" width="50" height="100"/>
<box name="box3" class="RedBox" x="190" y="10" width="20" height="100"/>
</boxes>;
// Pull out some core data we need from the xml
var basePackage:String = xmlData.@["base-package"];
var boxes:XMLList = xmlData.*;
var box:XML;
var boxInstance:DisplayObject;
// Loop through each box node and build it's class
for each(box in boxes)
{
// It is very important that we use quotes for the class
// because it is a reserved word
var tempClass : Class = getDefinitionByName(basePackage+"."+box.@["class"]) as Class;
// Here we type box to DisplayObject since we are not
// sure what class it actually is
boxInstance = new tempClass();
boxInstance.x = Number(box.@x);
boxInstance.y = Number(box.@y);
boxInstance.width = Number(box.@width);
boxInstance.height = Number(box.@height);
addChild(boxInstance);
}
}
Make sure you also import the follow:
import flash.display.DisplayObject;
There is a lot more going on this time and I added some comments to help out. In the above code, we take some xml (in this case we just use an inline xml var but we could just as easily load this in with a URLLoader) and we loop through each of the box nodes. A good trick I use to cut down on repetitive writing is to put the package in the base node as an attribute base-package. Next you will see that each box has a name, class, x, y, width and height. The final thing you should notice is that I have changed the local var of redBox to boxInstance and it now has a type DisplayObject. Lets run it and see what happens.
So now you will see we have an error.
ReferenceError: Error #1065: Variable RedBox is not defined. at global/flash.utils::getDefinitionByName() at DynamicClassDemo/createRedBox() at DynamicClassDemo()
There is no longer a reference to com.flashartofwar.example.RedBox since nothing is typed to it so the compiler ignored importing it. This is a common problem when dynamically creating classes from XML, we have to use a little trick to force the compiler to include the class. Lets create a new class called IncludeClasses in com.flashartofwar.example package:
package com.flashartofwar.example {
/**
* @author jessefreeman
*/
public class IncludeClasses {
RedBox;
}
}
I know you are thinking this is crazy but this one class is a good place to store all of our dynamically created classes. By simply putting the class name inside of a class the compiler will automatically import it even though we have no other variables typed to it directly. The final thing we need to do is add this class as a variable to our doc class. Here is how you do that:
// This class forces the compiler to import your dynamic Classes
var includeClasses:IncludeClasses;
also make sure you import it:
import com.flashartofwar.example.IncludeClasses;
Now if you run it again you will see 3 red boxes dynamically being initialized and configured from XML. It couldn't be any easier! You can test that this is working by creating a new class, such as GreenBox or BlueBox, then add it to the IncludeClasses and put it in the xml. They should load up just like our RedBox.
I hope this simple example has illustrated the power of getDefinitionByName and how we can easily create classes on the fly as long as you know their package and class name. Please feel free to leave any comments bellow or talk about how you use this technique in your own project!




Facebook Application Development
I'm not understanding how this is any different from simply storing the x,y,width, and height information in the XML, and creating 3 vars of type RedBox. It seems like you are not creating the class dynamically, but instead simply referencing it differently.
Am I just not understanding how the concept is working?
How is this any different:
private function createRedBox() : void {
// This is some sample XML data, we can just as easily
// loda this at runtime instead of compiling it in.
var xmlData:XML = [boxes]
[box name="box1" x="10" y="10" width="100" height="100"/]
[box name="box2" x="120" y="10" width="50" height="100"/]
[box name="box3" x="190" y="10" width="20" height="100"/]
[/boxes];
// Pull out some core data we need from the xml
var boxes:XMLList = xmlData.*;
var box:XML;
var boxInstance:RedBox;
// Loop through each box node and build it's class
for each(box in boxes)
{
boxInstance = new RedBox();
boxInstance.x = Number(box.@x);
boxInstance.y = Number(box.@y);
boxInstance.width = Number(box.@width);
boxInstance.height = Number(box.@height);
addChild(boxInstance);
}
}
I appreciate the content, but let's focus on that content, instead of your shtick. It's a bit of a distraction.
@Ross
I think the point is not to simply set properties of a class at runtime (which your example shows) but to create an entirely new class at runtime based on the class name provided in the xml. For example you could have 3 classes: RedBox, BlueCircle, GreenTriangle. Each of these is a different class with different properties etc. Given that they are all displayObjects you could create these on the fly based on the xml. You could create 10 RedBoxes, 3 BlueCircles and 7 GreenTriangles or any mix.
You could also have the option of loading in a different swf file into your main swf that had other shape classes available. Let's just say you had a site that needed 100 different shape classes but no more than 3 at a time. Let's just imagine that the shape classes are HUGE and you don't want to load all 100 classes at once. You could dynamically load the swf you need with the shape classes you wish to create and have it all be CMS/XML driven without having to recompile. It's pretty badass.
I'm using a similar tactic in my current project. I have rooms of galleries where everything is hung dynamically, size is shrank so for scaling and the nav is all feed through xml.
To get the images the right size and position, in the Flash IDE, I put them on a background with a jpg of where all the pieces are, then after I put the movieclips on their position on the jpg. I run the swf and it traces out in xml (through a function I wrote) the all the properties I need. When I'm done with this project, I may never have to open up my AS project. Even if they want to add new rooms.
Well, I can admit that "dynamic" is a little deceiving but in the last example I am showing you how you can reference a new class through the XML instead of using the same class and simply configuring it with XML. Where this becomes interesting is when you use the technique to load in new classes from an external swf at run time. I will do a follow up on how to load up classes on the fly using an external swf similar to how RSL work in Flex. I think what is missing from my example is using different classes in the same XML as Al pointed out.
@Al
Thanks for the clarification.
I can see this as potentially useful when a SWF containing classes is loaded at runtime, and then you do this. That would help shrink the file-size quite a bit with very large projects. And it would allow for the addition of new classes without recompile.
Thats a pretty neat trick. I'll have to add that to my arsenal.
This is a nice technique for dynamically *instantiating and building* an object at runtime, but you're not *dynamically creating a class*. The class definition itself still has to exist and be compiled into the SWF (or a child SWF). When I read the title I thought this was going to involve bytecode manipulation to actually generate a class definition that didn't already exist in the ApplicationDomain (which would be a pretty cool thing).
So, for me at least, the title was somewhat misleading. That said, techniques like this can be quite useful, so thanks for the post.
Nice article.
Is really possible to create dynamically a class as Kotek says? With the class definition being loaded in a XML or Text?
Thanks for all!
You can use a few techniques to include the classes rather than the IncludeClasses class.
the Frame metadata can be used to include extra classes not referenced anywhere.
i.e.
[Frame( extraClass="com.flashartofwar.example.RedBox" )]
public class ... {
...
@ Fernando .. No. The article is poorly named. It is not about creating classes - only instantiating them.
Dynamic class creation is not possible in ActionScript / Flash.
@Tim Not exactly true. You can dynamically create classes in AS3, you just can't use the language to help you. Because AS3 can dynamically load and instantiate any bytecode in a SWF, and because you can generate that bytecode using ByteArrays and AS3, you can effectively generate classes dynamically.
I don't know the specifics of class definitions at the bytecode level, so I can't help you on the specifics, but it's definitely doable, and considering that the SWF bytecode format is documented (and source code for doing this is available in the Tamarin project), all the info you need is there for the taking.
Of course, it's ain't easy, but what cool stuff is?
Hey Jesse. I think that Brian Kotek has hit the nail on the head. What is done here is instantiating an instance of a class. It seems to me that the "dynamic" part is you are getting the class name from a string rather than hardcoding a new RedBox() in the application. Another way of saying that is that you are delaying the decision of what type of class your object will be until runtime. This is simular to the Factory method design Pattern.
Your exactly right. Delaying the type of object until runtime is excellent for template based things, such as a CMS. You can create an Interface and then implement some classes that represent different things. Then let the user decide which one they want when the application is running.
There's a library called spicelib that you may be interested in that handles mapping from XML to actionscript (http://www.spicefactory.org/parsley/docs/2.1/manual/). It removes the need to loop through and set each property in your code.
I agree with Brian again too about the "Dynamically Creating Classes" from AVM bytecode creation. What would be really cool would be:
var myClass:DynamicClass = new DynamicClass("com.shannonbox.Sum");
myClass.addPublicProperty("value1");
myClass.addPublicProperty("value2");
myClass.addMethodSource("Number", "addThem", "{ return value1 + value2; }");
myClass.compile();
myClass.exportToDynamicModule("com.shannon.Sum");
Alert.show(myClass.exportAsActionscriptSource());
// and then
var tempClass : Class = getDefinitionByName("com.shannonbox.Sum") as Class;
tempClass.value1 = 10;
tempClass.value2 = 20;
var result:Number = tempClass.addThem();
Anyway, really enjoyed your article.
Hey Shannon, I have really thought about renaming the post. I think all the feedback has been great and like I had mentioned earlier even I think the title is a little deceiving. One of the things I deal with all the time is the fact that I am a self taught developer so sometimes I don't pick the "correct" terminology. The best part about writing for insideRIA is the feedback and I learn a little more from each post I write.
I think in the end I have decided to leave the title since it would be out of place with all the comments and moving forward I will work a little harder on my titles to make sure the describe exactly what you are going to get.
Thanks to everyone who has left feedback and I am glad so many of you have enjoyed the post. Make sure you check out Amy Blankenship's continuation of my post here.