Building Spark Components in Flex 4+

What follows is a consolidated guide to building
custom components using the Flex Spark Architecture.

Creating complex custom components in Flex can yield some very powerful results. The Spark architecture creates a separation between the component’s visual appearance and some if not all of its logic. So consider the high-level view of a spark component:

INVISIBLE_TEXT

We have a Class file that subclasses something like SkinnableComponent or an even higher level component like ComboBox. We also have a Skin File which contains a hierarchy of other controls that represent the visual appearance.

INVISIBLE_TEXT

There are many things that we will want to do to satisfy the requirements of a working component. Let’s discuss where these tasks should be done and overall mindset for working with Spark components.

Tip #1 : Mindset

The idea is to think about this kind of development in a very decoupled way. Ideally, the class file should not be dependent upon the skin file. You should first think about doing things without bringing references of the children from the skin file into the class. The class should just provide properties, logic, and method callbacks that the skin and class file make use of. Let me repeat that, you should not reach into the skin and get access to its children from the class file. This paradigm can be broken, but try to make that your goal first.

When starting out, think about the final usage of the Component before writing any code. Actually write it out in the location where it will be used. This is a key part of component architecture. Let’s say we have a control to build that is used in several locations, but for different purposes. Below is an image and description of what we want to build:

INVISIBLE_TEXT

A Button that needs an icon for the image (in this case an envelope), a text label for the button descriptor, and content for the popup. The popup appears on hover, but on click, an event is fired off.

INVISIBLE_TEXT

So before anything is built, I would highly recommend typing out its usage:

[cc lang=”mxml” escaped=”true”]

<cc:ButtonHoverPop icon=”{this.EnvelopeImage}” label=”Messages”
click=”{MessagesButton_Clicked(event)}”>
<cc:popupContent>
<s:List dataProvider=”{MessageData}” itemRenderer=”{messageItemRenderer}”/>
</cc:popupContent>
</cc:ButtonHoverPop>

[/cc]

INVISIBLE_TEXT

This exercise helps you work out some of the essential thinking to begin effectively. I would argue that it is a user-centered approach to development, where the user is another developer, maybe even you. Out of this exercise we get:

  • The name of the control
  • The properties that are necessary and their names.
  • A start at how we will provide our popup with content
  • We can even work though some of the things that we will need to provide to our component from the outside.
  • If we wanted any custom states, we could mock up their usage as well.

I am not looking to take this particular example all the way through. Perhaps in another blog post, but from here you can begin creating the class and skin files. I would likely subclass a Button for this example. The Skin file would start out as a copy of a button’s skin file, but I would add a popup control to the skin and bind its content to our “PopupContent” property within the class. More on how to do that below.

Now let’s move on to some of the mechanisms for pulling off the things that typically come up.

Tip #2 : What to Subclass?

From the standpoint of creating a custom Spark component there are many choices for what we can subclass. All of the default Spark components that ship with the Flex libraries derive from SkinnableComponent. There are a few other so-called base classes that are good entry points for your component’s base-class. Mull over the following inheritance graph:

The items marked in yellow are some primitive types that the default Spark components derive from and are all great choices for your component. They do not provide any superfluous functionality. But you will often subclass other Spark components like Button, ComboBox, etc… It all depends on what your component does, what properties it needs, what if any events it needs to fire off. Choose wisely and as low as possible without going so low-level that you need to rewrite a bunch of functionality that another higher-level component would otherwise provide.

Tip #3 : hostComponent

From within the skin file, we have a powerful property for communicating with the class file, and that is “hostComponent”. This opens up many things for functionality. Here are some scenarios:

Property Binding

Let’s say you have a “text” property defined in your class file:

[cc lang=”actionscript3″ escaped=”true”]

[Bindable]
public var text:String;

[/cc]

In your skin file you have a label control that is meant to display the value of that property. Just do a simple binding:

[cc lang=”mxml” escaped=”true”]

<s:Label text=”{hostComponent.text}”/>

[/cc]

If instead of a read-only label it is a TextInput then you’ll need a two way binding (see the ‘@’ symbol):

[cc lang=”mxml” escaped=”true”]

<s:TextInput text=”@{hostComponent.text}”/>

[/cc]

There is absolutely no need to bring a reference to our TextInput into the class file. That is just messy, complicated, and breaks our nice decoupling that you can see above.

Method Calling

Let’s say that our component is for obtaining a file path and looks something like this:

When the “Browse” button is clicked we want to launch a file browser. So in our class file we create the handler method:

[cc lang=”actionscript3″ escaped=”true”]

public function browseButton_Clicked(event:MouseEvent):void
{
// code for showing the file browser
}
[/cc]

Then within our skin file we can hook the Button’s click event to the handler:

[cc lang=”mxml” escaped=”true”]

<s:Button click=”{hostComponent.browseButton_Clicked}”/>

[/cc]

BAM! Button clicked, button handled.

Passing in an Image

Here, our component needs an image that will be supplied or changed in the parent view. The example usage of the component, during its instantiation is like so:

[cc lang=”mxml” escaped=”true”]

<components:MyImaginaryControl source=”{this.someImage}” />

[/cc]

Our class file would define the “source” property which would be of type “Class”

[cc lang=”actionscript3″ escaped=”true”]

[Bindable]
public var source:Class;

[/cc]

In our Skin file, we would presumably have an Image control, so let’s bind it to the “source” property:

[cc lang=”mxml” escaped=”true”]

<s:Image source=”{hostComponent.source}”/>

[/cc]

INVISIBLE_TEXT

Passing in Arbitrary Content

In our ButtonHoverPop example above, we wanted to pass in arbitrary content that was meant to be displayed from within the skin. So in our class file, define the property:

[cc lang=”actionscript3″ escaped=”true”]

[Bindable]
public var popupContent:UIComponent;

[/cc]

Now we want to bind to that property from within the Skin file. The best way that I’ve found to achieve the desired result is below. I am still looking for a slightly cleaner implementation, but this does work very well:

[cc lang=”mxml” escaped=”true”]

<s:DataGroup>
<s:dataProvider>
<s:ArrayCollection source=”{ArrayUtil.toArray(hostComponent.popupContent)}” />
</s:dataProvider>
</s:DataGroup>

[/cc]

You’ll need to add an import statement for the ArrayUtil class, so in your skin file, also add this:

[cc lang=”mxml” escaped=”true”]

<fx:Script>
<![CDATA[
import mx.utils.ArrayUtil;
]]>
</fx:Script>

[/cc]

INVISIBLE_TEXT

Tip #3 : Custom States

One way of achieving certain functionality is through states. Flex Spark components have 2 different collections of states that are not synchronized by default. They are “Component States” and “Skin States”. Let’s start by taking a look at SkinStates using Spark’s default Button as an example.

SkinStates

Buttons have various Skin States: Pressed, Hovered, etc… Developers do not have access to setting these states programmatically. Instead the component switches the Skin States internally when the appropriate MouseEvent is triggered. This is an excellent example of SkinStates and their use, i.e. to be triggered upon some internal logic within the component which allows the visual appearance to be changed.

Other than changing SkinStates based on MouseEvents, some components will change SkinStates on property changes. For example, say we have a public property called: isInError that could be set externally and is meant to tell the component to display its visual error indication. That property could change the SkinState when its setter is called. Below is the class file.

[cc lang=”actionscript3″ escaped=”true”]

package components.custom
{
import spark.components.supportClasses.SkinnableComponent;

// Define SkinStates above the class definition
[SkinState(“normalState”)]
[SkinState(“errorState”)]

public class ErrorControl extends SkinnableComponent
{

// Private member variable
private var _isInError:Boolean;

// Public Accessors
[Bindable]
public function get isInError():Boolean
{
return _isInError;
}

public function set isInError( value:Boolean ):void
{
_isInError = value;
invalidateSkinState();
}

// Overridden method that gets called when invalidateSkinState() is called
override protected function getCurrentSkinState():String
{
if (_isInError)
return “errorState”;

return “normalState”
}

// Constructor
public function ErrorControl()
{
super();
this.setStyle(“skinClass”, ErrorControlSkin );
}
}
}

[/cc]

At the beginning of the code above, we are defining our custom SkinStates. This happens outside of the class definition and is essentially custom metadata on the class. Within the class we are defining a member variable called “_isInError” then we are defining the getter and setter for a public facing property. The key to updating the SkinState lies in calling “invalidateSkinState()” within the setter. As you can imagine, “invalidateSkinState()” triggers the skinStates to be updated. During that invalidation process, the method “getCurrentSkinState()” gets called to determine what should be the current SkinState. We are choosing to override that method in our class and returning the correct SkinState by checking the value of the _isInError variable.

Once the class is in place, we can implement our skin file to change the visual appearance as the skinState changes. Here is a super-simple example of how that could work:

[cc lang=”mxml” escaped=”true”]

<?xml version=”1.0″ encoding=”utf-8″?>
<s:Skin xmlns:fx=”http://ns.adobe.com/mxml/2009″
xmlns:s=”library://ns.adobe.com/flex/spark”>

<fx:Metadata>
[HostComponent(“components.custom.ErrorControl”)]
</fx:Metadata>

<s:states>
<s:State name=”normalState”/>
<s:State name=”errorState”/>
</s:states>

<s:Label text=”Good” text.errorState=”ERROR” color=”black” color.errorState=”red”/>

</s:Skin>

[/cc]

Within the skin file, we need to define the same states that we defined in the class. Once defined, we can do all of the typical things that we would want based on those states.

The result here is a component whose look and feel will change when the application sets the component’s isInError property.

Component States

The intended use of Component States, or just “States”, is much less defined. They provide an outward facing mechanism for doing something. But that something may not have anything to do with the skin or the visual appearance in general. It could change the visual appearance, but it could change something about the internal logic of the component just as easily.

Remember that the SkinStates we talked about above are not accessible outside of the component. States however are accessible and can be changed simply. For example, let’s say we have an instance of a custom Component with an id of “myComponent”. In the view where instance is instantiated, we could change the state like so:

[cc lang=”actionscript3″ escaped=”true”]
myComponent.currentState = “happyState”;
[/cc]

So what can we do when states change? Pretty much anything we want within the class definition. You could change properties, call methods, trigger events, perform logic, etc… This also includes updating the SkinState and therefore changing the visual appearance. So in this next example, we are going to rewrite our “ErrorControl”. This time, instead of changing the SkinState with a property change, we are going to change the SkinState during the component State change. So let’s look at the class file:

[cc lang=”actionscript3″ escaped=”true”]
package components.custom
{

import mx.events.StateChangeEvent;
import mx.states.State;
import spark.components.supportClasses.SkinnableComponent;

// Define SkinStates above the class definition
[SkinState(“normalState”)]
[SkinState(“errorState”)]

public class BrowseControl extends SkinnableComponent
{
// Constructor
public function BrowseControl()
{
super();
setStyle(“skinClass”, BrowseControlSkin );

// Define our “Component” states
states = [
new State({name:”normalState”}),
new State({name:”errorState”}) ];

currentState = “normalState”;

// listen to the stateChange event
addEventListener(StateChangeEvent.CURRENT_STATE_CHANGE, onStateChanged);
}

private function onStateChanged(event:StateChangeEvent):void
{
invalidateSkinState();
}

// Overridden method that gets called when invalidateSkinState() is called
override protected function getCurrentSkinState():String
{
return currentState;
}
}
}
[/cc]

In the code above, we are defining both SkinStates and Component States. We are giving them the same name, but this does not need to be the case, nor does it have any effect on how everything works. It does simplify one aspect of the code which I’ll get to in a moment.

Within the constructor, you can see that the “States” property is an Array of “State” objects. We are subscribing to the CURRENT_STATE_CHANGE event, and this is the key to synchronizing the SkinStates and States. When the State changes, our callback method named onStateChanged is called. This invalidates the SkinStates which in turn calls our overridden getCurrentSkinState method. Now, since our SkinStates and our States have the same name, we can just return the “currentState” property which is, conveniently, a string. If our SkinState and Component States had different names, then we would have some additional logic in the getCurrentSkinState override in order to choose the correct SkinState.

INVISIBLE_TEXT

Tip #4 : Referencing Skin Components (Parts) within Class File

At the start of this article I stressed that you should not reach into the skin and get access to its children from the class file. There are times when you will need to do this, just try to use this method very infrequently. It adds overhead to your component, it makes the component’s logic and visual appearance more tightly coupled, and it adds a bunch of junk to your Class file that in most cases can be avoided using some of the methods detailed above.

With that in mind let’s look at how it’s done. In Tip #3 above we looked at a file path browser control and showed how to trigger a method handler that lives in the class file from a button click in the skin file (The preferred method). It looked like this:

So let’s wire that browse button within the class file instead. You could get access to any child component within your skin with this method.

First define a Button within your class and decorate it with the “SkinPart” metadata. We are choosing to make this part required by the skin file:

[cc lang=”actionscript3″ escaped=”true”]
[SkinPart(required=”true”)]
public var browseButton:Button;
[/cc]

Then in our Skin, we need to place a Button control with the same name:

[cc lang=”mxml” escaped=”true”]

<s:Button id=”browseButton” label=”Browse”/>

[/cc]

That is all you need to do to get access to the button, but you will find that you cannot make use of it until the Skin’s Button is added to the control. This is where the partsAdded method comes into play. It gets called whenever a SkinPart is added to the Skin and associated with the component.

[cc lang=”actionscript3″ escaped=”true”]
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);

if (instance == browseButton)
{
browseButton.addEventListener(MouseEvent.CLICK, browseButton_Clicked);
}
}
[/cc]

So within this overridden method, we need to check if the part that triggered the method call was our browseButton and if so, add the appropriate event listener.

Here is the full code, minus any real functionality:

[cc lang=”actionscript3″ escaped=”true”]

Class File:

package components.custom
{
import flash.events.MouseEvent;
import spark.components.Button;
import spark.components.supportClasses.SkinnableComponent;

public class BrowseControl extends SkinnableComponent
{
// Define SkinPart for Browse Button
[SkinPart(required=”true”)]
public var browseButton:Button;

// Constructor
public function BrowseControl()
{
super();
setStyle(“skinClass”, BrowseControlSkin );
}

// partAdded Method override
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);

if (instance == browseButton)
{
browseButton.addEventListener(MouseEvent.CLICK, browseButton_Clicked);
}
}

public function browseButton_Clicked(event:MouseEvent):void
{
// code for showing the file browser
}
}
}

[/cc]

Skin File:
[cc lang=”mxml” escaped=”true”]
<?xml version=”1.0″ encoding=”utf-8″?>
<s:Skin xmlns:fx=”http://ns.adobe.com/mxml/2009″
xmlns:s=”library://ns.adobe.com/flex/spark” >

<fx:Metadata>
[HostComponent(“components.custom.BrowseControl”)]
</fx:Metadata>

<s:Button id=”browseButton” label=”Browse”/>

</s:Skin>
[/cc]

INVISIBLE_TEXT

Tip #5 : Important Overrideable Methods

So far we have mentioned and used two different overridden methods from the SkinnableComponent base class (or its ancestors). Those are partAdded and getCurrentSkinState. Let’s take a look at those again and some other important ones:

partAdded

Called when a skinPart is associated with the component. Used for getting access to children of the skin.

[cc lang=”actionscript3″ escaped=”true”]
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
}
[/cc]

getCurrentSkinState

Called when the skinStates are invalidated. Allows you to perform custom logic for determining what the skinState should be set to.

[cc lang=”actionscript3″ escaped=”true”]
override protected function getCurrentSkinState():String
{
return “normalState”;
}
[/cc]

commitProperties

Called just before the measure pass of the layout is called. There are many possible uses for this. One is if you need to perform something when the control changes size.

[cc lang=”actionscript3″ escaped=”true”]
override protected function commitProperties():void
{
super.commitProperties();
}
[/cc]

updateDisplayList

Called after Measure in the layout pass. The bounds of the control are passed into this method and can be used for many things. Custom layouts will have a lot of code here for placing the children. This method is also the location where you can do programmatic drawing.

[cc lang=”actionscript3″ escaped=”true”]
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
[/cc]

styleChanged

Whenever a style property is changed, this method gets called.

[cc lang=”actionscript3″ escaped=”true”]
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
}
[/cc]

INVISIBLE_TEXT

Tip #6 : ActionScript within the Skin

One last note. All of the actionScript that we have written above is in the class file not the skin file. There will certainly be times where you will write code in the skin file. I would suggest making sure that that code is appropriate and targets the look and feel of your component and not the component’s logic. Remember, we really want to separate the logic from the UI in all cases of development nowadays.