Modified

10 September 2007

Requirements

   
Prerequisites
To benefit most from this article, you should have a working knowledge of MXML and ActionScript.
 
User level
Intermediate
 
Required products
Flex Builder 3 (Download trial)
 
 

   

As Joe Nuxoll of JBuilder and JavaPosse fame will tell you (given the slightest provocation), one place where the Java designers completely dropped the ball is in Java's component model. This becomes especially clear when you compare it with a system like Adobe Flex, which has full language support for components.
 
Not only does Flex have built-in support for properties and events, it includes a higher-level abstraction language (MXML) for laying out and configuring components, which greatly simplifies the development process. You can even create new components directly in MXML, although that's more commonly done using ActionScript. This article shows you how to create components in Flex and how clean the resulting applications that use those components can be.
 
You'll see that ActionScript is quite similar to Java in many ways, but I hope you'll also see that ActionScript includes time-saving features that don't appear in Java.
 
Although you can write all your Flex code by hand and compile it using mxmlc (the free command-line compiler), it's much easier to use Adobe Flex Builder which is built on top of Eclipse and has command completion, context help, built-in debugging and more. Some of the examples in this article use Adobe AIR, Adobe beta software that allows you to create desktop applications that access files and other aspects of your local machine (rather than being limited to the web sandbox). You can download a beta of Flex Builder including AIR support from Adobe Labs. (You can also build these examples using the command-line compiler).
 

 
MXML components

The easiest way to create a component is using MXML. First, to create a new application: from the Flex Builder menu, choose File > New > Flex Project, and fill out the rest of the wizard queries. Next, within Flex builder again, select File > New > MXML Component. This produces a wizard that guides you through the process, including the choice of a base component to inherit from. I chose "Button" and called my new component "RedButton." The result is a file called RedButton.mxml (the name of the file determines the name of the component) containing the following:
 
<?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="/2006/mxml"></mx:Button>
This new component is based on Button and can be used in an application as follows:
 
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="/2006/mxml" layout="absolute" xmlns:local="*"> <local:RedButton label="Not so Red"/> </mx:Application>
All the original capabilities of a button (including the ability to set a label property , used here) are preserved.
 
We can set the background color of the button to make the name of the component honest, add a tooltip and a default click action property:
 
<?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="/2006/mxml" fillColors="['red', 'blue']" toolTip="A Red Button" click="clicked()"> <mx:Script> <![CDATA[ private function clicked():void { label = "Quite Red"; setStyle("fillColors", ['red', 'red']); } ]]> </mx:Script> </mx:Button>
The clicked() function is defined inside a Script tag, within a CDATA block (inserted automatically by Flex Builder) because you're in XML. Note that the function definition includes a return type, void—optional static typing is an ActionScript feature that allows Flex Builder to do a better job of context help and command completion.
 
You can continue to add more sophistication to components this way, but MXML components are best used for simple things. More complex MXML components can become tedious and you're better off creating them directly in ActionScript (the compiler turns MXML into ActionScript anyway).
 

 
ActionScript components

Flex Builder also has a wizard to help you create ActionScript components. To use it, select File > New > ActionScript Class. Because all classes must be in packages (like Java), you have the option of selecting a package name (if you don't, you get the default unnamed package). The wizard also allows you to select a superclass. In the following example I've specified that the wizard should automatically generate the framework for the constructor:
 
package SimpleComponents { import mx.controls.Button; public class ASRedButton extends Button { public function ASRedButton() { super(); } } }
The import statement was automatically created by Flex Builder, and the file's name is ASRedButton.as. Although the component is created in ActionScript, it is available as MXML:
 
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="/2006/mxml" layout="absolute" xmlns:local="*" xmlns:SimpleComponents="SimpleComponents.*"> <SimpleComponents:ASRedButton label="Not so Red"/> </mx:Application>
When I began typing ASRedButton in Flex Builder, it performed completion on the name, then added the XML namespace before the tag, and also included the xmlns attribute in the Application tag. Even if you ultimately plan to use the free command-line compiler, it's worth starting with the free download of Flex Builder just to get initial help with details like this.
 
With ActionScript it's easier to create more sophisticated components. We can give ASRedButton the same behavior as RedButton:
 
package SimpleComponents { import mx.controls.Button; import flash.events.MouseEvent; public class ASRedButton extends Button { public function ASRedButton() { super(); label = "Reddish"; setStyle("fillColors", ['red', 'blue']); toolTip="A Red Button"; } override protected function clickHandler(event:MouseEvent):void { label = "Quite Red"; setStyle("fillColors", ['red', 'red']); } } }
There are a number of ways to respond to events, but in an ActionScript component, the easiest way is usually to override a method. Note that the override keyword ensures that you don't accidentally create a new method (which would not produce the desired result).
 
Again, the import statements were automatically included by Flex Builder.
 

 
Properties

You can create an attribute that is readable and writable inside MXML by simply creating a public field, but ActionScript also contains get and set keywords to indicate methods for reading and writing a property. Here's a Label subclass where you can see both approaches:
 
package SimpleComponents { import mx.controls.Label; import flash.events.Event; public class MyLabel extends Label { public var labelStates:Array = ["Ontological", "Epistemological", "Ideological"]; private var state:uint = 0; // unsigned integer public function set textValue(newValue:String):void { text = newValue; } public function get textValue():String { return text; } public function onClick(event:Event = null):void { text = labelStates[state++ % labelStates.length]; } } }
This example uses optional static typing on labelStates, which is an Array in this case . There are a number of ways to create and populate an Array, the simplest being the use of square brackets. Arrays are not typed; they simply hold Objects. However, you are not forced to downcast when pulling items out of an Array if the dynamic typing mechanism can handle the result.
 
Note that labelStates is public, so it can be accessed if you set an MXML attribute. The textValue has both a get and set method, making it a property and allowing you to execute code when that property is changed. Here I've just assigned it to the text field for demonstration purposes.
 
I've also added the onClick() method which cycles the text field through the elements in the labelStates array. The onClick() method also accepts an event argument, which defaults to null so that it's optional—this allows it to be used as an event listener, as you'll see shortly. The following is an example that uses the component:
 
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="/2006/mxml" xmlns:SimpleComponents="SimpleComponents.*" > <SimpleComponents:MyLabel fontSize="24" id="display" textValue="Hello, World!" click="display.onClick()" /> <mx:Button click="if(display.labelStates.indexOf('Logical') == -1) display.labelStates.push('Logical')" /> </mx:Application>
Note that I still have access to properties like fontSize in MyLabel.
 
The assignment to textValue results in "Hello, World!" appearing as the label text. In order for click to be assigned to the onClick() method, the MyLabel instance must have an object identifier, and thus, is given an id of display.
 
The code assigned to click in the Button shows that you aren't required to just reference a function; you can define the code inline (although this can rapidly become messy). Here, you can add "Logical" to the list if it isn't already there.
 

 
Events

Everything in Flash is event-based, and as a programmer you can hook into either the framework-generated events or user-generated events. If you want to insert some code at a particular point in the life cycle of an application, you must find the appropriate framework event, such as the creationComplete event, which happens after the application has been constructed (there are many other framework events that you can look up in the online help system).
 
As a simple example, you can drive MyLabel using a timer:
 
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="/2006/mxml" xmlns:SimpleComponents="SimpleComponents.*" creationComplete="init()"> <SimpleComponents:MyLabel fontSize="24" id="display" textValue="Hello, World!" click="timer.stop()" /> <mx:Script> <![CDATA[ private var timer:Timer = new Timer(1000); private function init():void { timer.addEventListener(TimerEvent.TIMER, display.onClick); timer.start(); } ]]> </mx:Script> </mx:Application>
This demonstrates three different events:
 
  1. The framework generates the creationComplete event, which is tied to the init() function. This creates a Timer that generates...
  2. The TimerEvent.TIMER events, which are tied to the display.onClick method.
  3. The MyLabel instance is configured to call timer.stop() when the user clicks the label.
In general, event handling is exactly this simple; note the succinctness of code when compared with Java event handling.
 
Flex also provides data binding, which allows one component to respond to a change in the data of another component. This was seen as a common activity that would ordinarily require a lot of event-handling code, so the Flex designers decided to do the work for the programmer. Here's a counter that uses data binding:
 
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="/2006/mxml" creationComplete="init()"> <mx:Label fontSize="24" text="{count}"/> <mx:Script> <![CDATA[ private var timer:Timer = new Timer(1000); [Bindable] public var count:uint = 0; private function init():void { timer.addEventListener(TimerEvent.TIMER, function(event:Event):void {count++}); timer.start(); } ]]> </mx:Script> </mx:Application>
In text="{count}", the curly braces bind the text field to the count var, which has been modified with the [Bindable] annotation so that the compiler wires up all the necessary event generation and handling for you. Every time the timer fires, count changes and the text field in the Label changes in response.
 
This example also shows the use of an inline function (a.k.a. a lambda) as the target of the event listener.
 
Note: When you create inline code inside MXML like this, the compiler actually creates a class to hold the code and an instance of that class. Although this is convenient for small blocks of code it can become confusing if you try to make that code too large or complex, in which case you should move it to a separate ActionScript file.
 

 
A programmed learning component

I'll finish with a more sophisticated component that will also demonstrate the use of AIR to read local files from the disk. In addition, I'll explore more ActionScript syntax including regular expressions.
 
This component was created to support the Flex Jams; in particular, for people who are using Flex for the first time and need step-by-step instructions. Programmed Learning is a fairly old practice; it focuses on learning through exercises and, instead of giving you the answer all at once, it guides you through by giving a series of hints and answers so that you get the sense of discovery and learning at every step.
 
Flex includes the perfect component for programmed learning, the Accordion component. This component contains a set of sliding "windows" so that you can move window each one up and expose more information as you go. Because I'm creating lots of exercises (and undoubtedly making lots of changes), it's far too much work to set them up as static MXML files; instead, I'll inherit a new component from theAccordion component and teach it to build itself by reading text files (which is where AIR comes in).
 
First, I've created a little language that allows me to describe the exercise, hints, intermediate solutions, and steps. Any line that begins with a period, as in "." contains a command. Here's an example, Exercise1.txt in the directory 1. Basics:
 
.ex Install Flexbuilder. Test it by creating "Hello world" in both AIR and Flex. .h1 Go to http://labs.adobe.com/ to get the public beta of FlexBuilder 3 (This includes support for AIR). .h2 Select File|New|Flex Project and follow the instructions to create a new Flex app. The second page allows you to specify either Flex or AIR. .h3 Place a Text component in the application. Type a '<' and begin typing "Text" and you'll see the context help pop up the possibilities. Choose and press Return to insert it, and close the tag with />. .s3 <mx:Text /> .h4 Now set the text property to "Hello, World!". With your cursor inside the tag, begin typing "text" and use command completion. .s4 <mx:Text text="Hello, World!" /> .h5 You may also want to choose the Design button that you'll see in the application area, then click and drag a Text component to add it to your application Panel. Use Flex Properties to add text "Hello World." If you use design mode, be sure to switch back to Source mode and look at the generated MXML for your program. .final <?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="/2006/mxml" > <mx:Text text="Hello, World!" /> </mx:WindowedApplication>
The .ex tag indicates the exercise description. Anything starting with .h is a hint followed by a hint number. A .s is an intermediate hint solution, which also has a number. The .final tag indicates the completed solution.
 
Although I could certainly have written a Python program to translate this language into something (like XML) that would be easy for the Flex app to consume, the intermediate step would have reduced the interactivity of the solution. In the ExercisePresenter component, I use AIR to find and read the text file, and regular expressions to break it up into directives:
 
package ProgrammedLearning { import mx.containers.Accordion; import mx.containers.VBox; import mx.controls.TextArea; import mx.controls.Alert; import flash.filesystem.File; import flash.filesystem.FileStream; import flash.filesystem.FileMode; import flash.net.FileFilter; import flash.events.Event; public class ExercisePresenter extends Accordion { public var dataDirectory:String; public var chapter:String; public var fileName:String; private var file:File = File.documentsDirectory; private var pathsResolved:Boolean = false; // Called when properties are set: protected override function commitProperties():void { super.commitProperties(); // Prevent multiple calls: if(!pathsResolved) { pathsResolved = true; resolvePaths([dataDirectory, chapter, fileName]); } } private function resolvePaths(paths:Array):void { for each(var path:String in paths) { var successfullyResolved:File = file; // Store as far as we've gotten file = file.resolve(path); if(file.exists) continue; else { // Files installed elsewhere var exFilter:FileFilter = new FileFilter("Exercise", "Exercise*.txt"); file = successfullyResolved; file.browseForOpen("File Not Found; Select File to Open", [exFilter]); file.addEventListener(Event.SELECT, parseFile); } return; // Wait for user to choose new path } parseFile(); } private function parseFile(event:Event=null): void { var stream:FileStream = new FileStream(); stream.open(file, FileMode.READ); var str:String = stream.readUTFBytes(stream.bytesAvailable); stream.close(); var entries:Array = []; // Each entry is delimited by a '.' at the start of a line: for each(var s:String in str.split(new RegExp("\\n\\.", "s"))) if(s.length > 0) entries.push(s); for each(s in entries) { // Split on first newline: // (Could do this with a regular expression, too!) var brk:uint = s.indexOf("\n"); var tag:String = s.substring(0, brk); var contents:String = s.substr(brk); // Strip leading newlines: while(contents.charAt(0) == '\n') contents = contents.substr(1); switch(tag.charAt(0)) { case 'e': addStep("Exercise", contents); break; case 'h': addStep("Hint " + tag.substr(1), contents); break; case 's': addStep("Hint solution " + tag.substr(1), contents); break case 'f': addStep("Completed solution", contents); break; default: } } } public function addStep(label:String, contents:String):void { // Create a new "fold" in the Accordion: var vbox:VBox = new VBox(); vbox.percentHeight = vbox.percentWidth = 100; vbox.label = label; var text:TextArea = new TextArea(); text.percentHeight = text.percentWidth = 100; text.text = contents; vbox.addChild(text); addChild(vbox); // Add to self (Accordion object) } } }
The dataDirectory, chapter, and fileName fields tell the component where to find the example. Because these are public fields and we need to wait until they are set before trying to use them (otherwise they will contain incorrect data), you override the commitProperties() method, which is automatically called by the framework. The commitProperties() method is typically called more than once, so the standard practice is to use a flag to keep track of whether you have performed your task; in this case, you only want to call the resolvePaths() method once.
 
The resolvePaths() method iterates through an array of sequential directories and verifies that each one is correct. If it gets all the way through the array, it calls parseFile() directly, but if it fails—which means the path information is incorrect, possibly because the user hasn't installed the files in the expected location—it allows the user to choose the directories and the file. The browseForOpen() opens a file browser window in the native operating system. Note that the last successfully found directory is kept and used so the user doesn't have to start from scratch.
 
Notice that calling browseForOpen() is almost like starting a separate thread (although Flex and Flash don't support programmer threads — a good thing, since thread programming is virtually impossible to get right — the new betas of the Flash VM use threads internally for speed and utilization of multiple cores). Once the user selects the new file, you must still call parseFile(). This is accomplished by setting up an event listener, which effectively establishes a callback. You see this kind of programming—passing control, then using an event listener as a callback—quite a bit in Flex code, especially in network programming.
 
The first few lines of parseFile() show the standard way to open and read a text file, which only works in an AIR application. Note that it looks slightly similar to Java code but is less verbose because the decorator pattern is not (mis)used as it is in Java.
 
Once the file is read, a regular expression breaks it into "entries," each of which includes a dot command and the text that follows it (blank entries are discarded). Note that the regular expression uses similar syntax as Java does, in particular the double backslash when you actually want a single backslash. Discovering how to use ActionScript regular expressions took me a bit of time because the documentation and examples are lacking (or at least, I couldn't find them); you might have more luck using the regular expression section in the Strings chapter of Thinking in Java.
 
Each entry is broken into its dot command and body text, then a switch statement adds each step as a window in the Accordion component by calling addStep().
 
To test the component, we can create and configure it in MXML:
 
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="/2006/mxml" layout="absolute" xmlns:ProgrammedLearning="ProgrammedLearning.*"> <ProgrammedLearning:ExercisePresenter width="100%" height="100%" dataDirectory="FlexJamProgrammedLearning" chapter="1. Basics" fileName="Exercise1.txt"/> </mx:WindowedApplication>
However, this isn't the whole solution. The enclosing application (which I will eventually write) will construct a main page consisting of all the exercises, each of which you can select to bring up the programmed learning accordion. To accomplish this, I must be able to manipulate the component at runtime—which I can, through both my own design choices and the structure of Flex:
 
package ProgrammedLearning { public class DynamicTest extends ExercisePresenter { function DynamicTest() { percentHeight = percentWidth = 100; dataDirectory = "FlexJamProgrammedLearning"; chapter = "1. Basics"; fileName = "Exercise1.txt"; } } }
One thing I'd really like to see in a future version of Flex is better string manipulation tools. A perfect model for this is the Python string library, which is tried and tested, and you even have the source code. So creating an ActionScript string library is a matter of translation.