« September 2007 | Main | November 2007 »
October 31, 2007
Component Class - Part Four
In the previous article in this series you saw how the Arrow part of the CycleSelectButton was created. In this article we'll write the CycleSelectButton from scratch by extending UIComponent. Watch how similar this component's construction is to the V2 and Arrows components.
Start by creating a new ActionScript class and call it
CycleSelectButtonV3 . Have it extend
UIComponent :
public class CycleSelectButtonV3 extends UIComponent
{
/**
* constructor function
*
* This is a good place to set inital styles
*/
public function CycleSelectButtonV3()
{
super();
}
To make this simple for you - and to drive home the point of how similar things are - copy the following items from the V2 component into this V3 component:
- The
[Event]metadata; - the linkButton and arrows variable definitions (be sure to copy the import statements, too);
- the
createChildren()function; - The
dataProviderproperty set and get functions; - The
selectedIndexproperty set and get functions; - The
commitProperties()function; - The
handleClick()event handler function.
So what's left? I'm not sure all of that will compile, but give it a try and if it does, put it into a Flex application and test it out. Not quite right, huh?
This zip file contains the source for this component and a sample application.
The V2 version of this component extends HBox which does a couple of things for you: it handles the placement or layout of the component. By using HBox you don't have to worry about how big things are and where they go. HBox always measures each child and sticks one after the other.
Since this V3 component extends UIComponent you don't have any of that help. You have to implement a couple of the Flex framework functions to make the component behave correctly.
measure()
Look back at the Arrows component and you'll see two things that are missing from this V3 component: the
measure() and
updateDisplayList() functions. Measure() is important because the Flex framework needs to know how big the component is in order to position it within a container. The updateDisplayList() function is important to position the arrows and linkButton - something HBox did for you.
override protected function measure() : void
{
super.measure();
measuredWidth = arrows.getExplicitOrMeasuredWidth() + linkButton.getExplicitOrMeasuredWidth();
measuredHeight= Math.max( arrows.getExplicitOrMeasuredHeight(), linkButton.getExplicitOrMeasuredHeight() );
}
The measure() function must set the
measuredWidth and
measuredHeight properties. Since the component's design is to be horizontal with the arrows followed by the linkButton, the width is then the sum of each child's width. The height is the largest of the two.
If your component is also given an explicit width and height, then this measure() method will not be called.
Noticed the call to
getExplicitOrMeasuredWidth (and
getExplicitOrMeasuredHeight ). Since the arrows child has been given a size of 20x20, these functions return the explicit size of 20. The linkButton however, was not given a size, so it has to be measured.
updateDisplayList()
Once the child components have been measured and an overall size for the component has been determined, the Flex framework calls the updateDisplayList() function.
Just as with the Arrows component, updateDisplayList's purpose is to position and size the child components to make this component look the way it is supposed to look.
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList( unscaledWidth, unscaledHeight );
arrows.move(0,0);
linkButton.move(arrows.width,0);
linkButton.setActualSize(unscaledWidth-arrows.width,unscaledHeight);
}
The arrows are moved to the (0,0) position. Then the linkButton is positioned immediately after it. The linkButton is also given a size. Here, unscaledWidth and unscaledHeight will be whatever measure() determined or they will be the explicit sizes given to your component.
And that's all there is. The download source file has it all put together for you along with a sample test program. But take a look at the V2 and V3 components; compare them with the Arrows component. In all cases the Flex framework operates consistently. Because V2 was based on HBox, the measure and updateDisplayList functions were not necessary. When writing a component from scratch you have to do these things yourself.
Now you can write components from scratch - either based on existing components or from scratch. The next article in this series looks at skinning and styling components.
Posted by pent at 11:36 AM | Comments (6)
October 24, 2007
Component Class - Part Three
In the previous article you saw how to create a component in ActionScript and how that mimics a component written in MXML:
| MXML | ActionScript |
|---|---|
| Root tag | class extends |
| Metadata tag | [Metadata above class definition] |
| Child component tags | override createChildren(), using new operator and addChild() function. |
| Properties | Set and Get functions; override commitProperties() |
| Events | Use addEventHandler and specify an event argument to the handler function. |
In this article we'll look at how to make that circle of arrows rotate.
This is a link to the same file download in the previous article; nothing has changed.
Arrow Class
In the first article the cycle arrows is a GIF. Easy to place using the Image component (either in MXML or in ActionScript). That would be enough, except we want to rotate the arrows as the user clicks on the link in the component.
So what's wrong with that? Rotation in Flash is pretty easy, you just set the rotation property to an angle and the object rotates - about its (0,0) point. That's the catch - in Flex a component has (0,0) as its upper-left corner. When you rotate a Flex component by changing its rotation property, it pivots on this corner - it does not rotate about its center.

There are ways around this using a translation matrix, but I think this alternative will prove educational and help you out when you have some awkward things to do in Flex.
Principle
Keep in mind that (0,0) is the upper-left corner of a Flex component and to make life very easy and simple in the Flex framework, the Arrow component is going to keep it this way. The difference is that inside of the Arrow component, the circle of arrows will appear and it will rotate and not the Arrow component itself.
Here's the Arrow component in its entirety, but broken into sections. I think it will be easier to explain this way.
package com.adobe.examples.skins
{
import mx.core.UIComponent;
import flash.display.Graphics;
import flash.display.Shape;
public class Arrows extends UIComponent
{
There really isn't any existing Flex component to extend so the Arrow class extends UIComponent - the base class for every Flex component. UIComponent is what every Flex component inherits from.
createChildren
private var canvas:Shape;
/**
* createChildren (override)
*
* Creates the shape in which the arrows appear. This shape can then
* be rotated.
*/
override protected function createChildren():void
{
canvas = new Shape();
// after drawing the arrows below, I realized they were too big, but my
// calculations for the lines and curves were already figured out. So I
// just scaled the graphic a bit to make it look better.
canvas.scaleX = 0.6;
canvas.scaleY = 0.6;
addChild(canvas);
}
This component has a single child, a
flash.display.Shape , where the arrows will appear. This is the part that actually rotates. The Shape class is a very basic, lightweight Flash class for drawing. It has very little overhead and is ideal for this purpose.
measure
/**
* measure (override)
*
* Return the default width and height
*/
override protected function measure():void
{
measuredWidth = measuredMinWidth = 20;
measuredHeight = measuredMinHeight = 20;
}
So far you've seen createChildren() and commitProperties() - functions which you override to make your component. Here is another -
measure() . This function is critical to making components behave properly with the Flex framework's layout manager. You don't need to have measure() in the CycleSelectButton components because the HBox does this for you - a benefit of using a Container as a basis for your own components.
The measure function is called only when the Flex framework does not know how large the component should be. If you supply an explicit width and height to a component, measure() is never called because the layout manager knows how big it is. Keep this in mind and do not write anything in this function that is critical since it is not always called.
The measure() function's job is to set the
measuredWidth and measuredHeight properties (and optionally, like here, the measuredMinWidth and measuredMinHeight properties). Sometimes measure() can be complex if you have lots of children to the component - you have to measure all of them and then figure out how large the overall component is.
In this case, I create the circle of arrows to occupy a 20x20 area. So that's what I set measuredWidth and measuredHeight to.
updateDisplayList
/**
* updateDisplayList (override)
*
* Position the canvas containing the arrows in the middle of the component. Then
* draw the arrows.
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList( unscaledWidth, unscaledHeight );
canvas.x = unscaledWidth/2;
canvas.y = unscaledHeight/2;
drawArrows( canvas.graphics );
}
The
updateDisplayList() is another function in the Flex framework called in the life cycle of a component. The updateDisplayList() function is perhaps the most fun - it is were you actually make things happens. In this case two things are done: the canvas Shape with the arrows is positioned and the arrows are drawn.
Remember that the arrows are being drawn within the canvas at (0,0) - so we need to place the canvas Shape where we want its (0,0) to be - and that's in the middle of the component which is (unscaledWidth/2, unscaledHeight/2). If this is confusing, change it to (0,0) and see what happens.
I moved the arrow-drawing into a separate function to make it clearer (this function appears at the end of the class):
/**
* drawArrows
*
* Draws the circle of arrows in the given graphic. The circle is centered
* at (0,0) to make it easy to rotate
*/
private function drawArrows( g:Graphics ) : void
{
g.clear();
g.lineStyle(0, 0x0000CC, 1);
g.moveTo(-10,0);
g.curveTo(-10,-10,0,-10);
g.curveTo(2.5,-11,8,-8);
g.moveTo(8,-8);
g.lineTo(6.5,-13);
g.moveTo(8,-8);
g.lineTo(2.5,-6);
g.moveTo(10,0);
g.curveTo(10,10,0,10);
g.curveTo(-2.5,11,-8,8);
g.moveTo(-8,8);
g.lineTo(-6.5,13);
g.moveTo(-8,8);
g.lineTo(-2.5,6);
}
The
flash.display.Graphics (g) is the Flash entity which places instructions into the Flash Player's display list. Ultimately every component does this. The first thing is almost always clear() so you don't add more graphics - you usually want to replace them. You can read more about the Drawing API in the Flex documentation. You can see that the arrows are a series of
lineTo and curveTo functions about (0,0).
The rotation Property
/**
* rotation - apply rotation to the canvas shape, not to this component
*/
override public function set rotation(value:Number):void
{
canvas.rotation = value;
}
override public function get rotation():Number
{
return canvas.rotation;
}
Here's a trick you may not expect: overriding a very basic property like rotation. If you were to do something like
arrows.rotation = 45 you would and should expect the component to rotate to 45 degrees. But if that happens, the component rotates about its upper-left corner. What we want to do is rotate the Shape instead. By overriding the component's rotate property, we re-direct the value down to the Shape and prevent the component itself from being rotated.
Another benefit of overriding the property is that it will make sense to the developer who uses the component. Telling them that they can rotate the arrows by changing the rotation property is what they expect. Trying to explain they should not use rotation but instead use another set of functions is awkward.
Use in CycleSelectButtonV2
Now open the CycleSelectButtonV2 code and replace the Image component with an instance of the new Arrows component. The code provided in the download already does this; you use the Arrows component like anything else:
- You have to create an instance of it using new Arrows().
- You can add it to the CycleSelectButtonV2 component with addChild().
override protected function createChildren() : void { arrows = new Arrows(); arrows.width = 20; arrows.height= 20; addChild(arrows); linkButton = new LinkButton(); addChild(linkButton); // add a listener for the click on the LinkButton. linkButton.addEventListener(MouseEvent.CLICK, handleClick); super.createChildren(); } - You make it spin by changing its rotation property in the LinkButton click event handler:
arrows.rotation = arrows.rotation + 45;
What's Next
In the next article in this series we'll write this component one more time, but completely from scratch by extending UIComponent.
Posted by pent at 07:22 PM | Comments (3)
October 22, 2007
Component Class - Part Two
In the previous article I showed you how to make a component based on an existing MXML component, the HBox. In this article we'll look at writing that same component in ActionScript. I think this is a worthwhile exercise because you'll see how similar MXML and ActionScript components are.
One good thing to try is to add
-keep-generated-actionscript=trueto your Flex compiler options. MXML files are first translated into ActionScript classes, then compiled into the SWF file. The-keep-generated-actionscriptswitch will leave those translations and you can compare what we're about to do with what the compiler has generated.
Take a look at the V1 MXML component. There are four parts to it: the root tag (the HBox), the Event metadata, the Script block, and the child components (LinkButton and Image). You'll see how these elements are reflected when you write the ActionScript version of the component.
This is the source code for this example in a package structure. The file for the icon is included.
Create a new ActionScript class called CycleSelectButtonV2 and have it extend HBox. Use the Flex Builder wizard if you can. Otherwise create the class using the following code:
package com.adobe.examples.buttons
{
import mx.containers.HBox;
public class CycleSelectButtonV2 extends HBox
{
public function CycleSelectButtonV2()
{
super();
}
}
}
One of the four elements has already been taken care of: the class extends HBox.
The MXML component also includes event metadata. Add that to the ActionScript class just above the class definition:
package com.adobe.examples.buttons
{
import mx.containers.HBox;
[Event(name="change",type="flash.events.Event")]
public class CycleSelectButtonV2 extends HBox
{
...
The child components, Image and LinkButton, require ActionScript code. In the first article of this series you read how the Flex framework operates with properties being set, with commitProperties() being called, and so forth. Creating child components is also part of that framework.
When a component is being created the Flex framework invokes its
createChildren() function. This is as true for Buttons as it is for Container classes like HBox. To create the LinkButton and Image components, you'll need to add your own override of createChildren().
Below the import statement for HBox in the V2 ActionScript file, add:
import mx.controls.LinkButton; import mx.controls.Image;
The MXML tags for Image and LinkButton created two members of the class. You'll do the same here, below the class constructor function:
private var linkButton:LinkButton; private var image:Image;
Component children in MXML files are public. I like to make them private, but you can use protected or public if you like.
You also need to bring in the cycle button graphic. In the MXML file it was embedded directly in the Image tag. There is no ActionScript equivalent for that. Instead, add this code below the variable declarations:
[Embed(source="../assets/cycle_component.gif")] private var cycleIcon:Class;
The embed metadata tells the compiler to grab the bytes from the file and generate a class to make an image out of them. The data type of the cycleIcon variable is
Class because you don't want to have duplicates of the image (takes too much memory) but rather have shared instances that refer to those bytes.
Now add the createChildren() function:
override protected function createChildren() : void
{
image = new Image();
image.width = 20;
image.height = 20;
image.source = cycleIcon;
addChild(image);
linkButton = new LinkButton();
addChild(linkButton);
super.createChildren();
}
An instance of the Image class is created, then its width and height are set as is its source property. The LinkButton is also created. Notice how both components are added as children to the component using
addChild() . This puts the children onto the component's display list. If you do not do this the children will not appear. Creating the component children with
new and using
addChild() is equivalent to the MXML tags.
The createChildren function is called before commitProperties() so that you can apply properties to the children. You should now copy the commitProperties() function, along with the setter and getter property functions from the V1 class.
With the LinkButton and Image child components created and set, you've knocked off another MXML component equivalent.
In the previous article, you read that part of the Flex framework includes the measurement of the components. One of the big things the MXML component does for you is figure out the size of everything and where to place it. You'll have to do that yourself with an ActionScript component.
Because we are using HBox as the base class for the component, the sizing and positioning of the children are taken care of. Later, when we write this component from scratch, you'll see how to do that for yourself.
At this point the only thing missing is the click event handler on the LinkButton. To add that, go back into the createChildren() method and add the following line after the linkButton has been created:
linkButton.addEventListener(MouseEvent.CLICK, handleClick);
You'll need to include an import statement for
flash.events.MouseEvent with the other import statements.
Using addEventListener() is the equivalent of putting the event handler in the MXML tag.
Now copy the handleClick() function from the V1 component and make the change shown:
private function handleClick( event:MouseEvent ) : void
{
selectedIndex = selectedIndex + 1;
// wrap back to zero if more than the # of items in the dataProvider.
if( selectedIndex >= dataProvider.length ) selectedIndex = 0;
dispatchEvent( new Event(Event.CHANGE) );
}
When you are using MXML tags you have the luxury of deciding what, if any, arguments to pass to an event handler. When using addEventListener the event handler function must take a single argument of some event type. If you read the documentation on the LinkButton and look at the click event, you'll see it dispatches a MouseEvent, so that's the type of data that will be passed.
Open the main Flex application and change the tag from CycleSelectButtonV1 to CycleSelectButtonV2 and run it. The component should look and behave the same way.
Rotating the Arrows
I said in the previous article that, in addition to writing this component in ActionScript, we'd also rotate the arrow cycle each time the LinkButton was clicked. I've included the code to do that in the source download, replacing the Image. Take a look and it will be the topic of the next article in this series.
Posted by pent at 04:50 PM | Comments (6)
Now Available in Chinese
Several articles have now been translated into Chinese. You can view them at http://www.zhuoqun.net/index.php/archives/tag/peter-ent on Dreamer's Blog.
It is quite an honor to have someone take time to do this work. Thank you.
Posted by pent at 09:55 AM
October 19, 2007
Flex Cookbook Authors Wanted
Do you have a good tip or receipe for a Flex solution? Do you want to see your name in print? Then check out this call for topics in the Flex Cookbook:
http://weblogs.macromedia.com/flexteam/archives/2007/10/call_for_topics.cfm
Posted by pent at 01:01 PM
Contribute CS3 Test
I've been searching for an easier way to edit my blog articles and I believe I found it, right in my own backyard, so to speak. I've been on this quest for a couple of weeks now and created blogs on some of the more popular free blogging sites all in an attempt to make writing articles easier. That's been a main reason why I don't have more articles - takes too much time to fiddle with the HTML.
My criteria was that the blog editor should have an easy way to include images, Flash SWFs, and source code snippets. Blogs came about because of computers and the internet and people have been blogging about source code since day one. So what's the big issue with including source code? Yeah, it needs HTML tags, but why should I have to do that? I was using a WYSIWYG editor in 1984 on my original Mac.
However, it seems to be a huge challenge. Well, once I got the style set for the <pre> tag which surrounds the source code, I moved to the next hurdle: SWFs. Most of the public blogs let you include images and upload them to the same blog server. Your blog account has a limited amount of space, but that's understandable. But SWFs, that's another story. Come on, they're moving images. They do require the use of an <embed> tag and that seems to be the crux of the problem. Some blog hosts just strip it out. Other blog hosts will allow the tags but you have to store the SWF someplace else. In both cases, a hassle for me.
I tried some third party blog editors which you can download and run on your computer. Like Contribute, they offer local editing and draft storage and then upload the finished article to the blog host. But there was always a gotcha in there. In one there wasn't a way to easily insert source code or SWFs without turning to the HTML editor. No improvement for me there. In another there was a problem connecting to the blog host; nice tools (no SWFs either), but without a reliable connection it wouldn't work.
I finally decided to try Contribute CS3. It connected right away to all of the blog servers I had set up. I could easily edit articles and create new ones. Adding in headings and source code was pretty easy, too. I can also drag and drop images and SWFs right onto the page. The problem then was uploading the SWFs. Only my current blog host at weblogs.macromedia.com (and the sister site, blogs.adobe.com) let me store the SWFs along with images and zip files.
In the end, I'm still on the same blog server, using a product Adobe makes. I'm pretty happy with this arrangement and I hope it fosters some more creativity.
Posted by pent at 10:48 AM
October 16, 2007
Component Class - Part One
This is a topic I visit from time to time - writing components. In this example I'll show you how to write a component from the ground-up. It will take several articles, but in the end you should be able to build your own components.
The component I've chosen I call a cycle-select button. This component shows a single entry from a dataProvider along with two arrows in a circle. When you select the button the arrow rotates a bit and the next value in the dataProvider is displayed. Think of it as a ComboBox with no drop-list.
Try clicking on the label and you'll see how it cycles through the values.
Flex Framework
In order to build a Flex component you must understand the Flex framework. Building a component is done in stages as the Flex framework makes multiple passes through the component hierarchy to determine layout.For example, consider an Application with two VBoxes. One VBox has a bunch of Buttons while the other has a bunch of labels. To create these components, the Flex framework has to first create the Application itself, then the VBoxes, and then the children of those VBoxes. That's one pass.
If neither VBox has been given a width or height you'd expect the VBoxes to be large enough to enclose all of its children, right? To figure out how big to make the VBoxes, the Flex framework has to determine how big each of its children are. Buttons and Labels do not typically get explicit sizes, so the Flex framework has to determine their sizes, too. That's another pass.
Once all of the measurements are done, the Flex framework has to get the components sized property and positioned. That's another pass.
As you can see, creating components isn't a simple task, but it also isn't that hard, once you get the hang of it.
To facilitate component creation, the Flex framework calls upon certain methods within each components. By implementing these methods, your component can fit nicely and easily into the framework.
There are two ways to begin a component: by extending a component that already exists which does most of what you want to do or by creating a component "from scratch" which is to say, from the base class all components extend.
Extending an existing component is the most common and it is what you do all the time when writing a Flex application. When you create your main application file with a root tag of <mx:Application> you've created a component (extending Application). When you make a new MXML file a root tag of any other component, that's doing the same thing; whether you extend a component through MXML or ActionScript.
First Draft
We'll start by modifying the HBox component but ultimately we'll want to extend UIComponent. Using HBox can provide a good proof of concept.Download File
This is the source code in a package structure. It includes the icon for the graphic as well.
Taking a look at the component's design, there are two obvious top-level pieces: the cycle button and the label. But since we want to select the label to cycle through the choices, using a Button seems more practical since a Button also provides some feedback as the mouse rolls over it and is pressed. But a Button doesn't look anything like the control above, so perhaps a LinkButton is closer in appearance.
Create a new MXML component using HBox as the root tag and call it CycleSelectButtonV1.mxml (for version 1). To that add two children: an Image and a LinkButton (set its id to "linkButton"). Set the Image to be 20x20 and set the HBox's verticalAlign property to "middle". If you used FlexBuilder to make the component, erase any pre-set width and height on the HBox.
Change the Image tag to this:
<mx:Image source="@Embed('../assets/cycle_component.gif')"
width="20" height="20" />
Picture how you'd use this new component. Perhaps something like this:
<CycleSelectButtonV1 dataProvider="{choices}"
change="handleCycleChange(event)" />
The HBox component doesn't have a dataProvider property nor does it have a change event. This is part of the customization you'll need to do.
Events and Properties
The component will dispatch achange event, so you'll need to tell the Flex compiler that the component will be dispatching the event. Add this below the HBox root tag:
<mx:Metadata>
Event(name="change",type="flash.events.Event")]
</mx:Metadata>
The Event metadata tells the Flex compiler that including change="..." on the MXML tag is OK. The class given as the data type of the event is the default, but I like to be explicit so there's no question about what event class is going to be expected in the event handling function.
For the dataProvider, which is a property, you'll need to write some ActionScript. Add a Script block below the Metadata tag:
<mx:Script> <![CDATA[ ]]> </mx:Script>
The <![CDATA and ]]> syntax is a way of telling XML that everything within those brackets is to be taken as-is with no XML parsing. You don't *need* to have the CDATA block inside the Script tags, but if you use < for example, the XML parser will think you are trying to start a new tag!Add this code within the CDATA block:
import mx.collection.ArrayCollection;
private var _dataProvider:ArrayCollection;
public function set dataProvider( value:ArrayCollection ) : void
{
_dataProvider = value;
}
public function get dataProvider() : ArrayCollection
{
return _dataProvider;
}
This is a standard way of writing a property - using set and get functions with the actual value the same name as the set and get functions, but prefixed with an underscore. You'll sometimes see this referred to as a backing variable.
At this point you can test the component, even including dataProvider and change in the component tag; they don't do anything yet.
When I thought up this component I envisioned the data being provided the same way you'd do for ComboBox. Here's an example:
[ {label:"Apples", value:1}, {label:"Oranges", value:2}, etc. ]
What you would like to see are the labels displayed. The trick is to make that happen.
commitProperties
Nothing in the component so far shows how to display the data in the LinkButton. For that we wil need some more ActionScript. Add this code within the Script block:
override protected function commitProperties() : void
{
super.commitProperties();
// we'll fill this in below
}
Part of the Flex framework cycle includes a call to commitProperties(). This function is called after all of a component's properties have been set. This is important because within any given property set function you don't know if another set function has already been called. If setting a third property depends on the value of two others, for example, the only logical place to set that third property is in commitProperties().
We're going to use commitProperties() to set the label of the LinkButton to a value in the dataProvider. For this first example it will be the first item. Add this below super.commitProperties();
linkButton.label = dataProvider[0].label;There are a lot of things that can go wrong here: the dataProvider property might never have been set or it might not have a label property in the first item. For now, believe everything is set.
Running the Flex app again and you should see "Apples" as the LinkButton's label.
What do you think should happen if you wrote some ActionScript code in your main file that changed the CycleSelectButtonV1's dataProvider? What happens if you change the dataProvider of a ComboBox or DataGrid? They change to show those new values, right? You'll want the CycleSelectButton to do the same. Making this happen will further illustrate the Flex framework.
If you do: cycleButton.dataProvider = newValue this calls the component's set function for the dataProvider property:
public function set dataProvider( value:ArrayCollection ) : void
{
_dataProvider = value;
}
Big deal: the _dataProvider internal member changes. But that won't change the label to the LinkButton. You could try adding linkButton.label=value[0].label right into this set function, but I hope you can see there are problems with that. For one, it just "feels" wrong. But really, the problem is that when the component is first being created and the properties are being set, the LinkButton's label property may not be able to accept the value. It will later, of course, but many components won't be able to be changed so easily during the property setting phase.
Which of course, leads to the commitProperties() function. The next logical thing to do then is call commitProperties right from the set function. Again, doesn't "feel" right and besides, all that does is just do the same thing with the same problem.
What you want to do is notify the Flex framework that commitProperties() needs to be called. To that you call invalidateProperties() from the set function. This sets a flag in the Flex framework. What is good about this is that you can set a hundred properties and call invalidateProperties() a hundred times, but commitProperties will be called just once. Much more efficient. Change the set function to this:
public function set dataProvider( value:ArrayCollection ) : void
{
_dataProvider = value;
invalidateProperties();
}
Cycling Through the Labels
That little exercise was the set up for the next thing you should do: change the label on the LinkButton to the next item in the dataProvider.What's the "next" item if zero is hard-coded in commitProperties()? Obviously we'll need a variable to hold this. Hmm, selectedIndex sounds like a good choice and is consistent with the ComboBox and many other Flex controls. Set up a set and get function for selectedIndex, too:
private var _selectedIndex:int = 0;
public function set selectedIndex( value:int ) : void
{
_selectedIndex = value;
invalidateProperties();
}
public function get selectedIndex() : int
{
return _selectedIndex;
}
You'll also need to change a line in commitProperties to use selectedIndex:
linkButton.label = dataProvider[selectedIndex].label;And make the click event on the LinkButton call a function to bump up the index:
private function handleClick() : void
{
selectedIndex = selectedIndex + 1;
if( selectedIndex >= dataProvider.length ) selectedIndex = 0;
}
Notice that the linkButton's label was not explicitly set in the handleClick() function. Because the selectedIndex property is set, and because that set function calls invalidateProperties(), the linkButton's label is changed in commitProperties(). As a rule of thumb, try to modify components in only one place.
Don't forget to hook up the handleClick() function to click event on the linkButton:
<mx:LinkButton label="linkButton" click=handleClick()" />
Now run the application and click the LinkButton in the component. It should cycle through all of the labels in the dataProvider. When the LinkButton is clicked, the handleClick() function is called. That increments the selectedIndex which sets a flag for commitProperties to be called. When commitProperties is called, it displays the label assoicated with item in the dataProvider at the selectedIndex index. Pretty cool, huh?
Dispatching Events
The last thing to do is dispatch a change event when the selectedIndex changes. You could put this into the selectedIndex set function. But that means any change via ActionScript will dispatch the event. That's not a normal Flex workflow. Instead, dispatch the event right from the LinkButton's click handler:
private function handleClick() : void
{
selectedIndex = selectedIndex + 1;
if( selectedIndex >= dataProvider.length ) selectedIndex = 0;
dispatchEvent( new Event(Event.CHANGE) );
}
In the main application you can handle the change event and use the component's selectedIndex property to find out what was selected.
Wrap Up
That's it for this article which is already long for a blog entry. You should be able to write components using any Flex container (eg, HBox, VBox, Canvas) as a base. Just toss in the child components as MXML tags into the file, create a Script block to add any properties (with set and get functions), and override commitProperties() to apply those properties.In the next episode of this series we'll look at writing this same component in ActionScript and rotate that circle image with each click.
Posted by pent at 09:00 AM | Comments (12)
Extra! BBC Announces Flash Video Adoption
The BBC and Adobe® Systems Incorporated (Nasdaq:ADBE), today announced a strategic relationship around the delivery of Web video. By adopting Adobe® Flash® Player software, the BBC will make its free catch-up TV service — BBC iPlayer —available as a streaming service across Macintosh and Linux®, as well as Windows®, by the end of the year. The strategic relationship will also allow the BBC to provide a single consistent user experience for the majority of streamed video and audio content on www.bbc.co.uk.
You can read more about it on our press release site Adobe Press Room or here on Read/Write Web
Posted by pent at 08:46 AM
October 12, 2007
Childhood Memories
I was cleaning out my hard drive the other day and came across a directory of old Flash programs. One of these is a demo of the Flash Drawing API that I had set into the background of old my website.
The algorithms are based on the Spirograph(tm) drawing kit I had as a kid. As I recall, no one but me was interested in it (I guess because it didn't come with an 'action figure').
Anyway, here it is running below and you can download the code from the link here Enjoy.
Posted by pent at 10:56 AM | Comments (2)
October 09, 2007
Flex and Hibernate
For the past month or so I've been working on a new example that shows how to use the Hibernate data assembler in LiveCycle Data Services ES. If you aren't familiar with Hibernate, it is a way to easily persist Java objects - either singularly or in collections and with complex relationships. For example, if you have a data base of employees using several tables, you can make a single Java class that represents the employees and tell Hibernate how to bring it all together. You can load and save these objects using Hibernate and all of the tables will be updated properly.
LiveCycle Data Services ES includes Hibernate. Using the AMF protocol between the Flex client and LC DS you can persist ActionScript objects, too. You can create an Employee ActionScript class and point to a LC DS destination which maps to Hibernate and save and load your ActionScript objects. There's a bit more to it than that, of course, which is why I wanted to make an example.
This example is complex enough that I have broken it into two parts. This is the first part and shows how to use Hibernate with LC DS along with a simple Flex application. The next part focuses a bit more on the Flex side with a more complex application but still using the same example code from this part.
Here are the basic steps:
- You need a database. I've used MySQL 5 for the example. You need to create tables and foreign key relationships. The schema which accompanies this article includes that.
- You need LiveCycle Data Services ES. Once that's installed you follow a few more steps to turn on Hibernate. There is nothing else to download.
- You make some Java beans that represent your objects. There is a Hibernate tool and Eclipse plugin which can do this but I did it all by hand. If this were a larger project I would opt for the tool. Hibernate persists Java objects, not ActionScript objects, which is why you need these Java classes. LC DS will take care of mapping your ActionScript classes to the Java classes.
- You make Hibernate configuration files which maps the Java classes to the database tables. The example I've written keeps these pretty minimal, which is good the first time out. In reading a Hibernate book I can see that very complex things can be done using these files.
- You make corresponding ActionScript classes. When you request data through the remote data service in LC DS you get back ActionScript objects. There are special ActionScript metadata tags which tell the compiler to expect this.
- You make a Flex application.
You also might want to visit this blog to get a more rapid introduction to Hibernate and Flex: Mind the Gap
About the Data
Before going further I should explain the data being used. I work in the Flex support group and so I modeled this example on some of the tools we use to open and track questions from our customers.
The tables include one for Accounts (customer companies), AccountContacts (people who have questions), Consultants (people like me), Cases (the questions asked), and CaseNotes (the progress of the Case). Accounts can also have a Consultant assigned to them and a Consultant can have multiple Accounts assigned. This is a one-to-many relationship. The Java and ActionScript classes reflect this: you'll see Sets in the Java classes and ArrayCollections in the ActionScript classes.
Setup and Installation
To begin this example, install your database and create the tables from my schema. Create a database called "support" and if you are using MySQL 5 you should get no errors when you run the SQL statements. If you are using a different database you may have to change the SQL. This is one good reason to use Hibernate as it masks the database peculiarities from you.
Now install LC DS if you haven't already. Install it using the embedded JRun instance. This will make debugging and development simple.
Active the Hibernate components in LC DS as follows: (steps from documentation here).
Copy <install root>/resources/hibernate/*.jar to your web application's WEB-INF/lib directory (for example, on windows, this might be C:\lcds\jrun4\servers\default\samples\WEB-INF\lib\
Create a new Flex Builder Java project. Have the bin directory point to the WEB-INF/classes directory. Copy the contents of the classes directory to the WEB-INF/classes directory in LC DS. You'll see that there are both .class and .hbm.xml files. It is best if the Hibernate configuration files shadow the Java class files.
Modify your data-management-config.xml file that is in WEB-INF/flex directory. Here's a tip: in Flex Builder, add a new folder to your Java project, but click the Advanced button to make it a link to an existing folder. Browse to the WEB-INF/flex directory. Now you can edit the configuration files right from Flex Builder.
Start LC DS. You should see a lot of output, but no errors. If you do get errors, double-check your work.
Create a new Flex Builder Flex project. Indicate that you want to use LC DS, but compile locally in Flex Builder. You can compile on the server, too if you like. I prefer to leave my source files off the server.
Import the Flex sources into this new project. Notice how the bin directory points to the LC DS flex context root directory. Your compiled files will go right out to the server so all you need to do is run it.
In fact, all you need to do is run the TestingApp application. This is what you should see:
|
|
Click to enlarge |
So how does all this work?
If you open the Flex application you'll see a couple of DataService tags. Note the destination values. Then open the data-management-config.xml file on the LC DS server and find the destination with the same name. Notice how this destination is tied to the Hibernate entity description.
When the Flex application requests all of the Account objects, LC DS uses the Hibernate adapter to get Hibernate to fulfill the query. Hibernate queries the database and assembles a collection of Java Account objects. LC DS then converts the Java classes into an AMF binary stream, including the class name meta data.
The AMF stream arrives at the Flash Player and the Flex code examines the metadata and matches it with ActionScript classes with the same metadata and creates the ActionScript objects.
What you get back in your Flex application is an ArrayCollection of ActionScript Account objects. But there is more than that. Notice how the Account class also defines a collection of AccountContacts. Hibernate has gone and fetches them, too.
I hope this is enough detail to get you going. Leave me some comments if there are parts I could explain better and I'll follow with another article. Once this part is well understood, I'll publish the next part which details a Flex application that makes use of this data.
Hibernate Mapping in Detail
Let's take a closer look at the Hibernate mapping files and how they relate to the application as a whole.
First open the data-management-config.xml file and locate the destination for account.hibernate:
<destination id="account.hibernate">
<adapter ref="java-dao" />
<properties>
<use-transactions>true</use-transactions>
<source>flex.data.assemblers.HibernateAssembler</source>
<scope>application</scope>
2. <metadata>
<identity property="id"/>
<one-to-many property="accountContacts" destination="accountContact.hibernate" read-only="true" lazy="true" />
<many-to-one property="consultant"
destination="consultant.hibernate" lazy="true" />
</metadata>
<server>1.
<hibernate-entity>support.Account</hibernate-entity>
<fill-configuration>
<use-query-cache>false</use-query-cache>
<allow-hql-queries>true</allow-hql-queries>
</fill-configuration>
</server>
</properties>
</destination>
#1: Notice in the <server> section the <hibernate-entity>? The value supplied is the name of the Hibernate entity class to use with this destination. Since it is "support.Account" you'll want to look into the support package for the entity configuration file. Open WEB-INF/classes/support/Account.hbm.xml:
<class name="support.Account" table="accounts"> This names the Java class associated with this Hibernate class entity and the database table it maps to.
<id name="id" column="accountid">
<generator class="native"/>
</id>
If you examine the database table description for "accounts" you'll see that its primary key is the account column. The Hibernate entity element, <id>, shows that the id property of the Java class maps to the accountid column of the "accounts" table.
Look back at the data-management-config.xml destination at #2, the <metadata> for the destination description. See how the <identity> element names the same property.
The data-management-config.xml destination <metadata> also names some associations:
<one-to-many property="accountContacts"
destination="accountContact.hibernate"
read-only="true" lazy="true" />
<many-to-one property="consultant"
destination="consultant.hibernate"
lazy="true" />
Go back to the Account.hbm.xml file and you'll see corresponding relationship mappings:
<set name="accountContacts" inverse="true" >
<key column="accountid" />
<one-to-many class="support.AccountContact" />
</set><many-to-one name="consultant"
column="consultantid"
class="support.Consultant"
not-null="false" />
Since an Account can have zero or more AccountContacts, the first association declares a one-to-many relationship (one Account to many AccountContacts). In Hibernate, this is represented as a Set and corresponds to the java.util.Set class in the Account java class. The Set's name (accountContacts) is the name of the property in the Java class; the set names the column to key off of and the one-to-many element identifies which Hibernate entity class to use for those elements.
Back in the data-management-config.xml file, the one-to-many association is repeated to let the Data Services Hibernate Assembler know the relationships should be formed.
The relationship between a Consultant and an Account is the reverse: many Accounts to one Consultant. So a many-to-one association is used. When Hibernate sees the many-to-one element it knows that the Java class property, "consultant" will be of the type, support.Consultant and be a single element, not a collection.
Where's the ActionScript?
When dealing with the server-side there is no ActionScript – that's in the Flex client. Hibernate uses Java classes. For example, support.Account.
The Data Services Hibernate Assembler takes the Java class instances returned by Hibernate (eg, support.Account) and creates a byte stream in AMF format. Part of the byte stream (or serialization) of the class instances contains the name of the Java class.
Back in the Flex client, the receipt of the byte stream causes a look-up of an ActionScript class with metadata that maps the Java class name to an ActionScript class. The Flex client code then instantiates an ActionScript class (eg, support.Account) and fills its properties with the corresponding values from the byte stream.
Posted by pent at 11:21 AM | Comments (12)
October 01, 2007
New Beta Versions Available
Available today, the next Beta versions for Flex 3 and AIR - check out Adobe Labs today.
Now available:
Flex 3 Beta 2
AIR Beta 2
Adobe Media Player (prerelease)
AIR Extension for Dreamweaver
AIR update for Flash CS3
Posted by pent at 02:00 PM
