Building a Custom Spark Component for Adobe Flex

Gears

Its taken me a while but I believe I have started to understand the new skinning architecture in Adobe Flex 4 and this blog article describes how to build a reusable custom component taking advantage of the architecture.

This custom component is simply a useful loading animation along with an optional loading message.  I often find myself loading remote data (usually from Domino) into multiple panels within an application and its good UI practice to give feedback to the user that the application is doing something.  Normally I would change the cursor to a busy indicator which is ok but not ideal, especially if your loading multiple panels with different datasets.

The aim is to have a custom component which will indicate something is loading for that specific pane so if you have multiple panes loading different data they will each have there own indicator.  The component will work with any standard Flex Spark container (Panel, Group etc).

Heres a quick demo so you can see in advance what the end result is (view source enabled).

The demo simulates the loading of data by using 2 buttons to kick off a load event and data loaded event.  Obviously these 2 events are normally controlled by your application and I would recommend looking at AS3 signals & Robotlegs as good frameworks for controlling this process.

Building the Component – first the state logic.

Our component needs to have 3 states – the default state, the loading data state and the data loaded state.

This state information needs to be passed to the skin so it can change its appearance basedon these states.

The component will consist of a simple custom component built in actionscript which will be responsible for passing the state information to the skin as well as having an optional loading title.  The other element will be the Skin file which is responsible for how the component looks in the 3 different states.

The actionscript component extends the SkinnableContainer which means we can wrap the component around any standard Flex container e.g.

skinClass=”shinydesign.skin.LoadingPaneSkin” id=”loadingPanel” height=”300” width=”400” loadingTitle=”Loading Demo Data>

left=”5” right=”5” top=”5” bottom=”5” cornerRadius=”5” backgroundColor=”0xcccccc>

We need some extra custom states in our component and the skin needs to know about them, to do this you add some meta data to the top of the class, in this case I have added 2 new states – loading and dataLoaded.

[SkinState(“loading”)]

[SkinState(“dataLoaded”)]

publicclass LoadingPane extends SkinnableContainer

If you were building your component in MXML you would do the following

> [SkinState("loading")] [SkinState("dataLoaded")] >

Next we have some properties which are used to set the current state.  NB you don’t use the normal method of setting a state component.currentState=  instead you use the setter on the desired property, in this case component.loading=true.

publicfunctionset loading(value:Boolean):void

{

if(_loading !=value){

_loading=value;

_dataLoaded=false;

invalidateSkinState();

}

}

This method first checks to see if the value is different then sets it if it is, it then changes the dataLoaded property to false (because we are now loading in new data) and finally it calls the standard invalidateSkinState method.  This methods forces the skin to reevaluate its skin state.

When the skin does its evaluation it will call the standard getCurrentSkinState method on the host component, therefore we override it so we can return the state value we want.

overrideprotectedfunction getCurrentSkinState():String {

if(dataLoaded)

return‘dataLoaded’;

if(loading)

return‘loading’;

returnsuper.getCurrentSkinState();

}

In this case if dataLoaded = true then we return the string ‘dataLoaded’, if loading = true we return ‘loading’.

Optional Loading Title

To allow a developer to use there own custom loading messages we need to have a custom skin part in the component – in this case we have made the skin part optional.

[SkinPart(required="false")]

publicvar LoadingLabel:Label;

We have a string property -loadingTitle on the compoent which is used to set the text value on our LoadlingLabel – this property can be set either through MXML or through actionscript.

loadingTitle=”Loading Demo Data>

dataLoadingPane.loadingTitle=“Loading Demo Data”

We then override the event partAdded to check to see when the LoadingLabel is added to the Skin – at this point we can set the text property of the label.  NB This is one way of doing this there are other ways.

//Add the loading text if the label is being added to the skin

overrideprotectedfunction partAdded(partName:String, instance:Object):void {

super.partAdded(partName, instance);

if (instance == LoadingLabel) {

LoadingLabel.text=loadingTitle;

}

}

The Skin

The Skin is a separate file which holds the UI for our component.  The idea being this separation of logic and UI means I could easily apply a different skin if I wanted to.

The Skin itself needs to be based on our custom component and we need to add our additional custom states (NB normal and disabled are the default inherited states).

[HostComponent(“shinydesign.container.LoadingPane”)]


name=”normal/>

name=”disabled/>

name=”loading/>

name=”dataLoaded/>

I then added a Label for the loading title – this ties uo with the optional SkinPart in the Custom Component – LoadingLabel – NB The ID of the label must match the ID in the custom component.

The actual loading graphic is a small Flash Movie – We are using the Flash Player already so why not?  You use a SWFLoader to load a Flash Movie within a Flex Application.  NB the movie has already been embedded within the Flex Application and is not loaded from an external source.

[Embed(source=“shinydesign/assets/GearsSmallMovie.swf”)]

publicstaticconst Gears:Class;

source=”{Gears}” width=”100” height=”100” id=”loadingGraphic”  alpha.dataLoaded=”0” alpha.loading=”1/>

The contentGroup Group is the standard area that is used by Flex to display any content which is within the container.

i.e. in this case the BorderContainer component will be within the contentGroup Group.

skinClass=”shinydesign.skin.LoadingPaneSkin” id=”loadingPanel” height=”300” width=”400” loadingTitle=”Loading Demo Data>

left=”5” right=”5” top=”5” bottom=”5” cornerRadius=”5” backgroundColor=”0xcccccc>

The Skin Effects

Like any good Flex application there is a subtle use of effects to cause a Fade between the loading graphic and the actual content.

This is done by the use of State Transistions.

fromState=”loading” toState=”dataLoaded>

target=”{contentGroup}” startDelay=”300/>

target=”{loadingThrobber}/>

fromState=”*” toState=”loading>

target=”{contentGroup}” startDelay=”300/>

target=”{loadingThrobber}/>

In my case I have targeted when the state is changed from loading to dataLoaded and from normal to loading.

The recommend method is to put the actual property values that will be changed within the component and just use the transitions to trigger the change.  You can see an example here.

id=”contentGroup” alpha.loading=”0” alpha.dataLoaded=”1” left=”0” right=”0” top=”0” bottom=”0” minWidth=”0” minHeight=”0” includeIn=”dataLoaded>

In this case I have set the alpha property for the loading state to be 0 on the contentGroup (in other words hidden) but for dataLoaded its set to 1.  Because its a fade transistion it will animate between the 2 alpha values.

The syntax for this is PROPERTY.STATENAME

An important extra is the startDelay property of the transistion.  I used this to force a consistent minimum delay before the loading graphic is faded away.

If you don’t do this then when the data loads very quickly you can end up with jarring / blinking effect.  It seems slightly strange to slow down the display of the data but in the end the experience is much smoother.

Grab the Source

The SWC & source code is available here on GitHub.