A Flex application consists primarily of code written in two different languages: ActionScript and MXML. Now in its 3.0 incarnation, ActionScript has gone from a prototype-based scripting language to a fully object-oriented, strictly typed, ECMAScript language. MXML is a markup language that will feel comfortable immediately to anyone who has spent time working with Hypertext Markup Language (HTML), Extensible Markup Language (XML), or a host of newer markup-based languages.
How do MXML and ActionScript relate to one another? The compiler, after parsing through the different idioms, translates them into the same objects, so that
<mx:Button id="btn" label="My Button" height="100"/>
and
var btn:Button = new Button(); btn.label = "My Button"; btn.height = 100;
produce the same object. The major difference is that whereas creating that object in ActionScript (second example) creates the button and nothing else, creating the object in MXML adds the button to whatever component contains the MXML code. The Flex Framework handles calling the constructor of the object described in MXML and either adding it to the parent or setting it as a property of a parent.
MXML files can include ActionScript within an <mx:Script>
tag,
but ActionScript files cannot include MXML. Although it’s tempting to
think of MXML as the description of the appearance and components that
make up your application, and of ActionScript as the description of the
event handlers and custom logic your application requires, this is not
always true. A far better way to think of their relationship is to
understand that both languages ultimately describe the same objects via
different syntax. Certain aspects of the Flash platform cannot be accessed
without using ActionScript for loops, function declarations, and
conditional statements, among many others. This is why the use of
ActionScript and the integration between MXML and ActionScript is
necessary for all but the very simplest applications.
This chapter discusses many aspects of integrating MXML and ActionScript: creating components in MXML, creating classes in ActionScript, adding event listeners, creating code-behind files by using ActionScript and MXML, and creating function declarations. Although it doesn’t contain all the answers, it will get you started with the basics of ActionScript and MXML.
Flex Builder is built on top of Eclipse, the venerable and well-respected integrated development environment (IDE) most strongly associated with Java development. Although Flex Builder certainly is not necessary for Flex development, it is the premier tool for creating Flex applications and as such provides a wealth of features to help you design and develop applications more effectively. You can use Flex Builder either as a stand-alone application or as a plug-in to an existing installation of Eclipse.
The first thing you need to do as a Flex developer is to create a Flex project. A Flex project is different from the other types of projects in Flex Builder because it includes theSWC (Flex library) Flex library SWC (unlike an ActionScript project) and is compiled to a SWF file that can be viewed in the Flash Player (unlike a Flex Library project). To create a project, right-click or Control-click (Mac) in Flex Builder’s project navigator to display the contextual menu (Figure 1-1), or use the File menu at the top of the application. From either, choose New→Flex Project. A dialog box appears to guide you through creating a project.
When prompted to specify how the project will get its data, choose Basic, which brings you to the New Flex Project dialog box (Figure 1-2).
Enter an application name and below, a location where the files will be stored on your system. The default location is C:/Documents and Settings/Username/Documents/workspace/Projectname on a Windows machine, and Users/Username/Documents/workspace/Projectname on a Mac. You can of course, uncheck Use Default Location and store your files wherever you like. The name of the project must be unique. The Application Type section lets you select whether you are making an Adobe Integrated Runtime (AIR) application or an application that will run in a browser via the Flash Player plug-in. Finally, the Server Technology settings let you indicate whether the application will be connecting to a server, and if so, what server type and separate configuration type are needed.
If you have nothing more to add, click Finish. To change the location where the compiled SWF file will be placed, click Next to reach the screen shown in Figure 1-3.
After the location of the generated SWF has been set, you can either finish or add source folders or SWC files to the project. To add another folder or set of folders, click the Source Path tab (Figure 1-4). To add SWC files to the project, click the Library Path tab (Figure 1-5). On this screen, you can also change the main MXML application file, which is by default the same name as the project.
With all paths and names specified, click Finish. Your project is now configured, and you are ready to begin development.
A Flex Library project does not have a main MXML file that it is compiled into a SWF. Instead the files are compiled into a SWC file that can be used in other applications or as the source for a runtime shared library (usually referred to as an RSL). The classes within the library are used to create a group of assets that can be reused in multiple projects at either compile time or runtime. To create a Flex Library project, right-click or Control-click (Mac) in the Flex Builder’s project navigator to open the contextual menu (Figure 1-6) or use the File menu. In either case, then choose New→Flex Library Project.
In the resulting dialog box (Figure 1-7), specify a name for your project as well as its location.
If you have nothing more to add, click Finish now. If you need to include files, assets, or other SWC files, including the Adobe AIR libraries, click Next and select them from the resulting screen. To set classes that can be selected and added into the recipe, first browse to a source path you would like to include and then set either classes or graphical assets that will be compiled into the library. Click Finish to create the project.
An ActionScript project is different from a Flex project in that it does not include the Flex Framework at all. ActionScript projects rely on the core ActionScript classes within the Flash code base and do not have access to any of the components in the Flex Framework. To create an ActionScript project, choose File→New→ActionScript Project (Figure 1-8).
In the resulting dialog box, specify a name for the project and a location where the files and compiled SWFs will reside. Click Finish to finalize the project with default settings, or click Next to add libraries or other source folders to the project, change the main file of the application, add SWC files that your code can access, or change the location of the output SWF. By default, the name of the main ActionScript file for the application will be set as the name of the project. The default output location of the SWF file will be the bin-debug folder in the project.
Set the options for the compiler arguments in the Flex Compiler screen of the Project Properties dialog box.
The MXML compiler, also called the mxmlc, is the application that compiles ActionScript and MXML files into a SWF file that can be viewed in the Flash Player. When you run or debug a Flex application in Flex Builder, the MXML compiler is invoked and the files are passed to the compiler as an argument to the application. When you debug the player, an argument to create a debug SWF is passed to the MXML compiler. Flex Builder lets you pass other arguments to the MXML compiler, as well; for example, you can pass arguments to specify the location of an external library path, allow the SWF to access local files, or set the color of the background.
To change the compiler settings for a project, right-click or Control-click (Mac) on the project and select Properties from the contextual menu (Figure 1-9), or choose Project→Properties from the menu bar.
In the resulting Project Properties dialog box (Figure 1-10), select Flex Compiler. Here you have several options to control how the SWF file is compiled. In the input field labeled Additional Compiler Arguments, you can add multiple options; simply type a hyphen (-) in front of each option and separate the options with spaces.
Some of the most commonly used options are as follows:
verbose-stacktraces
Specifies whether the SWF will include line numbers and filenames when a runtime error occurs. This makes the generated SWF larger, and a SWF with
verbose-stacktraces
is different than a debug SWF.source-path path-element
Adds any directories or files to the source path to have any MXML or ActionScript files be included. You can use wildcards to include all files and subdirectories of a directory. Also you can use
+=
to append the new argument to the default options or any options set in a configuration file, for example:-source-path+=/Users/base/Project
include-libraries
Specifies a SWC file to be compiled into the application and links all the classes and assets in the library into the SWF. This option is useful if the application will load in other modules that may need access to the classes in a SWC that the SWF will not be using.
library-path
Similar to the
include-libraries
option but includes only classes and assets that are used in the SWF. This lets you keep the size of the SWF file manageable.locale
Specifies a locale to be associated with a SWF file. For example, use
-locale=es_ES
to specify that the SWF is localized for Spanish.use-network
Indicates whether the SWF will have access to the local file system and is intended for use on a local machine, or whether the standard Flash Player security will apply. For example, use
-use-network=false
to specify that the SWF will have local file system access but will not be able to use any network services. The default value is true.frames.frame
Enables you to add asset factories that stream in after the application and then publish their interfaces with the
ModuleManager
class. The advantage of doing this is that the application starts faster than it would have if the assets had been included in the code, but does not require moving the assets to an external SWF file. One of the more difficult and more useful parameters.keep-all-type-selectors
Ensures that all style information, even if it is not used in the application, is compiled into the SWF. This is important if the application will be loading other components that require style information. The default value is false, which means that style information not used in the application is not compiled into the SWF.
After setting the options for the compiler, click the Apply button to save the options for that project.
Although Flex Builder is a powerful tool for Flex development, it is certainly not necessary for creating Flex applications. The MXML compiler (mxmlc) is free to anyone and can be downloaded from Adobe. To compile a Flex application outside of Flex Builder, open a command prompt (Windows) or a terminal (Mac OS X), invoke the MXML compiler, and pass the file containing the application as an argument, using a command such as the following:
home:base$ . /Users/base/Flex SDK 3/bin/mxmlc ~/Documents/FlexTest/FlexTest.mxml
This will compile the MXML file into a SWF that by default compiles into the folder where the MXML file is located. Any warnings or errors from the compiler will be displayed in the terminal or command-prompt window. To add further options to the MXML compiler, you append arguments to the call to the compiler. For example:
home:base$ ./mxmlc ~/Documents/FlexTest/FlexTest.mxml -output=/Users/base/test/genera ted/Index.swf -library-path+=/Users/lib/MyLib.swc
generates a SWF file named Index.swf, places it in the directory at /Users/base/test/generated/, and includes the SWC library /Users/lib/MyLib.swc.
To invoke the MXML compiler directly from the command line
without providing the full path to your SDK installation (which in
this example is C:\flex_sdk_3), you will need to add the /bin
directory the compiler resides in to the Path
systems
variable.
- On Windows:
Select the Advanced tab.
Click Environment Variables.
Within the System variables grid, navigate to and double-click Path.
In the Variable Value field, if the last character is not set to a semicolon (;), enter a semicolon and then the path to the /bin folder within your Flex SDK installation directory (Figure 1-11).
With the path to the MXML compiler directory set, open a command prompt, navigate to your project directory, and enter the following command:
C:\Documents\FlexTest> mxmlc FlexTest.mxml
This generates the FlexTest.swf file within C:\Documents\FlexTest, just as the first command presented in this section does. Setting the path to the /bin directory of the Flex 3 SDK installation lets you invoke the compiler from any directory including, in this example, your current project directory.
If step 6 results in the following error message
Error: could not find JVM
you must manually enter the path to the directory in which the Java Runtime Environment (JRE) is installed on your machine. To manually enter the path, navigate to the /bin directory of your Flex 3 SDK installation. Open the jvm.config file in a text editor, and append the path to your JRE installation directory to the variable
java.home
. Assuming the Java installation is on the root of your drive, you enter the following:java.home=C:/Java/jre
- On Mac OS X or Linux:
Open your .bash_profile file (if you are using Bash) and edit the path variable by adding the location of the MXML compiler. Your .bash_profile file should look something like this:
PATH="${PATH}:~/flex3SDK/bin" export PATH
The .bash_profile will be located in your user home directory (which you can always access via a command line by typing
cd ~
). If you are using tsch, the path to the MXML compiler should be added to the .profile file.If the Java runtime is not set properly, set the following path variable in your terminal shell:
PATH="${PATH}:~/flex3SDK/bin" export PATH
You need to add an event listener in MXML that will listen for any events dispatched by children within the MXML file.
Flex components dispatch events whenever an action occurs, such as a user clicking a button, the selected item in a combo box changing, or data loading. To listen to these events being broadcast, simply add a reference to a function that will handle the event. For example:
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300"> <mx:Script> <![CDATA[ private function buttonClick():void { trace(" Button has been clicked "); } ]]> </mx:Script> <mx:Button click="buttonClick()" label="Click Me"/> </mx:Canvas>
Adding click="buttonClick()"
invokes the function buttonClick
whenever the button dispatches a click event.
You can also pass the event object itself to the function. Every
time a component dispatches an event, the component sends an object of
type Event
that any object
listening to the event can receive. For example:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300"> <mx:Script> <![CDATA[ private function buttonClick(event:Event):void { trace(event.target.id); if(event.target.id == "buttonOne") { trace(" button one was clicked") } else { trace(" button two was clicked") } } ]]> </mx:Script> <mx:Button click="buttonClick(event)" label="Click Me One" id="buttonOne"/> <mx:Button click="buttonClick(event)" label="Click Me Two" id="buttonTwo"/> </mx:HBox>
By telling the event listener to listen for an object of type
Event
, you can send the event to
the event listener and then respond to that event in different ways
depending on specified criteria. In this example, the response depends
on where the event originated.
The event object and the event dispatching system in Flex are
some of the most important things to understand. All events contain a
type that is used when the event is being listened for; if an event is
of type click
, then the
event-listening method will be added to the click event of the
child:
<mx:Button click="trace('I was clicked')"/>
Notification for user interactions, messages sent to an application from a server, or timers are sent via events. The event object defines several properties that you can access in any listening function. They are as follows:
bubbles
Indicates whether an event is a bubbling event, that is, whether it will be redispatched from the object that has received the event to any listeners further up the event chain.
cancelable
Indicates whether the behavior associated with the event can be prevented.
currentTarget
The object that is actively processing the event object with an event listener.
eventPhase
Target
The event target, which is the object that has dispatched the event.
Type
You can also write event handlers in the MXML itself by using
the binding tags {}
to indicate that
the code inside of the braces should be executed when the event is
fired. For example:
<mx:Button click="{textComponent.text = 'You clicked the button}" label="Click Me"/ > <mx:Text id="textComponent"/>
When it compiles this code, the Flex compiler creates a function
and then sets textComponent.text = 'You
clicked the button'
as the body of that function. It may
look different from the previous method, but the end result of this
function is the same: It listens for the event and executes its code.
There’s nothing inherently wrong with this approach, but for anything
more complex than setting a single property, use a defined function to
make your code easier to read and understand.
Refer to the child component by its id
property, and
set properties or call methods by using the id
property.
It’s easy to think of the script part of a component as being somehow separate from the MXML part, but they are not separate at all. Consider an example:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300"> <mx:Script> <![CDATA[ private function changeAppearance():void { this.width = Number(widthInputField.text); this.height = Number(heightInputField.text); } ]]> </mx:Script> <mx:Image id="imageDisplay"/> <mx:Text text="Enter a width"/> <mx:TextInput id="widthInputField"/> <mx:Text text="Enter an height"/> <mx:TextInput id="heightInputField"/> <mx:Button click="changeAppearance()" label="Change Size"/> </mx:HBox>
As you can see in the changeAppearance
method, the use of this
refers to the component itself, the
HBox
that contains the child
components; that reference changes the width and height of the
component. References to the two TextFields
, widthInputField
and heightInputField
, are also being used to
grab the text data from those TextInputs
. Each TextInput
is being referred to by its
id
, in much that same way that you
would refer to the id
of an element
in Document Object Model (DOM) scripting. The id
must be a unique name that can be used throughout the application to
refer to the child components Within one component there is a
single-level hierarchy, so no matter where a child is nested within
the component (for example, within another child), the id
still refers to that child. Look at a
similar example of actively setting the property of a child:
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="520" height="650"> <mx:Script> <![CDATA[ private var fileName:String = ""; private function saveResume():void { //....a service call to send the data and set the filename fileNameDisplay.text = "Your resume has been saved as "+fileName; } ]]> </mx:Script> <mx:Text id="fileNameDisplay" text="" width="500"/> <mx:RichTextEditor id="richTextControl" width="500" height="400"/> <mx:Button id="labelButton" label="Submit Resume" click="saveResume()"/> </mx:VBox>
In the preceding example, the child component is referenced by
its id
property and can have its
properties set by using that id
.
All components added in MXML are by default visible to any component
that has access to the parent component.
Use the ActionScript syntax for creating a new object or array with a constructor call, or define them in MXML.
As with many things in Flex, arrays and objects, the two most
commonly used types for containing data, can be defined in
ActionScript or in MXML. To define an array in MXML, use the <mx:Array>
tag to wrap all the items inside the array:
<mx:Array> <mx:String>Flex</mx:String> <mx:String>Flash</mx:String> <mx:String>Flash Media Server</mx:String> <mx:String>Flash Lite</mx:String> <mx:String>AIR</mx:String> </mx:Array>
All the items in the array are accessible through their
zero-based index. You can create multiple nested arrays in MXML by
adding multiple Array
tags within
an array:
<mx:Array> <mx:Array> <mx:String>Flex</mx:String> <mx:String>Flash</mx:String> <mx:Array> </mx:Array>
To create an object in MXML, use the <mx:Object>
tag and add all the
properties of the object and their values as attributes in the tag.
For example:
<mx:Object id="person" firstName="John" lastName="Smith" age="50" socialSecurity="123-45-6789"/>
The limitation of creating an object in MXML is that you cannot
create multiple nested objects. By creating your objects within a
script tag, however, you can create objects containing multiple
complex objects within them. You can create an object by creating a
variable of type Object
and calling
the constructor on it and then attaching properties later:
var object:Object = new Object(); var otherObject:Object = new Object(); object.other = otherObject;
You can also create an object by simply using curly brackets to delimit the properties within the object. For example:
var person:Object = {name:"John Smith", age:22, position:{department:"Accounting", salary:50000, title:"Junior Accountant"}, id:303};
Note how for the Person
object the position
properties
point to another object that contains its own distinct properties.
Notice how you don’t even need to declare a variable name for the
object that the position
property
will point to.
To create an array in ActionScript, create a variable and call
the Array
constructor:
var arr:Array = new Array("red", "blue", "white", "black", "green", "yellow");
You can also create an array without using the constructor by using square brackets and supplying any objects that will populate the array, as shown:
var noConstructorArray:Array = [2, 4, 6, 8, 10, 12, 14, 16];
This is the same as calling the Array
constructor and then passing the
objects in the constructor.
Within an ActionScript file or MXML file, there are different scopes of variables. Private variables and functions are visible only within the component itself; no other component can access them. This definition is useful for variables or methods that are used by a component only, that contain data only a single component should modify, or that are not modified except within the class. When you design complex classes, a good practice is to mark all properties that are not explicitly needed by outside components as private. Public variables are visible to any object that has a reference to the object of the class defining the property. Carefully considering which properties will be needed by outside classes and limiting access to those properties creates a far better class structure and frequently helps the programmer clarify the needs of a certain part of the application. Properties marked as private will not be visible to inheriting classes either, only the class or component in which they are defined. Finally, protected variables are accessible only to any object of a class that extends the class in which the property is defined, but not to any external objects.
Variables and functions marked private are visible only within the class or component in which they are defined and any inheriting class. For example, here is a class with protected and private properties:
package oreilly.cookbook { public class Transport { protected var info:Object; private var speed:Object; } }
By using the extends
keyword,
you can share the properties and methods of one class with another. In
the following example, marking the Car
class with the keyword extends the
Transport
class so that Car
inherits all of its nonprivate
properties and methods. Car
does
not have access to the private properties of the Transport
class, however, and attempting to
access them results in an error stating that the properties cannot be
found.
package oreilly.cookbook { public class Car extends Transport { public function set data(value:Object):void { info = value; speed = value.speed; /* this will throw an error because the speed variable is private and access to it is not allowed to any other class */ } } }
The protected properties of the Transport
class are available to the
Car
class, but they are not
available to any class that has an instance of either of these classes
as a property.
Any public properties of both classes, however, are available to
any class that instantiates one of these objects. The static
keyword indicates that any object
that references the class can access the specified property without
needing to instantiate an object of that class. In other words,
the static
keyword
defines a variable for all instances of a class. Adding the following
line to the Car
class
public static const NUMBER_OF_WHEELS:int = 4;
means you can now access the NUMBER_OF_WHEELS
property with a reference
to the class without needing to instantiate an object of that class,
such as
import oreilly.cookbook.Car; public class CompositionTest { private var car:Car; public function CompositionTest() { trace(" a Car object has "+Car.NUMBER_OF_WHEELS+" wheels"); } }
So far, this recipe has examined the scope of variables that are affiliated with classes. Variables, however, need not be properties of a class. They can be created and destroyed within a function, being meaningful only within a function body. For example:
private function processSpeedData():void { var speed:int; var measurement:String = "kilometers"; }
Outside the body of this function, the variables speed
and measurement
are meaningless. ActionScript
uses something called variable hoisting,
which means that variables defined anywhere within a function
are in scope throughout the function. Unlike languages that support
block-level scoping, the following code traces the newCar
object as being valid:
private function makeCars():void { for(var i:int = 0; i<10; i++) { var newCar:Car = new Car(); carArray.push(newCar); } trace(newCar); }
The reference to newCar
goes
out of scope only when the function returns.
ActionScript also defines a final
modifier that indicates to the compiler that the method cannot
be altered by any classes that extend that class. This modifier
enables you to define methods that are not private but that will not
be altered or overridden. For example, defining this method in the
Transport
class
public final function drive(speed:Number):void{ /* final and cannot be overriden in any other class*/ }
indicates that any class that extends Transport
will possess this method. Its
public scoping means any properties will be accessible to any object
with access to that Transport
object but that subclasses
cannot redefine the method.
In addition to creating components in MXML, you can create them in ActionScript without using any MXML at all. You just need to do a few things differently. The first is to ensure that your class is properly packed in relation to the main application file. In the following example, the component is from the application-level folder; specifically, it’s within oreilly/cookbook/, which is reflected in the package name:
package oreilly.cookbook {
The next difference is that any classes to be included or
referenced in the component must be imported by using their full
package name. This includes any class that the component will extend,
in this case mx.containers.Canvas
:
import mx.containers.Canvas; import mx.controls.Text; import mx.controls.Image; import oreilly.cookbook.Person; public class PersonRenderer extends Canvas {
Any constants and variables for the components are listed just
below the class declaration generally. In the following example, all
the private properties of this class are listed. These are properties
that the component can access but that no other component can access
or alter. To access these properties, get
and set
methods will be provided so that if any
processing is necessary when these properties are accessed, it can be
done when the property is accessed. Getter and setter methods are
common ways to provide access to private variables through
functions.
private var _data:Object; private var nameText:Text; private var ageText:Text; private var positionText:Text; private var image:Image;
In ActionScript, the constructor function always must be public, not return a value, and have the same name as the class itself. For example:
public function PersonRenderer () { super();
Any components that will be added to the component need to have
their constructors called and then passed as a parameter to the
addChild
method in order to be added to the display list and so that
their properties can be altered by the component.
nameText = new Text(); addChild(nameText); ageText = new Text(); addChild(ageText);
In the following example, the ageText
Text
component is manually positioned, a necessity because
its component, PersonRenderer
, is a
Canvas
and does not have any layout
management, unlike a VBox
or
HBox
component.
ageText.y = 20; positionText = new Text(); addChild(positionText); positionText.y = 40; image = new Image(); addChild(image); image.y = 60; }
If a component already defines a method to set data, such as
mx.containers.Canvas
here, you must
override the method if you want the component to perform any custom
actions. To do so, use the override
keyword
to indicate to the compiler that you intend to override the method of
the superclass. For example:
override public function set data(value:Object):void { _data = value; nameText.text = value.name; ageText.text = String(value.age); positionText.text = value.position; image.source = value.image; } override public function get data():Object { return _data; }
A final method finishes the component; this one originates in the class and is publicly scoped:
public function retrievePerson():Person { /* do some special employee processing */ return null; } } }
To add this class to any other component, you can use ActionScript:
var renderer:PersonRenderer = new PersonRenderer(); addChild(renderer);
Or you can use MXML:
<renderers:PersonRenderer id="renderer"/>
In ActionScript, the constructor is explicitly called, and in
the MXML version it is called when the constructor for the component
that the PersonRenderer
object is
nested inside of gets called.
You want to listen for events passed up from children components to parent components without adding a long chain of event listeners.
Understanding bubbled events requires looking at several classes. Several types of events can be bubbled up: mouse-down events, click events, and keyboard events. The term bubbling up refers to the event working its way up through the display list to the application container, like a bubble rising to the surface through the water. When the user clicks on any component, that event is passed up through the hierarchy. This means that the parent of a component can listen on a component for a click event, and if one is dispatched anywhere within the child component, the parent will be notified. To have the parent listen for all events of a certain type within the child, the parent simply needs to add an event listener to that child to receive all bubbled-up events.
Consider this class defined in BubblingComponent.mxml:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="200"> <mx:Script> <![CDATA[ private function sendClick():void { trace(" BubblingComponent:: click "); } ]]> </mx:Script> <mx:Button click="sendClick()"/> </mx:HBox>
This component contains a button that will dispatch a click
event up the display list to any component that contains an instance
of the BubblingComponent
. To listen
to this event, use the click handler in a component that contains
BubblingComponent
:
<cookbook:BubblingComponent click="handleClick()" id="bubbler"/>
A BubblingHolder
that
contains a BubblingComponent
could
be defined as shown in the following code snippet:
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300" xmlns:cookbook="oreilly.cookbook.*" creationComplete="complete()"> <mx:Script> <![CDATA[ private function handleClick():void { trace(" BubblingComponentHolder:: click "); } ]]> </mx:Script> <cookbook:BubblingComponent click="handleClick()" id="bubbler"/> </mx:Canvas>
This component will dispatch an event up to any component
listening, even to the application level. When we add the BubblingHolder
to the main application
file
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:cookbook="oreilly.cookbook.*"> <mx:Script> <![CDATA[ public function createName():void { name = "Flex Cookbook"; } ]]> </mx:Script> <cookbook:BubblingComponentHolder click="handleClick()"/> </mx:Application>
the click event from BubblingComponent.mxml will be broadcast all the way up to the application level.
The sequence of events in a MouseEvent
sends the information about the
event, such as a click and the location of the click, up the display
list through all the children, to the child that should receive the
event, and then back down the display list to the Stage.
The Stage detects the MouseEvent
and passes it down the display
list until it finds the target of the event, that is, the last
component that the user’s mouse is interacting with. This is
called the capturing phase. Next, the
event handlers within the target of the event are triggered. This is
called the targeting phase, when the event is
given an actual target. Finally, the bubbling
phase occurs, sending the event back up the display list to
any interested listeners, all the way back to the Stage.
Create a component in ActionScript that extends the Flex library class that provides any needed functionality and then add any properties and methods there. Then create an MXML file that extends that class you’ve created.
If you’re familiar with ASP.NET development, you’ve doubtless heard the term code-behind—likewise, if you’re familiar with the notion of separating the controller and the view in any type of application that mixes markup and another language (Ruby on Rails, JavaServer Pages (JSP) developments, PHP, and so forth). Separating the actual layout elements from the code that controls them is a good strategy for keeping the different aspects of a view cleanly separated for readability and clarity. There are times when the sheer number of files required to use this approach throughout an application makes navigating a project difficult, because two files are created for each component. Moreover, separating business logic and view logic is frequently difficult and can lead to difficult-to-follow separations of code within a component. Many developers, however, prefer this approach, and sometimes it helps clarify an application and its workings.
To begin, first look at the behind part of
the code-behind: a component that extends the class (mx.containers.Canvas
in this example) and
contains methods to listen for the component to be added to the stage,
as well as a method that can handle any event but is intended to
handle button clicks specifically.
package oreilly.cookbook { import mx.containers.Canvas; import flash.events.Event; public class CodeBehindComponent extends Canvas { public function CodeBehindComponent() { super(); addEventListener(Event.ADDED_TO_STAGE, addedToStageListener); } protected function addedToStageListener(event:Event):void { trace(" Added to Stage from Code Behind "); } protected function clickHandler(event:Event):void { trace(" Click handled from component "+event.target); } } }
In this example, methods that would normally be marked as
private are scoped as protected. This is because the code portion of
the code-behind, the MXML, will inherit from the CodeBehindComponent
class and to function,
requires access to these methods. Here you have the MXML component of
the component:
<cookbook:CodeBehindComponent xmlns:mx="http://www.adobe.com/2006/mxml" width="200" height="400" xmlns:cookbook="oreilly.cookbook.*"> <mx:Button click="clickHandler(event)"/> </cookbook:CodeBehindComponent>
You are creating a component and would like to allow properties on that component to be bindable, for other components to bind to.
Create getter and setter methods, and mark those methods with a Bindable
metadata tag that contains the name
of the event that the methods will dispatch when the property is
set.
Any object can define bindable properties by dispatching an
event when the property’s changed and using the Bindable
metadata tag above the property. The best practice is to use
get
and set
functions to define these bindable
properties. When the property is set, an event is dispatched using the
same name indicated in the Bindable
tag. For example:
package oreilly.cookbook { import flash.events.EventDispatcher; import flash.events.Event; public class Person extends EventDispatcher { public static var NAME_CHANGE:String = "nameChange"; private var _name:String; [Bindable(event=NAME_CHANGE)] public function get name():String { return _name; } public function set name(value:String):void { dispatchEvent(new Event(NAME_CHANGE)); _name = value; } } }
The Bindable
tag requires the
name of the event that will be dispatched when the name
property is set. This ensures that any
component binding a property to the name
property of the Person
object is notified when the value
changes.
Now that the bindable property has been set on the Person
object, you can use all instances of
Person
in binding
expressions:
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300"> <mx:Script> <![CDATA[ [Bindable] private var _person:Person; public function set person(person:Person:void { _person = person; } ]]> </mx:Script> <mx:Label text="{_person.name}"/> </mx:Canvas>
Create a class that extends the flash.events.Event
class and create a property for the data that you would like to be
available from the event.
At times you may need to dispatch data objects with events so
that listeners can access that data without accessing the object that
dispatched the event. Renderers or deeply nested objects that are
dispatching events up through multiple components to listeners will
frequently want to send data without requiring the listening component
to find the object and access a property. As a solution, create an
event type and add any data types that you want to include to the
constructor of the event. Remember to call the super method of the
Event
class so that the Event
object is properly instantiated. For
example:
package oreilly.cookbook { import flash.events.Event; public class CustomPersonEvent extends Event { public var person:Person; public var timeChanged:String; public function CustomPersonEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false, personValue:Person=null, timeValue:String="") { super(type, bubbles, cancelable); person = personValue; timeChanged = timeValue; } override public function clone():Event { return new CustomPersonEvent(type, bubbles, cancelable, personValue, timeValue); } } }
In this custom Event
class, the inherited Event.clone
method is overridden as well in
order for the CustomPersonEvent
to
be able to duplicate itself. If an event listener attempts to
redispatch this custom event, as shown here:
private function customPersonHandler(event:CustomPersonEvent):void { dispatchEvent(event); }
the event that is dispatched is not the event that is received;
instead, it is a copy of the CustomPersonEvent
created using the clone
method. This is done inside the
flash.events.EventDispatcher
class.
If the clone
method is not
overridden to ensure that all properties of the CustomPersonEvent
are carried into a clone
of itself, then the event returned from the clone will be of type
flash.events.Event
and will not
have any properties of the CustomPersonEvent
.
You need to listen for the user to press a key and determine which key was pressed and handle the event accordingly.
Add an event listener for the keyDown
event
either on the component or on the stage of the application and
read the KeyboardEvents
keyCode
property.
To listen for a KeyboardEvent
, use the keyDown
event handler, which all classes
that extend UIComponent
possess. The KeyboardEvent
class
defines a keyCode
property
which contains the code for the key that the user pressed. For
example:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300" keyDown="keyHandler(event)" backgroundColor="#0000ff"> <mx:Script> <![CDATA[ import flash.events.KeyboardEvent; private function keyHandler(event:KeyboardEvent):void { switch(event.keyCode) { case 13: trace(" Enter pressed "); break; case 32: trace(" Space Bar pressed "); break; case 16: trace(" Shift Key pressed "); break; case 112: trace(" F1 pressed "); break; case 8: trace(" Delete pressed "); break; } } ]]> </mx:Script> <mx:Button label="One"/> </mx:HBox>
A note about this component: It will listen only for events that
occur while the button has focus. If you remove the button from this
component, there is nothing left that can have focus, and the keyHandler
function will never be called. To
catch all KeyEvents
that occur in
the application, whether or not the component has focus, add the
following to the opening tag of the component:
addedToStage="stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHandler)"
This ensures that the keyHandler
method will handle all KeyEvents
that the stage catches, which
would be all of them.
You want to define methods for a parameter that have default values or null values so that the method does not always need those values passed.
Use default values or null values in the method declaration by setting the parameter equal to a default value or equal to null.
To define an optional method or multiple optional methods for a
method, simply set the default value of an object to null
in the signature of the event. The ActionScript primitives
String
, Number
, int
, and Boolean
cannot be null values, however; you
must supply a default value. For example:
public function optionalArgumentFunction(value:Object, string:String, count:int = 0, otherValue:Object = null):void { if(count != 0) { /*if the count is not the default value handle the value the call passes in*/ } if(otherValue != null) { /* if the otherValue is not null handle the value the call passes in */ } }
Another strategy for providing not only optional parameters to
the method but also an indeterminate number of arguments is to use the
...
marker in front of a variable
name. This is referred to officially as the ...(rest)
parameter. This variable will
contain an array of arguments that can be looped over and
processed.
public function unlimitedArgumentsFunction(...arguments):void { for each(var arg:Object in arguments) { /* process each argument */ } }
To determine the type of an object, ActionScript provides the
is
operator, which tests the type
of an object and returns true or false. The is
operator returns true if the object is of
the type tested against or if the object extends the type indicated.
For example, because the Canvas
object extends UIComponent
, the
is
operator returns true if the
Canvas
object is tested as being of
type UIComponent
. A UIComponent
however, will not return true if
tested as being of type Canvas
, because UIComponent
does not inherit from Canvas
. Consider this code:
public function TypeTest() { var uiComponent:UIComponent = new UIComponent(); var canvas:Canvas = new Canvas(); trace(" uiComponent is UIComponent "+(uiComponent is UIComponent)); trace(" uiComponent is Canvas "+(uiComponent is Canvas)); trace(" canvas is UIComponent "+(canvas is UIComponent)); }
which produces the following output:
uiComponent is UIComponent true uiComponent is Canvas false canvas is UIComponent true
A common use of type testing is to determine the component that has thrown an event. This lets you use a single method for simple event handling and test the type of the object to determine appropriate actions.
private function eventListener(mouseEvent:MouseEvent):void { if(mouseEvent.target is Button) { /* handle button specific actions */ } else if(mouseEvent.target is ComboBox) { /* handle combobox specific things */ } else { /* handle all other cases */ } }
Create an ActionScript file, declare that file as an interface,
and define any methods you would like the interface to require. To
implement the interface, use the implements
keyword in the class declaration of the component that will use
the interface.
Interfaces are powerful tools that let you describe a contract that an object must fulfill: the interface must contain a specified set of methods with a certain scope, name, parameters, and return type; components using the object, in turn, will expect that this set of methods is present. This lets you create lightweight descriptions of a class without actually creating a new class that would clutter your inheritance trees. Classes that implement an interface are considered to be of that interface type, and this can be used to set the types for parameters of methods or to set the return types of methods as shown here:
public function pay(payment:IPaymentType):IReceipt
This method can accept any object that implements IPaymentType
and will return an object that
implements the IReceipt
interface.
The interface cannot define the method body nor can it define
any variable. In the following code snippet, IDataInterface
is declared and defines five
methods that any object that implements the interface must also
possess and define:
package oreilly.cookbook { public interface IDataInterface { function set dataType(value:Object):void; function get dataType():Object; function update():Boolean; function write():Boolean; function readData():Object; } }
To implement the interface, declare the class and add the
implements
marker to the class
declaration. All methods defined in an interface must be implemented
by the class. In the following code snippet, all the methods of the
preceding interface are included and are given function bodies:
package oreilly.cookbook { import flash.events.EventDispatcher; import flash.events.IEventDispatcher; public class ClientData extends EventDispatcher implements IDataInterface { private var _dataType:Object; public function ClientData(target:IEventDispatcher=null) { super(target); } public function set dataType(value:Object):void { _dataType = value; } public function get dataType():Object { return _dataType; } public function update():Boolean { //do the actual updating var updateSuccessful:Boolean; if(updateSuccessful) { return true; } else { return false; } } public function write():Boolean { var writeSuccess:Boolean; if(writeSuccess) { return true; } else { return false; } } public function readData():Object { var data:Object; //get all the data we need return data; } } }
To implement an interface in MXML, use implements
. in the top-level tag for the
component. For example:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300" implements= "IDataInterface">
Get Flex 3 Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.