Specialized Collections

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.