Monthly Archives: February 2012

… Only one, but the Collection has to want to change

Using an ArrayCollection as the data provider for a list (or any display object) is easy and powerful. Somehow, collection variables fire change events and update their binding targets not only when the whole list variable changes, but also when any element in that list changes. Lists and Grids (which love ArrayCollections) explicitly listen for COLLECTION_CHANGE events from inside their dataProvider as well as wholesale changes.

Note that only the dataProvider seems to do this: binding an ArrayCollection to any other property will not automatically get notifications when a collection item changes (although binding to arrayCollection.length will). If you use BindingUtils or variable injection to a mutator, it will only get updated when the whole value changes, not when an item changes.

Given that the elements know nothing about the Collection containing them (indeed, an object can be contained by several Collections and Arrays simultaneously), and the Collection cannot require that elements descend from some sort of CollectionItem ancestor, or implement a specific ICollectionItem interface, or even call a method in the Collection when they change, the only way the Collection can monitor elements is if it takes advantage of a protocol that most objects do anyway: the Bindable/PROPERTY_CHANGE event.

The ArrayCollection does not, despite appearances, wrap an Array; it wraps an ArrayList that wraps an Array. The ArrayList monitors every element in its array by adding a listener for PropertyChangeEvent.PROPERTY_CHANGE events on every array element that happens to implement the IEventDispatcher interface. A listener can listen for any type of event it wants to without being dependent on the event dispatcher; they are loosely coupled. As it happens, any class with a [Bindable] property is automatically an IEventDispatcher, and it dispatches the PROPERTY_CHANGE event every time a [Bindable] property changes. The class can also dispatch this event explicitly.

The ArrayList listens for these events and dispatches a COLLECTION_CHANGE event summarizing and describing the changes to the Array’s items. The ArrayCollection uses this event from the ArrayList to gather information for its own COLLECTION_CHANGE event, and the List or Grid uses the COLLECTION_CHANGE event to refresh or rebuild their ItemRenderers.

To be clear: array element objects fire PROPERTY_CHANGE events automatically for every change to their [Bindable] properties. If you want properties that are not bound or are bound to explicit event ([Bindable("readOnlyPropertyChange")]) to trigger COLLECTION_CHANGE events in the collections that hold them, those property mutators have to explicitly fire the PROPERTY_CHANGE event (in addition to any other events they dispatch).

[Bindable]
public function get foo() : String
{
    return _foo;
}
public function set foo(value : String) : void
{
    _foo = value;
}

[Bindable("readOnlyPropertyChange")]
public function get readOnlyProperty() : int
{
    return _readOnlyProperty;
}

public function clearBar() : void
{
    var oldValue : String = _readOnlyProperty;
    _readOnlyProperty = 0;

//    signal for binding/listeners directly on this object
    this.dispatchEvent(new Event("readOnlyPropertyChange"));

//    signal for binding/listeners on this object's Collection
    if (hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE ))
    {
        var event : PropertyChangeEvent =
                PropertyChangeEvent.createUpdateEvent( this, "property", oldValue, 0);
        dispatchEvent( event );
    }
}

Arrays do not detect or fire events; they are just a collection of items.

Sequence diagram of the events between Lists, ArrayCollections, ArrayLists, Arrays, and the items in Arrays
Sequence diagram of the events between Lists, ArrayCollections, ArrayLists, Arrays, and the items in Arrays