March 27, 2008
itemRenderers: Part 4: States and Transitions
Communicating the with the user of your application is what your itemRenderer does best. Sometimes that communication is as simple as presenting a name; sometimes more elborately using colors; and sometimes with interactivity.
itemEditors are truely interactive controls, but they are not the focus of this article. In these examples we'll look at itemRenderers that change their visual appearance based on either the data itself or the user's actions.
States
The Flex <mx:State> is a very good way to change the appearance of an itemRenderer. States are easy to use, and when combined with Transitions, give the user feedback and pleasent experience.
In this example we'll create a new MXML itemRenderer (and remember, you can do this completely in ActionScript if you prefer) for the List. All this shows is the image, title, author, price, and a Button to purchase the book.
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" >
<mx:Image id="bookImage" source="{data.image}" />
<mx:VBox height="115" width="100%" verticalAlign="top" verticalGap="0" paddingRight="10">
<mx:Text text="{data.title}" fontWeight="bold" width="100%"/>
<mx:Label text="{data.author}" />
<mx:HBox id="priceBox" width="100%">
<mx:Label text="{data.price}" width="100%"/>
<mx:Button label="Buy" />
</mx:HBox>
</mx:VBox>
</mx:HBox>
What we want however, is if the book is not in stock (the data has <instock> nodes which are either yes or no) for the price and Book to be invisible. I've made things a bit convenient for myself here because I gave the HBox parent of the price and Button an id. This allows me to change the visibility of both of those items by changing the visibility of the HBox, priceBox.
You can do this by overridding the set data function, which we'll do, but instead of directly changing the visibility of priceBox, we'll use this state defintion:
<mx:states>
<mx:State name="NoStockState">
<mx:SetProperty target="{priceBox}" name="visible" value="false"/>
</mx:State>
</mx:states>
Place this just below the root tag.
This example is a bit far-fetched in that it is overly complicated to do a simple task, but it shows how to use states. There are 2 states:
- The base state - this is the normal state of a component. Components that do not use states simply have this base state. In this example, the base state has the priceBox visible property as true (the default). This is the case when instock is "yes".
- The NoStockState - this is the state when the value of nostock is "no". When this state is active the SetProperty instructions are carried out. The target determines which member of the class is in question, the name property is the name of the property to change on the target, and value is the new value for the property.
The set data function determines which state to use by looking at the value of instock:
override public function set data( value:Object ) : void
{
super.data = value;
if( data )
{
if( data.instock == "yes" )
currentState = "";
else
currentState = "NoStockState";
}
}
The currentState is a property of all UIComponent controls. It determines which State is the active one. When switching between states the Flex framework begins with the base state and then applies the rules for the given state.
Remember that itemRenderers are recycled, so you must always restore values; never leave an if without an else in an itemRenderer.
If you are feeling adventurous, you can do away with the set data override in this example. Instead, set currentState directly in the root tag by using data binding expression:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400"
currentState="{data.instock == 'yes' ? '' : 'NoStockState'}" >
The currentState's value is set by examining data.instock right inline with the root tag. A nice trick, but it might be harder to maintain.
Adding Elements
In this itemRenderer the price and Buy button appears only if the instack value is yes. You could do this without a state of course, but if your itemRenderer has more controls to be added or removed, a state will make more sense as their appearance is controlled simply by setting the itemRenderer's currentState property.
Instead of just removing the price and Button, we'll have the state add a label telling the user the item is out of stock. Here's the modified state:
<mx:states>
<mx:State name="NoStockState">
<mx:SetProperty target="{priceBox}" name="visible" value="false"/>
<mx:AddChild relativeTo="{priceBox}" position="before">
<mx:Label text="-- currently not in stock --" color="#73DAF0"/>
</mx:AddChild>
</mx:State>
</mx:states>
The <mx:AddChild> tag says to add the Label into the priceBox. In addition to setting the priceBox's visible property to false, a friendly message replaces it.
Again, you could add this label in the set data function - or add it initially and just set its visibility to false and change it to true in the set data function. But I think you can see the value of the State: if the requirement for the instock being no condition gets more complex, all you need to do is adjust the NoStockState; the ActionScript code which switches the state remains the same.
You can modify states in Flex Builder's Design View.
Expanding List
This example does not work well for list controls but does perform nicely for a VBox and Repeater. This question of expanding an item in place becomes dicy when the list has to be scrolled. Imagine this: you've got a list of items will all the same height. Now you exand the height of item 2. So far so good - item 2 is taller than the other visible items. And there's the catch: the visible items. Now scroll the list. Remember that itemRenderers are recycled. So when item 2 scrolls out of view, its itemRenderer will be moved to the bottom of the list. You've got to reset its height. OK, that can work pretty simply. Now scroll the list so item 2 is back in view. You would expect it to be the expanded height. How does the itemRenderer know to do that? From previous articles you know that information either comes from the data itself or from some external source.
I think a resizing itemRenderer is too complex and not really worth the effort. I believe there is a better way to do this using VBox and Repeater. However, the catch with Repeater is that every child will be created. If you have 1,000 records and use a Repeater you will get 1,000 instances of your itemRenderer.
For this example you'll still write an itemRenderer but will use it as the child of a VBox. The elements of a list look pretty simple: the name of a book and its author. But click the itemRenderer and it expands in place. This is accomplished using:
- The itemRenderer has a state which includes the additional information.
- The itemRenderer uses a Resize transition to give a smoother expansion and contraction of the itemRenderer.
The base state of the itemRenderer is pretty simple:
<mx:HBox width="100%">
<mx:Label text="{data.author}" fontWeight="bold"/>
<mx:Text text="{data.title}" width="100%" fontSize="12" selectable="false"/>
</mx:HBox>
The ExpandedState adds the additional elements which contribute to the itemRenderer's height:
<mx:states>
<mx:State name="ExpandedState">
<mx:AddChild position="lastChild">
<mx:HBox width="100%">
<mx:Image source="{data.image}"/>
<mx:Spacer width="100%"/>
<mx:Label text="{data.price}"/>
<mx:Button label="Buy"/>
</mx:HBox>
</mx:AddChild>
</mx:State>
</mx:states>
Getting the itemRenderer to change size is as simple as adding a Transition:
<mx:transitions>
<mx:Transition fromState="*" toState="*">
<mx:Resize target="{this}" />
</mx:Transition>
</mx:transitions>
Place this below the <mx:states>
The Transition is applied whenever the state changes because its fromState and toState properties are wildcards. Now all you have to do is add event handler for clicking on the itemRenderer (add a click event to the root tag) and change the state:
<mx:Script>
<![CDATA[
private function expandItem() : void
{
if( currentState == "ExpandedState" )
currentState = "";
else
currentState = "ExpandedState";
}
]]>
</mx:Script>
Summary
States are a great way to make a number of modifications to the visual appearance of the itemRenderer. You can group the changes in a State and simply make it all happen by setting the currentState property of the itemRenderer.
In the next article we'll look at writing more efficient itemRenderers by extending UIComponent.
Posted by pent at 04:22 PM | Comments (5)
February 06, 2008
creationPolicy vs Binding
I just read a comment on an article I wrote a few years ago, creationPolicy vs. Form Data. The commenter suggested that the Save button must be able to read the values from the controls, even if those controls haven't been created - probably to make the code easier to write or at least consistent.
I disagree with this, but after reading that article again, I see that I could have given more information to support my position.
The article suggests using a <mx:Model> to hold the data and then use data binding to transfer the information from the model to the UI controls. That's fine for presenting the information, but it doesn't help when you to save the information.
ActionScript 3 Approach
Here's a different approach that works better with ActionScript 3 (for Flex 2 or Flex 3). Let's suppose you have an Accordion with two forms: Payment Information and Shipping Address. Let's also suppose you have two ActionScript classes that correspond to these forms: PaymentInfo and ShippingAddress. Here's the definition of the PaymentInfo class:// file: PaymentInfo.as
package mydata
{
[Bindable]
public class PaymentInfo
{
public var cardType:String;
public var cardNumber:String;
public var expireDate:Date;
}
}
Here's the definition of the ShippingAddress class:
// file: ShippingAddress.as
package mydata
{
[Bindable]
public class ShippingAddress {
public var firstName:String;
public var lastName:String;
public var street:String;
public var city:String;
public var state:String;
public var zipCode:String;
}
}
These look like ordinary ActionScript classes, but notice the [Bindable] meta tag above the class definition? That tag tells the compiler that all public properties can be used in data-binding. It is a short-cut to putting [Bindable] before each public variable.
In the Form to be used for the Payment Information you may have controls like this one to get the credit card number:
<mx:TextInput id="creditCardNumber" />
Since you want the PaymentInfo's cardNumber property to appear in the TextInput, you can use data-binding to associate the property with the field:
<mx:TextInput id="creditCardNumber" text="{data.cardNumber}" />
That's great for showing the credit card number if the value already exists. But how about saving that information?
Binding Tag
What you want to do is a "reverse" data-binding. Right now the {data.cardNumber} binding is one-way: whatever appears in the PaymentInfo's cardNumber property appears in the TextInput control. What you'd like is that if you change the value in the UI control to be automatically copied back into the PaymentInfo.You can do this by using the <mx:Binding> tag. Here's how to do that for the card number:
<mx:Binding source="creditCardNumber.text" destination="data.cardNumber" />
Any change made to the text property of the creditCardNumber TextInput control is reflected back to the cardNumber property of the PaymentInfo class instance for this Form.
Note: You can also do this type of binding in ActionScript using the mx.binding.utils.BindingUtils class.
Example
Picture this: the Payment Information form appears (blank). You fill in the fields and pick Save. The Save button's code does not have to access the UI controls at all: it saves the contents of the PaymentInfo class because the Binding took care of transferring the data from the control back to the data class.<mx:Script><![CDATA[
[Bindable] private var payment:PaymentInfo; // remember to create an instance using the new operator
[Bindable] private var shipping:ShippingAddress; // remember to create an instance using the new operator
]]></mx:Script>
<mx:Accordion>
<forms:PaymentInfoForm data="{payment}" />
<forms:ShippingForm data="{shipping}" />
</mx:Accordion>
Now suppose the Shipping Address were used in the same way, but in this case, the user never opened that form. Since the Save button code is not accessing any of the UI controls on the form, only the ShippingAddress properties, it doesn't matter if the UI controls were ever created.
In the above code example, the Binding tags will transfer changes to the controls in the PaymentInfoForm to the payment variable. Changes to the controls in the ShippingForm (if created by the user visiting it in the Accordion) will likewise be transferred to the shipping variable.
The Save button code then saves the contents of the payment and shipping variables.
Summary
Using the Binding tag to "reverse" data-binding on editable controls will make your code easier to write, understand, and more reliable. You won't have to worry if user's never touch containers on controls like Accordion or TabNavigator. And if the user does visit a form, data binding will populate the UI controls and Binding will read them.And yes, if you have 100 UI controls and fields you will need 100 Binding tags.
Posted by pent at 08:50 AM | Comments (9)
January 04, 2008
Using SQL with Adobe AIR
I've not been happy about the performance of my Atmospheres Music Player. I installed it on my home computer where I have a network disk that holds all my CDs - about 240 of them. When I pointed Atmospheres at the network drive, it took a long time for it to read all of the music information from the disk. I had taken steps to make the program responsive while the files were being read, but still, it took a long time. I told my teammate, Kyle Quevillon, about this and he suggested that I cache the information much like other music players do.
What he meant was that Atmospheres shouldn't read the file system, especially one on a remote disk, as its primary source of information in the music library. A light went off in my head when I realized I could use the SQL Lite database built into Adobe AIR. This was a perfect application of the database: I could have Atmospheres read the file system and store information into a local database, reading that information whenever it started. I'd only read the file system when new CDs were ripped.
If you didn't know it already, AIR comes with a genuine, lightweight, database - an implementation of SQL Lite. You can create tables, views, insert data, delete data, and run queries. The API to do this is the flash.data package. If you are interested in using the SQL capabilities of AIR, then read on.
Here's what I did:
I created a class to read the file system and build the Album objects. The Album class already existed and in fact, the code to read the file system already existed, too, in the MusicLibraryViewer class. I created a new class (FileReader) which creates the Album objects and notifies another class once an album has been read.
The other class is the Cache. One function of the Cache class is to take the information from the FileReader (ie, Albums) and store them into a database. Another function is to read the database and build a list of Albums from that information.
Atmospheres starts by getting the Cache to read the database and build the Album list. This is presented in the Atmosphere's user interface. At any time, especially the first time you run Atmospheres, you can refresh the music library. Doing this causes the FileReader to create an Album which the Cache compares to what it has read from the database. Anything new is put into the database as well as in memory.
Scanning the file system isn't lightning fast, but when you are expecting it to be slow, it moves along quickly enough and builds the local database. Reading the database is, in my opinion, astonishingly fast. I've got about 2,800 tracks from those CDs stored in the local database. It reads that data very quickly and builds the Album list in an eye-blink.
Download
You can download the code here. This is a Flex 3 Beta 3 project archive. If you do not hav Flex 3 Beta 3, find it here.
Now when Atmospheres starts on my home computer, it comes up right away, showing all 240 albums and is ready to play any of them.
Posted by pent at 06:36 PM | Comments (1)
December 24, 2007
Component Pack from ILOG
I've been asked a number of times if there are more chart types, as well as other controls, available for Flex. Perhaps I'm late to the party, but I just came across this announcement (from October 2007) that Adobe and ILOG are teaming up to enhance Flex 3.
Here's a quick list of what's available in the ILOG ELIXR package. You can find out more on Adobe Labs: http://labs.adobe.com/wiki/index.php/Flex_3:ILOG
- Radar charts (also named spider charts)
- Full 3D charts including bar/column, area, line and pie
- Treemap component, for analyzing large data sets
- Scheduling component, allowing users to view and manipulate time series data
- Organizational charts
- Country maps for creating interactive reports or dashboards
Posted by pent at 11:07 AM | Comments (1)
December 20, 2007
Component Class - Part Five
The last article in this series showed how to write the CycleSelectButton from scratch. In this article I'll look at styling and skinning the component.
Skins versus Styles
One frequently asked question is "what's the difference between styles and skins?" This is a good question and it is confusing a bit because you specify a component's skins using specific styles on the component. For example, the upSkin style on the Button component.
Styles control the appearance of a component while skins are the appearence. Or put another way, skins use styles to present the component. Take the borderColor style. It's purpose is to specify the color of a component's edge. The component's skin can use that style to draw its border - which may be round or square, thick or thin (using the borderThickness style). The skin contains the look of the component.
The reason skins are specified as style is so you can build an entire look or theme using just style sheets (.CSS files). If skins were specified in ActionScript you would have to deliver a new SWF for each theme. Having the skins and other styles in CSS means you can change the theme of an application with a new style sheet.
There are two types of skins: graphical and programmatic. Graphical skins are bitmaps: GIFs, JPGs, PNGs, etc. In Flex 3 you can import graphical skins from Adobe Illustrator and Adobe Photoshop. You can use Adobe Flash CS3 to create animated skins (picture a button that pulses with color).
Programmatic skins are written in ActionScript. They are class files that usually extend mx.skins.ProgrammaticSkin which is a very lightweight class. The class uses its override of updateDisplayList() to render the skin using the drawing API (see flash.display.Graphics).
Each component has its own set of skins. For a Button there are 8 possible skins: upSkin (its normal state), overSkin (when the mouse hovers over it), downSkin (when the mouse is pressed over it), disabledSkin (when the Button's enabled property is false), selectedUpSkin (when the Button's toggle property is true), selectedOverSkin (toggle is true and mouse is hovering over the Button), selectedDownSkin (toggle is true and mouse is pressed over it), and selectedDisabledSkin (toggle is true and enabled is false). If you want to use graphical skins for a Button, you should supply 8 different image files. If your Button will never be a toggle, then you can supply just 4 skins.
Download Example
This is a zip file and contains a full Flex Builder 3 project. You will either need Flex Builder 3 from Adobe Labs or you can use Flex Builder 2 and import the sources into a project. This project contains the source from the previous articles as well.
If you decide to use a programmatic skin you can either make separate skin classes, or use a single class, or a combination. A programmatic skin can detect which skin style it is being used for and code within the programmatic skin class can adjust for it. If, for example, the skin class is being used for a Button's upSkin, overSkin, downSkin, and disabledSkin, the class can decide to draw a green-filled circle for the upSkin, a blue-filled circle for the overSkin, a blue-filled circle for the downSkin, and a gray-filled circle for the disabledSkin.
You decide what works best for the look you want. You can wind up with a collection of skins - both graphical and programmatic - that make your application look unique (or follow your company's user interface guidelines).
Applying Skins
Applying the skins is simple. I prefer to do it in a style-sheet to make them easier to change:
Button {
upSkin: Embed('assets/BlueButtonUp.gif');
overSkin: Embed(source='assets/CompanyIcons.swf',symbol='GreenButton');
downSkin: ClassReference('com.mycompany.skins.StandardButtonSkin');
disabledSkin: ClassReference('com.mycompany.skins.StandardButtonSkin');
}
This pretty wild Button has a mixture of skins: one is a GIF, another is a symbol out of a SWF, and two come from the same ActionScript class.
Specifying a graphical skin uses the Embed directive. For a simple image file the Embed names the file using a path that is relative to the application's main file, or an absolute path within the project. The example above uses a relative path. When a Flash SWF is used, the skin can be the entire SWF file or a specific symbol within the SWF. If you chose to use a specific symbol, the Embed directive names the file and the symbol within it.
Specifying a programmatic skin uses the ClassReference directive. The full class name, including its package, is given for the reference. The compiler will find that class and pull it into the SWF.
The main advantage of programmatic skins over graphical skins is scaling. Because programmatic skins use the Flash Drawing API, the skins scale and rotate very well. Graphical skins can easily become distorted unless you scale9grid specifications in the Embed directive. The scale9grid specifications let you specify a grid overlay on the graphic that tells the Flash Player which parts of the graphic to scale. Think of a rectangle where you want the 4 corners to never scale, the top and bottom to scale only when the graphic is stretched horizontally, the left and right edges to scale when the graphic is stretched vertically, and the center to always scale.
Going back to the CycleSelectButton component, here is how the createChildren() function looks currently:
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();
}
To redo this component using skins, you have to think about which parts of the component should be skinable. It seems like a good idea for the circle of arrows to be a skin. Maybe you want to make your own arrows using Photoshop, for instance.
Here is the modified createChildren() function that introduces skins:
var skin:Class;
skin = getStyle("arrowSkin");
if( skin == null ) skin = CycleSelectArrowSkin;
_arrowSkin = new skin();
_arrowSkin.name = "arrowSkin";
if( _arrowSkin is ProgrammaticSkin ) (_arrowSkin as ProgrammaticSkin).styleName = this;
_arrowSkin.width = 20;
_arrowSkin.height= 20;
addChild(_arrowSkin as DisplayObject);
This is a bit different. First, the value for the arrowSkin style is retrieved. The arrowSkin style is specified by metadata above the class declaration:
[Style(name="arrowSkin",type="Class",inherit="yes")]
Notice that the type of the style data is "Class" - you want to load the class definition for the skin, not just the name of the class. This works for Embed as well since a class is created from the embedded image data.
If no arrowSkin style has been specified, then the default class, CycleSelectArrowSkin, is given. Then the arrowSkin member variable is set with a new instance of whatever skin class was selected. This is the standard way to specify skins using styles.
Once the class instance has been created and arrowSkin is now set, you'll see it is given a name ("arrowSkin") and its style is set to this. What it means is that the skin will get all of the styles set on the component. For example, the CycleSelectArrowSkin uses a style called "arrowColor" to draw the arrow graphic. There isn't any way from outside of the CycleSelectButton code to associate this style with the arrow skin; the style is set on the component, along with the arrowSkin style shown above:
[Style(name="arrowColor",type="Number",format="Color",inherit="yes")] [Style(name="arrowSkin",type="Class",inherit="yes")]
With the skin inheriting the component's style, arrowColor among them, the skin code can draw the arrows.
Details
In this section I go through the steps in more detail . I'll use the sample CycleSelectButton available from the download with this article, but I will only show the skin for the arrows; the skins for the rest of the component work the same way and it will be less confusing to focus on one skin.
Step 1: Figure out what you want the skin to be used for. In this case, it is for the cycle of arrows and by making it a skin, gives a developer the chance to change the look of the component without re-writing the component.
Step 2: In the component class file (CycleSelectButton.as), define the style for the skin above the class definition:
[Style(name="arrowSkin",type="Class",inherit="yes")]
public class CycleSelectButton extends UIComponent
{
Make sure the type of the style is "Class". The name will be used in the style sheet or on the MXML tag for the component:
StyleSheet.css:
CycleSelectButton {
arrowSkin: ClassReference('com.adobe.examples.skins.CycleSelectArrowSkin');
or
arrowSkin: Embed('assets/ArrowSkin.png');
}
MXML:
<buttons:CycleSelectButton arrowSkin="com.adobe.examples.skins.CycleSelectArrowSkin"... />
or
<buttons:CycleSelectButton arrowSkin="@Embed('assets/ArrowSkin.png')" ... />
Step 3: Declare a member variable to hold the skin instance:
private var _arrowSkin:IFlexDisplayObject;
Notice that the type of the variable is IFlexDisplayObject - not CycleSelectArrow skin, not UIComponent, and not even ProgrammaticSkin. If you want your skin to be either a programmatic skin or a graphic skin, you need to use a data type that is common to both. IFlexDisplayObject fills that need. It is generic enough, but also allows you to position and size the skin.
Step 4: Create the skin. You can do this either in createChildren() or in commitProperties().
var skin:Class;
skin = getStyle("arrowSkin");
if( skin == null ) skin = CycleSelectArrowSkin;
_arrowSkin = new skin();
_arrowSkin.name = "arrowSkin";
if( _arrowSkin is ProgrammaticSkin ) (_arrowSkin as ProgrammaticSkin).styleName = this;
_arrowSkin.width = 20;
_arrowSkin.height= 20;
addChild(_arrowSkin as DisplayObject);
The getStyle() function is used to get an alternative skin class (ProgrammaticSkin or graphic) from the styles for the component. This is how a custom skin can be used from a style sheet or MXML tag (from Step 2 above). If no skin was specified getStyle() returns null. In this case a default skin is used. It is important that when using skins you are consistent and create a default skin; creating a skin as a default is always a good idea and perhaps it too can be extended and customized.
Once the skin class is chosen, the arrowSkin member (from Step 3) is set with an instance of this class. Now it is either a graphic skin or a ProgrammaticSkin. If the latter you must set the styleName of the skin to be this (or some other object instance which holds the styles). If you don't do this, the ProgrammaticSkin will fail when it uses getStyle().
You can size the skin at this step IF you know the size. If your skin is going to occupy the entire component's space, you can set it within updateDisplayList() (see Step 5).
Finally you add the skin as a child of the component. Note that you have to cast the skin as a DisplayObject since addChild does not accept IFlexDisplayObject parameters.
Step 5: Position (and optionally, size) the skin in updateDisplayList():
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList( unscaledWidth, unscaledHeight );
// position the arrowSkin
_arrowSkin.move( 10,10 );
Here the arrowSkin is moved into position. If you were to have a skin that required it to be sized, then you can do that too using skin.setActualSize( width, height ) where the width and height might be unscaledWidth, unscaledHeight or some derivative of those values.
That's all you need to do to use a skin in your component. Notice that none of the component's look has been done by the actual component code - it is all done by the skin. This gives your component a tremendous amount of flexability in how it is presented, not in how it behaves.
The Skin Itself
The CycleSelectArrowSkin is one of the files available in the download with this article. Here are some of the highlights:
public class CycleSelectArrowSkin extends ProgrammaticSkin
The class extends mx.skins.ProgrammaticSkin which extends flash.display.Shape. That's because skins should be very light-weight and be limited to just presenting graphics. However, one of the most powerful properties of Flex and the Flash Player is its flexability. You do not have to make your skins extend ProgrammaticSkin. You can use any class which implements the IFlexDisplayObject interface.
Since skins are so lightweight there isn't much else they do except override updateDisplayList():
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList( unscaledWidth, unscaledHeight );
var color:Number;
switch( name )
{
case "arrowSkin":
color = getStyle("arrowColor");
if( isNaN(color) ) color = getStyle("themeColor");
break;
case "arrowDisabledSkin":
color = 0xAAAAAA;
break;
}
drawArrows( graphics, color );
}
In this function, the skin's name is used to determine its color. If the skin is the "arrowSkin" then the color is extracted from the "arrowColor" style. If that was not defined, then the skin's color defaults to the "themeColor".
Using the themeColor as a default - whether it is the actual color or a darker or lighter version (see mx.utils.ColorUtil class) - is a good idea since that color flows nicely with the style sheet and theme idea.
Once the color is chosen the skin is drawn. The drawArrows function is the same as it was before:
private function drawArrows( g:Graphics, color:uint ) : void
{
g.clear();
g.lineStyle(0, color, 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);
}
Summary
That's all there is to skinning components:
- Figure out what you want to skin,
- extract the drawing into a class based on ProgrammaticSkin,
- add the skin as a style to your component,
- create an instance of the skin, and position it.
You should now be able to make reusable components for all of your Flex projects.
Posted by pent at 02:35 AM | Comments (5)
December 13, 2007
Flex 3 Beta 3 Available
The third and final public beta releases of Flex 3 and Adobe AIR are now available for download on Adobe labs. These releases are focused on quality and performance, resolving numerous bugs from beta 2. This is your final chance to provide feedback on the release before launch, so please take this opportunity to take a final, thorough look. You can download the final beta releases of Flex 3, Flex Builder 3, and Adobe AIR on Adobe Labs at: http://labs.adobe.com/. Please also be sure to check out BlazeDS, the newest open source Remoting and Messaging project from Adobe.
Posted by pent at 08:20 AM
November 29, 2007
PopUp, Can You Hear Me?
Communicating with a PopUp is a common task. This article explores how to do that.
The first example shows how to use Alert to pose a question and get answer.
private function showQuestion() : void
{
Alert.show("Do you like Flex?", "Question", Alert.YES|Alert.NO,this,processAnswer);
}
private function processAnswer( event:CloseEvent ) : void
{
if( event.detail == Alert.YES ) {
response.text = "Of course you do!";
} else {
response.text = "Hmm. Really? Keep reading then.";
}
}
Now that you've seen how Alert works, let's look at a more complex example. Here, a TitleWindow is used to present a choice of themes (you can see one like it in my Atmospheres AIR Music Player). When the user picks the Apply button the theme changes.
This is the code for the pop-up, ThemeChooser.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
width="338" height="252"
title="Choose Your Theme"
showCloseButton="true"
horizontalAlign="center"
verticalAlign="middle"
close="PopUpManager.removePopUp(this)">
<mx:Metadata>
[Event(name="changeTheme", type="theme.ThemeEvent")]
</mx:Metadata>
<mx:Script>
<![CDATA[
import theme.ThemeEvent;
import mx.managers.PopUpManager;
private function dispatchThemeChoice( themeName:String ) : void
{
dispatchEvent( new ThemeEvent(themeName) );
PopUpManager.removePopUp(this);
}
]]>
</mx:Script>
<mx:VBox horizontalAlign="left" verticalGap="17">
<mx:RadioButton label="Adobe Red" click="dispatchThemeChoice('adobeRed')"/>
<mx:RadioButton label="Orange Neon" click="dispatchThemeChoice('orangeNeon')"/>
<mx:RadioButton label="Blue Wave" click="dispatchThemeChoice('blueWave')"/>
</mx:VBox>
</mx:TitleWindow>
Notice that when a RadioButton is clicked, an event is dispatched. The event is a
ThemeEvent - a custom event type. This is the code for the event, ThemeEvent.as:
package theme
{
import flash.events.Event;
public class ThemeEvent extends Event
{
public static const CHANGE_THEME:String = "changeTheme";
public function ThemeEvent(themeName:String, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(CHANGE_THEME, bubbles, cancelable);
this.themeName = themeName;
}
public var themeName:String;
}
}
This is the code for the main application to demonstrate it:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" height="271" width="532"
borderStyle="solid"
borderColor="black"
borderThickness="1">
<mx:Script>
<![CDATA[
import theme.ThemeEvent;
import theme.ThemeChooser;
import mx.managers.PopUpManager;
import mx.core.IFlexDisplayObject;
private function showThemeDialog() : void
{
var pop:IFlexDisplayObject = PopUpManager.createPopUp(this, theme.ThemeChooser, true );
pop.addEventListener( ThemeEvent.CHANGE_THEME, selectTheme );
PopUpManager.centerPopUp(pop);
result.text = ""; // reset
}
private function selectTheme( event:ThemeEvent ) : void
{
result.text = event.themeName;
}
]]>
</mx:Script>
<mx:Button x="24" y="27" label="Select Theme" click="showThemeDialog()"/>
<mx:Label x="24" y="57" fontWeight="bold" color="#FFFFFF" id="result"/>
</mx:Application>
I think this is the easiest solution: a custom event. The event contains all of the information from the pop-up and lets the pop-up disappear without having to hang around while you get information from it. If you have a lot of data (e.g., a user's profile) consider creating a class to represent it and have the custom event contain an instance of the class. This also makes it easier to change the information later.
Both of these examples use window-type classes as the base for the pop-ups. But you can use any component for a pop-up. Here's a pop-up that's a shape with text; it has no buttons nor a frame. Click it make it go away. While not very intuitive, it does make the point.
Download Files
You can download the code for this specialized pop-up here.This zip file is a Flex Builder 3, Beta 2, project archive. If you do have Flex Builder 3, Beta 2, you can import the source files directly from the zip.
When you use a modal pop-up, which is what all these examples have been so far, you'll notice that the background becomes lighter and blurred. This is governed by styles on the Application which you can change. You can change the color and amount of blur. You can even get rid of both.
Mode-less pop-ups are common as floating tool bars. There's no difference in how you make them nor interact with them, just in how you present them. Using a mode-less pop-up enables the user to continue working with the application while the pop-up is still present.
I hope this gives you some ideas in case you need to pop-up dialogs or warnings. Be unconvential and use events to communicate the result of the pop-up back to the main application.
Posted by pent at 11:52 AM | Comments (2)
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 (4)
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)
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)
June 12, 2007
Adobe® Flex™ 3 Public Beta and Adobe® AIR™
The first public beta release of Flex™ 3 is now available on Adobe labs. This major functional release adds rich new UI capabilities, enhanced developer productivity, desktop deployment and enterprise testing and performance profiling tools. The Flex 3 public beta also marks the first major deliverable for the open source Flex project, beginning the availability of nightly builds, a public bug base and roadmap.
Beta 1 is an early preview of the Flex 3 release. The goal of the release is to give the community an opportunity to provide feedback on the feature design and to help identify bugs and missing capabilities. Not all of the planned features have been implemented, and you should expect to find significant bugs, so be sure to save and back up your work often.
Some of the new major features include:
- Built-in support for the new Adobe Integrated Runtime (Apollo)
- code re-factoring
- memory and performance profilers
- SWF file size reduction using a persistent framework cache in Flash Player
- advanced datagrid
- CS3 and Flex integration
- web service introspection
Get the complete details and download the beta on Adobe Labs at: http://labs.adobe.com/technologies/flex.

The Adobe Integrated Runtime - AIR™ - code named Apollo - is now available on Adobe labs. Using AIR, developers can use their web skills and tools (Flex Builder) to builld desktop applications that fit seamlessly with web applications. AIR applications can contain an integrated HTML control for displaying web pages and interact with those pages using Flex controls and events. AIR applications also have the ability to use system resources, such as the local file system.
Get complete details on Adobe Labs at: http://labs.adobe.com/technologies/air
The AIR SDK is included with the Flex 3 Beta; all you need is the AIR player.
Posted by pent at 12:28 PM | Comments (1)
Adobe® Flex™ 3 Public Beta and Adobe® AIR™
The first public beta release of Flex™ 3 is now available on Adobe labs. This major functional release adds rich new UI capabilities, enhanced developer productivity, desktop deployment and enterprise testing and performance profiling tools. The Flex 3 public beta also marks the first major deliverable for the open source Flex project, beginning the availability of nightly builds, a public bug base and roadmap.
Beta 1 is an early preview of the Flex 3 release. The goal of the release is to give the community an opportunity to provide feedback on the feature design and to help identify bugs and missing capabilities. Not all of the planned features have been implemented, and you should expect to find significant bugs, so be sure to save and back up your work often.
Some of the new major features include:
- Built-in support for the new Adobe Integrated Runtime (Apollo)
- code re-factoring
- memory and performance profilers
- SWF file size reduction using a persistent framework cache in Flash Player
- advanced datagrid
- CS3 and Flex integration
- web service introspection
Get the complete details and download the beta on Adobe Labs at: http://labs.adobe.com/technologies/flex.

The Adobe Integrated Runtime - AIR™ - code named Apollo - is now available on Adobe labs. Using AIR, developers can use their web skills and tools (Flex Builder) to builld desktop applications that fit seamlessly with web applications. AIR applications can contain an integrated HTML control for displaying web pages and interact with those pages using Flex controls and events. AIR applications also have the ability to use system resources, such as the local file system.
Get complete details on Adobe Labs at: http://labs.adobe.com/technologies/air
The AIR SDK is included with the Flex 3 Beta; all you need is the AIR player.
Posted by pent at 12:28 PM | Comments (1)
June 07, 2007
Flex, Apollo, and Flash CS3
The Flex/Flash/Apollo Project
I've been at my latest project for a while, writing bits and pieces of it when I've gotten a chance. Since the Flash CS3 Flex Component kit was announced I've wanted to build something that used it. I had the idea of making an Apollo application to show that the kit wasn't just for Flex. Then I had a better idea: write an application for both Apollo and Flex, using Flash CS3 Flex Component Kit as well as a Flex Library Project to tie everything together.
I made what is perhaps a simple or silly application, but it does show how to do a number of different things. I call it the Flickr Scrapbook. The idea is that you make a scrapbook for your friends and family using photographs from Flickr - ideally your own photos, but anything public can be displayed. Each page can hold multiple photographs with captions and notes can be added as well. The pages and text can be colored, the photographs rotated and resized, and the whole thing can be displayed on a web page using Ely's FlexBook control.
Scrapbook Editor |
![]() |
Scrapbook Viewer |
![]() |
The project is two applications. One is written for Apollo and is the Scrapbook Editor. The other is written for Flex and is the Scrapbook Viewer. It seemed appropriate that an editor would be written as an Apollo application because you may want to save your work onto your local machine. The viewer would of course, be on a web page so Flex was the obvious choice. I also created a Flex Library project to keep the common classes they both use. I used Flash CS3 to make skins and components that would be difficult in Flex alone.
You can view a couple scrapbooks I made by following these links:
If you have the Apollo runtime (labs.adobe.com/apollo) you can download the Scrapbook Editor and try it out:
(note: you may be asked to save the file as a .zip - do not do that - rename the file with a .air extension)
Disclaimer
Aside from the usual, "use at your own risk, this is not supported by Adobe, no animals were harmed in the making of this application, etc. etc." disclaimers, I have to emphasize that I am NOT a developer. So my architecture may not be what you are used to. I know I didn't use enough interfaces to make software gurus swoon, but I think there is enough substance to at least give you ideas and to show you how to do things with Flex.
Source Code
The link here will download a zip file. The zip file contains everything: the source to both projects, the Flash CS3 source, and the Scrapbook Flex library project. You should be able to import the projects right into Flex Builder 2. If you have later version of Flex Builder, create new projects and just import the source files.
Bits and Pieces
- Use these two applications as a resource. Here are some things you'll find as you explore them:
- Reading and Writing Files using the Apollo File class. This class is so cool. It is cross-platform and easy to use.
- Making data service calls with HTTPService. I used my own Flickr API, but the one from Adobe Labs is cool too. I would have used that one but I had already started down my own path.
- Displaying Images. Using the Image tag is relatively straightforward. But mostly you'll want to show something while the image is loading and then switch to the image once it has loaded. I used Alex Uhlmann's "Flip" effect to spin the images into view.
- Using States and Transitions. This is one of the neatest features of Flex. I use states in several places, but if you start the Editor and start selecting things like photographs, notes, and the cover page, you'll see the property panels slide in and out. Hiding and showing the property area in the Editor is also a state change.
- Using Effects. As I just mentioned, I use effects to show the images, to switch property panels, among other things.
- Using Drag and Drop with the DragManager. Components like the DataGrid and Tree have drag and drop capability built in. I show you how to use it when that isn't available - just drag a photograph onto a scrapbook page and see.
- Making Flex Components and Skins with Flash CS3. Finally, Flash is in its rightful place along side Flex. I use the Flex Component Kit for Flash CS3 two ways: to make actual components (eg, the handle to hide and show the property area) and skins (most of the buttons).
- Creating and Using Custom Events. You can't write a decent application without custom events. And they are pretty easy, too.
- Using Cascading Style Sheets. I tried to put most of the styles into an external style sheet.
- Interaction Using the Mouse: Drag, Resize, and Rotate. Dragging things around the screen is very helpful with an editor. I show you how to do that along with resizing and rotating the photographs - using effects no less.
- Using Embedded Fonts. You must use embedded fonts if you want to scale text or modify it in any graphical way.
- Creating Components using ActionScript. Laying out your application using MXML tags is great, but it makes your application static. You'll need to know how to create and add components while the application is running. An example is creating a new photograph on a scrapbook page.
- Deploying Flex Applications to a Web Server. Once you've made your scrapbook file you need to put it to your webserver for people to see.
- Using a Custom HTML Wrapper. The Scrapbook Viewer uses a custom HTML wrapper which takes reads the scrapbook file name from the URL (query parameter) and passes it to the Flex application.
- Subclassing Components and Using Interfaces. You need to do this to get mileage out of your components. No need to write everything twice when you can extend something.
The majority of these items are found in the Scrapbook Editor. The Srapbook Viewer makes use of a few of the techniques and if you are interested in Ely's FlexBook, the Scrapbook Viewer code shows how to use it.
Here's an example of what you'll find in the code: I created the ActionScript class Photograph to encapsulate all the things a photograph on a scrapbook page can be. The Photograph class has properties such as the URL to the image on Flickr, the caption with the photograph, its rotation, its placement on the page, and so forth. However, that's not enough for either the Editor or the Viewer. In the Editor I want you to be able to select a photograph and use the mouse to resize it, rotate it, and delete it. In the Viewer I want you to click it to enlarge it.
To do those things I put the Photograph class in the Flex Library project to be used as the base class for both applications. Then I created the EditorPhotograph class for the Editor application and added the extras (resizing, rotation, etc). I did a similar thing with the Viewer.
There is also an example of an interface. A page in the scrapbook can hold two types of objects: Photographs and Notes. I wanted to write code as cleanly as possible and not be burdened with figuring out what type of object it had. This is a good place for an interface and I created the IScrapbookItem interface (also in the Flex Library project). Both Photographs and Notes implement it. The Editor extends that with IEditorItem as its needs a few extra things.
Scrapbook Editor Instructions
When you start the Scrapbook Editor you'll see two main areas: the one on the left is where your Flickr search results appear; the area on the right is where you build the scrapbook, page by page.
Begin by entering some tags (separated by commas) into the search box at the top left. Try simply miami beach and press Enter. After a bit of searching a page of images will appear.
![]()
Now give your scrapbook at title. Just enter the new title into the text box below the cover page.
You need a scrapbook page to place photos, so use the large green plus-sign above the scrapbook area to add a page. The page will be placed after the page you are viewing.
Now you can drag photos over to the page and drop them in place. The photos will become selected with the non-selected photos dimmed.
![]() |
The red x deletes the photograph. Drag the green arrow to resize the photograph. Drag the blue arrows to rotate the photograph. |
When a photograph is selected you can change its properties. Use the controls on the photograph to delete it, resize it, or rotate it. Below the page are other properties.
![]() |
You can change the photograph's caption, remove the drop shadow, change the matte color, text color, and so forth. |
Click on the background of the page to de-select the photograph and the properties change to allow you to work with the page itself.
![]() |
You can change the page's color and also add notes which you can position around the page by dragging them with the mouse. |
Continue to add pages and photographs. When are ready to save your work, use the File menu above the scrapbook. Select Save, locate a place, and give your file a name; the file's name will be changed to add .xml as the extension.
![]() |
New will make a new, empty scrapbook. Open will open an existing scrapbook. Save and Save as save the current scrapbook. Print prints the scrapbook. |
Deploying the Scrapbook
When you are ready to show the scrapbook to the world, copy the scrapbook .xml file you made, along with the contents of the deployment directory, to your web server. Send a URL to your friends and family:
http://your.domain/scrapbooks/Viewer.html?book=miami_vacation.xml
Adding the query string, book=miami_vacation.xml will automatically load that scrapbook file into the viewer.
Things to To Do
As you use these applications you may find yourself asking why this or that feature is missing. Well, I wanted to get this project done so I left some things out and I wanted to give the 'interested reader' the opportunity to modify the code. Here's some items I'd like to see added and I may get around to doing them myself some day:
- You can add photographs from Flickr to your scrapbook. It would be nice to use photographs that are already on your computer, too.
- How about adding video clips? Imaging flipping to a page and there's a clip of you on the beach. Or may be not.
- It would be nice to add more properties for the title and notes fields such as size and font family.
- It would be nice to rotate and resize the notes on the scrapbook pages.
- You should be able to add textures to pages. Actually, some of the code is already there and needs to be finished.
- You should be able to add photographs and additional text to your scrapbook cover.
- It would be nice to add gradient fills to the scrapbook cover; may be the pages, too.
- It would be cool to have transparent pages. Check out Ely's FlexBook demo and the medical text book example.
- It would be nice to view thumbnails of the scrapbook pages in the editor so you could re-arrange them (or delete them).
- Right now in the Scrapbook Viewer, clicking on a photograph zooms it up on the current page. It would be nice if the photograph spanned the pages and could become larger.
This list could probably go on and on. But you get the idea - even if you don't want a scrapbook editor or viewer, this should provide you with a good opportunity to explore and try some things out. I hope the comments in the code are helpful enough to guide through these applications.
Summary
The time is right to put these technologies together. You can really make the experience matter.
Posted by pent at 09:22 PM | Comments (16)
April 27, 2007
Example of the Flex Component Kit for Flash CS3
I've now had a chance to use the Flex Component Kit for Flash CS3. It worked as advertised for me. Check out Glenn's presentation on the Adobe Labs page. If you are interested in this example, you will need to follow the instructions given on that page to upgrade Flex 2 and get the Kit for Flash CS3. Both are pretty easy to install.
I'm going to use the ball-and-star Flash SWF from the previous examples. This really shows how far things have come. When you compare this to the first article using ActionScript 2 SWFs with Flex 2, you'll see what I mean.

Here's how I went about the process of using the Kit. I started with the same FLA I used in the last article.
Create a New Flash CS3 Document
It's important to create a new Flash CS3 document. If you want to use symbols from another Flash 8 (or earlier) document, copy them to the new document's Library. That's what I did with the example and called it star_and_ball.fla. I copied over the Star and Planet symbols.
You can only use symbols with the Flex Component Kit. Since my previous example had all of the tweens and ActionScript code on the root timeline, I needed to create a new symbol for them in the new document. I created a new, plain, MovieClip symbol which I called StarAndBall (yes, it should have been StarAndPlanet). I placed the Star and Planet symbols into the new StarAndBall symbol in separate layers and copied the guide layer as well and the tween.
Note: Symbols should have (0,0) as their registration point. I made sure that my Star and Planet symbols were positioned so that as the Planet orbits the Star it all stays within these boundries. However, you can also add a special boundingBox to your symbol and Flex will use that for your component's size. You can read about that in the documentation.
Creating the Flex Component
Once the symbol was working I selected it in the Library. Then I picked the new command, "Make Flex Component" from the Commands menu. Several things happened:
- Since my movie wasn't set to 24fps, I was asked if I wanted to change it to 24fps. I responded Yes (see documentation for explanation).
- Then the output window showed that UIMovieClip was added to my Library and that my symbol, StarAndBall, was ready for export.
That's really all there was to it. If you looked at the symbol's properties before doing this you would have seen that it didn't have a linkage name. Now, you'll see that it has an ActionScript class (more on that later), that its base class is UIMovieClip, and that it is being exported.
Publish
You must publish your movie. This not only creates the SWF, but it also creates a SWC. Since my file is named star_and_ball.fla, publishing created star_and_ball.swf and star_and_ball.swc. When using the Flex Kit, ignore the SWF. Maybe a future version will even let you avoid creating the SWF, but for now just ignore it.
Using the Flash Component in Flex
Being able to pick any MovieClip symbol in your Flash libary and selecting a command to turn it into a Flex component was easy. Now the fun part. I created a new Flex project and opened its Project Properties dialog. I then went to the Build Path and selected the Library tab. Then I picked "Add SWC" and browsed to the star_and_ball.swc and brought it in.
Flex now believes that star_and_ball.swc contains a true Flex component named StarAndBall. To use I started typing:
<Star
Flex Builder's code assistant brought up the StarAndBall class, so I selected it and <local:StarAndBall was inserted. I closed the tag and ran my application. The Flex application ran and the planet was orbiting the star.
At this point you probably think this may be no better than just using SWFLoader. That would be less steps, but did you see how Flex Builder found the Flash symbol as a class? That's due to UIMovieClip in the SWC, making the symbol a true Flex component.
Going For Objects
In the past examples you could control the Flash SWF (stopping the ball from orbiting, scaling and rotating the star). In the first example it was all controlled by LocalConnection. In the second example (just a few days ago, really), you can directly invoke functions on the root timelime.
Using the Flex Kit also allows you to invoke methods, not on the root timeline, but on the symbol itself, just like any Flex component.
Going backt to the Flash CS3 document, star_and_ball.fla, I opend the properties for the StarAndBall symbol. You can see that there is a class named: StarAndBall. If I click on the pencil (edit) button, I'm told that the class does not exist but one will be generated for me in the SWC. Very nice. But you can make your own, too.
I created a class, StarAndBall.as and used the root timeline functions as class methods:
package {
import flash.display.MovieClip;
import mx.flash.UIMovieClip;
public class StarAndBall extends UIMovieClip
{
public function StarAndBall():void
{
}
public function rotateStar( angle:Number ) : void {
star_mc.rotation = angle;
}
public function zoomStar( factor:Number ) : void {
star_mc.scaleX = factor;
star_mc.scaleY = factor;
}
public function stopPlanet() : void
{
stop();
}
public function resumePlanet() : void
{
play();
}
}
}
Hmm, very similar to Flex component code. Of course, this is ActionScript 3, so you have packages and public functions and import statements. Now when the FLA is published, my class gets put into the SWC.
Back to Flex
In the Flex application I gave the component a name: <local:StarAndBall id="star" />. I added a button to pause the orbit of the ball/planet and made the click event handler invoke the stopPlanet() method of the StarAndBall class:
star.stopPlanet();
Flex Builder was happy to code-assist me with this one, too. When I typed the period, Flex Builder showed me all of the possible properties and methods, stopPlaying() being one of them!
Now that interacting with the Flash symbol as a bonafide Flex component works, I added the Pause/Play button and Slider controls for rotation and scaling. The event handlers for those controls directly invoke the methods on the Flash component. For example, the rotation HSlider does this:
star.rotateStar(event.target.value)
I hope this gives you some idea of the possibilies with Flash CS3 and Flex. Read the information on the Flex Connectivity Kit for Flash CS3 page; there are more things you can do with this tighter integration between Flex and Flash.
Posted by pent at 02:19 PM | Comments (30)
March 29, 2007
LiveCycle Data Services Beta
Check out Adobe® LiveCycle Data Services 2.5 Beta on Adobe Labs. Here are just a few of the new features:
- Server-side PDF Generation
- AJAX Data Services
- A new SQL Adaptor
- JSP Tag Library
This is a beta test and your input is welcome and appreciated.
Posted by pent at 02:52 PM
February 28, 2007
USA Map for Flex 2.0.1
I've ported my original USA Map Project to Flex 2.0.1. You can read about it and download the code on my new (temporary) Flex 2 Blog.
You'll find a number of differences, most notably that the USA Map SWF is now an ActionScript 3 SWF, built with the Flash 9 preview. This eliminates the need for LocalConnection as the Flex app can work directly with the objects in the Flash movie.
Posted by pent at 03:00 PM
November 10, 2006
Example of Flex 2 Coldfusion Connectivity
Many months ago I wrote this sample application to explore the new connectivity package for Flex 2 and Coldfusion 7.0.2. It was a lot of fun and a great learning experience. I've taken this long to post it because there isn't any way to run it from here since it requires Coldfusion and a database. This blog doesn't have access to that and my personal ISP does not have Coldfusion.
So I've decided to just put up some pictures, explain the application, and let you download it and run it for yourselves.
What You'll Need
- Flex 2 (Flex Builder 2 is needed to get the full flavor of the Connectivity package)
- Coldfusion MX 7.0.2 (the 7.0.2 is critical)
- MySQL (or some database that works with CF)
In addition to the source and assets, the download contains a readme.txt, instructions.rtf, and in the sql directory, the schema for creating the database.
Mystic Support
Back when the CF Connectivity package was in development it was code-named Mystic and I've called my program Mystic Support. Since I'm a technical support engineer, I decided to put together my vision of what a support engineer needs to open new cases (tickets) and track their progress. We use a similar tool internally, but it isn't written in Flex :-(
The application is pretty simple and assumes that every support engineer is assigned one or more customers that they work closely with (modeled on Adobe's Gold and Platinum support plans). A customer can have zero or more tickets open at any given time. A ticket has some details (which product, version, description, etc.) and zero or more notes that track the progress of the case from start to finish.
At any given time the support engineer can see what tickets they have open and can easily open a new ticket for any of their customers.
The program also assumes that some administrator has populated the database with engineers and customers and made the relationship between them (assigning customers to engineers). This might be something you can do as an exercise.
When the program first starts you'll see the inital screen to let you log in. There's no security here - you just pick your name from the list. Should this be the first time the program is used, there may be a wait as Coldfusion and the database spin up.
Figure 1: Initializing |
Figure 2: Select an Engineer |
![]() |
![]() |
Once an engineer is selected, their customers and tickets are fetched and displayed in the panels on the left.
Figure 3: Open tickets and customers
To work on an open ticket, the engineer selects it from the list. This causes the ticket's details to be fetched and a ticket form slides out (I used a Move effect).
Figure 4: Editing a ticket
Coldfusion Connectivity
The list of engineers that is presented is read from the database. Rather than covering all the details of the application in this blog entry, I'll focus on this one detail, but it should give you an idea of how everything works.
I started with the database table, engineers. The table is pretty simple. Here is what it looks like using the RDS Viewer in Flex Builder 2.
Figure 5: The Flex Builder 2 RDS viewer
To make the application work I needed CFCs and ActionScript classes. Not being a wiz at CF (which proves anyone can use this stuff), I right-clicked the table in the RDS Viewer and selected the CF wizard.
Figure 6: The Coldfusion CFC Wizard
The wizard constructed the CFC for me. Notice that it actually created 2 CFCs: one represents an "engineer" as an "object". The other CFC provides an interface (gateway) with functions to get all of the engineers, get a specific engineer, update an engineer, or delete an engineer. How cool is that?
Once I had the CFC created I wanted a corresponding ActionScript object. So I right-clicked the CFC and picked the CF wizard that generates an ActionScript object. Here is the class it created:
Figure 7: The generated CFCs and ActionScript class
| The engineers_gateway.cfc | The engineers.cfc |
![]() |
![]() |
| The Engineers.as ActionScript class | |
![]() |
Notice the [RemoteClass] metadata - it names the CFC. This is key to the whole thing. When the program makes a RemoteObject call to the CFC (see figure 8), the CFC is set to return an Array of engineering CFC structures. The new 7.0.2 AMF gateway in Coldfusion will package them up into a binary format with clues as to what they are.
Figure 9: The Flex RemoteObject
When the data is received by the Flash Player, the clue for the CFC structure is matched with the RemoteClass metadata and rather than plain Objects, com.adobe.actors.Engineer objects are created with their properties populated from the data.
Figure 9: Flex Builder 2 debugging showing the contents of the returned result
Figure 9 shows what this looks like in the Flex Builder 2 debugger perspective. The program has a break-point in the result handler of the RemoteObject. You can see that when the event.result is expanded, it is in fact, an Array of Engineers.
Summary
You can whip up a Flex application fairly quickly using the Coldfusion Connectivity add-in for Flex Builder 2. Once you have your database tables in place, the CF wizards can create your CFCs and ActionScript classes.
In the code for Mystic Support you'll see that I have condensed the 'gateway' CFCs (there would be one for each of the structure CFCs) into a single file. That's another option you have: the ability to edit the files and use them as you please. After I worked with the code enough, I tossed out all of the functions I wasn't going to use and repacked them into a more efficient bundle.
If you have Coldfusion and want to give your applications some FLEXappeal, give the Coldfusion Connectivity package a try.
Posted by pent at 02:07 PM
November 07, 2006
Tree Drag and Drop Part 2
In Tree Drag and Drop Part 1 I covered how to drag items within the Tree control and from outside of the Tree control onto the control. In this article I cover how to drag items from the Tree.
To drag items from a Tree you have to set the Tree's dropEnabled property to false and its dragEnabled property to true:
<mx:Tree x="34" y="81" width="181" height="189"
dataProvider="{treeData.node}"
labelField="@label"
dropEnabled="false"
dragEnabled="true"
dragMoveEnabled="false"
/>
For this example, suppose the data in the tree represents US states, cities in those states, and restaurants within those cities. Further, suppose the target of the drop is a DataGrid which is defined like this:
<mx:DataGrid x="291" y="81" height="189"
dataProvider="{dataGridProvider}"
dragEnter="onDragEnter(event)"
dragOver="onDragOver(event)"
dragDrop="onGridDragDrop(event)"
dragExit="onDragExit(event)">
<mx:columns>
<mx:DataGridColumn headerText="Label" dataField="label"/>
<mx:DataGridColumn headerText="Type" dataField="type"/>
</mx:columns>
</mx:DataGrid>
As with any drag-and-drop operation, the event handlers are the key to making it work successfully.
The dragEnter event (on the DataGrid) determines whether or not the drop will be processed. In this example, only tree nodes which are restaurants will be accepted by the DataGrid. A typical tree node would look like this:
<node label="Lobster Pot" type="restaurant" />
where the @type attribute can be used to determine if the items being dropped are acceptable. The dragEnter event can be defined as:
private function onDragEnter( event:DragEvent ) : void
{
if( event.dragInitiator is Tree ) {
var ds:DragSource = event.dragSource;
if( !ds.hasFormat("treeItems") ) return; // no useful data
var items:Array = ds.dataForFormat("treeItems") as Array;
for(var i:Number=0; i < items.length; i++) {
var item:XML = XML(items[i]);
if( item.@type != "restaurant" ) return; // not what we want
}
}
// if the tree passes or the dragInitiator is not a tree, accept the drop
DragManager.acceptDragDrop(UIComponent(event.currentTarget));
}
If the control initiating the drag is a Tree control, then the dragSource is examined to see if holds any "treeItems" and if they are all of @type "restaurant". Should the test succeed, the DataGrid (the event.currentTarget) accepts the drop.
The dragOver event handles the feedback which the user is moving the dragProxy:
private function onDragOver( event:DragEvent ) : void
{
if( event.dragInitiator is Tree ) {
DragManager.showFeedback(DragManager.COPY);
} else {
if (event.ctrlKey)
DragManager.showFeedback(DragManager.COPY);
else if (event.shiftKey)
DragManager.showFeedback(DragManager.LINK);
else {
DragManager.showFeedback(DragManager.MOVE);
}
}
}
When the dragInitiator is the Tree, the feedback is always set to COPY. You can of couse, set the feedback to anything you like, but for the sake of the example, the feedback is for a copy operation so the information does not have to be deleted from the Tree.
The dragDrop event on the DataGrid handles the release of the mouse over the DataGrid. The data is examined and then added to the DataGrid.
private function onGridDragDrop( event:DragEvent ) : void
{
var ds:DragSource = event.dragSource;
var dropTarget:DataGrid = DataGrid(event.currentTarget);
var arr:Array;
if( ds.hasFormat("items") ) {
arr = ds.dataForFormat("items") as Array;
} else if( ds.hasFormat("treeItems") ) {
arr = ds.dataForFormat("treeItems") as Array;
}
for(var i:Number=0; i < arr.length; i++) {
var node:XML = XML(arr[i]);
var item:Object = new Object();
item.label = node.@label;
item.type = node.@type;
dataGridProvider.addItem(item);
}
onDragExit(event);
}
Since it is possible that another control has initiated the drag-drop, the dragSource is examined and the treeitems extracted. Then a loop is created to make new items to add to the DataGrid. Finally, the onDragExit method is called:
private function onDragExit( event:DragEvent ) : void
{
var dropTarget:ListBase=ListBase(event.currentTarget);
dropTarget.hideDropFeedback(event);
}
This function just makes sure the visual feedback is removed.
Dragging from the Tree is pretty easy - just examine the dragSource for "treeItems" and handle them according to what behavior your application should have. In this example, only"restaurant" type tree nodes are allowed to be dropped onto the DataGrid and, by being handled early in the DragEnter method, eliminates the need to test the value again in later steps.
Posted by pent at 02:27 PM
November 06, 2006
Filtering Collections
Example
This article is a basic introduction to Collection filtering.
This example uses 2 ComboBoxes. You may have seen ComboBoxes like these on automotive web sites. One ComboBox contains a list of makes (Ford, Kia, Volkswagen, etc.) and the other ComboBox contains a list of models (Mustang, Passat, A4, etc.). The second ComboBox however, only displays models which come from the maker choosen in the first ComboBox. In other words, the models are filtered based on the selection of the make.
| Figure 1: The Make ComboBox: | Figure 2: The Model ComboBox: |
![]() |
![]() |
Run the Example
Suppose you have a database that you query for all of the auto makers and all of the models. Normally you might get just the makers and fill the first ComboBox. When the user picks a make, another query is made for all of the models that match the maker. In this example, ALL of the models are returned, each keyed with their maker. Something like:
{make:"Ford", model:"Mustang"},
{make:"Ford", model:"Fusion"},
...
{make:"Volkswagen", model:"Beetle"},
{make:"Volkswagen", model:"Passat}
Suppose the data were assigned to ArrayCollections when retrieved from the server:
[Bindable] private var makeCollection:ArrayCollection;
[Bindable] private var modelCollection:ArrayCollection;private var queryResultHandler( event:ResultEvent ) : void
{
makeCollection = new ArrayCollection( event.result.makes );
modelCollection = new ArrayCollection( event.result.models );
}
and these Collections are the dataProviders for the ComboBoxes:
<mx:ComboBox id="makeCombo" prompt="Select" dataProvider="{makeCollection}" labelField="make" />
<mx:ComboBox id="modelCombo" dataProvider="{modelCollection}" labelField="model" />
At this point the modelCombo shows ALL of the models - no matter what is selected in the makeCombo. This is not what you want. Initially, without anything selected in the makeCombo, the modelCombo should be empty.
filterFunction
To do this you need two things: filter criteria and a filter function.
The filter criteria for this example is simple: the selection of the makeCombo. The filter function is almost as simple:
private function filterByMake( item:Object ) : Boolean
{
return( makeCombo.selectedItem.make == item.make );
}
Assuming that the item parameter in the filterByMake function is an element of the modelCollection, this function returns true whenever the item's make property value matches the makeCombo's selectedItem's make property value.
The filterByMake function needs to be assigned to the modelCollection as its filterFunction:
modelCollection.filterFunction = filterByMake;
The final piece is triggering the filter by calling the Collection's refresh() method:
modelCollection.refresh();
Putting it all together
Now that you have the parts, here is how it all fits together with some missing statements to make it work.
[Bindable] private var makeCollection:ArrayCollection;
[Bindable] private var modelCollection:ArrayCollection;private function queryResultHandler( event:ResultEvent ) : void
{
makeCollection = new ArrayCollection( event.result.makes );
modelCollection = new ArrayCollection( event.result.models );
// set the filterFunction and refresh the data so that the collection is initially empty
// (there is no selection on the makeCombo so the test fails for every item)
modelCollection.filterFunction = filterByMake;
modelCollection.refresh();
}private function filterByMake( item:Object ) : Boolean
{
var makeItem:Object = makeCombo.selectedItem;
if( makeItem == null ) return false;
return( makeItem.make == item.make );
}<mx:ComboBox id="makeCombo" prompt="Select"
dataProvider="{makeCollection}" labelField="make"
change="modelCollection.refresh(); modelCombo.selectedIndex=-1" />
<mx:ComboBox id="modelCombo" dataProvider="{modelCollection}" labelField="model" />
When a selection is made on the makeCombo, the change event handler calls the modelCollection's refresh() function and clears the selection from the modelCombo. This runs all of the modelCollection items through the filterFunction, filterByMake(), screening out all those models that do not have the same maker.
The modelCombo dropdown list is automatically updated because it is data-bound to the modelCollection.
Posted by pent at 11:29 AM
November 02, 2006
Tree Drag and Drop Part 1
This article is about dropping leaves - or rather dragging and dropping leaves.Because this is a somewhat complex topic and blog entries shouldn't be pages long, I'm breaking this topic into several entries. I'll cover dragging and dropping within a Tree control, from a Tree control to something else, and from something else onto a Tree control.
If you are using a Tree control then you've already decided what type of data to use: XMLListCollection vs. ArrayCollection (see Tree Control DataProviders). Whichever you use the Tree uses hierarchical structures. This is ideal for XML; you'll need ArrayCollections with ArrayCollections as children to provide the structure.
I mention this because if you have not used a Tree control before, get familiar with how to provide data to the Tree. Since I believe most applications use XML as the data for the Tree, these drag and drop examples will use XML.
Drag and Drop Within the Tree
It's pretty easy to enable a Tree control to allow the user to drag nodes around to re-arrange the tree. Think of a file browser where you can drag a file from one folder to another.
Set up your Tree control like this:
<mx:Tree x="162" y="122" width="279" height="278"
dataProvider="{treeDataList}"
labelField="@label"
dragEnabled="true"
dragMoveEnabled="true"
dropEnabled="true"
/>
By setting dragMoveEnabled and dropEnabled to true, the Tree's nodes can be moved around.
DragSource
Before discussing drag and drop, it is important to understand the DragSource class. An instance of this class is provided in the event (mx.events.DragEvent) argument of the drag event handlers. The DragSource contains, among other things, the control which initiated the drag operation, the formats of the data, and access to the data keyed by the format.
For example, if you drag an item from a catalog, the DragSource data could be the image of the item and the data about the item. So the DragSource would have two formats: images and items.
In these examples you will see "items" used as the name of the format to access the data in the DragSource. Flex controls such as Tree, DataGrid, and List, use "items" as the name of the format when they automatically create a DragSource.
Dragging onto the Tree
Use dropEnabled="true" to allow the Tree control to receive drag events and listen for the following events:
<mx:Tree x="162" y="122" width="279" height="278"
dataProvider="{treeDataList}"
labelField="@label"
dropEnabled="true"
dragEnter="onDragEnter(event)"
dragOver="onDragOver(event)"
dragDrop="onDragDrop(event)"
/>
The dragEnter event is dispatched when the mouse drags the drag-proxy onto the Tree. The Tree must determine if it will accept the drop or not. Just because dragEnabled has been set doesn't mean every drop has to be honored. The dragEnter event can use information in the event - such as the control that initiated the drag (called the dragInitiator) or the data being dropped.
Here is a simple dragEnter event handler:
private function onDragEnter( event:DragEvent ) : void
{
DragManager.acceptDragDrop(UIComponent(event.currentTarget));
}
The dragOver event is dispatched while the mouse is dragging the drag-proxy within the Tree's boundaries. You could ignore this event, but you can also use it as an opportunity to provide feedback to the user. For example, the node the mouse is over could be highlighted. You could even look at the data being dragged and at the node under the mouse and decide if the drop should be allowed. For example, if the Tree represents a car dealership and you are dragging a sports car proxy over the tree, you can change the drag cursor if the sports car proxy goes into the mini-van category telling the user the drop would be unacceptable there.
Here is a dragOver event handler which determines the node the mouse is over and
private function onDragOver( event:DragEvent ) : void
{
// r is the visible index in the tree
var dropTarget:Tree = Tree(event.currentTarget);
var r:int = dropTarget.calculateDropIndex(event);
tree.selectedIndex = r;
// retrieving the newly selected node, you can examine it and decide to tell
// the user the drop is invalid by changing the feedback.
var node:XML = tree.selectedItem as XML;
if( node.@type == "minivan" ) {
DragManager.showFeedback(DragManager.NONE);
return;
}
// the type of drop - copy, link, or move can be reflected in the feedback as well.
// Here the control and shift keys determine that action.
if (event.ctrlKey)
DragManager.showFeedback(DragManager.COPY);
else if (event.shiftKey)
DragManager.showFeedback(DragManager.LINK);
else {
DragManager.showFeedback(DragManager.MOVE);
}
}
The dragDrop event is dispatched when the mouse is released. The Tree can still ignore the drop here (may be the mouse was released over the mini-van category by mistake), but it is this event that copies the data being dropped into the Tree. All, none or part of the data can be used. You can create nodes or replace nodes or even make new branches - whatever is called for by the application design.
private function onDragDrop( event:DragEvent ) : void
{
var ds:DragSource = event.dragSource;
var dropTarget:Tree = Tree(event.currentTarget);
// retrieve the data associated with the "items" format. This will be the data that
// the dragInitiator has copied into the DragSource.
var items:Array = ds.dataForFormat("items") as Array;
// determine where in the tree the drop occurs and select that node by the index; followed by
// retrieving the node itself.
var r:int = tree.calculateDropIndex(event);
tree.selectedIndex = r;
var node:XML = tree.selectedItem as XML;
var p:*;
// if the selected node has children (it is type==city),
// then add the items at the beginning
if( tree.dataDescriptor.hasChildren(node) ) {
p = node;
r = 0;
} else {
p = node.parent();
}
// taking all of the items in the DragSouce, insert them into the
//
tree using parent p.
for(var i:Number=0; i < items.length; i++) {
var insert:XML = <node />;
insert.@label = items[i];
insert.@type = "restaurant";
tree.dataDescriptor.addChildAt(p, insert, r+i);
}
}
The dragComplete event is registered on the drag initiator (a List, for example) which will be invoked once the drag operation has finished - either by the drop being accepted and processed, the drop being rejected outright, or by the user dropping the object over a non-drop zone.
In this example, the only thing that's done is to clear the tree of the selection made in the dragOver and dragDrop operations.
private function onDragComplete( event:DragEvent ) : void
{
tree.selectedIndex = -1;
}
Summary
Dragging information onto a Tree control involves deciding if the drop should be accepted. This can be done at the outset of the operation in the dragEnter event or dynamically as the drag proxy is moved around the Tree control in the dragOver event.
Next time: dragging data from the Tree control.
Posted by pent at 12:06 PM
October 28, 2006
Tree Control DataProviders
Lately I've been thinking of trees. Maybe it's because it is autumn in the northern hemisphere. So it seems an appropriate time to talk about Flex Tree controls. I'm going to publish a small series on the Flex 2.0 Tree component as there is a lot of ground to cover. In this series I'll present information on Tree dataProviders, itemRenderers, and drag-and-drop.
Data Providers
The Tree uses heirarchical data. That is, data that has levels: nodes that are branches and leaves. The DataGrid, by contrast, presents non-heirarchical data as does the List control.
XMLListCollection is an excellent dataProvider for the Tree and is the most commonly used class for this purpose. You can also use ArrayCollection and I'll go into more detail on that later in this article.
XML is a good because it is, by nature, heirarchical. Consider this simple XML:
[Bindable]
var company:XML =
<node>
<node label="Finance" dept="200">
<node label="John H" />
<node label="Sam K" />
</node>
<node label="Engineering" dept="300">
<node label="Erin M" />
<node label="Ann B" />
</node>
<node label="Operations" dept="400" isBranch="true" />
</node>
You can easily see the Tree structure in that XML. But also notice something else: all of the elements in the XML are <node /> elements. Every element is the same in that it has the name "node" and it has a label; branches and leaves as well. If you need to have a node that should be a branch but has no leaves, use isBranch="true" to tell the Tree the node should be treated like a branch.
The Tree likes this very much. You can give that XML to the Tree and you will see that structure without any fuss. But is this realistic to ask that all of the nodes in the data be the same? Consider this XML:
[Bindable]
var company:XML =
<list>
<department title="Finance" code="200">
<employee name="John H" />
<employee name="Sam K" />
</department>
<department title="Engineering" code="300">
<employee name="Erin M" />
<employee name="Ann B" />
</department>
<department title="Operations" code="400" isBranch="true" />
</list>
This XML is not uniform. If you give this to a Tree component you will get the XML dumped in the control because the Tree doesn't know what to do with it without extra instructions.
This XML can be displayed in the Tree with the help of a labelFunction.
private function treeLabel( item:Object ) : String
{
var node:XML = XML(item);
if( node.localName() == "department" )
return node.@title;
else
return node.@name;
}
The labelFunction simply returns the value of the name or title attribute depending on the type of node it is.
XMLListCollection
Earlier I mentioned XMLListCollection as a good dataProvider for the Tree, but I've been using XML so far. Here is the proper way to supply the Tree with data:
[Bindable]
var companyList:XMLListCollection = new XMLListCollection( company.department );
...
<mx:Tree dataProvider="{companyList}" labelFunction="treeLabel" />
An XMLListCollection is better as a dataProvider because the Tree can manipulate it (for editing, drag-and-drop, etc.) and because changes to the XMLListCollection are reflected in the Tree. That is, if I were to change the company XML object, you would not see the change in the Tree. If change the XMLListCollection, companyList, then not only would the underlying XML be changed, but so would the Tree.
Use XMLListCollection to supply the Tree with its data; you can change the collection and both the Tree and underlying XML will get changed, too.
If you cannot supply a very uniform XML structure to the Tree, use a labelFunction (or itemRenderer, but that's coming later) to supply the label for the display.
ArrayCollection
You can also use an ArrayCollection for the Tree. You can make an ArrayCollection heirarchical by embedding one ArrayCollection inside another:
[Bindable]
private var companyData:ArrayCollection = new ArrayCollection(
[ {type:"department", title:"Finance", children:new ArrayCollection(
[ {type:"employee", name:"John H"},
{type:"employee", name:"Sam K"} ] ) },
{type:"department", title:"Engineering",
children: new ArrayCollection(
[ {type:"employee", name:"Erin M"},
{type:"employee", name:"Ann B"} ] ) },
{type:"department", title:"Operations",
children: new ArrayCollection()}
] );
With this structure you'll notice that whenever a node has children, the name of the field is children - the Tree looks for this to identify branches from leaves.
You will also need a labelFunction with this data, too, so the Tree knows what to display on each node:
private function treeLabel( item:Object ) : String
{
if( item.type == "department" )
return item.title;
else
return item.name;
}
Adding Nodes
You make changes to the Tree through the dataProvider, not through the Tree control itself. When you want to add a node you add it to the dataProvider and the Tree will be changed automatically.
To add a node to the XMLListCollection you need to have a handle on the parent node. For example, to add a new department which is a top-level node, you can do that like this:
var newNode:XML = <department title="Administration" code="500" >
<employee name="Mark C" />
</department>;
companyList.addItem(newNode);
Here is how to add a new employee to the existing Operations department:
var newNode:XML = <employee name="Beth T" />;
var dept:XMLList =company.department.(@title == "Operations");
if( dept.length() > 0 ) {
dept[0].appendChild(newNode);
}
Once you identify a specific node and have its XML equivalent, you can use the appendChild() method.
To add a node to the ArrayCollection you just append it to whatever part of the structure requires the node. Here's how to add a new department (top-level) node:
var newNode:Object = {type:"department",title:"Administration"};
var newEmployee:Object = {type:"employee",name:"Mark C"};
newNode.children = new ArrayCollection( [newEmployee] );
companyData.addItem(newNode);
Here is how to add a new employee to the existing Operations department:
var newNode:Object = {type:"employee", name:"Beth T"};
for(var i:Number=0; i < companyData.length; i++) {
var item:Object = companyData.getItemAt(i);
if( item.title == "Operations" ) {
var children:ArrayCollection = item.children;
children.addItem(newNode);
companyData.itemUpdated(item);
empName.text = "";
break;
}
}
As you can see, using an ArrayCollection to add a node is a bit more complicated than using XML.
Removing Nodes
If you know you are removing a top-level node you can do that through the XMLListCollection's removeItemAt() method - but you have to know the index of the item. In the following example, all you know is the name, "Operations", so you have to loop through the nodes and when a match is found, remove the item.
var deptTitle:String = "Operations";
for(var i:Number=0; i < companyData.length; i++) {
var item:XML = XML(companyData.getItemAt(i));
if( item.@title == deptTitle ) {
companyData.removeItemAt(i);
break;
}
}
Removing the selected top-level node little easier:
var index:Number = tree.selectedIndex;
companyData.removeItemAt(index);
Here is how to remove a leaf node:
var node:XML = XML(tree.selectedItem);
if( node == null ) return;
if( node.localName() != "employee" ) return;
var children:XMLList = XMLList(node.parent()).children();
for(var i:Number=0; i < children.length(); i++) {
if( children[i].@name == node.@name ) {
delete children[i];
}
}
The same technique applies for ArrayCollection in that you have to search for the item, but once you find it you can use the ArrayCollection removeItemAt() method since the index is always valid for an ArrayCollection.
Here's how to remove a leaf node if the Tree's dataProvider is an ArrayCollection:
var node:Object = tree.selectedItem;
if( node == null ) return;
if( node.type != "employee" ) return;
var children:ArrayCollection = node.parent().children() as ArrayCollection;
for(var i:Number=0; i < children.length; i++) {
if( children[i].name == node.name ) {
children.removeItemAt(i);
break;
}
}
t is not possible to give a Collection a node of the Tree data and have the Collection remove it - you must hunt for it and delete it yourself.
Next Time
The next article will cover drag and drop - within a tree, from a tree, and onto a tree. Stay tuned.
Posted by pent at 05:01 PM
September 01, 2006
ColdFusion Connectivity
One of the new features with Flex 2.0 is better connectivity with ColdFusion. Using ColdFusion MX with the 7.0.2 updater (or later) you can use RemoteObject to address your CFCs and exchange data structures. For example, a CFC that contains the results of query and that has functions to return the individual values (getter functions) can be mapped to an ActionScript 3.0 class with those getter functions as properties.
You can read more about it here: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=4ac796d0
As wonderful as it is, a number of people have had problems installing and getting it to work. The problem isn't when you are working locally, but later when you deploy your Flex 2 SWF to your web server and your production CF is on another machine.
If you are using Flex Builder 2, you can take advantage of the CF connectivity plugin which has wizards to create CFCs from ActionScript 3 classes or vice-versa.
One of our Flex 2 forum users, JigHead, has kindly put his experience and instructions on the Flex 2 forum to help everyone out. I've copied those instructions here:
1.) Open the servics-config.xml file in C:\CFusionMX7\wwwroot\WEB-INF\flex\
and find and edit the vale for
<endpoint uri="http://{server.name}:{server.port}{context.root}/flex2gateway/" class="flex.messaging.endpoints.AMFEndpoint"/>
to
<endpoint uri="http://localhost/flex2gateway/" class="flex.messaging.endpoints.AMFEndpoint"/>
port number is optional, 80 is default as usual.
2.) In FlexB right click your project folder and click properties > flex complier
and paste in this line in the addtional args field.
-services "C:\CFusionMX7\wwwroot\WEB-INF\flex\services-config.xml" -locale en_US.
That's it!
However I believe there are bugs in FB2 to watch for. When you change the properties and click apply. The flex build path likes to reset the output folder to
c:\cfusionmx7\wwwroot\bin which then results in your page not loading 404 error in the browser. You will need to reset the path to your projects output folder again.
These issues with remoting are the reasons why all the FLEX examples show you how do do it in the cfroot rather than in your webroot. There are other pathing issues with flexbuilder2 as well that I found but thats for another topic.
Also check out this seminar if you can: http://www.adobereg.com/flex2&coldfusion/
Posted by pent at 04:38 PM
August 07, 2006
Flex 2 Component: Masked Text Input
Don't you hate those web forms that ask for a phone number without telling you how you should enter it? You know, you enter 999 555-1212 and you get an error saying you should enter it as 9995551212. Or what about those forms that put the phone number into three fields but don't automatically skip to the next field after you've completed a field?
Flex is partially guilty of this, too. You can put up a TextInput control to accept a phone number and associate a PhoneNumberValidator with it. The user can enter the number a variety of ways: 9995551212, (999) 555-1212, and 999 555-1212 are a few of the accepted ways. OK, that's great but each person enters the number in a different way. And how is that stored in your database? Do you have code which normalizes all of the phone numbers? Or do you just plant it into the database in the way it was entered?
A masked input (called "pictures" in Cobol and PL/I) can help solve those problems. For example, suppose the user sees (___) ___-____ in an input field and then just types 9995551 but sees it being entered as (999) 555-1___, automatically skipping over the parentheses and hypen. Wouldn't that be great? And suppose you can extract the value either as 9995551212 or (999) 555-1212 and store the values consistently in your database?
I've created a new Flex 2 component which does this by extending the TextInput control and capturing the keystrokes. The component is called MaskedTextInput and here is how it works.
You can download the zip file containing the control and a sample application by clicking here.
MaskedInput
Creating this control was not that difficult. You need to intercept some keystrokes and compare them to a masking pattern. For the masks, I used # to indicate a numeric-only position; C to indicate an alphabetic that would automatically get capitalized; c to indicate an alphabetic that would automatically get folded to lowercase, and A (or a) to indicate any character. So a phone number mask can be "(###) ###-####" and a first name field can be "Ccccccccccc". You can take this code and expand on these if you like.
The tricky parts were handling Backspace, Space, Delete, Home, End, and the left and right arrow keys. To make things simple I did not allow for insertion, just overwriting. That is, if you have (999) 555-____ and you reposition the insertion point before the first 5 and then press the 6 key, the 6 is not inserted but rather replaces the 5 so you wind up with (999) 655-____ with the insertion point now after the 6.
The new control has the following properties:
inputMask: This is the mask to use, such as the phone number mask above or ###-##-#### for a US Social Security number.
blankChar: This is the character to display in the text field in place of the mask characters. In other words, the phone number appears a (___) ___-____ with the default blankChar as underscore.
defaultChar: This character is substituted for the blankChar when the text is retrieved from the control using the actualText property. For example, if you want to make sure that any # is retrieved as a zero, make the defaultChar a 0. This character is also used when the Space bar is pressed.
text: The TextInput text property is overridden to make it easier to set and get the text from the control. For example, if you have the phone input mask you can set the data as text="9995551212" and the characters will be divided out according to the inputMask and show up as "(999) 555-1212" in the control. When you use the text property to get the text, the raw data is returned, without mask or blankChar characters.
The new control also has the following event:
inputMaskEnd: This event is dispatched when the last character of the mask has been entered. You can use this to skip to the next field. If you have a Form that is made up of many MaskInput controls, this event coupled, with the code to skip to the next field, would allow users to quickly enter form data, automatically skipping from field to field.
updateDisplayList
As with most Flex 2 components, a key function that is overridden is updateDisplayList(). This function is called whenever the display should change. You might, for example, draw the background or if the component is a skin, draw the border. Flags are often used to indicate what has changed so not everything is drawn or changed each time updateDisplayList is called. For MaskedInput, updateDisplayList does three things, all dictated by flags.
If the inputMask is changed, an internal working array which is filled as the user types, has to be changed. If the text property changes it is copied into the working buffer. Finally, if the working buffer changes because the user has entered characters, the display has to be refreshed.
Keep in mind that when writing components for Flex 2, most of the work is done in or called by updateDisplayList. The other functions are generally used to set up the conditions and values for updateDisplayList to do its job.
Posted by pent at 12:11 PM
July 17, 2006
Writing Flex 2 Components
I've gone back and forth over this, trying to decide how complex to make this blog entry. The truth is, writing components in Flex 2 is pretty easy. What I was deciding between is whether or not to start with a component that does not use skins and then build up to adding skins.
I decided that skins are pretty easy (for us non-artists) so we'll make a component, "from scratch" skins and all.
You can see a running example by clicking here . Some of the gauges are connected, meaning that changing values in one changes others. The ones labeled "interactive" respond to the mouse. Yours to explore.
First, a few words about components. The Flex class, UIComponent, is the base for all components. This example component will extend UIComponent directly - hence my use of the term "from scratch". Using UIComponent makes sure that the component fits into the Flex framework and plays well with the other components.
Being part of the Flex framework insures that the component is created properly and receives events at the right time. For example, when the component is created its constructor is called, but that's not the time to make any children (the skins - needle, frame, and cover). When it is time for the children to be created, the Flex framework invokes the component's createChildren() method. But that's not when the component is visualized (or rendered). For that, the Flex framework invokes the component's updateDisplayList() method.
I'm going to use the overly simple Gauge component as the example. It is simple because there is the logic to make the gauge work plus three skins. In general, that's how components are: there is a class for the component and then skins to visualize the component. You can have many classes which make up the component, and indeed some of the Flex components do that. For the sake of brevity, I've limited this to a single class file.
|
|
|
You can think of a component like this: a core class to give the component its function (positioning the needle of the gauge and responding to events), a skin class to give the component its look, and styles to influence the skin.
The Gauge component has a class called gauge.Gauge (in the gauge package) and default skin class, gauge.skins.programmatic.GaugeSkin. There are also alternative skins in this example, too: gauge.skins.alternatives.SquareFrameSkin as well as graphic skins as PNG files.
I'm not going to get into a lot of the code; you can download that and read the comments. I am going to try to point out the important parts here.
| Download file | You will need either the Flex 2 SDK or Flex Builder 2 to use these files. You do not need Flex Data Services. |
The Gauge
The Gauge is very simple: there is the face or frame, a needle, and a cover for the needle. These are the skins. The Gauge has properties, too: minimum, maximum, and value influence the position of the needle. The Gauge also dispatches a "gaugeClick" event when the mouse is clicked on the frame somewhere.
Since the Gauge is going to follow the Flex framework, some functions need to be written:
createChildren() is where the skins are created since they are actually children of the component.
measure() is called when the Flex framework wants to know the component's dimensions.
updateDisplayList() is called when it is time to visualize the component.
In addition to functions, specifications for the styles and events the Gauge requires are also given.
Styles and Events
Before the definition of a class, you can specify that styles the class can use. This is done with the [Style] metadata. These specifications tell the compiler that it is OK for a certain name=value pair to used with the class.
createChildren
The Gauge is given the opportunity to create any child objects it needs. For example, you could add a Text component to display the current value. Or if you are writing a slider, you would want to create the thumb. The same is true for the Gauge which has its frame, needle, and needle cover. Creating the skins is pretty straightforward. Here is how the frame is created:
When you specify the skin for a component, you specify the Class of the skin, not a specific instance. This is true for graphic skins as well as programmatic skins. The gauge has some variables which hold the names of the classes and these names are intialized by the styles of the same name.
For example, to create the frame skin:
var skin:Class = Class(getStyle("frameSkin"));
var newSkin:IFlexDisplayObject = new skin();
newSkin.name = "frameSkin";
newSkin.styleName = this;
addChild(newSkin);
First the class of the skin is obtained using getStyle(). Then an instance of the skin is created using the new operator. The newSkin is given a name (which, for programmatic skins, tells it what to do), and its style is set to be that of the Gauge component itself. This is an important point. The skins need to get style information and it is easier for the Flex coder to put the style onto the component itself:
<Gauge backgroundColor="blue" ... />
The Gauge doesn't care about its background color, but the frame skin does. By setting the styleName of the skin to be the component, invoking getStyle() from within the skin's code will fetch the style from the Gauge instance.
Finally, the skin is added as a child of the Gauge.
updateDisplayList
The updateDisplayList function is one you get very familiar with. It is where "it all happens" so to speak. Many of the other functions in a component go toward setting up properties or making things that get used in updateDisplayList().
For the Gauge, updateDisplayList() is basically:
- Get the skins drawn.
- Rotating the needle.
Getting the skins drawn is really: sizing them (the frame for example, needs to be made the size of the Gauge to cover the area occupied by the control) and positioning them (the needle should be placed in the center of the control, the cover should go over the needle).
The Gauge's needle needs to be spun so that it points to a value between the minimum and maximum values. That's also a job done in updateDisplayList().
Properties
The remainder of the Gauge class is devoted to the properties, such as minimum, maximum, and value. You might think that when you set a value you go ahead and make the corresponding visual change. Not so.
The properties are initially set well before the visual aspect of the component is created. For example, if you have the value of the Gauge set to 30 but there is no needle skin created to spin to 30, then you obviously cannot change the visuals.
Normally properties simply set an internal variable and then set a flag. They also call an invalidate procedure. For instance, when the value of the Gauge is changed, you know the display list has to be updated, so invalidateDisplayList() is called. This sets a flag to the Flex framework to call the updateDisplayList() method at the proper time. In other words, when a property that affects the display is changed, it flags the change by calling invalidateDisplayList(). If a property affects the component size, invalidateSize() should be called. The Flex framework then only needs to call the update functions once.
Properties, by convention, are set and get methods that store into variables beginning with an underscore. For example, the set function for maximum sets the variable, _maximum. These are called "backing variables".
Skins
You saw that the skins are created in the createChildren() method of the Gauge. But what exactly are "skins" anyway? Skins are object that fit into the Flex framework in manner similar to that of components. A skin is added to a component as one of its children. Skins normally do not have children of their own for several reasons.
- Skins are intended to be "lightweight" because they are called upon frequently.
- Skins are normally extending the mx.skins.ProgrammaticSkin or mx.skins.Border classes. These classes do not inherit from UIComponent and thus do not support children.
You are not restricted to using ProgrammaticSkin or Border as the base classes for your skins. You can use UIComponent if you like. Just remember that the more complex the base class the more work has to be done to render the skin.
Skin Types
There are two types of skins: Programmatic and Graphic. This demonstration of the Gauge shows how to use both.
Programmatic skins are completely code-based. You use ActionScript to make the graphics. The drawing API available in the Flash Player makes it easy to draw lines and shapes (circles, rectangles, rounded rectangles, etc.) and fill them with either a solid color or gradients of color. Your artistic ability is the only limiting factor.
If you look at the gauge.skins.programmatic.GaugeSkin class you'll spot the following:
- public class GaugeSkin extends Border GaugeSkin extends Border which has the potential for a background image.
- switch( name ) in the updateDisplayList() method. Many programmatic skin classes actually draw many skins. For instance, a Button has a number of skins: when it is up, when the mouse is over it, when the mouse is press down over it, etc. You could use a different class for each skin, but since the skins are so similar, a single class is used to draw them all. In the GaugeSkin, the needle, frame, and cover are all simple in appearance, but since there is nothing else different between them, it makes since to put the 'skins' into a single class.
You should also note that the GaugeSkin class is pretty simple. The updateDisplayList() method is where all the work is done and that is mostly getting the styles set up correctly.
For example, if the GaugeSkin is drawing the frameSkin (name == "frameSkin"), then the backgroundColor, backgroundAlpha, borderColor, borderAlpha, and borderThickness styles have to retrieved from the component. Those styles that have not been set are given default values.
Drawing the Skin
Drawing the skin uses the Graphics object. The frameSkin is drawn with the following statements:
g.clear(); g.lineStyle( borderSize, borderColor, borderAlpha ); g.beginFill( bgColor, bgAlpha ); g.drawEllipse(x,y,w,h); g.endFill();
First the graphic surface is cleared; failure to do that will make your skin look like someone scribbled all over it. The line style and fill colors are set next. Notice they are set from the styles. An ellipse is drawn - it will have a border of the line style and be filled with the given color. The fill is ended because you can have many drawing commands all being filled.
Graphic Skins
A graphic skin is an image, either a PNG (my favorite), JPG, or GIF. Making a graphic skin is pretty easy:
- Start your favorite graphic editor. Fireworks 8 for instance.
- Decide how big the component should be. Say 100x100 pixels.
- Draw something in that space and save the image. It is best to use either PNG or GIF because you can have transparent areas.
- Specify the graphic as the skin. You can do this in several ways:
- Right in the tag: <mx:GaugeSkin faceSkin="@Embed('myface.png')" ... />
- As part of a style: SampleStyle { faceSkin: Embed("myface.png") }
Notice how much code in the Gauge class was changed to make use of graphic skins: none! And that's the way it should be. A user of your components should be able to switch between programmatic and graphical skins in a style sheet.
I believe using styles is far easier than coding things in-line. Styles are quick to make, find, and change. Quick enough for development, too, so there's really no excuse not to use them.
Extending the Gauge
That's really all there is to making a component: setting styles and properties, calculating values, drawing skins. This particular component, the Gauge, is mildly interesting. But suppose you wanted to have 2 needles? Do you need to write a new component?
The answer is no IF you've written your original component with extensibility in mind.
Take a look at the DoubleGauge class:
- public class DoubleGauge extends Gauge The class extends Gauge, getting the benefit of all of its styles and functions.
- protected var secondNeedleSkin The object which holds the skin for the second needle. This class is designed to allow the second needle to have its own skin, not reuse the needleSkin of the Gauge class.
- var angle:Number = calculateAngleFromValue(_secondValue); in updateDisplayList(). The calculateAngleFromValue function was written to allow an inherited class access to the same rotation calculation. If this function were not present - meaning the calculation were done in the Gauge updateDisplayListMethod() - you would have to write it all over again.
That's really all there is to making a Gauge with two needles. You can go ahead and make a Gauge with as many needles as you need.
Summary
I hope you find writing components in Flex 2 easy. Obviously this sample component was not very complex, but it did touch upon all the things which are necessary in a component:
- How the component fits into the Flex framework by providing the right functions.
- How to specify styles and how to get the skins to make use of the styles
- How to specify events, dispatch them, and handle them.
- How to write a skin class - all you need to do is make it pretty.
- How to use a graphic skin - notice that there were no code changes to use it.
- How to extend a skin and make use of its protected functions - good code reuse.
Good luck with your components. I can't wait to see what you all come up with.
Posted by pent at 11:20 AM
July 12, 2006
Using ActionScript 2 SWFs with Flex 2
Introduction
One of the benefits of the Flash Player technology is being able to write applications in the manner that best suits you. For example, you can use Flash 8 to develop high-impact animations or use Flex to build compelling business applications.
What is even better is being able to combine them together. It turns out that creating an animiation in Flash and incorporating it into a Flex application is not uncommon. And because both Flex and Flash are language driven via ActionScript, the Flex application could control the Flash animiation or vice-versa.
Doing this with Flash 8, Flash MX 2004 or Flash MX and Flex 1.5 was relatively easy. But that is not the case with Flex 2 and Flash Player 9. I'll show how to do this and I hope you see that this is in fact, a better way to do it.
As you may know, a Flex 2 SWF requires Flash Player 9. That's because Flex 2 uses ActionScript version 3 (AS3) and Flash Player 9 was developed (from the ground up, I'm told) to use ActionScript 3. ActionScript 3 with its stronger data typing and object-oriented structure provides a better platform for a higher performing Flash Player.
But as with all versions of the Flash Player, Flash Player 9 is backward-compatible. That is, it will run SWFs built for earlier Flash Player versions. But this happens differently than before. When the Flash Player 9 loads a SWF that uses ActionScript 2 (or less), it creates a virtual machine - a self-contained area which is loaded with the logic to run ActionScript 2 SWFs. We call this an AVM. When Flash Player 9 has loaded a Flex 2 SWF which then requests to load an AS2 SWF, that SWF is loaded into its own AVM. Thus the two SWFs run in different areas of the Flash Player. Different and isolated areas.
What this means is that your Flex 2 application cannot address the AS2 SWF and reach inside and reference its functions, variables, and timeline. Likewise, the AS2 SWF cannot get at anything in the Flex 2 application simply by referencing it.
The solution is to use LocalConnection - the Flash Player to Flash Player messaging system. If you haven't used LocalConnection before, take a moment and read up on it. You might get some inspiration for your next project.
To see a demonstration of this, click this link: Flex 2 and Flash Demo and be sure your browser has Flash Player 9. This will open in a new window.
Download source here
Briefly, LocalConnection lets one Flash Player send messages to one or more other Flash Players running on the same desktop. The Flash Players can be on the same HTML page, in different frames, or even in different browsers. For example, suppose you are creating an HTML page with 3 frames: a top one for a banner, a side one for a menu, and a center one for content. You want to have the banner and menu be Flash movies. You'd also like any selection from the menu SWF change the content of the banner SWF. Since these SWFs are running inside of different Flash Players, you can use LocalConnection to have the menu SWF send messages to the banner SWF.
LocalConnection is a one-way message. One SWF listens for messages that another SWF sends. If you want bi-directional communication, both SWFs need to create LocalConnections and name them uniquely.
LocalConnection also works between Flash Player 9 AVMs.
Flash
In your Flash application create a LocalConnection object and add to it functions that you want the Flex application to call. These are the 'messages' I've been mentioning.
var fromFlex_lc:LocalConnection = new LocalConnection();
fromFlex_lc.stopMe = function() { stop(); }
fromFlex_lc.playMe = function() { play(); }
fromFlex_lc.connection( "lc_from_flex" );
The stopMe and playMe functions are very simple and just stop and play the timeline. But they can of course, be much more complex. Flex is going to invoke them when the user clicks a button.
Flex
In your Flex application, create a LocalConnection object and use it to invoke the stopMe and playMe functions in the Flash SWF.
import flash.net.LocalConnection;
var toFlash_lc:LocalConnection = new LocalConnection();
<mx:Button label="Stop" click="toFlash_lc.send('lc_from_flex', 'stopMe')" />
<mx:Button label="Play" click="toFlash_lc.send('lc_from_flex', 'playMe')" />
The buttons invoke the LocalConnection.send() method, naming the connection that the Flash SWF is listening on. The second argument to the send() method is the name of the function to invoke. If the functions have any arguments, they follow the method name.
Flash to Flex
As you might expect, having the Flash SWF send messages to the Flex SWF is just the reverse. And that is true with the exception of where you define the messages the LocalConnection.send() method invokes.
In the Flex application you create an object which has the functions you want invoked from the LocalConnection. For example:
<mx:Application xmlns:mx="..." initialize="initApp()" >
<mx:Script>
<![CDATA[
import flash.net.LocalConnection;
public function showDetails() : void {
// do something
}
private var fromFlash_mc:LocalConnection;
private function initApp() : void {
fromFlash_lc = new LocalConnection();
fromFlash_lc.client = this;
fromFlash_lc.connect("lc_from_flash");
}
]]>
</mx:Script>
...
</mx:Application>
The Flash SWF can now invoke any public method defined on the object named as the "client" to the LocalConnection. In this case the intent is to have the Flash SWF invoke the showDetails() method.
Posted by pent at 01:13 PM
June 30, 2006
New Flash Player 9 Debug Version
A new debug version of Flash Player 9, 9.0.16.0 to be exact, is available at this link:
http://www.adobe.com/support/flashplayer/downloads.html
As of today, the Flex Builder 2 download contains this new debug Player.
We released version 9.0.16 as a Release (non-debug) version and 9.0.15.0 as the debug version. If you had already installed 9.0.16 you were not able to 'update' it to the debug version, 9.0.15, because of the rev number.
The updated Flash Player 9, debug version, 9.0.16, fixes that problem.
Posted by pent at 04:40 PM
June 28, 2006
Flex 2.0 Now Available
Today is the day! After months of development, thousands of hours of QA, and engaging many of you in Alpha and Beta tests, Adobe is making Flex 2 available for sale.
Flex is now more affordable than ever. Here are some details:
- Adobe Flex 2 SDK Price: FREE
- Flex Data Services Express (1 non-clustered CPU, 1 application, unlimited users): FREE
- Adobe Flex Builder 2: $499
- Adobe Flex Builder 2 with Charting: $749
- Flex Charting Components (Alone): $299
- Flex Data Services (Departmental license) : $6,000 per CPU, with a limit of 100 concurrent users per cluster
- Flex Data Services (Enterprise license): $20,000 per CPU
Here are some FAQs to go along with this:
Q1: What are the limitations of Flex 2 SDK?
A: The Flex 2 SDK is production use software that allows the deployment of any number of applications, on any number of CPUs to any number of users. There are some limitations in the license that restricts the redistribution of the SDK in commercially distributed software.
Q2: How do you quantify the limitations for Flex Data Services Express?
A: Flex Data Services Express is production software that can be deployed only on a single, non clustered CPU. The Express license does not allow (1) install, use or access of Adobe Flex Data Services Express software on more than one CPU (2) cluster any CPUs, and/or use load balancing software or hardware; and (3) can not deploy any unique application on multiple disconnected single CPUs, including kiosks and other such devices.
Q3: How do you measure concurrent users in the Flex Data Services departmental license?
A: Concurrent users will be counted by the number of unique IP connections to the CPU and/or CPU cluster.
Q4: Is there a limit on concurrent users for Flex Data Service (Enterprise)?
A: No, there are no concurrent user limitations for Flex Data Services Enterprise license.
Q5: What is the stand alone version of Adobe Charting Components?
A: The Adobe Charting Components are intended to be used with the Flex SDK to provide developers access to the charting library.
Q6: Can ISV companies redistribute the SDK?
A: There are some limitations in the license that restricts the redistribution of the SDK in commercially distributed software.
Q7: What is flex.org?
A: Flex.org is a starting point for developers working with Adobe Flex Builder 2, Flex Data Services 2, and the Flex 2 SDK. Here you will find links to technical and support resources from both Adobe and the developer community. We are working to evolve this site over time.
Q8: How can community members let us know about issues or bugs?
A: If you found a bug, or didn't find the feature you were looking for, the Flex team wants to hear from you http://www.adobe.com/go/wish
Follow these links for more information:
http://www.adobe.com/devnet/flex - the developer center for the Flex product line.
http://www.flex.org - a starting point for finding Flex resources online.
Flex 2 Migration Guide - provides extremely useful information if you intend to port Flex 1.5 to Flex 2.
I want to thank all of our customers for their hard work and committment to Flash Technology and for making Flex 2 the best product we've ever released (my opinion).
I also want to thank the Flex engineering, ColdFusion engineering, QA, and Documentation teams for making an outstanding product.
If you haven't tried Flex 2 yet, you'll be amazed at what it can do.
Posted by pent at 07:44 AM
April 24, 2006
Programmatic Skinning in Flex 2 Beta 2
This example application shows how to skin components using programmatic skins. These are skins which are written in ActionScript. The other type of skins, graphic skins, are images. You can combine the two skins as well as use them separately.
If you open the main application file, SkinningExample.mxml, you will see the <mx:Style> section. When you create skins you typically want all of the controls to have the same look. You may, for example, want only LinkButtons to have your special look, or, as in this example, you want many components to have your look.
You can run the application from this URL: http://www.keaura.com/skinning
This application works only with Flex 2 Beta 2 and the Flash Player 8.5 that goes with that release. The source is available from a right-click on the application once you have launched it. I used the application source packaging feature of Flex Builder 2.
Since I have created so many skins you might think to yourself that I have created a theme. This is fairly accurate, but I have not packaged these skins as such. I'll leave that for a future article.
Disclaimer
I am not an artist. I will not be offended if you think these skins are unattractive. The point is learn how to do skinning, not to make a MOMA piece. You can call this collection of skins my Orange Period.
Specifying Skins
We'll start with a top-down approach and look at how you specify the skins and relate them to the components. I've done this with styles, but you can also place these styles on individual component definitions - it just depends on what you want skinned.
This is the style definition for a Button:
Button {
upSkin: ClassReference("skins.MyButtonSkin");
overSkin: ClassReference("skins.MyButtonSkin");
downSkin: ClassReference("skins.MyButtonSkin");
disabledSkin: ClassReference("skins.MyButtonSkin");
}
The names of the skins are found in the Flex component API under the style section. The ClassReference style attribute names the ActionScript class which defines that skin. You can see that I have specified the same ActionScript class for each of the skins listed for the Button. When you see the code for the class it will be more obvious why I did it that way. Many times a skin for each state or part of a component share the same code and it often makes sense to use the same class. But you do not have to do that, you can write a separate class for every skin.
This is the code for the skins.MyButtonSkin class.
package skins {
import mx.skins.ProgrammaticSkin;
import flash.display.*;
public class MyButtonSkin extends mx.skins.ProgrammaticSkin {
private var _backgroundFillColor:uint;
private var _lineThickness:int;
public function MyButtonSkin()
{
_backgroundFillColor = 0xFFBB00;
_lineThickness = 1;
}
override protected function updateDisplayList( w:Number, h:Number ) : void
{
var g:Graphics = graphics;
if( getStyle("lineThickness") ) {
_lineThickness = getStyle("lineThickness");
}
if( getStyle("backgroundFillColor") ) {
_backgroundFillColor = getStyle("backgroundFillColor");
}
switch( name ) {
case "upSkin":
break;
case "overSkin": // make lighter
_backgroundFillColor = 0xFFCC00;
break;
case "downSkin": // make darker
_backgroundFillColor = 0xFFAA00;
break;
case "disabledSkin":
_backgroundFillColor = 0x999999;
break;
}
g.clear();
// now draw the real button
g.beginFill(_backgroundFillColor,1.0);
g.lineStyle(_lineThickness,0x000000,0.4);
g.moveTo(w,0);
g.lineTo(10,0);
g.curveTo(0,0,0,10);
g.lineTo(0,h);
g.lineTo(w-10,h);
g.curveTo(w,h,w,h-10);
g.lineTo(w,0);
g.endFill();
}
}
}
I have two class variables: _backgroundFillColor and _lineThickness which I initialize in the class constructor function.
Perhaps the most important piece of the skin is the updateDisplayList function. This function is declared as an override because it is replacing the function of the same signature in the base class, mx.skins.ProgrammaticSkin. It is in this function that the skin is drawn. Flex will call upon this function whenever it needs to have the skin drawn.
Within the updateDisplayList function you can see where the name of the skin is being tested. Look back to the Style section and you can see those names: "upSkin", "downSkin", and so forth. These names correspond to the case clauses in the switch statement. The only difference between the skins is the color. For "upSkin" and "downSkin" is tried to lighten and darken the color a bit. For "disabledSkin" I made it gray.
The button graphic is drawn using the drawing functions of the graphics property of the component. The graphics property does have some handy drawing functions, such as drawRect and drawCircle. You can read more about this class under flash.display.Graphics.
Another thing to notice in the updateDisplayList function is how the color and line style are set. While I did initialize these in the class constructor, I test the component's style for settings that would override the defaults.
The rest of the skins work in a similar fashion and you should have no problem adapting them. Just be sure to read all of the API information for the components you want to skin. Skins for scrollbars are often referenced via custom styles. One example is the Panel which you can see in the Style section.
Skinning in Flex 2 is fairly straightforward and once you get the hang of it you will be able to create innovative skins. Just let your imagination run wild.
Posted by pent at 10:21 PM
April 20, 2006
Updated Flickr PhotoSearch for Flex 2 Beta 2
It has been awhile since I last updated the Flickr PhotoSearch application. This new version, for Flex 2 Beta 2, is essentially the same as the previous version, but I changed the experience a little.
In the last version, clicking a thumbnail opened a dialog box with the enlarged image. In this version the gallery of thumbnails resizes to make room for the enlarged image. I also updated the search capabilities to include search by date. You will also have to click the Search button (magnifying glass) after clicking a row in the search history grid. I decided that you might want to add additional tags or other search options before launching the search. Of course once you take the source you can change it back if you wish.
I included with the Favorites panel a slide-show view. There is a small slide icon on the lower left corner of the Favorites panel. When you click it the Gallery turns into a slide viewer.
Once again I made heavy use of states and transitions. If you are having problems with them, take a look throughout the code to see working examples.
In the original Flex 1.5 version of this application I used a Tile with Repeater to build the Gallery and Favorites panels. In the first Flex 2 Alpha 1 version of this application I switched to using a TileList and a renderer. For this version I have switched back to the Tile and Repeater. I found that since I am only grabbing enough images to fill the screen (and not all the images), the performace of the Tile and Repeater were better than the TileList. This was especially true when using a Resize effect during the transition from the full Gallery state to the enlarged image state.
Finally, if you are unsure of what something does, hover your mouse over it and a toolTip should appear.
The source and zip file are now available directly from the program. I used Flex Builder 2's source code packaging tool. Once the application has started, right click and pick View Source.
Launch the application from this URL: http://www.keaura.com/flex2b2flickr/ from a browser with the Flash Player 8.5 from the Flex 2 Beta 2 site at Adobe Labs
I will try to keep this application more up-to-date as Flex 2 evolves.
This application is compatible only with Flex 2 Beta 2 and the Flash Player 8.5 that accompanies that release. If you have any other version of Flex 2 you will need to recompile (and possibly change) the source code to work with your Flash Player 8.5
Posted by pent at 10:02 PM
April 14, 2006
A List itemRenderer using States and Transitions
Here's a little demo of how to expand a list item in place. This demo uses CheckBoxes for the itemRenderer, but when you click a box, the list item grows larger revealing more data.
The itemRenderer is actually a Canvas with a CheckBox for a child. The itemRenderer uses states. The initial state contains this CheckBox. The "expandedState" contains a List. A transition is used to hide and show the list by using a Resize effect.
The List must have variableRowHeight set to true, otherwise this won't work.
Compatable with Flex 2 Beta 2 Public release. Bear in mind that Flex 2 is in beta and changes will occur before the final release.
Main Application
Let's start with the main application which I call "ExpandoList.mxml":
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" layout="absolute">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
// This collection shows just the title in the list as a CheckBox. When
// one is checked, it expands to review the models array.
[Bindable]
public var dp:ArrayCollection = new ArrayCollection(
[ {title:"Ford", models:["Fusion","Taurus","Mustang"]},
{title:"Volkswagen", models:["Passat","Jetta","Beetle", "Golf", "GTI"]},
{title:"Infiniti",models:["FX35","GX35","Q45","M35"]},
{title:"Audi",models:["A3","A4","A6"]}
]);
]]>
</mx:Script>
<!-- Simple list with 2 important points:
1. variableRowHeight = "true" allows for rows to be of differing height
2. itemRenderer = "DetailItem" specifies the DetailItem.mxml as the renderer
-->
<mx:List dataProvider="{dp}" width="270" height="315"
horizontalCenter="0"
verticalCenter="0"
columnCount="1"
columnWidth="250"
variableRowHeight="true"
itemRenderer="DetailItem"
selectionColor="0xFFFFFF"/>
</mx:Application>
The Script block just defines the ArrayCollection. Assume this data is the result of a data services call.
The List is defined with this ArrayCollection as its dataProvider and variableRowHeight is set to true. The itemRenderer is set to the class name "DetailItem".
The itemRenderer
The DetailItem component is shown next. I've broken the code into parts for readability. You can combine them in the order given to get working code.
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" width="201" height="36">
<!-- This is the base state and it just defines the simple checkbox
-->
<mx:CheckBox x="10" y="10" label="{data.title}" width="181"
click="changeState(event)"/>
The component extends Canvas. This is a good choice because Canvas does not impose any restrictions on the layout of its children. When you make an itemRenderer, consider how much effort you want to put into positioning the items. For example, if you want all of the components in the renderer on one line, use an HBox. Since this component is going to change size, Canvas is a perfect choice.
A single alternate state (from the default base state) is defined, "expandedState". You can see that there are two elements. SetProperty changes this component's (Canvas) height. The second adds a List with the ac member as its dataProvider.
<mx:states>
<mx:State name="expandedState">
<mx:SetProperty name="height" value="123"/>
<mx:AddChild position="lastChild">
<mx:List left="10" y="36" right="10" dataProvider="{ac}" height="77"></mx:List>
</mx:AddChild>
</mx:State>
</mx:states>
The Script block defines the ArrayCollection and an event handler for the CheckBox (see above). When the box is selected (checked), the currentState is changed to "expandedState", triggering the items in the states above.
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var ac:ArrayCollection;
private function changeState(event:flash.events.Event) : void
{
if( event.target.selected )
currentState = "expandedState";
else
currentState = "";
}
// the setting of the data property is overridden to create the
// ArrayCollection from the Array of models listed in the dataProvider
// this this List.
override public function set data(value:Object) : void
{
super.data = value;
ac = new ArrayCollection(value.models);
}
]]>
</mx:Script>
The transition defines how the state changes take place. Transitions are optional and apply effects to the change. You can really see what this transition does by first running this example WITHOUT the transition. All this transition does is provide a better visual experience.
When you run this example without the transition, clicking the CheckBox just makes the canvas bigger with the List of models.
Now run the example using this transition. When you click the CheckBox the list now opens smoothly, revealing the models. Click the CheckBox again and the item glides back up.
The fromState and toState are both *, wildcards, meaning that this transition applies when going from any state to any other state. The transition is just a Resize effect on the entire component (Canvas). What's interesting about this is that Flex will figure out what the before and after sizes are for the Resize effect without needing to specify that in the Resize tag.
<mx:transitions>
<mx:Transition fromState="*" toState="*">
<mx:Resize target="{this}" />
</mx:Transition>
</mx:transitions>
Finally, end the component with the matching Canvas tag.
</mx:Canvas>
Quick Summary
You can use states and transitions pretty much anywhere, such as in an itemRenderer. Transitions make the user experience better and often do not take much coding. States and transitions can become quite complex. I suggest that you start off with something simple, such as this example, to get familiar with them. Perhaps add another state to this example and use a different transition.
Posted by pent at 08:31 PM
March 27, 2006
Component "Templates" in Flex 2.0
First, let me be very clear: Flex 2.0 does NOT support templates. But this article will show you a way to make it look and act as if it does.
A "template" is different from a custom component. A template typically specifies a number of parts with some of the areas left open for substitution. I think of templates as being done exclusively in MXML whereas components can be 100% ActionScript or a combination of AS and MXML. Dreamweaver has the ability to do templates using regions.
Note: An example is available for download at the end of the article.
Panel Template
Suppose you want to create a Panel with a certain look and use it frequently in your application. Perhaps the Panel has a ControlBar with a "Search" and "Help" button. You want to make sure the Panel has the ControlBar and Buttons all of the time, but you want to change the contents of the Panel. A template, called SearchPanel.mxml, to do this might look like:
<mx:Panel xmlns:mx="...." layout="absolute">
<mx:Metadata>
[Event("search")]
[Event("help")]
</mx:Metedata>
<!-- put children here -->
<mx:ControlBar>
<mx:Button label="Search" click="dispatchEvent(new Event('search'))" />
<mx:Button label="Help" click="dispatchEvent(new Event('help'))" />
</mx:ControlBar>
</mx:Panel>
To use this you might do:
<SearchPanel title="Employee Search" search="findEmployees()">
<mx:Form left="10" top="10" right="10" bottom="10">
... form items here ...
</mx:Form>
</SearchPanel>
If you were to write this in Flex today, you would get a compilation error saying that SearchPanel already had children. You cannot add more children from outside of the component. If you had written SearchPanel without the ControlBar, then this would work. But then every SearchPanel would not automatically have a ControlBar.
One solution is to write this as a custom component which creates the ControlBar and Buttons in ActionScript. I won't do that here because I have a much better way to do it.
Making the 'Template'
Let's go back to SearchPanel and keep it the same, except add in this Script block:
<mx:Script>
<[[CDATA
private var _components:Array;
public function set subComponents( a:Array ) : void
{
_components = a;
}
private function addComponents() : void
removeAllChildren();
for(var i:int=0; i < _components.length; i++) {
addChild( _components[i] );
}
}
]]>
</mx:Script>
and modify the root tag: <mx:Panel xmlns:mx="...." layout="absolute" creationComplete="addComponents()">
Now use this in your program:
<SearchPanel title="Employee Search" search="findEmployees()">
<subComponents>
<mx:Form left="10" top="10" right="10" bottom="10">
... form items here
</mx:Form>
</subComponents>
</SearchPanel>
Notice that the Form, and any other children you want to add to the Panel, are defined by the <subComponent> property. In the Script block for the SearchPanel, subComponents is a set method which takes an Array. This Array will be 1 or more components defined in MXML. In this example, that Array is the <mx:Form>. Notice that the simple loop just takes each element of the Array and adds it to the Panel.
Now you have a "template"!
Default Property
But wait - there's more. This is the really cool part. Go back into SearchPanel.mxml and add the following line to the <mx:Metadata> right after [Event("help")]:
[DefaultProperty("subComponents")]
Now change the main program just slightly by removing the <subComponents> tag:
<SearchPanel title="Employee Search" search="findEmployees()">
<mx:Form left="10" top="10" right="10" bottom="10">
... form items here
</mx:Form>
</SearchPanel>
Notice that <subComponents> is gone and this now looks like a real container. This works because of the DefaultProperty metadata. You've set the SearchPanel's default property name to be "subComponents" which automatically places the <mx:Form> into the subComponent array.
Conclusion
While real templates are not available in Flex 2.0 you can come mighty close to making it look as though they are. This is possible because of the way components are now created in Flex 2.0 (using the new operator) and the DefaultProperty metadata.
Download Example
This example is similar to the one described above. Here, the component is a Panel wrapped by a Canvas. This is done so that minimize and maximize buttons can float over the Panel's title bar.
Posted by pent at 11:25 AM | Comments (25)
March 21, 2006
Flex 2 Beta 2
In case you haven't heard... Flex 2 Beta 2 is now publically available from our Adobe Labs site.
If you have been using the Beta 1 version, you MUST read the migration document as quite a few things have changed. The Flex 2 Team has been hard at work, not only making Flex 2 better and incorporating a number of suggestions received during the Alpha and Beta 1 phases, but also making sure Flex 2 was even easier to learn for newcomers.
So when you find that you have to make some spelling changes (eg, Void is now void), keep in mind that we a) want to be in sync with the standard and b) we want to clarify classes and code names to make the easier to remember for everyone.
It's all good, trust me.
Posted by pent at 12:07 PM
December 18, 2005
Day 6: Finished Product
2006-Apr-16My "port" of the Flickr PhotoSearch with Flex project from Flex 1.5 to Flex 2 is done. There is a link below where you can download the ZIP file and import it into your Flex Builder 2 workspace.
The code shown and provided by this article will not work with Flex 2 Beta 2 and beyond. This code was written with the Flex 2 Alpha 1 release and was meant to show what Flex 2 is or will be like.
This code has been ported to the latest Flex 2 Beta release on the labs.adobe.com site. Check in the showcase area.
|
Make sure you click this button within a browser that has the Flash Player 8.5 plug-in. Launch Application |
For Alpha-level software, Flex 2 did a great job, in opinion, and I don't think I'm being biased. Because I could not spend 100% of my time on this project it lasted over two week, so don't take that as an indication of how long it takes to work with Flex 2. Bear in mind there is a learning curve - even if you know Flex 1.5 already. You'll really want to understand what new things Flex 2 has to offer and how developing applications in Flex 2 differs from Flex 1.5.
To Port or Not to Port
I don't think you should port a perfectly good application from Flex 1.5 to Flex 2 unless the application falls into one of these categories:
- There is some deficiency in the application that Flex 2 can solve. For example some UI technique that your application lacks that is now possible with Flex 2.
- Your application is ready for its next version and you see features in Flex 2 that will benefit your application. Perhaps you wrote your own data-push code using XML sockets and you would like to abandon that in favor of Flex 2 Messaging (which is not yet available as an Alpha)
- You want to understand what Flex 2 is like and since you know your application thoroughly, porting it to Flex 2 will help you learn and understand Flex 2 better.
Keep in mind that porting an application is not just bringing it into Flex Builder 2 or running the Flex 2 compiler on the code. Sure you can fix the syntax errors and the data-typing issues, but are you really taking advantage of what Flex 2 has to offer? Just consider that before doing a port.
Caveat: when you do a port like this, it is essentially a re-write. This means you may introduce new errors.
The Flex 2 Code
Before you download this code, you must have the Flex 2 Alpha 1 installed. You must have the Flash Player 8.5 or this will not work.
Bear in mind this is the first public Alpha version of this software. Neither Flex Builder 2, Flex Framework 2, nor the Flash Player 8.5 have been optimized or tuned. While you may think the resulting SWF is large, just keep those facts in mind.
Flex 2 Update - Feb 1 2006
The source is no longer available from this entry. Instead, go to the Adobe Labs showcase page, run the Flickr PhotoSearch application and right-click on it to View the source.
PhotoSearch in Flex 2
Here's a brief list of what's different between the two versions:
- There is no need for a Flickr account - anyone can use this application. That's not a Flex 2 issue because I realized I did not need that requirement. Perhaps a later version will need authentication, but now you do not.
- Drag and drop to the Trashcan does not work correctly. It is because I cannot tell where the data is coming from - the Gallery, the Search History, or Favorites. I believe this will be rectified in the future. In the meantime, you can drag things from Search History and Favorites to the Trashcan where they will be discarded. But you can also drag Thumbnails from the Gallery to the Trashcan (it will not really delete the images from Flickr!) and you can duplicate images in Favorites. These last two things you should not be able to do, but you are not prevented from doing it right now.
- There is no Progress Bar because I could not figure out how to make the Gallery into a source for the Progress Bar. I decided not to spend too much time on this because it was not important.
- I used a TileList with the Thumbnail as a renderer versus using a Tile container and the Thumbnail as a child. To the end-user there is no visual difference. But I believe the application is now simpler.
- I used States in a number of places: the main application (welcome vs. running), Thumbnails (loading vs. image), SearchPanel (normal vs. advanced options), ImageViewer (loading vs. image), and even the Trashcan has multiple states.
There is more "code" in this Flex 2 version because I created a couple more classes, such as the SearchEvent class. I could have done that with the Flex 1.5, but Flex 2 encourages you to do this.
Flex 2 Differences
This is not meant to be an exhaustive list of the differences between Flex 1.5 and Flex 2. In fact, if I were to port another application I'm certain I would uncover other differences simply because I was following a different path. These items are in no particular order.
Data typing. You must data-type every variable now. This not only makes the code run faster, but you will also uncover errors sooner.
Runtime errors. Before Flash Player 8.5, errors during the execution of the SWF went unsaid. For example, if you simply said x += 5 without defining x you would not get a warning and the variable would assume to be undefined. In Flash Player 8.5 you will get a runtime error. Sometimes these unspoken errors were deliberately coded (I know I used that fact), but if they were not it could become difficult to find the error.
Packages. Formal packages are now introduced in ActionScript 3. You can see this in the com.macromedia.flickr package in this project.
Eclipse. Almost without saying, Eclipse is the base for the new Flex Builder 2. Java developers will find this a familiar environment. Flex Builder 2 is offered (or will be) in two flavors: a standalone version and a plug-in version if you already use an Eclipse environment.
Cell renderering. It is now much, much easier to make renderers for lists of all sorts. Most Flex classes now implement the DataObject interface and include a dataObject property which is set when the class is used as a cell renderer. The dataObject contains the current record for which the renderer is being used.
Cell editing. This project did not use any cell editors, but they are worth mentioning. In Flex 1.5 you had only a cell renderer. In Flex 2, editing a cell is different from rendering a cell. For example, if you have a Boolean value for a cell, you can render it as "Yes" and "No" while editing it with the CheckBox derivitive.
States. The idea of a State is simple: the user interface has a different appearance in each state is may be used. The SearchPanel is a good example. It has a normal state which shows just the TextInput and button for doing a search. But there is also an "advanced" button which changes the State and causes more controls to appear. Now you can certainly do this would states (just look at the Flex 1.5 version of this code), but having all of the controls appear and disappear just by changing a variable makes it easy and reliable.
Collections. Arrays should not be used as dataProviders for List (or Tree) classes unless you are happy with 1-time binding. Collections (such as ArrayCollection) now implement the proper ChangeEvent code.
Bindable. In Flex 1.5 all variables were automatically flagged as bindable. In Flex 2 you must flag a variable using the compiler directive, [Bindable], immediately above the variable declaration.
Import. You'll find you need to import more classes than before. That's because a number of classes that were included by default are no longer brought into the SWF. You will need to understand and use the import statement.
Events. Flex (and Flash) are event driven. This is even more apparent in Flex 2/ActionScript 3. Many of the classes now dispatch very specific event types. For example, the ListEvent, is dispatched by the list-bases classes. Before you could just dispatch an Object and add whatever values you like. Now the dispatchEvent function demands that its first argument be derived from the base Event class, making it impossible (or at lease, very hard) to do this.
Event Model. The event model is a bit more complex now. For example, you can have events bubble up from lower classes without having to make all of the intervening classes pass them on. You can see this in the events dispatched by the ImageInfo class.
Alphas and Scale. In Flex 2, the values for Alpha are 0 to 1 (not 0 to 100) and scale works the same way.
Layout Constraints. New to Flex 2 are layoutConstraints. When using a Canvas container you can specify how UI components are positioned relative to the Canvas' side in addition to or in place of their (x,y) location.
I'm sure there are obvious things I'm forgetting.
Conclusion
Don't port a perfectly good Flex 1.5 just to bring it into Flex 2. There is effort involved. It is certainly a good learning exercise, but to just fix compliation and link errors will not teach you anything. You need to sit down with Flex 2 and understand what it is capable of.
I'm very pleased with my approach - I used my Flex 1.5 version as a guide, but re-coded the UI and ported the AS classes. I also added classes, specifically events, to fill in the missing pieces.
While the application has a few issues, it is a useable application that demonstrates some of the new features of Flex 2. Remember, this is Alpha software and with that are no guarantees that this is the final code. New functions may come along in the future and whole classes may be dropped.
In the meantime, have fun!
Posted by pent at 01:36 PM | Comments (17)
December 17, 2005
Please wait...
If you've been anxiously waiting for the finished Flex 2 version of my Flickr PhotoSearch app - I'm almost done.
As many of you know, Macromedia merged with Adobe and we've all been learning new things, so my Flickr project has been on hold for a bit. I promise I will get to it this week, in time for the holidays.
Thanks for your patience.
Posted by pent at 07:22 PM | Comments (2)
December 12, 2005
Day 5: Nearing the End
Getting closer now. It is really just finding solutions at this point. For example, the old issue of having one of something is not the same as having an Array of 1 item, bit me. I had to use the ever-trusty ArrayUtil class:
var ac:ArrayCollection = new ArrayCollection( ArrayUtil.toArray( tags ) );
I spent a number of hours getting the ImageView and ImageInfo screens to work again. I found myself not really looking at the Flex 1.5 version code and just re-coded in the spirit of Flex 2. Sometimes I did reach over for an algorithm or technique, but mostly I just wrote new code. I guess I like typing. When you are thinking of using Flex 2 on an existing Flex 1.5 project, bear in mind that you will have to change code in some places, so approaching the application from a fresh perspective may turn out to be a better strategy in the long run.
ImageViewer and ImageInfo
The ImageViewer is the TitleWindow that pops up when you click on an image in either the Favorites Panel or the Photo Gallery. You'll definitely want to use the itemClick event instead of change on any list you intend to use with drag and drop. Remember: change signifies a change in the selection state of the list.
The ImageViewer works like this: a Thumbnail is clicked which causes a itemClick to be raised by the TileList. This is caught by the PhotoSearch component. If the ImageViewer is not already in existance, a new one is created via the PopUpManager. A number of events are registered for the ImageViewer (more on that below) and the ImageViewer is displayed.
Just before the click event handler exits, it makes a request to the Flickr service to get the image details. When those details arrive, they are given to the ImageViewer via its photoInfo property (a setter function).
When the ImageViewer first appears there is no image and no extra information (remember, that just went off to Flickr). So the ImageViewer is in its base state which shows the word "Loading..." in the lower right corner. As soon as the image information is given to it (via the photoInfo property), the ImageViewer shifts into the "loadedState".
The user can click on the Info icon to see more details. I decided this would be another opportunity to use States. The ImageInfo panel would be displayed in the ImageViewer's "infoState" which would be based upon the "loadedState". Here are a couple of benefits to doing that:
- I do not have to write any code to make the ImageInfo appear; the state-change mechanism does that
- If I am viewing image information and I click on another image in the gallery, this causes the ImageViewer to go to the base state and the ImageInfo is taken down - and - the owner's name is replaced with "Loading..."
This is all very neat and clean. And while you may have been thinking that you could just use a ViewStack instead of States, what I just described cannot be done with a ViewStack. Plus, having written this out the long way in Flex 1.5, I definitely think States have provided a very useful alternative.
Events
I was originally going to title this section, Death by One Thousand Events but that was too dramatic, even for me. Truth is, Events are our friends. Our very good friends. In Flex 2, it is just finding the right event.
Since this is an exploration of what is new with Flex 2, I decided that it was time I bubbled an event. That means I want a lower component to dispatch an event that a higher component (in the child-parent heirarchy) will catch, but without needing to involve any of the components in between.
For example, when you pick the info button on the ImageViewer, you see in the ImageInfo component a list of tags for an image. A search is kicked off if you click one of those tags. So how to do this?
The tags which appear in the ImageInfo window are Links. When a link is clicked, its click event handler dispatchs a com.macromedia.flickr.SearchEvent which contains the tag.
I want the SearchEvent to be caught by the PhotoSearch component, not ImageInfo's parent, ImageViewer. In Flex 1.5 you have to intercept the event in ImageViewer, then dispatch it again so PhotoSearch can receive it. In Flex 2, I just have PhotoSearch listen for a SearchEvent on ImageViewer. Very simple. Very loosely coupled. We like that.
Another cool thing I did, which is not specific to Flex 2, is have other components handle events. For example, on the ImageViewer you can click the Next Image button and the next image in the gallery (Favorites or Photo) will appear. The ImageViewer dispatches the nextImage event.
In the PhotoSearch class, I set up the event listener like this:
imageViewer.addEventListener("nextImage",gallery.nextImage);
imageViewer.addEventListener("nextImage",favorites.nextImage);
Now PhotoSearch isn't even involved with these events as only the Gallery and Favorites care about them. What this means however, is that both components will receive the same event - it is up to the components to know if they should handle it or not.
To make that happen, the ImageViewer is told for which TileList it is displaying images. That is, whenever an image is clicked, not only is that image's information given to the ImageViewer, that image's TileList is given, too.
Both the Gallery and Favorites, in their nextImage event handler, ask the event target (the ImageViewer) what gallery it is showing and if it is themselves, that gallery dispatches an imageClick event as if the user click on the image.
Again, loosely coupled, re-used event handling code.
Problems
Well, it would not be interesting without some problems to solve. Here's today's list of things I either could not figure out or have determined are missing in the Flex 2 Alpha 1 release.
- Effects for state changes. I was able to, with the help of a Flex 2 Forum member, make the ImageInfo window appear to Fade in, but I cannot get it to Fade out. I believe this is due in part to how children added by a State are removed (rather unceremoniously, no double) so there is no time for an effect to take place. We should see effects for States appear in a later release.
- Some of the image information descriptions contain HTML, specifically hyper-links. In Flex 1.5 I used a StyleSheet object to make any links appear blue and have an underline when you hover the mouse over them. That appears to be broken in Flex 2. So the link is there and it works, you just don't know it until the mouse pointer changes to a hand. I'm sure this will get fixed.
- It took me the longest time to figure out how to create the Links for the tags in ActionScript. I don't know why, it is so easy - it is just different from Flex 1.5. For example, in Flex 1.5 you used one of the
createmethods like,createChild( datatype, undefined, initprops ). In Flex 2, it is easier. See the code snippet below. - Drag and drop are easy, to a point. Since I am using TileList instead of Tile, I can have the TileList handle the initiation of a drag operation simply by coding
dragEnabled="true"on their definitions. The problem is, I cannot tell how started a drag operation. For example, you are supposed to be able to drag a Favorite over the Trashcan to delete it, but you are not supposed to be able to drag a Thumbnail from the Gallery. Only a drag out of Favorites should light up the Trashcan. But I cannot tell who started the drag, so the Trashcan cannot tell the difference. The jury is out on this one.
Here's how to create a Link using ActionScript. You'll find this in the ImageInfo component code when I put the code up on the weblog server.
import mx.controls.Link; ... var link:Link = new Link(); link.label = tagName; link.addEventListener( "click", handleLinkClick ); addChild(link);
Conslusion
Well, I'm almost ready to publish this code. I have to put in more comments, clean up some debug code, and hope that I can get drag and drop to work.
Stay tuned...
Posted by pent at 08:06 AM | Comments (1)
December 09, 2005
Day 4: Making Progress
At this point I have a lot more working. Here's what happened to get to this point.
Recap
Once I was able to receive the XML from Flickr containing the list of photos, I had to give it to the TileList. Then the TileList's listItemRenderer would take the data and build the URL to display the actual thumbnail image. This turned out to be pretty easy:
- The Gallery component has a setter for
dataProviderwhich expects an Array. This will be the Array of photo information from Flickr. - The Gallery
dataProvidersetter takes this Array and creates a new ArrayCollection.[Bindable] public var ac:ArrayCollection; ... // inside of dataProvider setter: ac = new ArrayCollection( ar );
- When the TileList has its dataProvider changed, it causes all of the renderers to receive new dataObjects (the elements of the ArrayCollection).
- The Thumbnail renderer has an override to the setter for
dataObjectwhich causes the Thumbnail to switch to its base state that displays the "Loading" message and hides the image. - The
dataObjectsetter also forms the URL for the thumbnail and assigns it to the Image. - When the Image
completeevent triggers, the Thumbnail switches to the image state, displaying the image and hiding the label.
That seems complicated, but the code is pretty clean right now. I know things need to become more complex in the next few days, but for right now, I'm getting to see thumbnails when I enter a tag into the Search Panel.
Not Much Progress
My next task was to get the Progress Bar to work. This is located in the ControlBar area of the Gallery panel and shows how the thumbnails are loading. In the Flex 1.5 version, I made the Gallery itself a source for the Progress Bar. This means that the Gallery dispatches progress and complete as well as implementing byteLoaded and bytesTotal methods. That was easy with the Gallery being a Tile container. But in my Flex 2 Alpha 1 version, the Gallery is a TileList and I need someway to know when each image has loaded.
I could not find out how a listItemRenderer knows what control it is a renderer for. In Flex 1.5, each cell renderer was given an object, listOwner. I do not see anywhere in the documentation what this is.
My idea was to tell the listOwner when an image was complete so it could bump up its byte count and be a good source to the Progress Bar.
Since I don't know how to do that know, I'm leaving out the Progress Bar.
Decisions
Up to this point the path has been straightforward. Now there are some choices: do I get the Image Viewer (the TitleWindow that displays the larger size image when you click) working? Do I get drag-and-drop working? Do I finish out the Search Panel and add the advanced options? Do I get saving the search history working?
I decided to do a little bit of everything.
Drag and Drop
The Flex 2 docs on how to do drag and drop are pretty good. I dragEnabled my TileList and it merrily allowed me to drag images all around. However, as soon as I pressed down the mouse, the TileList emitted a change event. Oops. After a quick read through the documentation, I found that I needed to use itemClick instead. You see, I want the ImageViewer to pop up when the mouse is clicked; I don't want it to pop up when I drag an image to the Favorites panel.
In the Favorites panel I also have TileList and on that I placed event handlers for dragEnter, dragOver and dragDrop.
The dragEnter event handler is where you determine if your component is a valid drop target. You get the dragSource from the DragEvent (in Flex 2 there are many more classes and this one is specific to drag and drop) and look at the dragFormats supported. If the items being dragged are what your component can handle, then you:
- Invoke
DragManager.acceptDragDrop(event.target)which is different from Flex 1.5. - Invoke
DragManager.showFeedback(DragManager.LINK). The default is a MOVE which, as it turns out, really removes the item. When I first implemented this in Flex 2, I dragged a thumbnail from the Gallery and dropped it onto the Favorites panel and the item was removed from the Gallery! So I changed this to a LINK operation. I could also have used COPY, but LINK seemed more appropriate.
The dragOver event handler is optional. I just set the focus to the Favorites' TileList.
The dragDrop event hanlder is where you add the data to the drop target. Since the data is what goes into the Gallery's ArrayCollection, it is perfectly suitable for the Favorites TileList since that also uses the Thumbnail for its listItemRenderer. All I did was add the data from the dragSource to the Favorites TileList dataProvider (also an ArrayCollection) and voila - thumbnail appears.
Search History
Once a search is successful, I want to remember it in the DataGrid, aka SearchHistory. I already had code to do this from the Flex 1.5 version, so I inserted that into the SearchHistory component and, with an adjustment for the ArrayCollection, things worked.
When using an ArrayCollection, you do use the addItem method just as you would have with an Array in Flex 1.5. Except, merely using addItem does not trigger an event to update the TileList. You also have to call notifyItemUpdate like this:
if( ac == null ) ac = new ArrayCollection(); ac.addItem( searchCriteria ); ac.notifyItemUpdate( searchCriteria );
When the SearchHistory DataGrid is bound to the ArrayCollection ac the notifyItemUpdate causes the new criteria to be placed into the DataGrid.
ImageViewer
This last task was much more complex than I would have liked. In hindsight, there wasn't much to do, but with 2 bugs in the Flex 2 Alpha 1, I had to find work-arounds.
First, I've only made the ImageViewer show the larger size image of the one clicked in either the Gallery or Favorites. None of the controls (information, print, etc.) work yet. Nor does the image information (tags, description, etc.). That will be later.
First issue: TitleWindows are very transparent. Panels are now given an alpha of about .6 I think. TitleWindows seem to be about .2 - nearly invisible. I wound up changing their style to have headerColors and an alpha of 1.
Second issue: The click event means 'click anywhere in the window', not just the close box. This means that if you have the ImageViewer open and click one of the control buttons (eg, Print), it triggers the click event for the TitleWindow. I had a listener for this event in the PhotoSearch component which dismisses the window and sets its instance variable to null. So no matter where I clicked, the ImageViewer disappeared.
To circumvent this, I removed the close button and added a specific "Close" button on the ImageViewer's ControlBar which dispatches a "close" event. Now the only way to dismiss the ImageViewer is to click that button.
Third Issue: Images, on the complete event have their width and height properties set to zero. This means that I could not resize the ImageViewer to see the entire larger image the way I did in Flex 1.5.
As I reported in the last journal, I found a response to that from likn who suggested that I use img.content.width and img.content.height. That worked. So now the ImageViewer reshapes itself properly.
Finally, in keeping with the flow from the Thumbnail, the ImageViewer now builds the URL to the full-size image itself, rather than it being part of the information in the TileList dataProvider ArrayCollection.
Conclusion
This 'day' was more intense as I uncovered some problems with the Flex 2 Alpha 1, but found reasonable work-arounds for them. The Flickr PhotoSearch application is about 50% of the way to running as a Flex 2 application. I need to save the search history and favorites list, get the image information/details to display, allow searchs from the image details tags, get searching by Flickr user to work, and more.
Getting drag and drop from the Gallery to the Favorites went really well. Getting the ImageViewer to work was harder due to the click event issue and then the image sizing issue. And I was disappointed by the Progress Bar, which I would probably have had issues with in Flex 1.5 if I had used a TileList there, too.
Stay turned...
Posted by pent at 06:08 PM | Comments (0)
December 06, 2005
Day 3: Here Comes Trouble
I finally got the user interface laid out without having to port any of the Flex 1.5 version files. This was a good thing because it forced me to use the Flex Builder 2 design tools and it made things much easier. The next task is to get the program to connect to Flickr and return some pictures.
As I suspected, it wasn't going to work right away. The com.macromedia.flickr.Flickr class was by and large fine. Instead of passing in a reference to an HTTPService that was created in the main application, the Flex 2 Flickr class creates an instance of HTTPService in its constructor function. It also used addEventListener to hook up result and fault handlers.
Delete Delegate
One thing I forgot to mention in my Day 1 notes is that you no longer need to use mx.utils.Delegate to scope your event handlers.
In other words, when you do this in Flex 1.5:
button.addEventListener("click",mx.utils.Delegate.create(this,doSearch));
you now do this in Flex 2:
button.addEventListener("click", doSearch);
ActionScript 3 now knows how to scope the functions properly.
Back to the Story
When I first created the Flex 1.5 PhotoSearch program, I found it a little confusing as to how you log in or authorize the application. But I followed the instructions on the Flickr site and got the program to work smoothly. So I used the same code with a minor change: getURL has been deprecated in favor of flash.network.navigateToURL which is just as simple to use.
I ran the program and the first phase, which gets what Flickr calls a FROB, returned what appeared to be a valid string. At least no error was returned and the value looks like it ought to look. The next phase involves taking that frob and, along with my application's API key and shared secret, uses navigateToURL to authenticate the session and get a token. This works like a charm in the Flex 1.5 version.
But in the Flex 2.0 version I cannot get it to work consistently. More than half the time I get an error from Flickr telling me my signature is incorrect. But then other times it works fine. I even ran the Flex 1.5 version in the Flash Player 8.5 Alpha and it worked consistently.
I tried everything, including getting a new API key from Flickr. Nothing makes it work consistently.
So I decided to abandon the Flickr authorization process for now. This is acceptable because PhotoSearch doesn't really need to be authenticated because it does not modify anything - it is just a search tool. I didn't realize this when I was first writing the Flex 1.5 version and I may go back and remove the need to authenticate from that version of PhotoSearch.
Since I didn't need to get the application authorizated, I still wanted to use States, so I replaced the AuthorizationWindow with a WelcomeWindow which just explains the program a little.
Thumbnails
Now that the trial of getting into Flickr has been resolved, I wanted to get searching by tags to work and displaying some images.
Remember that this version is using a TileList which is a control and not the Tile which is a container. This means my Thumbnail component now has to be a renderer. This turns out to be very easy in Flex 2.
A simple Thumbnail could look like this:
<mx:Canvas width="104" height="104" xmlns:mx="..." >
<mx:Image x="2" y="2" source="{dataObject.thumbURL}" />
</mx:Canvas>
If the dataProvider to the TileList is a collection of objects, each of which has a field called "thumbURL" that points to a remote image, then the TileList will display the thumbnail.
But I want this to be a bit more complicated because I want the user to see "Loading" while waiting for the image to display. So the actual Thumbnail has 2 states: loading and image. The loading state has a Label component with the word, "Loading" in it. The image state has the image of the thumbnail. Both the image and the label are contained in a Canvas and the Canvas provides the root tag for my Thumbnail.
I created the two states and set the image's complete event to switch from the loading state to the image state. But as I mentioned in the last article, I need to switch back to the loading state when a new search is done or else the images will not change (or at least, they won't show "Loading" while the user waits).
The Thumbnail now looks like this:
<mx:Canvas width="104" height="104" xmlns:mx="...">
<mx:states>
<mx:State name="imageState">
<mx:RemoveChild child="{loadingLabel}" />
<mx:PropertyOverride target="{img}" property="visible" value="true" />
<mx:PropertyOverride target="{img}" property="alpha" value=".8" />
</mx:State>
</mx:states>
<mx:Script>
<![[
[Bindable]
public var thumbURL:String;
override public function set dataObject( value:Object ) : Void {
super.dataObject = value;
currentState = ""; // reset
thumbURL = "http://static.flickr.com/"+dataObject.server+"/"+dataObject.id+"_"+dataObject.secret+"_t.jpg";
}
]]>
</mx:Script>
<mx:Image id="img" x="2" y="2" complete="currentState='imageState'" source="{thumbURL}" visible="false" />
<mx:Label id="loadingLabel" text="Loading" y="44" width="100%" textAlign="center" />
</mx:Canvas>
Notice that the source for the Image is bound to the variable thumbURL. Then note that the setter for the dataObject property has been overriden (it took me a number of hours before I went back to the documentation and found this). This override does 3 important things:
- it invokes the
super.dataObjectsetter method; - it sets the state to the base state, causing the image to become invisible and the label to reappear;
- it creates the
thumbURLvariable from the items in the dataObject. In the Flex 1.5 version I created this value in the Gallery when I received the data from Flickr. Now this is encapsulated as part of the thumbnail - it knows how to form the correct URL.
The data binding on the image's source property causes the image to be loaded. When the load completes, the state of the Thumbnail switches over to the image state: the label is removed and the image is made visible. When a new search is made the process repeats itself when the TileList calls upon the dataObject setter.
There seems to be a bug with the Image component in that the image's width and height are reported to be zero when the complete event is fired. In the Flex 1.5 version, I use that event as an opportunity to center the image within the Thumbnail. I posted a question on the Flex 2 Framework forum and received a reply (thanks xxx) that I should try not img.width, but img.content.width. That worked, so now the Thumbnails appear centered once again. I will apply this technique to the ImageViewer when the time comes.
Making the Request and Processing the Results
When the Go Search button is picked, the text from the TextInput field is given to the Flickr class and then an HTTP request is made to the Flickr service.
When the result comes back, the Flickr class dispatches a com.macromedia.flickr.FlickrEvent which contains the results.
The PhotoSearch component has an event listener for this which looks something like:
private function searchComplete( event:FlickrEvent ) : Void {
totalPhotos = event.data.total; // for the bottom of the Gallery
totalPages = event.data.pages; // for the bottom of the Gallery
pageNumber.value = event.data.page; // sets NumericStepper
gallery.dataProvider = data.photo; // this is an array
}
The searchComplete method sets some variables that are data-bound to controls and then it passes the list of photos over to the Gallery.
The Gallery component has a setter called dataProvider which simply converts the Array of photos in the result to an ArrayCollection> which is bound to the TileList displaying the Thumbnails.
In Flex 1.5 you could use an Array as the dataProvider to a DataGrid or any other List control. In Flex 2, you need to use a collection so I converted the photos into an ArrayCollection. In the Flex 1.5 version I took the result list of photo information and created an Array of com.macromedia.flickr.PhotoInfo objects, which included setting the URLs to the thumbnail and full-size images. In the Flex 2 version, I do not need to use the PhotoInfo class (because Thumbnail and make its own URL).
Conclusion
At first I was disappointed that I could not get this Flex 2 version of PhotoSearch to authenticate with Flickr. But when I realized I didn't need to do that, I switched gears and moved toward getting the thumbnails to display.
In the Day 2 journal entry I said that I did not want to port the Flex 1.5 application to Flex 2. Since I decided to use a TileList instead of a Tile container, this was a good decision. It made me rethink how the Thumbnail code works and it in fact simplified it. Using states in the Thumbnail made it easier (less code) to switch between displaying the "Loading" message and the image.
When a new search is made and the results come back, the TileList (or the Flex 2 framework) takes the contents of the dataProvider and invokes the dataObject setter on each renderer, aka Thumbnail. Because I overrode the setter, I was able to intercept this change, reset the state, and build a new URL for the new image.
In the Flex 1.5, the setValue method takes care of that, but setValue also gets called on many other occaisons, such as when the mouse rolls over the cell, so it can be difficult to code setValue properly.
My conclusion at the end of this day is that Flex 2 is good stuff. Once over the learning curve, the code has become more streamlined and even more object-oriented.
Posted by pent at 07:22 PM | Comments (0)
December 05, 2005
Day 2: A New State of Affairs
Once I had the simpler classes compiling, I had to move on to getting the user interface developed. I did not want to just copy the original Flex 1.5 version for several reasons.
- First, I wanted to use Flex Builder 2 to make the components from scratch. I simply wanted the experience of using the design tools.
- Second, I knew there would be some compatability issues and may be lots of errors. I didn't want to have to solve every error in all the files just to see something working. Imagine if this were an application that had a hundred components. I would have to make sure they all compiled cleanly, then cross my fingers that it would work. I did not want to face that amount of work (and frustration). So I decided the incremental approach was better.
- Finally, there are some new features in Flex 2 that I wanted to use and it would be easier to add them to a clean slate than to retrofit them.
The second point is very important, especially if you are porting a working application. If you copy all of your files and think you can just get the compilation errors removed and it will work, think again. In my first day's experience I found that I had to make some adjustments in how the application was written. And that was with a handful of files. So I believed it would be best to start over, but to use my already working, Flex 1.5 application, as my base. Many of the functions I wrote would still work and I would not have to re-write everything.

States
One of the new features of Flex 2 are states. States are a way of dividing a component into 2 or more related behaviors. For example, in this PhotoSearch application there is the "authorization" state and the "running" state. In the authorization state you see the panels that direct you through the Flickr authorization process. When that is satisfied the application switches to the running state. Likewise on the Search Panel, there is the normal state with just the TextInput field for the tags, the Search button and the Advanced Options button. When you pick the Advanced Options button, the state of the Search Panel changes to show more options. Click the Advanced Options button toggles the Search Panel between these two states.
In the Flex 1.5 version, I used a ViewStack in place of states. But the state concept is much more powerful.
In the Flex 1.5 version of PhotoSearch I used a Tile with a number of Thumbnail components as children. I debated whether or not to use TileList and decided that using a Tile container was easier. In the Flex 2 version I am going to use a TileList for both the Gallery and the Favorites panels. I'm doing it this way not only to be different, but because cell renderers (now called listItemRenderers) are so much easier to write.
I began by creating my main application and the AuthorizationWindow component. Since I knew that I wanted to use states, I also created a PhotoSearch component to handle the running state. In Flex Builder 2 I set up the authorization state to display the AuthorizationWindow and created a new state, "runningState", to display the PhotoSearch component. I tested this code by simply having the AuthorizationWindow dispatch an event when the button was picked which caused the main application to switch to the running state.
The AuthorizationWindow I initially created just had a button and it was time to modify it. I suddenly realized that this component also had states: the initial state and the "permission granted" state. So I did the same thing: created the first state with the title and text areas and a button. Then I created another state and changed the text and button label. When you get is something like this:
<mx:states>
<mx:State name="runningState">
<mx:PropertyOverride target="{text1}" property="text" value="Return to this window..." />
<mx:PropertyOverride target="{text2}" property="text" value="Once you are done..." />
<mx:PropertyOverride target="{button1}" property="label" value="Complete Authorization" />
</mx:State>
</mx:states>
In Flex 2, when a component's state is changed, the PropertyOverride instructions are employed - in this case the text is changed and so is the button's label. If you were to switch back to the initial state, those overrides are removed and the controls revert to their original values.
With the State definition you can also add and remove whole components along with changing components' properties.
I think you will find states to be a very convenient way to express a control's multiple personalities, if that's appropriate. For example, I think the Thumbnails also have 2 states: the one where the "Loading" text is displayed and the other where the image is displayed.
ListItemRenderers
While my goal at the moment is not to run the application, just lay it out, I did want to tinker with some of the new features to see how they work, if they work in this Alpha release, and see how to adopt them to the application. I already tried out states and am happy with how easy they are to work with.
One of the biggest - and best - changes in Flex 2 is the cell renderer, now known as the ListItemRenderer. If you have used cell renderers in Flex 1.5, you can use the essence of your code, but you should create a new component and not port it.
You have many options how how you want to use renderers: there are drop-in components, in line components, and custom components which are like the Flex 1.5 components. I know that I'm going to use TileList this time and so I wanted to know how to create a custom component (I'll be using it in both the Gallery and Favorites panels). This is my initial Thumbnail:
<mx:Canvas width="104" height="104" xmlns:mx="..." ><mx:states>
<mx:State name="imageState">
<mx:RemoveChild child="{loadingLabel}" />
<mx:PropertyOverride target="{img}" property="visible" value="true" />
</mx:State>
</mx:states><mx:Script>
<![CDATA[
private function showImage() : Void {
currentState = "imageState";
}
]]>
</mx:Script><mx:Image id="img" x="2" y="2" source="{dataObject.thumbURL}"
visible="false"
complete="showImage()" /><mx:Label textAlign="center" id="loadingLabel" text="Loading" x="0" y="40" width="104" />
</mx:Canvas>
You'll notice the absence of the setValue method and the presence of dataObject. Practically every Flex component can now be used as a renderer because they all implement the IListItemRenderer API. One of the properties the components now have is a dataObject which is set in the instance to be the data for that cell in the list. In the PhotoSearch application it will be the PhotoInfo which will have a property. The ThumbNail will be showing with a "Loading" label and as soon as the image is loaded, it will switch to displaying the image and not the label. At least that's the idea. We'll see what happens when I get this running.
I can see a potential problem with this already. What happens when the data changes - I do a new search? As the TileList is given a new set of data, I presume that each instance of the Thumbnail renderer is going to change the dataObject. So how does the state get reset to show the "Loading" message until the images arrive?
After doing some more reading, I think I found the answer: override the setting of the dataObject. If it works, it will be deceptively simple. Here's what I've changed to the Thumbnail.mxml file:
override public function set dataObject( value:Object ) : Void
{
super.dataObject = value;
currentState = "";
}
I took the function signature right out of the Flex 2 documentation. In Flex 2 you have to indicate you are overriding a method (hence the override keyword). The dataObject is passed to the super method and the currentState is reset. With luck, this will work.
DataProvider and Bindable
In Flex 1.5 it was pretty common to use an Array as a dataProvider to a List component, such as the DataGrid. That's because in Flex, the Array class implements the DataProviderAPI which provides data binding and change events. I Flex 2.0, the Array no longer implements the DataProviderAPI.
Flex 2 provides a more robust set of Collection classes, much like Java. For the Search History (DataGrid) and TileList components, I decided to use the ArrayCollection class since that was most like what I was using and has many of the same functions as the Flex 1.5 DataProviderAPI.
What this means is that when Flickr returns the array of information about the photos which meet the search criteria, I cannot just bind that result, I have to convert it to an ArrayCollection. Fortunately, that turns out to be pretty easy:
import mx.collections.ArrayCollection; [Bindable] public var ac:ArrayCollection = new ArrayCollection( photos );
You'll notice that a) I had to place the [Bindable] metatag above the variable declaration and b) I had to declare the variable public. The latter is a Flex 2 Alpha restriction.
You must place [Bindable] above every variable or class (to include all variables in the class) to tell the Flex 2 compiler for which variables data-binding code variables should be generated; in Flex 1.5 the code was generated for all variables.
Conclusion
Overall the design process went well. There was a bit of time where some of my panels did not have titles when I ran them, but then I discovered the "Run->Clean" option in Flex Builder 2 which did a full recompile and that seemed to have fixed everything.
I needed to write some quick experimental code to get the feel for renderers and dataProviders, and the documentation really helped on that. I only used the simple ArrayCollection, but I can see there are more options now than before.
At this point I have the PhotoSearch application displaying the AuthorizationWindow. When I click the button, it switches to its next state and when I click the Continue button, the application switches to the running state, displaying the three main windows and the trash can.
In the Search Panel I can click the Advanced Button and it causes the other options to appear. And that's all the program does. The core code in the com.macromedia.flickr package is not tied into this yet. That's the next task.
Stay tuned.
Posted by pent at 01:38 PM | Comments (2)
December 02, 2005
Day 1: The Port Begins
This section of my blog is devoted to the port of my recent Flickr PhotoSearch with Macromedia Flex application. As of this writing I do not have the app ported to Flex 2, I have just started. I thought it might be good to do a running journal of the effort just so I don't lose any interesting and useful facts along the way.
First, let me set some expectations. This is Flex 2 Alpha we are talking about. This is not a final product. It may turn out that I redo this effort a couple of times. I'm not even expecting to get through it all now nor have a working app. If it does work, then all the credit goes to the Flex 2 team.
Second, I think most people who have running Flex 1.5 applications will not port them to Flex 2 unless there is a compelling reason. Oh, you may want to add new features to your app once you see what Flex 2 has to offer, but I don't think you'd take a perfectly good application and port it. Afterall, you can run your Flex 1.5 app in the Flash Player 8.5 and just get a better performing app. My goal here is not a 'port' this application, but to enhance it with new Flex features and to take advantage of the improvements Flex 2 offers over Flex 1.5.
With the spirit of Rewrite sitting on my shoulder, I decided to take the plunge.
My plan of attack is to start at the bottom and work up. Afterall, I have to get the classes that are used throughout the application to compile before I can get the rest to work. So I brought over the com.macromedia.flickr.* files - the package in Flex 2/ActionScript 3.0 terms.
Even with my careful planning of putting 'public' and 'private' before all of my methods and variables and with data-typing everything there were some issues to resolve.
- I didn't data-type everything as it turns out. Some little things slipped by me.
- I had to change every
undefinedtonull. I personally likeundefined. It means something to me. A variable that is set tonullhas a value. But what can I do? - Of course I had to use the
packagesyntax in the files. Could not have done that in Flex 1.5. - And then there were the Events. Those I had not data-typed and now, not only did I have to select a data-type, I also had to fix my quick syntax of
dispatch( {type:"click"} )to use a real Event class as the argument todispatchEvent.
Data-typing
Using data-types is a good thing. Your code is more readable, the code executes faster, and errors can be found sooner. Plus you can alway use Object if you aren't sure. This is especially true if you have a dynamic object - one that gets new properties or members at runtime. For example:
var test:Object = new Object(); test.firstName = "Peter";
That is perfectly acceptable in ActionScript 3.
Numbers are another matter. In AS3 you now have int and uint. There may also be floating point data-types, too; I haven't looked them up yet. But the Number type is still there and should be used for values which will exceed the capacity of int and uint. What that means is something simple like:
for( var i:Number=0; i < dp.length; i++) { /* do something */ }
should now be changed to:
for( var i:int=0; i < dp.length; i++) { /* do something */ }
and the loop will run much faster.
I retained the Number type for use in the MD5 class because the produces very large integers during the encoding process.
Events
The com.macromedia.flickr.Flickr class was the biggest challenge. The Flickr class in Flex 1.5 extended UIObject so I could easily dispatch events. For Flex 2, I have the class extending EventDispatcher. That gets me the functions (dispatchEvent, addEventListener, etc.), but I still need events.
In the Flex 1.5 version, I just dispatched events without thinking about them. For example, when there is a failure from Flicker, I just dispatched a "fail" event with the Flickr code and message. For others, I just dispatched a simple event with a type. Cannot do this so easily in Flex 2.
So I decided to embrace the Event system and created a FlickrEvent class which extends the flash.events.Event class. I made the constructor of my class take 4 arguments and made 3 of them have default values:
package com.macromedia.flickr {
class FlickrEvent extends flash.events.Event {
public function FlickrEvent( type:String, data:Object=null, code:Number=-1, msg:String=null ) {
super(type); // there are other arguments to this which I'm sure I will use in the near future.
this.data = data;
this.code = code;
this.msg = msg;
}
// data definitions here
}
Now I can easily dispatch events from the Flickr class:
dispatchEvent( new FlickrEvent("authorized") );
dispatchEvent( new FlickrEvent("photos",photos) );
dispatchEvent( new FlickrEvent("fail", null, rsp.code, rsp.msg) );
HTTPService
Along with events was how to use HTTPService in Flex 2. Turns out there isn't much difference, but there is a new way to use it. First, you can still use the MXML tag. But now you can create instances of HTTPService from ActionScript. This is really how I wanted to use it in the Flickr class anyway. In the Flex 1.5 version, it was awkward to declare the HTTPService as an MXML tag and then pass a reference to it to the Flickr class which is the place it was being used. Now in the Flex 2 version, the Flickr class creates an instance of HTTPService and everything is nicely encapsulated.
getURL
If you didn't know it, getURL is history in Flex 2. The Flex 2 documentation tells you why. All I had to do was change over to navigateToURL and include an import statement to find it. Oh, and use the URLRequest on the URL string. But that was all.
Flex Builder 2 Quirks
I've found Flex Builder 2 to be quite stable and pretty quick. Except for the following things:
- It can take a long time to build the project when you save a file. I eventually turned the automatic build off. Now I realize Flex Builder is not optimized or tuned yet, so I'm not complaining.
- Double-clicking does not select what I want. For example, if I have
event.typeand I double-click the mouse over "type" it selects "event.type". If I have_styleand I double-click the mouse over it, Flex Builder selects just "style" and not the preceeding underscore. May be there is a settng for this, but any code editor ought to know the rules of variable names. - Not everything is documented. Again, Flex 2 is now in Alpha, so I shouldn't complain. But it is a little frustrating trying to figure out where some class is and how to use it.
Conclusion
In this phase of the port, the Flickr class has undergone the most change. It now contains its own instance of HTTPService, throws its own events, and should work like a charm. I hope.
That's all I have for this entry. At this point the base classes have been ported and compile cleanly. Now it is time to build the UI.
Posted by pent at 01:21 PM | Comments (3)











