Does a particular feature have you 'living large' in for-loops? Do you spend more time in the day typing 'for each (blah... blah..' than you do brushing your teeth in the morning? If you answered yes to these questions... implementing a specialized collection may help.
First let's start with the object of our passion... the "Thing" object. Get it? object... thing... ok - never mind. This little guy doesn't do much - but we want to know when he's selected or not. The event business will become apparent as we take a look at the ThingCollection.
/**
* @author Rick Winscot
*/
package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import mx.core.IUID;
import mx.utils.UIDUtil;
/**
* Dispatched when the selected property is changed.
*
* If the value of the selected property is true
* and the value true is passed to the property - the event
* will not be dispatched.
*
* @eventType selectedChange
*/
[Event(name="selectedChange", type="flash.events.Event")]
/**
* The meta-object used as an 'item' in a ThingCollection.
*/
public class Thing extends EventDispatcher implements IUID
{
/**
* Constructor.
*/
public function Thing()
{
super();
uid = UIDUtil.createUID();
}
[Bindable]
/**
* @private
*/
private var _uid:String = "";
/**
* The UID of the object.
*
* @default A system generated UID
*/
public function get uid():String
{
return _uid;
}
public function set uid( value:String ):void
{
_uid = value;
}
[Bindable(event="selectedChange")]
/**
* @private
*/
private var _selected:Boolean = false;
/**
* The property that we are interested in and will be watching.
*
* @default false
*/
public function get selected():Boolean
{
return _selected;
}
public function set selected( value:Boolean ):void
{
if ( value == _selected )
return;
_selected = value;
dispatchEvent( new Event( "selectedChange" ) );
}
}// end Thing
}// end package
The ThingCollection is a specialized collection that derives from the ArrayCollection. As such - it has the ability to serve as a data provider for grids, lists, and whatnot. Cool right? Not really... we still have a problem with 'seeing' updates to items within the collection. Remember that collection events only get fired when items are added or removed? Ah! So... if you change a property on one of the items - nothing happens.
This is the root-cause behind developers leaning on 'fruity loops of doom' - so they can keep track of [insert-property-here]. There is another way to do it... implement a specialized collection for your Things.
/**
* @author Rick Winscot
*/
package
{
import flash.events.Event;
import mx.collections.ArrayCollection;
import mx.events.CollectionEvent;
/**
* Storage class for items of type Thing.
*/
public class ThingCollection extends ArrayCollection
{
/**
* Constructor.
*/
public function ThingCollection( source:Array = null )
{
super( source );
}
/**
* @private
*/
private var _selectedItems:int = 0;
[Bindable(event="collectionChange")]
/**
* The total number, as an int, of Things that are
* currently marked as selected within the collection.
*/
public function get selectedItems():int
{
return _selectedItems;
}
/**
* @private
* Used to update the number of selected Things within the collection. Handles
* add, remove, and update.
*/
private function updateSelecedCount( item:Object, remove:Boolean = false ):void
{
if ( item is Thing )
{
if ( remove == true )
{
if ( item.selected == true )
_selectedItems -= 1;
removeEventListeners( item );
}
else
{
if ( item.selected == true )
_selectedItems += 1;
addEventListeners( item );
}
}
}
/**
* @inheritDoc
*/
override public function addItem(item:Object):void
{
if ( item is Thing )
super.addItem( item );
}
/**
* @inheritDoc
*/
override public function addItemAt( item:Object, index:int ):void
{
if ( item is Thing )
{
updateSelecedCount( item );
super.addItemAt( item, index );
}
}
/**
* @inheritDoc
*/
override public function removeItemAt( index:int ):Object
{
var target:Object = getItemAt( index );
updateSelecedCount( target, true );
return super.removeItemAt( index );
}
/**
* @inheritDoc
*/
override public function setItemAt( item:Object, index:int ):Object
{
var result:Object;
if ( item is Thing )
{
var target:Object = getItemAt( index );
updateSelecedCount( target, true );
updateSelecedCount( item );
result = super.setItemAt( item, index );
}
return result;
}
/**
* @inheritDoc
*/
override public function removeAll():void
{
for each ( var t:Thing in this )
this.removeEventListeners( t );
_selectedItems = 0;
super.removeAll();
}
/**
* @private
* Adds an event handler to the new item|Thing so that changes to
* the selected property get propogated to the collection.
*/
private function addEventListeners( item:Object ):void
{
if ( item.hasEventListener( "selectedChange" ) == false )
item.addEventListener( "selectedChange", handleThingEvent );
}
/**
* @private
* Removes, as a clean-up step, the event handler for selection
* changed events for existing items|Things.
*/
private function removeEventListeners( item:Object ):void
{
if ( item.hasEventListener( "selectedChange" ) == true )
item.removeEventListener( "selectedChange", handleThingEvent );
}
/**
* @private
* Handles the selectedChange event for a Thing. The selected propery
* has already been changed at this point.
*/
private function handleThingEvent( event:Event ):void
{
if ( event.currentTarget is Thing && event.type == "selectedChange" )
{
var t:Thing = event.currentTarget as Thing;
if ( t.selected == true )
_selectedItems += 1;
else
_selectedItems -= 1;
dispatchEvent( new CollectionEvent( CollectionEvent.COLLECTION_CHANGE ) );
}
}
}// end ThingCollection
}// end package
This is overkill right? It depends... if you are looking for a way to get a little better performance out of storage classes (collections) this is a fair way to do it.