Home  >  

Custom SortableList Component using Flex 4

Author photo
| | Comments (2)
AddThis Social Bookmark Button

Introduction

Recently, I decided to get my hands dirty with the latest build of Flex 4 SDK and Flash Builder 4. The new IDE has a lot of really exciting new features that will save developers a lot of time. The features that excite me the most are the new Spark components and skinning capabilities in the new SDK.

Towards the beginning of the year I had read through the first few drafts of the new Spark component architecture, the style changes and how easy it is to skin components. Lots of things have changed since then, so I decided to refresh my memory and build a simple component to test my knowledge.

Behold the SortableList component.

Custom Component

The component extends spark.components.List and adds a spark.components.Button as a required skin part to sort the list items. The metadata tag [SkinPart(required="true")] above the header declaration means that the particular item must be declared in the associated skin file.


package com.thoughtfaqtory.components.list
{
 	[SkinState("normal")]
 	[SkinState("disabled")]
 
 	public class SortableList extends List
 	{	
  		private const SORT_ASCENDING:int = 0;
  	
  		private const SORT_DESCENDING:int = 1;
  	
  		private var _sorting:int = SORT_DESCENDING;
  	
  		private var _sort:Sort;
  	
  		private var _sortField:String;
  
  		[SkinPart(required="true")]
  		public var header:Button;
  	}
}

There are two methods that need to be overridden, one being partAdded() and the other partRemoved(). These two methods inform you when the skin part has been added and removed. This is where you should take the opportunity to push any data you want down into the skin part and hook up any event listeners. On the reverse, when the parts get removed do the opposite.

 

package com.thoughtfaqtory.components.list
{
 	[SkinState("normal")]
 	[SkinState("disabled")]
 
 	public class SortableList extends List
 	{
  		override protected function partAdded(partName:String, instance:Object) : void
  		{
   			super.partAdded(partName, instance);
   	
   			if (instance == header)
   			{
    				header.addEventListener(MouseEvent.CLICK, header_clickHandler);
    			}
   		}	
  		
  		override protected function partRemoved(partName:String, instance:Object) : void
  		{
   			super.partRemoved(partName, instance);	
   	
   			if (instance == header)
   			{
    				header.removeEventListener(MouseEvent.CLICK, header_clickHandler);
    			}
   		}
  	}
}

I added an event listener on the header instance to manage the sorting of the list when a user interacts with the button. This in turn calls a private method sortList() to sort the list in ascending and descending order.


package com.thoughtfaqtory.components.list
{	
 	[SkinState("normal")]
 	[SkinState("disabled")]
 
 	public class SortableList extends List
 	{
  		protected function header_clickHandler(event:MouseEvent):void
  		{
   			sortList();
   		}
  		
  		private function sortList():void
  		{
   			var dp:ICollectionView = this.dataProvider as ICollectionView;
   
   			this._sort.fields = [new SortField(this._sortField)];
   			
   			if (dp != null)
   			{
    				if (this._sorting == SORT_ASCENDING)
    				{
     					this._sort.reverse();
     					this._sorting = SORT_DESCENDING;
     				}
    				else
    				{
     					this._sorting = SORT_ASCENDING;
     				}
    				
    				dp.sort = this._sort;
    				dp.refresh();
    			}
   		}
  	}
}

The method gets a reference to the data provider as an mx.collections.ICollectionView, sets the field property of the _sort instance to an array of one mx.collections.SortField item. Then follows a few checks to make sure the dataProvider is not null and whether to sort in ascending or descending order. Once all the checks have been done the dataProvider sort property is set and refreshed to reflect the changes on the view.

The next step is to create the skin file (SortableListSkin.mxml) that will be associated with the SortableList class. The custom skin is very similar to the default spark.components.List skin but there is an extra button for the sorting functionality. The button must be declared in the skin with an id of header to adhere to the skinning contract. If the required property of the metadata tag were set to false then this would not be the case. Note that the HostComponent metadata points to the component you are skinning and must be used to access if you require to access the component from the skin.


<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark"
	alpha.disabled="0.5">
	
	<fx:Metadata>
		[HostComponent("com.thoughtfaqtory.components.list.SortableList")]
	</fx:Metadata>
	
	<s:states>
		<s:State name="normal" />
		<s:State name="disabled" />
	</s:states>
	
	<!-- border -->
	<s:Rect left="0" right="0" top="19" bottom="0">
		<s:stroke>
			<s:SolidColorStroke color="0x686868" weight="1"/>
		</s:stroke>
	</s:Rect>

	<!-- fill -->
	<!--- Defines the background appearance of the list-based component. -->
	<s:Rect id="background" left="1" right="1" top="20" bottom="1">
		<s:fill>
			<s:SolidColor color="#EEEEEE" />
		</s:fill>
	</s:Rect>

	<s:Scroller id="scroller"
		left="0"
		top="19"
		right="0"
		bottom="0"
		minViewportInset="1"
		focusEnabled="false">

		<!--The container for the data items.-->
		<s:DataGroup id="dataGroup" itemRenderer="spark.skins.spark.DefaultItemRenderer">
			<s:layout>
				<s:VerticalLayout gap="0" horizontalAlign="contentJustify" />
			</s:layout>
		</s:DataGroup>
	</s:Scroller>

	<s:Button id="header" label="Sort the List" left="0" right="0" height="20"	  skinClass="com.thoughtfaqtory.components.skins.HeaderButtonSkin" />
	
</s:SparkSkin>

There is one more skin file (HeaderButtonSkin.mxml) required for the header property of the CustomSortableList component. The header property is of type spark.components.Button and allows the user to sort the list component. I tried to make the header look and feel similar to the headers used within the data grid Halo component.

 

<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009"
	xmlns:s="library://ns.adobe.com/flex/spark"
	xmlns:mx="library://ns.adobe.com/flex/halo"
	alpha.disabled="0.5">
	
	<fx:Metadata>
		[HostComponent("spark.components.Button")]
	</fx:Metadata>
	
	<s:states>
		<s:State name="up" />
		<s:State name="over" />
		<s:State name="down" />
		<s:State name="disabled" />
	</s:states>
	
	<!-- UP SKIN-->
	<s:Rect left="0" right="0" top="0" bottom="0" includeIn="up">
		<s:fill>
			<s:LinearGradient rotation="90">
				<s:GradientEntry color="0xFFFFFF" 
								 alpha="0.85" />
				<s:GradientEntry color="0xD8D8D8" 
								 alpha="0.85" />
			</s:LinearGradient>
		</s:fill>
	</s:Rect>
	
	<!-- layer 3: fill lowlight -->
	<s:Rect left="0" right="0" bottom="0" height="9" includeIn="up">
		<s:fill>
			<s:LinearGradient rotation="90">
				<s:GradientEntry color="0x000000" alpha="0.0099" />
				<s:GradientEntry color="0x000000" alpha="0.0627" />
			</s:LinearGradient>
		</s:fill>
	</s:Rect>
	
	<!-- layer 4: fill highlight -->
	<s:Rect left="0" right="0" top="0" height="9" includeIn="up">
		<s:fill>
			<s:SolidColor color="0xFFFFFF" 
						  alpha="0.33" />
		</s:fill>
	</s:Rect>
	
	<!-- Over SKIN-->
	<s:Rect left="0" right="0" top="0" bottom="0" includeIn="over">
		<s:fill>
			<s:SolidColor color="#8EB3E7"
						  alpha="0.5"/>
		</s:fill>
	</s:Rect>
	
	<!-- DOWN SKIN-->
	<s:Rect left="0" right="0" top="0" bottom="0" includeIn="down"
		<s:fill>
			<s:SolidColor color="#8EB3E7" />
		</s:fill>
	</s:Rect>
	
	<s:Rect left="0" right="0" bottom="0" top="0" includeIn="up, down, over">
		<s:stroke>
			<s:SolidColorStroke color="#686868" weight="1"/>
		</s:stroke>
	</s:Rect>
	
	<!-- Text SKIN-->
	<s:SimpleText id="labelDisplay"
		textAlign="center"
		verticalAlign="middle"
		maxDisplayedLines="1"
		horizontalCenter="0"
		verticalCenter="1"
		left="10"
		right="10"
		top="2"
		bottom="2">
	</s:SimpleText>
	
</s:SparkSkin> 

The skin contains four states, up, down, over and disabled. You will notice a big difference in how states work in Flex 4. You no longer have to define your states within a nested state tag. This can become very messy and unreadable by most developers especially if it is not your code.

So the Flex engineers came up with a much cleaner way to handler states. Firstly your will notice at the top of the skin file (HeaderButtonSkin.mxml) the property “alpha.disabled = 0.5”, which basically is property.state = the value within that state. So you may have guessed that when the header and or component is disabled the value will be 0.5.

Also included in the new state syntax is the includeIn and excludeFrom attributes, which is available on every MXML component except the following:

  • The root tag of an MXML document, such as the Application tag or the root tag in an MXML component.
  • Tags that represent properties of their parent tag. For example, the tag of the tag.
  • Descendants of the <fx:XML>, <fx:XMLList>, or <fx:Model> tags.
  • Any language tags, such as the <fx:Binding>, <fx:Declarations>, <fx:Metadata>, <fx:Script>, and <fx:Style> tags.

Note that includeIn and excludeFrom attributes are treated as reserved language keywords by the MXML compiler.

One last thing regarding the new state syntax, the includeIn and excludeFrom attributes accept a comma delimited list of state names e.g. includeIn="up, down, over". All of the states must have been declared within the documents states array.

The last item declared in the header skin is a SimpleText component, which displays the label for the button. This is required and part of the skinning contract discussed earlier in the article. The host component (spark.components.Button) knows that the component with an id labelDisplay is the label.

Conclusion

Skinning in Flex 4 Beta has gone through a much needed change since Flex 2 and 3. There is no need to create your skins in ActionScript anymore and does not require in-depth knowledge of the Flash drawing API. Thanks to the introduction of FXG and the new states syntax, this is now possible.

This example was created using the Adobe Flex 4.0.0.8847 SDK build available from http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4

Source code

Read more from Simon Barber. Simon Barber's Atom feed spbarber on Twitter

Comments

2 Comments

Flex 4 (Gumbo) nightly build (build 5101 or later) and compile an application, you will probably notice that the look of your application may have completely changed. Recently, new spark skins were implemented for all of our Halo Flex 3 components. This includes a new “Spark” skin for components like DataGrid, DateChooser, DateField, TabNavigator etc, which do not have equivalent components in the new Gumbo architecture.

julien said:

Very nice example thanks for sharing,

Leave a comment


Tag Cloud

Question of the Week: Dream App

If you had an unlimited budget and unlimited resources what application would you build and why would you build it?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.