I thought I’d share a couple small classes that I had to put together recently for a project.

The goal was to create a list of toggle buttons but that would be laid side by side and then wrap at the end of the line. This allows for a really nice multiple selection component in contrast with having just a long list of checkboxes.

We extending the Spark List component by creating the following class:

package controls {
 
	import layouts.FloatLayout;
	import flash.events.MouseEvent;
	import mx.core.IVisualElement;
	import spark.components.IItemRenderer;
	import spark.components.List;
 
	public class ToggleList extends List {
 
		public var itemColors:Array = [0xcccccc, 0x555555, 0x000000, 0x000000, 0x000000, 0xffffff];
 
		public function ToggleList () {
			super();
 
			setStyle('borderVisible', false);
			setStyle('contentBackgroundAlpha', 0);
 
			minHeight = 20;
 
			allowMultipleSelection = true;
 
			// layout
			layout = new FloatLayout();
			(layout as FloatLayout).horizontalGap = 1;
			(layout as FloatLayout).verticalGap = 1;
		}
 
		//----------------------------------
		//  dragEnabled
		//----------------------------------
 
		override public function set dragEnabled (value:Boolean):void {}
 
		//----------------------------------
		//  dragMoveEnabled
		//----------------------------------
 
		override public function set dragMoveEnabled (value:Boolean):void {}
 
		//----------------------------------
		//  dropEnabled
		//----------------------------------
 
		override public function set dropEnabled (value:Boolean):void {}
 
		//----------------------------------
		//  hasValue
		//----------------------------------
 
		public function get hasValue ():String {
			var v:String = (selectedIndices.length > 0) ? 'yes' : undefined;
			return v;
		}
 
		//----------------------------------
		//  selectedIndices
		//----------------------------------
 
		private var _maxSelectedIndices:uint = 0;
 
		public function get maxSelectedIndices ():uint {
			return _maxSelectedIndices;
		}
 
		public function set maxSelectedIndices (value:uint):void {
			if (value == _maxSelectedIndices) return;
 
			if (value == 1) {
				allowMultipleSelection = false;
			}
 
			_maxSelectedIndices = value;
		}
 
		//----------------------------------
		//  handlers
		//----------------------------------
 
		override protected function item_mouseDownHandler (event:MouseEvent):void {
			event.ctrlKey = true;
 
			if (_maxSelectedIndices == 0) {
				super.item_mouseDownHandler(event);
			} else {
				if (selectedIndices.length < _maxSelectedIndices) {
					super.item_mouseDownHandler(event);
				} else {
					var newIndex:int;
					if (event.currentTarget is IItemRenderer) newIndex = IItemRenderer(event.currentTarget).itemIndex;
					else newIndex = dataGroup.getElementIndex(event.currentTarget as IVisualElement);
 
					if (allowMultipleSelection && (selectedIndices != null)) {
						if (selectedIndices.indexOf(newIndex) != -1) super.item_mouseDownHandler(event);
					} else {
						if (newIndex == selectedIndex) super.item_mouseDownHandler(event);
					}
				}
			}
		}
 
	}
 
}

As you can see there’s not much going on here except that we’re initializing the component with allowMultipleSelection and we’re setting its layout to be of type FloatLayout. Apart from that I’ve also added a function called hasValue which we can use with validators.

For this ToggleList component we will use the following custom ItemRenderer:

 
		<![CDATA[
 
			private var _data:XML;
 
			[Bindable] private var _normalColor:Number = 0xcccccc;
			[Bindable] private var _hoveredColor:Number = 0x555555;
			[Bindable] private var _selectedColor:Number = 0x000000;
			[Bindable] private var _textColor:Number = 0x000000;
			[Bindable] private var _textColorHovered:Number = 0x000000;
			[Bindable] private var _textColorSelected:Number = 0xffffff;
 
			[Bindable] private var _label:String;
 
			override public function set data (value:Object):void {
				super.data = value;
 
				if (super.data) {
					if (ToggleList(owner).itemColors.length == 6) {
						_normalColor = ToggleList(owner).itemColors[0];
						_hoveredColor = ToggleList(owner).itemColors[1];
						_selectedColor = ToggleList(owner).itemColors[2];
						_textColor = ToggleList(owner).itemColors[3];
						_textColorHovered = ToggleList(owner).itemColors[4];
						_textColorSelected = ToggleList(owner).itemColors[5];
					}
					_data = value as XML;
					if (_data &#038;& _data.hasSimpleContent()) _label = _data.@label;
				}
			}
 
			override protected function updateDisplayList (unscaledWidth:Number, unscaledHeight:Number):void {
				super.updateDisplayList(unscaledWidth, unscaledHeight);
			}
		]]>
 
	<!-- border -->
 
	<!-- fill -->

The ToggleListItemRenderer is very simple. It has some colors that can be customized if you want and assumes you’re using xml as the List’s DataProvider but you can change it to fit your needs, what’s important is that you can toggle it.

Now, the FloatLayout class:

package layouts {
 
	import mx.core.ILayoutElement;
	import mx.core.IVisualElement;
 
	import spark.components.supportClasses.GroupBase;
	import spark.layouts.supportClasses.LayoutBase;
 
	public class FloatLayout extends LayoutBase {
 
		/** @private */ private var _padding:Number = 0;
		/** @private */ private var _horizontalGap:Number = 0;
		/** @private */ private var _verticalGap:Number = 0;
 
		/** @private */ private var _rowCount:int = 0;
		/** @private */ private var _elementCount:int = 0;
 
		/** @private */ private var _width:int = 0;
		/** @private */ private var _height:int = 0;
 
		//----------------------------------
		//  padding
		//----------------------------------
 
		public function set padding (val:Number):void {
			_padding = val;
			var layoutTarget:GroupBase = target;
			if (layoutTarget) {
				layoutTarget.invalidateDisplayList();
			}
		}
 
		//----------------------------------
		//  horizontalGap
		//----------------------------------
 
		public function set horizontalGap (val:Number):void {
			_horizontalGap = val;
			var layoutTarget:GroupBase = target;
			if (layoutTarget) {
				layoutTarget.invalidateDisplayList();
			}
		}
 
		//----------------------------------
		//  verticalGap
		//----------------------------------
 
		public function set verticalGap (val:Number):void {
			_verticalGap = val;
			var layoutTarget:GroupBase = target;
			if (layoutTarget) {
				layoutTarget.invalidateDisplayList();
			}
		}
 
		//----------------------------------
		//  overrides
		//----------------------------------
 
		override public function updateDisplayList (containerWidth:Number, containerHeight:Number):void {
			var layoutTarget:GroupBase = target;
			if (!layoutTarget) return;
 
			var xPos:Number = _padding;
			var yPos:Number = _padding;
			var maxWidth:Number = 0;
			var maxHeight:Number = 0;
			var rowMaxHeight:Number = 0;
 
			// loop through all the elements
			var count:int = layoutTarget.numElements;
 
			_rowCount = (count &gt; 0) ? 1 : 0;
			_elementCount = count;
 
			for (var i:int = 0; i &lt; count; i++) {
				var element:ILayoutElement = null; 
				if (useVirtualLayout) {
					element = layoutTarget.getVirtualElementAt(i);
					if (element is IVisualElement) IVisualElement(element).visible = true; 
				} else {
					element = layoutTarget.getElementAt(i);
				}
				if (!element || !element.includeInLayout) continue;
 
				// resize the element to its preferred size
				element.setLayoutBoundsSize(NaN, NaN);
				var elementWidth:Number = element.getLayoutBoundsWidth();
				var elementHeight:Number = element.getLayoutBoundsHeight();
 
				// doesn't fit, move to next line
				if (xPos + elementWidth + _padding &gt; containerWidth) {
					_rowCount++;
					xPos = _padding; // reset the x value
 
					// move to the next line and add the gap, but not if it's the first element
					if (i &gt; 0) {
						yPos += rowMaxHeight + _verticalGap;
						rowMaxHeight = 0; // new row, so reset max height of this row
					}
				}
 
				// position the element
				element.setLayoutBoundsPosition(xPos, yPos);
 
				// update max dimensions (needed for scrolling)
				maxWidth = Math.max(maxWidth, xPos + elementWidth);
				maxHeight = Math.max(maxHeight, yPos + elementHeight);
 
				// update the current pos, and add the gap
				xPos += elementWidth + _horizontalGap;
 
				// update the max height of current row as necessary
				if (elementHeight &gt; rowMaxHeight) {
					rowMaxHeight = elementHeight;
				}
			}
 
			_width = Math.ceil(maxWidth + _padding);
			_height = Math.ceil(maxHeight + _padding);
 
			// make the list the same size as the content
			clipAndEnableScrolling = false;
			layoutTarget.measuredHeight = _height;
			layoutTarget.height = _height
 
			updateScrollRect(_width, _height);
 
			// set final content size (needed for scrolling)
			layoutTarget.setContentSize(_width, _height);
		}
 
	}
 
}

The hard part, for some reason, was getting the list’s content height to be set to the layout’s height, sometimes it failed but with the code in the last lines I was able to do it and haven’t seen it fail yet. Maybe someone can improve on it.

The end result should look something like this:

Multiple Select List