by Peter Ent

Peter Ent

Table of contents

Created

14 October 2008

Requirements

Prerequisite knowledge

To benefit most from this article, it is best if you are familiar with Flex Builder and ActionScript 3.0.

 

User level

Beginning

Required products

Flex Builder 3 (Download trial)

Communicating with the user of your application is what your itemRenderer does best. Sometimes that communication is as simple as presenting a name; sometimes more elaborately using colors; and sometimes with interactivity.
 
itemEditors are truely interactive controls, but they are not the focus of this article. In these examples I'll demonstrate itemRenderers that change their visual appearance based on either the data itself or the user's actions.
 
This series includes the following articles:
 

 
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, you'll create a new MXML itemRenderer (and remember, you can do this completely in ActionScript if you prefer) for the list. All this itemRenderer 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="/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 you want, however, is, if the book is not in stock (the data has <instock> nodes that are either yes or no) the price and "Buy" Button will be invisible. I've made things a bit convenient for coding here by giving the HBox parent of the price and Button an id property. 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 you'll do, but instead of directly changing the visibility of priceBox, you'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 <mx:HBox> 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 two 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 instock is no. When this state is active, the SetProperty instructions are carried out. The target property 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.
 
Note: 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 a data binding expression:
 
<mx:HBox xmlns:mx="/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 the controls' appearance can be controlled simply by setting the itemRenderer's currentState property.
 
Instead of just removing the price and Button, you'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.
 
Note: You can modify states in Flex Builder's Design View.
 

 
Expanding list items

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 dicey when the list has to be scrolled. Imagine this: you've got a list of items with all the same height. Now you expand 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 1000 records and use a Repeater, you will get 1000 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 two tactics:
 
  • The itemRenderer has a state that 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> tag.
 
The transition is applied whenever the state changes, because its fromState and toState properties are wildcards. Now all you have to do is add an event handler for clicking 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>

 
Where to go from here

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.