Adding Separators in a Spark List with Grouping

On December 20, 2010, in Flex, by Anuj Gakhar

I had this requirement recently come up at work, where I had to add separators to a List. The List was grouped by items of certain types and after each type of items, a separator was required. To give you a better idea of what I am talking about, I took this menu from my browser window (Firefox).

In the above menu, there are separators after “Web Search”, “Gears Settings” and so on…. this is the kind of thing I was trying to do. So, I will be working with the above menu in this post as well.

The approach I am going to take for this task is to create a custom Spark List that will be a subclass of List and will use the updateRenderer() function to determine if the item is the last item in the group or not. If the item is the last item in the group, then it would set a flag on the custom Item renderer and the renderer will then draw a horizontal separator only if the flag is set to true. Ofcourse, in this approach, the key is to have a field in the data which tells us how the items are grouped. Also, in order for the updateRenderer() function to be able to set the flag on the actual item renderer, I will be using an interface that the renderer will implement.

Let’s look at some code :-

The code for the main mxml file using the custom List is below. As you will see, I have created a simple VO/DTO called DataItem that holds 2 properties. type and label. type is the property that tells us how to group the items ie when to add a separator.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx"
			   minWidth="599" minHeight="600"
			   creationComplete="init()"
			   xmlns:components="com.anujgakhar.components.*">

	<fx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;

			[Bindable]
			public var dataset:ArrayCollection;

			private function init():void
			{
				dataset = new ArrayCollection();
				dataset.addItem(generateDataItem('search','Web Search'));
				dataset.addItem(generateDataItem('browsertools','Downloads'));
				dataset.addItem(generateDataItem('browsertools','Add-ons'));
				dataset.addItem(generateDataItem('browsertools','Gears Settings'));
				dataset.addItem(generateDataItem('devtools','Charles'));
				dataset.addItem(generateDataItem('devtools','Web Developer'));
				dataset.addItem(generateDataItem('devtools','Firebug'));
				dataset.addItem(generateDataItem('devtools','AMF Explorer'));
				dataset.addItem(generateDataItem('devtools','Error Console'));
				dataset.addItem(generateDataItem('devtools','FireFTP'));
				dataset.addItem(generateDataItem('devtools','Page Info'));
				dataset.addItem(generateDataItem('devtools','Dust-Me Selectors'));
				dataset.addItem(generateDataItem('browsing','Start Private Browsing'));
				dataset.addItem(generateDataItem('browsing','Clear Recent History'));
				dataset.addItem(generateDataItem('browsing','Pencil Sketching'));
			}

			private function generateDataItem(type:String, label:String):DataItem
			{
				var item:DataItem = new DataItem();
				item.type = type;
				item.label = label;
				return item;
			}
		]]>
	</fx:Script>
	<s:HGroup paddingTop="20" paddingLeft="20">
		<components:GroupedList
				id="menu"
				groupedItem="type"
				dataProvider="{dataset}"
				width="200" height="320"
				itemRenderer="GroupedListItemRenderer" />
	</s:HGroup>
</s:Application>

The DataItem VO/DTO looks like this :-

package
{
 public class DataItem
 {
 [Bindable] public var type:String;
 [Bindable] public var label:String;
 }
}

The Custom ItemRenderer (GroupedListItemRenderer) looks like this :-

<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
				xmlns:s="library://ns.adobe.com/flex/spark"
				xmlns:mx="library://ns.adobe.com/flex/mx"
				autoDrawBackground="false"
				implements="com.anujgakhar.components.IGroupedItemRenderer"
				xmlns:components="com.anujgakhar.components.*">
	 <fx:Script>
		<![CDATA[

			[Bindable]
			private var item:DataItem;

			[Bindable]
			private var _isLastItemInGroup:Boolean;

			public function set isLastItemInGroup(value:Boolean):void
			{
				_isLastItemInGroup = value;
			}

			override public function set data(value:Object):void
			{
				if(value && value is DataItem)
				{
					item = DataItem(value);
				}
			}
		]]>
	</fx:Script>

	<s:layout>
		<s:VerticalLayout gap="3"/>
	</s:layout>

	<s:Label text="{item.label}" width="100%"
			 verticalAlign="middle"
			 horizontalCenter="0" height="20" paddingLeft="5" paddingTop="2"/>

	<components:HDivider
		width="100%"
		includeInLayout="{_isLastItemInGroup}"
		visible="{_isLastItemInGroup}"/>
</s:ItemRenderer>

You will notice that this ItemRenderer implements the Custom Interface IGroupedItemRenderer which is simply forcing the ItemRenderer to implement a set isLastItemInGroup() function.

package com.anujgakhar.components
{
	import mx.core.IDataRenderer;

	public interface IGroupedItemRenderer extends IDataRenderer
	{
		function set isLastItemInGroup(value:Boolean):void;
	}
}

And finally, the Custom GroupedList component :-

package com.anujgakhar.components
{
	import mx.core.IVisualElement;

	import spark.components.List;

	public class GroupedList extends List
	{
		public function GroupedList()
		{
			super();
		}

		[Bindable]
		public var groupedItem:String;

		override public function updateRenderer(renderer:IVisualElement, itemIndex:int,
												data:Object):void
		{
			if (renderer is IGroupedItemRenderer)
			{
				var lastItemInGroup:Boolean = false;
				if (dataProvider.length > (itemIndex + 1))
				{
					var thisItem:String = dataProvider.getItemAt(itemIndex)[groupedItem];
					var nextItem:String = dataProvider.getItemAt(itemIndex + 1)[groupedItem];
					if (thisItem != nextItem)
					{
						lastItemInGroup = true;
					}

				}
				IGroupedItemRenderer(renderer).isLastItemInGroup = lastItemInGroup;
			}

			super.updateRenderer(renderer, itemIndex, data);
		}
	}
}

And this is how the final product looks like. View source is enabled so you can right click and view source for this if you wish to. Please note that in this example, I have not tried to mimick the look n feel of the original menu I used so there is pretty much no skinning/styling on this.

[SWF]/wp-content/listdemo/SparkMenu.swf, 390, 390[/SWF]

 

P.S.  A reader has posted a Romanian translation of this blog post here. http://webhostinggeeks.com/science/adding-separators-ro

Tagged with:  

3 Responses to Adding Separators in a Spark List with Grouping

  1. Koen says:

    Hi great article, but the source of the HDivider seems to be lost in translation 🙂

  2. I must say that I did have a hard time adding separators in the spark list along with grouping a while back. I guess starting off with a custom Spark List was a brilliant move from your part since it automatically creates a subclass of the list thereby using the function of updaterender to conclude the position of the item. Furthermore, the user interface that you have employed here very much helps the whole function to get implemented.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 445 other subscribers

© 2011 Anuj Gakhar
%d bloggers like this: