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.