Component Class – Part One

This is a topic I visit from time to time – writing components. In this example I’ll show you how to write a component from the ground-up. It will take several articles, but in the end you should be able to build your own components.

The component I’ve chosen I call a cycle-select button. This component shows a single entry from a dataProvider along with two arrows in a circle. When you select the button the arrow rotates a bit and the next value in the dataProvider is displayed. Think of it as a ComboBox with no drop-list.

Try clicking on the label and you’ll see how it cycles through the values.

Flex Framework

In order to build a Flex component you must understand the Flex framework. Building a component is done in stages as the Flex framework makes multiple passes through the component hierarchy to determine layout.

For example, consider an Application with two VBoxes. One VBox has a bunch of Buttons while the other has a bunch of labels. To create these components, the Flex framework has to first create the Application itself, then the VBoxes, and then the children of those VBoxes. That’s one pass.

If neither VBox has been given a width or height you’d expect the VBoxes to be large enough to enclose all of its children, right? To figure out how big to make the VBoxes, the Flex framework has to determine how big each of its children are. Buttons and Labels do not typically get explicit sizes, so the Flex framework has to determine their sizes, too. That’s another pass.

Once all of the measurements are done, the Flex framework has to get the components sized property and positioned. That’s another pass.

As you can see, creating components isn’t a simple task, but it also isn’t that hard, once you get the hang of it.

To facilitate component creation, the Flex framework calls upon certain methods within each components. By implementing these methods, your component can fit nicely and easily into the framework.

There are two ways to begin a component: by extending a component that already exists which does most of what you want to do or by creating a component “from scratch” which is to say, from the base class all components extend.

Extending an existing component is the most common and it is what you do all the time when writing a Flex application. When you create your main application file with a root tag of <mx:Application> you’ve created a component (extending Application). When you make a new MXML file a root tag of any other component, that’s doing the same thing; whether you extend a component through MXML or ActionScript.

First Draft

We’ll start by modifying the HBox component but ultimately we’ll want to extend UIComponent. Using HBox can provide a good proof of concept.

Download File
This is the source code in a package structure. It includes the icon for the graphic as well.

Taking a look at the component’s design, there are two obvious top-level pieces: the cycle button and the label. But since we want to select the label to cycle through the choices, using a Button seems more practical since a Button also provides some feedback as the mouse rolls over it and is pressed. But a Button doesn’t look anything like the control above, so perhaps a LinkButton is closer in appearance.

Create a new MXML component using HBox as the root tag and call it CycleSelectButtonV1.mxml (for version 1). To that add two children: an Image and a LinkButton (set its id to “linkButton”). Set the Image to be 20×20 and set the HBox’s verticalAlign property to “middle”. If you used FlexBuilder to make the component, erase any pre-set width and height on the HBox.

Change the Image tag to this:

<mx:Image source="@Embed('../assets/cycle_component.gif')"
width="20" height="20" />

Picture how you’d use this new component. Perhaps something like this:

<CycleSelectButtonV1 dataProvider="{choices}"
change="handleCycleChange(event)" />

The HBox component doesn’t have a dataProvider property nor does it have a change event. This is part of the customization you’ll need to do.

Events and Properties

The component will dispatch a change event, so you’ll need to tell the Flex compiler that the component will be dispatching the event. Add this below the HBox root tag:

<mx:Metadata>
Event(name="change",type="flash.events.Event")]
</mx:Metadata>

The Event metadata tells the Flex compiler that including change=”…” on the MXML tag is OK. The class given as the data type of the event is the default, but I like to be explicit so there’s no question about what event class is going to be expected in the event handling function.

For the dataProvider, which is a property, you’ll need to write some ActionScript. Add a Script block below the Metadata tag:

<mx:Script>
<![CDATA[
]]>
</mx:Script>

The <![CDATA and ]]> syntax is a way of telling XML that everything within those brackets is to be taken as-is with no XML parsing. You don’t *need* to have the CDATA block inside the Script tags, but if you use < for example, the XML parser will think you are trying to start a new tag!

Add this code within the CDATA block:

import mx.collection.ArrayCollection;
private var _dataProvider:ArrayCollection;
public function set dataProvider( value:ArrayCollection ) : void
{
_dataProvider = value;
}
public function get dataProvider() : ArrayCollection
{
return _dataProvider;
}

This is a standard way of writing a property – using set and get functions with the actual value the same name as the set and get functions, but prefixed with an underscore. You’ll sometimes see this referred to as a backing variable.

At this point you can test the component, even including dataProvider and change in the component tag; they don’t do anything yet.

When I thought up this component I envisioned the data being provided the same way you’d do for ComboBox. Here’s an example:

[ {label:"Apples", value:1}, {label:"Oranges", value:2}, etc. ]

What you would like to see are the labels displayed. The trick is to make that happen.

commitProperties

Nothing in the component so far shows how to display the data in the LinkButton. For that we wil need some more ActionScript. Add this code within the Script block:

override protected function commitProperties() : void
{
super.commitProperties();
// we'll fill this in below
}

Part of the Flex framework cycle includes a call to commitProperties(). This function is called after all of a component’s properties have been set. This is important because within any given property set function you don’t know if another set function has already been called. If setting a third property depends on the value of two others, for example, the only logical place to set that third property is in commitProperties().

We’re going to use commitProperties() to set the label of the LinkButton to a value in the dataProvider. For this first example it will be the first item. Add this below super.commitProperties();

linkButton.label = dataProvider[0].label;

There are a lot of things that can go wrong here: the dataProvider property might never have been set or it might not have a label property in the first item. For now, believe everything is set.

Running the Flex app again and you should see “Apples” as the LinkButton’s label.

What do you think should happen if you wrote some ActionScript code in your main file that changed the CycleSelectButtonV1′s dataProvider? What happens if you change the dataProvider of a ComboBox or DataGrid? They change to show those new values, right? You’ll want the CycleSelectButton to do the same. Making this happen will further illustrate the Flex framework.

If you do: cycleButton.dataProvider = newValue this calls the component’s set function for the dataProvider property:

public function set dataProvider( value:ArrayCollection ) : void
{
_dataProvider = value;
}

Big deal: the _dataProvider internal member changes. But that won’t change the label to the LinkButton. You could try adding linkButton.label=value[0].label right into this set function, but I hope you can see there are problems with that. For one, it just “feels” wrong. But really, the problem is that when the component is first being created and the properties are being set, the LinkButton’s label property may not be able to accept the value. It will later, of course, but many components won’t be able to be changed so easily during the property setting phase.

Which of course, leads to the commitProperties() function. The next logical thing to do then is call commitProperties right from the set function. Again, doesn’t “feel” right and besides, all that does is just do the same thing with the same problem.

What you want to do is notify the Flex framework that commitProperties() needs to be called. To that you call invalidateProperties() from the set function. This sets a flag in the Flex framework. What is good about this is that you can set a hundred properties and call invalidateProperties() a hundred times, but commitProperties will be called just once. Much more efficient. Change the set function to this:

public function set dataProvider( value:ArrayCollection ) : void
{
_dataProvider = value;
invalidateProperties();
}

Cycling Through the Labels

That little exercise was the set up for the next thing you should do: change the label on the LinkButton to the next item in the dataProvider.

What’s the “next” item if zero is hard-coded in commitProperties()? Obviously we’ll need a variable to hold this. Hmm, selectedIndex sounds like a good choice and is consistent with the ComboBox and many other Flex controls. Set up a set and get function for selectedIndex, too:

private var _selectedIndex:int = 0;
public function set selectedIndex( value:int ) : void
{
_selectedIndex = value;
invalidateProperties();
}
public function get selectedIndex() : int
{
return _selectedIndex;
}

You’ll also need to change a line in commitProperties to use selectedIndex:

linkButton.label = dataProvider[selectedIndex].label;

And make the click event on the LinkButton call a function to bump up the index:

private function handleClick() : void
{
selectedIndex = selectedIndex + 1;
if( selectedIndex >= dataProvider.length ) selectedIndex = 0;
}

Notice that the linkButton’s label was not explicitly set in the handleClick() function. Because the selectedIndex property is set, and because that set function calls invalidateProperties(), the linkButton’s label is changed in commitProperties(). As a rule of thumb, try to modify components in only one place.

Don’t forget to hook up the handleClick() function to click event on the linkButton:

<mx:LinkButton label="linkButton" click=handleClick()" />

Now run the application and click the LinkButton in the component. It should cycle through all of the labels in the dataProvider. When the LinkButton is clicked, the handleClick() function is called. That increments the selectedIndex which sets a flag for commitProperties to be called. When commitProperties is called, it displays the label assoicated with item in the dataProvider at the selectedIndex index. Pretty cool, huh?

Dispatching Events

The last thing to do is dispatch a change event when the selectedIndex changes. You could put this into the selectedIndex set function. But that means any change via ActionScript will dispatch the event. That’s not a normal Flex workflow. Instead, dispatch the event right from the LinkButton’s click handler:

private function handleClick() : void
{
selectedIndex = selectedIndex + 1;
if( selectedIndex >= dataProvider.length ) selectedIndex = 0;
dispatchEvent( new Event(Event.CHANGE) );
}

In the main application you can handle the change event and use the component’s selectedIndex property to find out what was selected.

Wrap Up

That’s it for this article which is already long for a blog entry. You should be able to write components using any Flex container (eg, HBox, VBox, Canvas) as a base. Just toss in the child components as MXML tags into the file, create a Script block to add any properties (with set and get functions), and override commitProperties() to apply those properties.

In the next episode of this series we’ll look at writing this same component in ActionScript and rotate that circle image with each click.