Data providers and collections

A collection object contains a data object, such as an Array or an XMList object, and provides a set of methods that let you access, sort, filter, and modify the data items in that data object. Several controls, known as data provider controls, have a dataProvider property that you populate with a collection.

The mx.collections.ArrayCollection and mx.collections.XMLListCollection classes are specific collection implementations that you can use with any data provider control in the Flex framework. Most data provider controls also support using the mx.collections.ArrayList class. This class is similar to the ArrayCollection class, except that is uses less memory, but does not have as many features.

For more information on Flex components that use data providers, see Spark list-based controls, MX data-driven controls, and Menu-based controls.

About collections and data provider components

Data provider components require data for display or user interaction. To provide this data, you assign a collection, which is usually an ArrayCollection, ArrayList, or XMLListCollection object, to the data provider component's dataProvider property.

Optionally, for MX controls only, you can assign a raw data object such as an Array, XML, or XMLList object to the data provider component's dataProvider property; however, this is not considered a best practice because of the limitations noted in About data provider components.

The source of data in a collection object can be local or a remote source, such as a web service or a PHP page that you call with a Flex data access component.

About collections

Collections are objects that provide a uniform way to access and represent the data contained in a data source object, such as an Array or an XMLList object. Collections provide a level of abstraction between components and the data objects that you use to populate them.

The standard collection types in the Flex framework, the ArrayCollection and XMLListCollection classes, extend the mx.collections.ListCollectionView class, which implements the mx.collections.ICollectionView and mx.collections.IList interfaces. These interfaces provide the underlying functionality for viewing and modifying data objects. An ArrayCollection object takes an Array as its source object. An XMLListCollection object take an XMLList object as its source object.

Collections provide the following features:

  • Ensure that a component is properly updated when the underlying data changes. Components are not updated when noncollection data objects change. (They are updated to reflect the new data the next time they are refreshed.) If the data provider is a collection, the components are updated immediately after the collection change occurs.

  • Provide mechanisms for handling paged data from remote data sources that may not initially be available and may arrive over a period of time.

  • Provide a consistent set of operations on the data, independent of those provided by the raw data source. For example, you can insert and delete objects by using an index into the collection, independently of whether the underlying data is, for example, in an Array or an Object.

  • Provide a specific view of the data that can be in sorted order, or filtered by a developer-supplied method. This is only a view of the data; it does not change the data.

  • Use a single collection to populate multiple components from the same data source.

  • Use collections to switch data sources for a component at run time, and to modify the content of the data source so that changes are reflected by all components that use the data source.

  • Use collection methods to access data in the underlying data source.

Note: If you use a raw data object, such as an Array, as the value of an MX control's data provider, Flex automatically wraps the object in a collection wrapper (either an ArrayCollection or XMLListCollection). The control does not automatically detect changes that are made directly to the raw object. A change in the length of an array, for example, does not result in an update of the control. You can, however, use an object proxy, a listener interface, or the itemUpdated() method to notify the view of certain changes. For Spark controls, you cannot use a raw object as the value of the control's data provider. You must specify an object that implements the IList interface. Classes that implement IList include ArrayCollection, ArrayList, and XMLListCollection.

Another type of collection, ArrayList, extends the IList interface but not the ICollectionView interface. As a result, it is more lightweight and provides most of the same functionality as the ArrayCollection class. It does not, however, support sorting, filtering, or cursors.

Collection interfaces

Collections use the following interfaces to define how a collection represents data and provides access to it. The standard Flex framework collections, the ArrayCollection and XMLListCollection classes, implement both the ICollectionView interface and the IList interface. The IList and ICollectionView interfaces provide alternate methods for accessing and changing data. The IList interface is simpler; it provides add, set, get, and remove operations that operate directly on linear data.

Interface

Description

IList

A direct representation of items organized in an ordinal fashion. The interface presents the data from the source object in the same order as it exists in that object, and provides access and manipulation methods based on an index. The IList class does not provide sorting, filtering, or cursor functionality.

ICollectionView

A view of a collection of items. You can modify the view to show the data in sorted order and to show a subset of the items in the source object, as specified by a filter function. A class that implements this interface can use an IList interface as the underlying collection.

The interface provides access to an IViewCursor object for access to the items.

IViewCursor

Enumerates an object that implements the ICollectionView interface bidirectionally. The view cursor provides find, seek, and bookmarking capabilities, and lets you modify the underlying data (and the view) by inserting and removing items.

The ICollectionView interface (also called the collection view) provides a more complex set of operations than the IList interface, and is appropriate when the underlying data is not organized linearly. Its data access techniques, however, are more complex than those of the IList interface, so you should use the IList interface if you need only simple, indexed access to a linear collection. The collection view can represent a subset of the underlying data, as determined by a sort or filter operation.

Collection classes

The following table describes the public classes in the mx.collections.* and spark.collections.* packages. It does not include constant, event, and error classes. For complete reference information on collection-related classes, see the MX collections, Spark collections, and collections.errors packages, and the CollectionEvent and CollectionEventKind classes in the ActionScript 3.0 Reference for Apache Flex.

Class

Description

ArrayCollection

A standard collection for working with Arrays. Implements the IList and ICollectionView interfaces.

ArrayList

A standard collection for working with Arrays. Implements the IList interface. You can use this class instead of the ArrayCollection class if you do not need to sort, filter, or use cursors in your collection.

AsyncListView

A standard collections that handles ItemPendingErrors thrown by the getItemAt(), removeItemAt(), and toArray() methods.Use this class to support data paging when accessing data from a remote server.

XMLListCollection

A standard collection for working with XMLList objects. Implements the IList and ICollectionView interfaces, and a subset of XMLList methods.

CursorBookmark

Represents the position of a view cursor within a collection.

You can save a view cursor position in a CursorBookmark object and use the object to return the view cursor to the position at a later time.

Sort

Provides the information and methods required to sort a collection.

SortField

Provides properties and methods that determine how a specific field affects data sorting in a collection.

ItemResponder

(Used only if the data source is remote.) Handles cases when requested data is not yet available.

ListCollectionView

Superclass of the ArrayCollection and XMLListCollection classes. Adapts an object that implements the IList interface to the ICollectionView interface so that it can be passed to anything that expects an IList or an ICollectionView.

About data provider components

Several Flex components, including all list-based controls, are called data provider components because they have a dataProvider property that consumes data from an ArrayCollection, ArrayList, XMLListCollection object, or a custom collection. For example, the value of an MX Tree control's dataProvider property determines the structure of the tree and any associated data assigned to each tree node, and a ComboBox control's dataProvider property determines the items in the control's drop-down list. Many standard controls, including the ColorPicker and MenuBar controls, also have a dataProvider property.

For Spark list-based controls, the value of the dataProvider property must implement the IList interface. Classes that implement IList include ArrayCollection, ArrayList, and XMLListCollection.

For the MX list-based controls, you can specify raw data objects, such as an Array of strings or objects or an XML object, the value of the dataProvider property. For MX controls, Flex automatically wraps the raw data in a collection.

It's best that you always specify a collection as the value of the dataProvider property. Using collections explicitly ensures data synchronization and provides both simpler and more sophisticated data access and manipulation tools than are available when you are using raw objects directly as data providers. Collections can also provide a consistent interface for accessing and managing data of different types. For more information about collections, see About collections and data provider components.

Although raw data objects for MX controls are automatically wrapped in an ArrayCollection object or XMLListCollection, they are subject to the following limitations:

  • Raw objects are often not sufficient if you have data that changes, because the data provider component does not receive a notification of any changes to the base object. The component therefore does not get updated until it must be redrawn due to other changes in the application, or if the data provider is reassigned. At that time, it gets the data again from the updated raw object.

  • Raw objects do not provide advanced tools for accessing, sorting, or filtering data. For example, if you use an Array as the data provider, you must use the native Adobe® Flash® Array methods to manipulate the data.

For detailed descriptions of the individual controls, see the pages for the controls in the ActionScript 3.0 Reference for Apache Flex. For information on programming with many of the data provider components, see MX data-driven controls.

Data objects

The Flex framework supports the following types of data objects for populating data provider components:

Linear or list-based data objects

are flat data structures consisting of some number of objects, each of which has the same structure; they are often one-dimensional Arrays or ActionScript object graphs, or simple XML structures. You can specify one of these data structures in an ArrayList, ArrayCollection or XMLListCollection object's source property or as the value of a data provider control's dataProvider property.

You can use list-based data objects with all data provider controls, but you do not typically use them with Tree and most menu-based controls, which typically use hierarchical data structures. For data that can change dynamically, you typically use an ArrayCollection, ArrayList, or XMLListCollection object to represent and manipulate these data objects rather than the raw data object. You can also use a custom object that implements the ICollectionView and/or IList interfaces. If you do not require sorting, cursors, and filtering, you can use a class that implements just the IList interface

Hierarchical data objects

consist of cascading levels of often asymmetrical data. Often, the source of the data is an XML object, but the source can be a generic Object tree or trees of typed objects. You typically use hierarchical data objects with Flex controls that are designed to display hierarchical data:

  • Tree

  • Menu

  • MenuBar

  • PopUpMenuButton

A hierarchical data object matches the layout of a tree or cascading menu. For example, a tree often has a root node, with one or more branch or leaf child nodes. Each branch node can hold additional child branch or leaf nodes, but a leaf node is an endpoint of the tree.

The Flex hierarchical data provider controls use data descriptor interfaces to access and manipulate hierarchical data objects, and the Flex framework provides one class, the DefaultDataDescriptor class, which implements the required interfaces. If your data object does not conform to the structural requirements of the default data descriptor, you can create your own data descriptor class that implements the required interfaces.

You can use an ArrayCollection object or XMLListCollection object, or a custom object that implements the ICollectionView and IList interfaces to access and manipulate dynamic hierarchical data.

You can also use a hierarchical data object with controls that take linear data, such as the List control and the DataGrid control, by extracting specific data for linear display.

For more information on using hierarchical data providers, see Hierarchical data objects.

Specifying data providers in MXML applications

The Flex framework lets you specify and access data for data provider components in many ways. For example, you can bind a collection that contains data from a remote source to the dataProvider property of a data provider component; you can define the data provider in a dataProvider child tag of a data provider component; or you can define the data provider in ActionScript.

All access techniques belong to one of the following patterns, whether data is local or is provided from a remote source:

  • Using a collection implementation, such as an ArrayList object, ArrayCollection object, or XMLListCollection object, directly. This pattern is particularly useful for collections where object reusability is not important.

  • Using a collection interface. This pattern provides the maximum of independence from the underlying collection implementation.

  • Using a raw data object, such as an Array, with an MX list-based control. This pattern is discouraged unless data is completely static.

Using a collection object explicitly

You can use a collection, such as an ArrayList, ArrayCollection, or XMLListCollection object, as a data provider explicitly in an MXML control by assigning it to the component's dataProvider property. This technique is more direct than using an interface and is appropriate if the data provider is always of the same collection type.

For list-based controls, you often use an ArrayList object as the data provider, and populate the ArrayList object by using an Array that is local or from a remote data source. The following example shows an ArrayList object declared in line in a ComboBox control:

<?xml version="1.0"?> 
<!-- dpcontrols\ArrayCollectionInComboBox.mxml --> 
<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"> 
    
    <s:ComboBox id="myCB"> 
        <s:ArrayList id="stateArray"> 
            <fx:Object label="AL" data="Montgomery"/> 
            <fx:Object label="AK" data="Juneau"/> 
            <fx:Object label="AR" data="Little Rock"/> 
        </s:ArrayList> 
    </s:ComboBox> 
</s:Application>

In this example, the default property of the ComboBox control, dataProvider, defines an ArrayList object. Because dataProvider is the default property of the ComboBox control, it is not declared. The default property of the ArrayList object, source, is an Array of Objects, each of which has a label and a data field. Because the ArrayList object's source property takes an Array object, it is not necessary to declare the <fx:Array> tag as the parent of the <fx:Object> tags.

The following example uses ActionScript to declare and create an ArrayList object:

<?xml version="1.0"?> 
<!-- dpcontrols\ArrayCollectionInAS.mxml --> 
<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" 
    initialize="initData();"> 
    
    <fx:Script> 
        <![CDATA[ 
            import mx.collections.*; 
            [Bindable] 
            public var stateArray:ArrayList; 
            
            public function initData():void { 
                stateArray=new ArrayList( 
                [{label:"AL", data:"Montgomery"}, 
                {label:"AK", data:"Juneau"}, 
                {label:"AR", data:"Little Rock"}]); 
            } 
        ]]> 
    </fx:Script> 
 
    <s:ComboBox id="myComboBox" dataProvider="{stateArray}"/> 
</s:Application>

After you define the ComboBox control, the dataProvider property of the ComboBox control provides access to the collection that represents the underlying source object, and you can use the property to modify the data provider. If you add the following button to the preceding code, for example, you can click the button to add the label and data for Arizona to the end of the list in the ComboBox control, as in the following example:

 <s:Button label="Add AZ" 
	click="stateArray.addItem({'label':'AZ', 'data':'Phoenix'});"/>

In many cases, an ArrayList class is adequate for defining a non-XML data provider. However, for web services, remote objects, and uses that require cursors, filters, and sorts, you use the ArrayCollection class.

The following example shows an ArrayCollection object that is populated from a remote data source, in this case a remote object, as the data provider of a DataGrid control. Note that the ArrayUtil.toArray() method is used to ensure that the data sent to the ArrayCollection object is an Array.
<?xml version="1.0"?> 
<!-- dpcontrols\ROParamBind22.mxml --> 
<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"> 
    <s:layout> 
        <s:VerticalLayout gap="10"/> 
    </s:layout> 
    
    <fx:Script> 
        <![CDATA[ 
            import mx.controls.Alert; 
            import mx.utils.ArrayUtil; 
        ]]> 
    </fx:Script> 
    
    <fx:Declarations> 
        <mx:RemoteObject 
            id="employeeRO" 
            destination="roDest" 
            showBusyCursor="true" 
            fault="Alert.show(event.fault.faultString, 'Error');"> 
            <mx:method name="getList"> 
                <mx:arguments> 
                    <deptId>{dept.selectedItem.data}</deptId> 
                </mx:arguments> 
            </mx:method> 
        </mx:RemoteObject> 
 
        <mx:ArrayCollection id="employeeAC" 
            source="{ArrayUtil.toArray(employeeRO.getList.lastResult)}"/> 
    </fx:Declarations> 
 
    <s:HGroup> 
        <s:Label text="Select a department:"/> 
        <s:ComboBox id="dept" width="150"> 
            <s:dataProvider> 
                <mx:ArrayCollection> 
                    <mx:source> 
                        <fx:Object label="Engineering" data="ENG"/> 
                        <fx:Object label="Product Management" data="PM"/> 
                        <fx:Object label="Marketing" data="MKT"/> 
                    </mx:source> 
                </mx:ArrayCollection> 
            </s:dataProvider> 
        </s:ComboBox> 
        <s:Button label="Get Employee List" 
            click="employeeRO.getList.send()"/> 
    </s:HGroup> 
    
    <mx:DataGrid dataProvider="{employeeAC}" width="100%"> 
        <mx:columns> 
            <mx:DataGridColumn dataField="name" headerText="Name"/> 
            <mx:DataGridColumn dataField="phone" headerText="Phone"/> 
            <mx:DataGridColumn dataField="email" headerText="Email"/> 
        </mx:columns> 
    </mx:DataGrid> 
</s:Application>

Accessing data by using collection interfaces

If you know that a control's data can always be represented by a specific collection class, use an ArrayList, ArrayCollection, or XMLListCollection object explicitly, as shown above. If your code might be used with different types of collections—for example, if you might switch between object-based data and XML data from a remote data service—then you should use the ICollectionView interface in your application code, as the following example shows:

 public var myICV:ICollectionView = indeterminateCollection; 
... 
 <s:ComboBox id="cb1" dataProvider="{myICV}" initialize="sortICV()"/> 

You can then manipulate the interface as needed to select data for viewing, or to get and modify the data in the underlying data object.

Using a raw data object as a data provider for an MX control

When the data is static, you can use a raw data object, such as an Array object, directly as a data provider for an MX control. For example, you could use an array for a static list of U.S. Postal Service state designators. Do not use the data object directly as the dataProvider property of a control if the object contents can change dynamically, for example in response to user input or programmatic processing.
Note: For Spark list-based controls, you cannot use a raw object as the value of the control's data provider. You must specify an object that implements the IList interface. Classes that implement IList include ArrayCollection, ArrayList, and XMLListCollection.

The result returned by an HTTP service or web service is often an Array, and if you treat that data as read-only, you can use the Array directly as a data provider. However, it is a better practice to use a collection explicitly. List-based controls turn Array-based data providers into collections internally, so there is no performance advantage to using an Array directly as the data provider. If you pass an Array to multiple controls, it is more efficient to convert the Array into a collection when the data is received, and then pass the collection to the controls.

The following example shows an MX ComboBox control that takes a static Array as its dataProvider value. As noted previously, using raw objects as data providers is not a best practice and should be considered only for data objects that will not change.

<?xml version="1.0"?> 
<!-- dpcontrols/StaticComboBox.mxml --> 
<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"> 
    
    <fx:Script> 
        <![CDATA[ 
            [Bindable] 
            public var myArray:Array = ["AL", "AK", "AR"]; 
        ]]> 
    </fx:Script> 
    
    <mx:ComboBox id="myCB0" dataProvider="{myArray}"/> 
</s:Application>

In the preceding example, the Array specified as the dataProvider is automatically wrapped in an ArrayCollection object. This is the default type for data providers to be wrapped in. If the data provider were an XML or XMLList object, it would be wrapped in an XMLListCollection object. In this example, the ArrayCollection does not have an id property, but you can use ArrayCollection methods and properties directly through the dataProvider property, as the following example shows:

<mx:Button label="Add AZ" 	 
	click="myCB0.dataProvider.addItem({'label':'AZ','data':'Phoenix'});"/>

Setting a data provider in ActionScript

You may set the dataProvider property of a control in ActionScript, as well as in MXML, as the following example shows:

<?xml version="1.0"?> 
<!-- dpcontrols/DataGridValidateNow.mxml --> 
<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" 
   initialize="initData();"> 
 
   <fx:Script> 
       <![CDATA[ 
    
            import mx.collections.ArrayList; 
 
            [Bindable] 
            private var DGArray:ArrayList = new ArrayList([ 
                {Artist:'Pavement', Album:'Slanted and Enchanted', Price:11.99}, 
                {Artist:'Pavement', Album:'Brighten the Corners', Price:11.99}]); 
         
            // Initialize initDG ArrayList variable from the ArrayList. 
            public function initData():void { 
                myGrid.dataProvider = DGArray; 
            } 
        ]]> 
   </fx:Script> 
 
   <mx:DataGrid id="myGrid"> 
      <mx:columns> 
         <mx:DataGridColumn dataField="Album"/> 
         <mx:DataGridColumn dataField="Price"/> 
      </mx:columns> 
   </mx:DataGrid> 
</s:Application>

In this example, you use the intialize event to set the dataProvider property of the DataGrid control.

In some situations, you might set the dataProvider property, and then immediately attempt to perform an action on the control based on the setting of the dataProvider property, as the following example shows:

<?xml version="1.0"?> 
<!-- dpcontrols/DataGridValidateNowSelindex.mxml --> 
<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" 
    initialize="initData();"> 
 
   <fx:Script> 
       <![CDATA[ 
            import mx.collections.ArrayList; 
 
            [Bindable] 
            private var DGArray:ArrayList = new ArrayList([ 
                {Artist:'Pavement', Album:'Slanted and Enchanted', Price:11.99}, 
                {Artist:'Pavement', Album:'Brighten the Corners', Price:11.99}]); 
         
            // Initialize initDG ArrayList variable from the ArrayList. 
            public function initData():void { 
                myGrid.dataProvider = DGArray; 
                myGrid.validateNow(); 
                myGrid.selectedIndex=1; 
            } 
        ]]> 
   </fx:Script> 
 
   <mx:DataGrid id="myGrid"> 
      <mx:columns> 
         <mx:DataGridColumn dataField="Album"/> 
         <mx:DataGridColumn dataField="Price"/> 
      </mx:columns> 
   </mx:DataGrid> 
</s:Application>

In this example, setting the selectedindex to 1 might fail because the DataGrid control is in the process of setting the dataProvider property. Therefore, you insert the validateNow() method after setting the data provider. The validateNow() method validates and updates the properties and layout of the control, and then redraws it, if necessary.

Do not insert the validateNow() method every time you set the dataProvider property because it can affect the performance of your application; it is only required in some situations when you attempt to perform an operation on the control immediately after setting its dataProvider property.

Example: Using a collection

The following sample code shows how you can use a standard collection, an ArrayCollection object, to represent and manipulate an Array for use in a control. This example shows the following features:

  • Using an ArrayCollection to represent data in an Array

  • Sorting the ArrayCollection

  • Inserting data in the ArrayCollection

Note that if the example did not include sorting, it could use an ArrayList rather than an ArrayCollection as the data provider.

This example also shows the insertion's effect on the Array and the ArrayCollection representation of the Array:

<?xml version="1.0"?> 
<!-- dpcontrols\SimpleDP.mxml --> 
<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" width="600" 
    initialize="sortAC();"> 
    <fx:Script> 
        <![CDATA[ 
            import mx.collections.ArrayCollection; 
            import spark.collections.Sort; 
            import spark.collections.SortField; 
    
            // Function to sort the ArrayCollection in descending order. 
            public function sortAC():void { 
                var sortA:Sort = new Sort(); 
                sortA.fields=[new SortField("label")]; 
                myAC.sort=sortA; 
                //Refresh the collection view to show the sort. 
                myAC.refresh(); 
            } 
            // Function to add an item in the ArrayCollection. 
            // Data added to the view is also added to the underlying Array. 
            // The ArrayCollection must be sorted for this to work. 
            public function addItemToMyAC():void { 
                myAC.addItem({label:"MD", data:"Annapolis"}); 
            } 
        ]]> 
    </fx:Script> 
 
    <fx:Declarations> 
        <!-- An ArrayCollection with an array of objects --> 
        <mx:ArrayCollection id="myAC"> 
            <!-- Use an fx:Array tag to associate an id with the array. --> 
            <fx:Array id="myArray"> 
                <fx:Object label="MI" data="Lansing"/> 
                <fx:Object label="MO" data="Jefferson City"/> 
                <fx:Object label="MA" data="Boston"/> 
                <fx:Object label="MT" data="Helena"/> 
                <fx:Object label="ME" data="Augusta"/> 
                <fx:Object label="MS" data="Jackson"/> 
                <fx:Object label="MN" data="Saint Paul"/> 
            </fx:Array> 
        </mx:ArrayCollection> 
    </fx:Declarations> 
 
    <s:HGroup width="100%"> 
        <!-- A ComboBox populated by the collection view of the Array. --> 
        <s:ComboBox id="cb1" dataProvider="{myAC}"/> 
        <s:Button id="b1" label="Add MD" click="addItemToMyAC();"/> 
    </s:HGroup> 
</s:Application>

Using simple data access properties and methods

Collections provide simple properties and methods for indexed access to linear data. These properties and methods are defined in the IList interface, which provides a direct representation of the underlying data object. Any operation that changes the collection also changes the data provider in a similar manner: if you insert an item as the third item in the collection, it is also the third item in the underlying data object, which is an Array when working with ArrayList or an ArrayCollection object. The underlying data object is an XMLList object when working with XMLListCollection objects.

Note: If you use the ICollectionView interface to sort or filter a collection, do not use the IList interface to manipulate the data, because the results are indeterminate.

Simple data access properties and methods let you do the following:

  • Get, set, add, or remove an item at a specific index into the collection

  • Add an item at the end of the collection

  • Get the index of a specific item in the collection

  • Remove all items in the collection

  • Get the length of the collection

You can use this functionality directly on ArrayList, ArrayCollection, and XMLListCollection objects and also on the dataProvider property of any standard Flex data provider component.

The following sample code uses an ArrayList object to display an Array of elements in a ComboBox control. For an example that shows how to manage an ArrayCollection of objects with multiple fields, see Example: Modifying data in a DataGrid control.

In the following example the Array data source initially consists of the following elements:

 "AZ", "MA", "MZ", "MN", "MO", "MS"

When you click the Button control, the application uses the length property of the ArrayList and several of its methods to do the following:

  1. Change the data in the Array and the displayed data in the ComboBox control to a correct alphabetical list of the U.S. state abbreviations for states that start with M:

     MA, ME, MI, MN, MO, MS, MT
  2. Display in a TextArea control information about the tasks it performed and the resulting Array.

The code includes comments that describe the changes to the data object.

<?xml version="1.0"?> 
<!-- dpcontrols\UseIList.mxml --> 
<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" 
    initialize="initData();"> 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
 
    <fx:Script> 
        <![CDATA[ 
            import mx.collections.*; 
 
            // The data provider is an Array of Strings 
            public var myArray:Array = ["AZ", "MA", "MZ", "MN", "MO", "MS"]; 
            // Declare an ArrayList that represents the Array. 
            [Bindable] 
            public var myAL:ArrayList; 
 
            //Initialize the ArrayList. 
            public function initData():void { 
                myAL = new ArrayList(myArray); 
            } 
 
            // The function to change the collection, and therefore 
            // the Array. 
            public function changeCollection():void { 
                // Get the original collection length. 
                var oldLength:int=myAL.length; 
 
                // Remove the invalid first item, AZ. 
                var removedItem:String=String(myAL.removeItemAt(0)); 
                // Add ME as the second item. (ILists used 0-based indexing.) 
                myAL.addItemAt("ME", 1); 
                // Add MT at the end of the Array and collection. 
                myAL.addItem("MT"); 
                // Change the third item from MZ to MI. 
                myAL.setItemAt("MI", 2); 
                // Get the updated collection length. 
                var newLength:int=myAL.length; 
                // Get the index of the item with the value ME. 
                var addedItemIndex:int=myAL.getItemIndex("ME"); 
                // Get the fifth item in the collection. 
                var index4Item:String=String(myAL.getItemAt(4)); 
 
                // Display the information in the TextArea control. 
                ta1.text="Start Length: " + oldLength + ". New Length: " + 
                    newLength; 
                ta1.text+=".\nRemoved " + removedItem; 
                ta1.text+=".\nAdded ME at index " + addedItemIndex; 
                ta1.text+=".\nThe item at index 4 is " + index4Item + "."; 
                // Show that the base Array has been changed. 
                ta1.text+="\nThe base Array is: " + myArray.join(); 
            } 
        ]]> 
    </fx:Script> 
 
    <s:ComboBox id="myCB" dataProvider="{myAL}"/> 
    <s:TextArea id="ta1" height="75" width="300"/> 
    <s:Button label="rearrange list" click="changeCollection();"/> 
</s:Application>

Working with data views

Classes that implement the ICollectionView interface, such as ArrayCollection and XMLListCollection, provide a view of the underlying data object as a collection of items. Although they are more complex, data views give you more flexibility than the simple data access methods and properties that are defined in the IList interface. Data views provide the following features:

  • You can modify the data view to show the data in sorted order or to show a subset of the items in the data provider without changing the underlying data. For more information, see Sorting and filtering data for viewing.

  • You can access the collection data by using a cursor, which lets you move through the collection, use bookmarks to save specific locations in the collection, and insert and delete items in the collection (and therefore in the underlying data source). For more information, see Using a view cursor.

  • You can represent remote data that might not initially be available, or parts of the data set that might become available at different times. For more information, see Collection change notification and Remote data in data provider components.

You can use data views directly on ArrayCollection and XMLListCollection objects and also on the dataProvider property of standard Flex data provider components except those that are subclasses of the NavBar class (ButtonBar, LinkBar, TabBar, and ToggleButtonBar). It is a better practice to work directly on the collection objects than on the dataProvider property.

Note that the ArrayList class implements the IList interface but not the ICollectionView interface.

Sorting and filtering data for viewing

Collections let you sort and filter data in the data view so that the data in the collection is a reordered subset of the underlying data. Data view operations have no effect on the underlying data object content, only on the subset of data that the collection view represents, and therefore on what is displayed by any control that uses the collection.

Sorting

A Sort object lets you sort data in a collection. You can specify multiple fields to use in sorting the data, require that the resulting entries be unique, and specify a custom comparison function to use for ordering the sorted output. You can also use a Sort object to find items in a collection. When you create a Sort object, or change its properties, you must call the refresh() method on the collection to show the results.

You use SortField objects to specify the fields to use in the sort. You create SortField objects and put them in the Sort class object's fields array.

To create case-insensitive sorts, use the ignoreCase property of the SortingCollator.

Filtering

You use a filter function to limit the data view in the collection to a subset of the source data object. The function must take a single Object parameter, which corresponds to a collection item, and must return a Boolean value specifying whether to include the item in the view. As with sorting, when you specify or change the filter function, you must call the refresh() method on the collection to show the filtered results. To limit a collection view of an array of strings to contain only strings starting with M, for example, use the following filter function:

 public function stateFilterFunc(item:Object):Boolean  { 
 	return item >= "M" && item < "N"; 
 }

Example: Sorting and filtering an ArrayCollection

The following example shows the use of the filter function and a sort together. You can use the buttons to sort the collection, to filter the collection, or to do both. Use the Reset button to restore the collection view to its original state.

<?xml version="1.0"?> 
<!-- dpcontrols\SortFilterArrayCollection.mxml --> 
<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" 
    width="600"> 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
    
    <fx:Script> 
        <![CDATA[ 
            import mx.collections.ArrayCollection; 
            import spark.collections.Sort; 
            import spark.collections.SortField; 
    
            /* Function to sort the ICollectionView 
               in ascending order. */ 
            public function sortAC():void { 
                var sortA:Sort = new Sort(); 
                sortA.fields=[new SortField("label")]; 
                myAC.sort=sortA; 
                //Refresh the collection view to show the sort. 
                myAC.refresh(); 
            } 
 
            /* Function to filter out all items with labels 
               that are not in the range of M-N. */ 
            public function stateFilterFunc(item:Object):Boolean { 
                return item.label >= "M" && item.label < "O"; 
            } 
            
            /* Function to apply the filter function the ICollectionView. */ 
            public function filterAC():void { 
                myAC.filterFunction=stateFilterFunc; 
                /* Refresh the collection view to apply the filter. */ 
                myAC.refresh(); 
            } 
 
            /* Function to Reset the view to its original state. */ 
            public function resetAC():void { 
                myAC.filterFunction=null; 
                myAC.sort=null; 
                //Refresh the collection view. 
                myAC.refresh(); 
            } 
 
        ]]> 
    </fx:Script> 
 
    <fx:Declarations> 
        <!-- An ArrayCollection with an array of objects. --> 
        <mx:ArrayCollection id="myAC"> 
            <fx:Array id="myArray"> 
                <fx:Object label="LA" data="Baton Rouge"/> 
                <fx:Object label="NH" data="Concord"/> 
                <fx:Object label="TX" data="Austin"/> 
                <fx:Object label="MA" data="Boston"/> 
                <fx:Object label="AZ" data="Phoenix"/> 
                <fx:Object label="OR" data="Salem"/> 
                <fx:Object label="FL" data="Tallahassee"/> 
                <fx:Object label="MN" data="Saint Paul"/> 
                <fx:Object label="NY" data="Albany"/> 
            </fx:Array> 
        </mx:ArrayCollection> 
    </fx:Declarations> 
 
    <!-- Buttons to filter, sort, or reset the view in the second ComboBox 
            control. --> 
    <s:HGroup width="100%"> 
        <s:Button id="sortButton" label="Sort" click="sortAC();"/> 
        <s:Button id="filterButton" label="Filter" click="filterAC();"/> 
        <s:Button id="resetButton" label="Reset" click="resetAC();"/> 
    </s:HGroup> 
    <s:ComboBox id="cb1" dataProvider="{myAC}"/> 
</s:Application>

For a more complex example of sorting a DataGrid control, which does both an initial sort of the data and a custom sort when you click a column heading, see Create a custom sort for the Spark DataGrid control.

Using a view cursor

You use a view cursor to traverse the items in a collection's data view and to access and modify data in the collection. A cursor is a position indicator; it points to a particular item in the collection. Collections have a createCursor() method that returns a view cursor. View cursor methods and properties are defined in the IViewCursor interface.

You can use view cursor methods and properties to perform the following operations:

  • Move the cursor backward or forward

  • Move the cursor to specific items

  • Get the item at a cursor location

  • Add, remove, and change items

  • Save a cursor position by using a bookmark, and return to it later

When you use standard Flex collection classes, ArrayCollection and XMLListCollection, you use the IViewCursor interface directly; as the following code snippet shows, you do not reference an object instance:

 public var myAC:ICollectionView = new ArrayCollection(myArray);  
 public var myCursor:IViewCursor; 
 . 
 . 
 myCursor=myAC.createCursor();

Manipulating the view cursor

A view cursor object includes the following methods and properties for moving the cursor:

  1. The moveNext() and movePrevious() methods move the cursor forward and backward by one item. Use the beforeFirst and afterLast properties to check whether you've reached the bounds of the view. The following example moves the cursor to the last item in the view:

     while (! myCursor.afterLast) { 
     		myCursor.moveNext(); 
     }
  2. The findAny(), findFirst(), and findLast() methods move the cursor to an item that matches the parameter. Before you can use these methods, you must apply a Sort to the collection (because the functions use Sort methods).

If it is not important to find the first occurrence of an item or the last occurrence of an item in a nonunique index, the findAny() method can be somewhat more efficient than either the findFirst() or the findLast() method.

If the associated data is from a remote source, and not all of the items are cached locally, the find methods begin an asynchronous fetch from the remote source; if a fetch is already in progress, they wait for it to complete before making another fetch request.

The following example finds an item inside a collection of simple objects—in this case, an ArrayCollection of state ZIP code strings. It creates a default Sort object, applies it to an ArrayCollection object, and finds the first instance of the string "MZ" in a simple array of strings:

 var sortD:Sort = new Sort(); 
 // The null first parameter on the SortField constructor specifies a 
 // collection of simple objects (String, numeric, or Boolean values). 
 // The true second parameter specifies a case-insensitive sort. 
 sortD.fields = [new SortField(null, true)]; 
 myAC.sort=sortD; 
 myAC.refresh(); 
 myCursor.findFirst("MZ");

To find a complex object, you can use the findFirst() method to search on multiple sort fields. You cannot, however, skip fields in the parameter of any of the find methods. If an object has three fields, for example, you can specify any of the following field combinations in the parameter: 1, 1,2, or 1,2,3, but you cannot specify only fields 1 and 3.

Both of the following lines find an object with the label value "ME" and data value "Augusta":

 myCursor.findFirst({label:"ME"}); 
 myCursor.findFirst({label:"ME", data:"Augusta"});
  1. The seek() method moves the cursor to a position relative to a bookmark. You use this method to move the cursor to the first or last item in a view, or to move to a bookmark position that you have saved.

Getting, adding, and removing data items

A view cursor object includes the following methods and properties for accessing and changing data in the view:

  • The current property is a reference to the item at the current cursor location.

  • The insert() method inserts an item before the current cursor location. However, if the collection is sorted (for example, to do a find() operation, the sort moves the item to the sorted order location, not to the cursor location.

  • The remove() method removes the item at the current cursor location; if the removed item is not the last item, the cursor points to the location after the removed item.

The following example shows the results of using insert() and remove() on the current property:

<?xml version="1.0"?> 
<!-- dpcontrols\GetAddRemoveItems.mxml --> 
<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" 
    initialize="initData();"> 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
        
    <fx:Script> 
        <![CDATA[ 
            import mx.collections.*; 
            public var myArray:Array = [{label:"MA", data:"Massachusetts"}, 
            {label:"MN", data:"Minnesota"}, {label:"MO", data:"Missouri"}]; 
            [Bindable] 
            public var myAC:ArrayCollection; 
            public var myCursor:IViewCursor; 
 
            /* Initialize the ArrayCollection when you 
               initialize the application. */ 
            public function initData():void { 
                myAC = new ArrayCollection(myArray); 
            } 
 
            /* The function to change the collection, 
               and therefore the Array. */ 
            public function testCollection():void { 
                /* Get an IViewCursor object for accessing the collection data. */ 
                myCursor=myAC.createCursor(); 
                ta1.text="At start, the cursor is at: " + myCursor.current.label + "."; 
                var removedItem:String=String(myCursor.remove()); 
                ta1.text+="\nAfter removing the current item, the cursor is at: " 
                    + myCursor.current.label + "."; 
                myCursor.insert({label:"ME", data:"Augusta"}); 
                ta1.text+="\nAfter adding an item, the cursor is at: " 
                    + myCursor.current.label + "."; 
            } 
        ]]> 
    </fx:Script> 
 
    <s:ComboBox id="myCB" dataProvider="{myAC}"/> 
    <s:TextArea id="ta1" height="75" width="350"/> 
    <s:Button label="Run Test" click="testCollection();"/> 
</s:Application>

Using bookmarks

You use a bookmark to save a cursor location for later use. You can also use the built-in FIRST and LAST bookmark properties to move the cursor to the first or last item in the data view.

Create and use a bookmark

  1. Move the cursor to a desired location in the data view.

  2. Assign the current value of the bookmark property to a variable, as in the following line:

     var myBookmark:CursorBookmark=myCursor.bookmark;
  3. Do some operations that might move the cursor.

  4. When you must return to the bookmarked cursor location (or to a specific offset from the bookmarked location), call the IViewCursor seek() method, as in the following line:

     myCursor.seek(myBookmark);

The following example counts the number of items in a collection between the selected item in a ComboBox control and the end of the collection, and then returns the cursor to the initial location:

<?xml version="1.0"?> 
<!-- dpcontrols\UseBookmarks.mxml --> 
<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" 
    initialize="run();"> 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
    
    <fx:Script> 
        <![CDATA[ 
            import mx.collections.ArrayCollection; 
            import mx.collections.IViewCursor; 
            import mx.collections.CursorBookmark; 
            import spark.collections.Sort; 
            import spark.collections.SortField; 
            private var myCursor:IViewCursor; 
 
            // Initialize variables. 
            public function run():void { 
                // Initialize the cursor. 
                myCursor=myAC.createCursor(); 
                // The findFirst() method, used in 
                // countFromSelection() requires a 
                // sorted view. 
                var sort:Sort = new Sort(); 
                sort.fields=[new SortField("label")]; 
                myAC.sort=sort; 
                //You must refresh the view to apply the sort. 
                myAC.refresh(); 
            } 
 
            // Count the items following the current 
            // cursor location. 
            public function countLast(theCursor:IViewCursor):int { 
                var counter:int=0; 
                // Set a bookmark at the current cursor location. 
                var mark:CursorBookmark=theCursor.bookmark; 
                // Move the cursor to the end of the Array. 
                // The moveNext() method returns false when the cursor 
                // is after the last item. 
                    while (theCursor.moveNext()) { 
                    counter++; 
                } 
                // Return the cursor to the initial location. 
                theCursor.seek(mark); 
                return counter; 
            } 
 
            // Function triggered by ComboBox change event. 
            // Calls the countLast() function to count the 
            // number of items to the end of the collection. 
            public function countFromSelection():void { 
                myCursor.findFirst(myCB.selectedItem); 
                var count:int = countLast(myCursor); 
                ta1.text += myCursor.current.label + " is " + count + 
                                " from the last item.\n"; 
            } 
        ]]> 
    </fx:Script> 
 
    <fx:Declarations> 
        <!-- The data provider, an ArrayCollection with an array of objects. --> 
        <mx:ArrayCollection id="myAC"> 
            <fx:Object label="MA" data="Boston"/> 
            <fx:Object label="ME" data="Augusta"/> 
            <fx:Object label="MI" data="Lansing"/> 
            <fx:Object label="MN" data="Saint Paul"/> 
            <fx:Object label="MO" data="Jefferson City"/> 
            <fx:Object label="MS" data="Jackson"/> 
            <fx:Object label="MT" data="Helena"/> 
        </mx:ArrayCollection> 
    </fx:Declarations> 
 
    <s:ComboBox id="myCB" 
        dataProvider="{myAC}" change="countFromSelection();"/> 
    <s:TextArea id="ta1" height="200" width="175"/> 
</s:Application>

Example: Updating an Array by using data view methods and properties

The following example uses the data view methods and properties of an ArrayCollection object to display an Array with the following elements in a ComboBox control:

 "AZ", "MA", "MZ", "MN", "MO", "MS"

When you click the Update View button, the application uses the length property and several methods of the ICollectionView interface to do the following:

  • Change the data in the array and the displayed data in the ComboBox control to a correct alphabetical list of the U.S. state abbreviations for the following states that start with the letter M:

     MA, ME, MI, MN, MO, MS, MT
  • Save a bookmark that points to the ME item that it adds, and later restores the cursor to this position.

  • Display in a TextArea control information about the tasks it performed and the resulting array.

When you click the Sort button, the application reverses the order of the items in the view, and limits the viewed range to ME–MO.

When you click the Reset button, the application resets the data provider array and the collection view.

<?xml version="1.0"?> 
<!-- dpcontrols\UpdateArrayViaICollectionView.mxml --> 
<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" 
    initialize="initData();"> 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
        
    <fx:Script> 
        <![CDATA[ 
            import mx.collections.ArrayCollection; 
            import mx.collections.CursorBookmark; 
            import mx.collections.IViewCursor; 
            import spark.collections.Sort; 
            import spark.collections.SortField;            
            
            // The data provider is an array of Strings. 
            public var myArray:Array = ["AZ", "MA", "MZ", "MN", "MO", "MS"]; 
            // Declare an ArrayCollection that represents the Array. 
            // The variable must be bindable so the ComboBox can update properly. 
            [Bindable] 
            public var myAC:ArrayCollection; 
            //Boolean flag to ensure the update routine hasn't been run before. 
            public var runBefore:Boolean=false; 
            //Initialize the ArrayCollection the application initializes. 
            public function initData():void { 
                myAC = new ArrayCollection(myArray); 
            } 
 
            // The function to change the collection. 
            public function changeCollection():void { 
                //Running this twice without resetting causes an error. 
                if (! runBefore) { 
                    runBefore=true; 
                    // Get an IViewCursor object for accessing the collection data. 
                    var myCursor:IViewCursor=myAC.createCursor(); 
                    // Get the original collection length. 
                    var oldLength:int=myAC.length; 
 
                    // The cursor is initially at the first item; delete it. 
                    var removedItem:String=String(myCursor.remove()); 
 
                    // Add ME as the second item. 
                    // The cursor is at the (new) first item; 
                    // move it to the second item. 
                    myCursor.moveNext(); 
                    // Insert ME before the second item. 
                    myCursor.insert("ME"); 
 
                    // Add MT at the end of the collection. 
                    //Use the LAST bookmark property to go to the end of the view. 
                    // Add an offset of 1 to position the cursor after the last item. 
                    myCursor.seek(CursorBookmark.LAST, 1); 
                    myCursor.insert("MT"); 
                    // Change MZ to MI. 
                    // The findFirst() method requires a sorted view. 
                    var sort:Sort = new Sort(); 
                    myAC.sort=sort; 
                    // Refresh the collection view to apply the sort. 
                    myAC.refresh(); 
                    // Make sure there is a MZ item, and no MI in the array. 
                     if (myCursor.findFirst("MZ") && !myCursor.findFirst("MI")) { 
                        // The IViewCursor does not have a replace operation. 
                        // First, remove "MZ". 
                        myCursor.remove(); 
                        // Because the view is now sorted, the insert puts this item 
                        // in the right place in the sorted view, but at the end of 
                        // the underlying Array data provider. 
                        myCursor.insert("MI"); 
                    } 
 
                    // Get the updated collection length. 
                    var newLength:int=myAC.length; 
 
                    // Set a bookmark at the item with the value ME, 
                    myCursor.findFirst("ME"); 
                    var MEMark:CursorBookmark=myCursor.bookmark; 
                    // Move the cursor to the last item in the Array. 
                    myCursor.seek(CursorBookmark.LAST); 
                    // Get the last item in the collection. 
                    var lastItem:String=String(myCursor.current); 
                    // Return the cursor to the bookmark position. 
                    myCursor.seek(MEMark); 
                    // Get the item at the cursor location. 
                    var MEItem:String=String(myCursor.current); 
 
                    // Display the information in the TextArea control. 
                    ta1.text="Start Length: " + oldLength + ". End Length: " 
                                    + newLength; 
                    ta1.text+=".\nRemoved " + removedItem; 
                    ta1.text+=".\nLast Item is " + lastItem; 
                    ta1.text+=".\nItem at MEMark is " + MEItem; 
                    // Show that the base Array has been changed. 
                    // Notice that the Array is NOT in sorted order. 
                    ta1.text+="\nThe base Array is: " + myArray.join(); 
                } // End runBefore condition 
            } 
 
            // Filter function used in the sortICV method to limit the range. 
            public function MEMOFilter(item:Object):Boolean { 
                return item >= "ME" && item <= "MO"; 
            } 
            
            // Sort the collection view in descending order, 
            // and limit the items to the range ME - MO. 
            public function sortICV():void { 
                var sort:Sort = new Sort(); 
                sort.fields=[new SortField(null, false, true)]; 
                myAC.filterFunction=MEMOFilter; 
                myAC.sort=sort; 
                // Refresh the ArrayCollection to apply the sort and filter 
                // function. 
                myAC.refresh(); 
                //Call the ComboBox selectedIndex() method to replace the "MA" 
                //in the display with the first item in the sorted view. 
                myCB.selectedIndex=0; 
                ta1.text="Sorted"; 
            } 
 
            //Reset the Array and update the display to run the example again. 
            public function resetView():void { 
                myArray = ["AZ", "MA", "MZ", "MN", "MO", "MS"]; 
                myAC = new ArrayCollection(myArray); 
                ta1.text="Reset"; 
                runBefore=false; 
            } 
        ]]> 
    </fx:Script> 
 
    <s:ComboBox id="myCB" dataProvider="{myAC}"/> 
    <s:TextArea id="ta1" height="75" width="300"/> 
    <s:HGroup> 
        <s:Button label="Update View" click="changeCollection();"/> 
        <s:Button label="Sort View" click="sortICV();"/> 
        <s:Button label="Reset View" click="resetView();"/> 
    </s:HGroup> 
</s:Application>

Collection events and manual change notification

Collections use events to indicate changes to the collection. You can use these events to monitor changes and update the display accordingly.

Collection events

Collections use CollectionEvent, PropertyChangeEvent, and FlexEvent objects in the following ways:

  • Collections dispatch a CollectionEvent (mx.events.CollectionEvent) event whenever the collection changes. All collection events have the type property value CollectionEvent.COLLECTION_CHANGE.

  • The CollectionEvent object includes a kind property that indicates the way in which the collection changed. You can determine the change by comparing the kind property value with the CollectionEventKind constants; for example, UPDATE.

  • The CollectionEvent object includes an items property that is an Array of objects whose type varies depending on the event kind. For ADD and REMOVE kind events, the array contains the added or removed items. For UPDATE events, the items property contains an Array of PropertyChangeEvent event objects. This object's properties indicate the type of change and the property value before and after the change.

  • The PropertyChangeEvent class kind property indicates the way in which the property changed. You can determine the change type by comparing the kind property value with the PropertyChangeEventKind constants; for example, UPDATE.

  • View cursor objects dispatch a FlexEvent class event with the type property value of mx.events.FlexEvent.CURSOR_UPDATE when the cursor position changes.

You use collection events to monitor changes to a collection to update the display. For example, if a custom control uses a collection as its data provider, and you want the control to be updated dynamically and to display the revised data each time the collection changes, the control can monitor the collection events and update accordingly.

You could, for example, build a simple rental-car reservation system that uses collection events. This application uses COLLECTION_CHANGE event type listeners for changes to its reservations and cars data collections.

The CollectionEvent listener method, named reservationsChanged, tests the event kind field and does the following:

  • If the event kind property is ADD, iterates through the objects in the event's items property and calls a function to update the reservation information display with boxes that display the time span of each reservation.

  • If the event kind property is REMOVE, iterates through the objects in the event's items property and calls a function to remove the reservation box for each item.

  • If the event kind property is UPDATE, iterates through the PropertyChangeEvent objects in the event's items property and calls the update function to update each item.

  • If the event kind property is RESET, calls a function to reset the reservation information.

The following example shows the reservationsChanged CollectionEvent event listener function:

 private function reservationsChanged(event:CollectionEvent):void { 
 	switch (event.kind) { 
 		case CollectionEventKind.ADD: 
 			for (var i:uint = 0; i < event.items.length; i++) { 
 					updateReservationBox(Reservation(event.items[i])); 
 			} 
 			break; 
  
 		case CollectionEventKind.REMOVE: 
 			for (var i:uint = 0; i < event.items.length; i++) { 
 				removeReservationBox(Reservation(event.items[i])); 
 			} 
 			break; 
  
 		case CollectionEventKind.UPDATE: 
 			for (var i:uint = 0; i < event.items.length; i++) { 
 				if (event.items[i] is PropertyChangeEvent) { 
 					if (PropertyChangeEvent(event.items[i]) != null) { 
 						 updateReservationBox(Reservation(PropertyChangeEvent( 
 							event.items[i]).source)); 
 					} 
 				} 
 				else if (event.items[i] is Reservation) { 
 					updateReservationBox(Reservation(event.items[i])); 
 				} 
 		} 
 		break; 
  
 		case CollectionEventKind.RESET: 
 			refreshReservations(); 
 			break; 
 	} 
 }

The updateReservationBox() method either shows or hides a box that shows the time span of the reservation. The removeReservationBox() method removes a reservation box. The refreshReservations() method redisplays all current reservation information.

For more information on the application and the individual methods, see the sample code.

Collection change notification

Collections include the itemUpdated() method, which notifies a collection that the underlying data has changed and ensures that the collection's data view is up to date when items in the underlying data object do not implement the IEventDispatcher interface. This method takes the item that was modified, the property in the item that was updated, and its old and new values as parameters. Collections also provide the enableAutoUpdate() and disableAutoUpdate() methods, which enable and disable the automatic updating of the data view when the underlying data provider changes.

Using the itemUpdated() method

Use the itemUpdated() method to notify the collection of changes to a data provider object if the object does not implement the IEventDispatcher interface; in this case the object is not monitorable. Adobe Flash and Flex Objects and other basic data types do not implement this interface. Therefore you must use the itemUpdated() method to update the collection when you modify the properties of a data provider such as an Array or through the display object.

You can also use the itemUpdated() method if you must use an Array, rather than a collection, as an MX control's data provider. Then the component wraps the Array in a collection wrapper. The wrapper must be manually notified of any changes made to the underlying Array data object, and you can use the itemUpdated()method for that notification.

You do not have to use the itemUpdated() method if you add or remove items directly in a collection or use any of the ICollectionView or IList methods to modify the collection.

Also, specifying the [Bindable] metadata tag above a class definition, or above a variable declaration within the class, ensures that the class implements the IEventDispatcher interface, and causes the class to dispatch propertyChange events. If you specify the [Bindable] tag above the class declaration, the class dispatches propertyChange events for all properties; if you mark only specific properties as [Bindable], the class dispatches events for only those properties. The collection listens for the propertyChange events. Therefore, if you have a collection called myCollection that consists of instances of a class that has a [Bindable] myVariable variable, an expression such as myCollection.getItemAt(0).myVariable="myText" causes the item to dispatch an event, and you do not have to use the itemUpdated() method. (For more information on the [Bindable] metadata tag and its use, see Data binding.)

The most common use of t he itemUpdate() method is to notify a collection of changes to a custom class data source that you cannot make bindable or modify to implement the IEventDispatcher interface. The following schematic example shows how you could use the itemUpdated() method in such a circumstance.

Assume you have a class that you do not control or edit and that looks like the following:

 public class ClassICantEdit { 
 	public var field1:String; 
 	public var field2:String; 
 }

You have an ArrayCollection that uses these objects, such as the following, which you populate with classICantEdit objects:

 public var myCollection:ArrayCollection = new ArrayCollection();

You have a DataGrid control such as the following:

 <s:DataGrid dataProvider="{myCollection}"/>

When you update a field in the myCollection ArrayCollection, as follows, the DataGrid control is not automatically updated:

 myCollection.getItemAt(0).field1="someOtherValue";

To update the DataGrid control, you must use the collection's itemUpdated() method:

 myCollection.itemUpdated(collectionOfThoseClasses.getItemAt(0));

Disabling and enabling automatic updating

A collection's disableAutoUpdate() method prevents events that represent changes to the underlying data from being broadcast by the view. It also prevents the collection from being updated as a result of these changes.

Use this method to prevent the collection, and therefore the control that uses it as a data provider, from showing intermediate changes in a set of multiple changes. The DataGrid class, for example, uses the disableAutoUpdate() method to prevent updates to the collection while a specific item is selected. When the item is no longer selected, the DataGrid control calls the enableAutoUpdate() method. Doing this ensures that, if a DataGrid control uses a sorted collection view, items that you edit do not jump around while you're editing.

You can also use the disableAutoUpdate() method to optimize performance in cases where multiple items in a collection are being edited at once. By disabling the auto update until all changes are made, a control like the DataGrid control can receive an update event as a single batch instead of reacting to multiple events.

The following code snippet shows the use of the disableAutoUpdate() and enableAutoUpdate() methods:

 var obj:myObject = myCollection.getItemAt(0); 
 myCollection.disableAutoUpdate(); 
 obj.prop1 = 'foo'; 
 obj.prop2 = 'bar'; 
 myCollection.enableAutoUpdate();

Example: Modifying data in a DataGrid control

The following example lets you add, remove, or modify data in a DataGrid control:

<?xml version="1.0"?> 
<!-- dpcontrols\ModifyDataGridData.mxml --> 
<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" 
    width="500" height="600" > 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
 
    <fx:Script> 
        <![CDATA[ 
            import mx.events.*; 
            import mx.collections.*; 
    
            // Add event information to a log (displayed in the TextArea). 
            public function collectionEventHandler(event:CollectionEvent):void { 
                switch(event.kind) { 
                    case CollectionEventKind.ADD: 
                        addLog("Item "+ event.location + " added"); 
                        break; 
                    case CollectionEventKind.REMOVE: 
                        addLog("Item "+ event.location + " removed"); 
                        break; 
                    case CollectionEventKind.REPLACE: 
                        addLog("Item "+ event.location + " Replaced"); 
                        break; 
                    case CollectionEventKind.UPDATE: 
                        addLog("Item updated"); 
                        break; 
                } 
            } 
            // Helper function for adding information to the log. 
            public function addLog(str:String):void { 
                log.text += str + "\n"; 
            } 
    
            // Add a person to the ArrayCollection. 
            public function addPerson():void { 
                ac.addItem({first:firstInput.text, last:lastInput.text, 
                    email:emailInput.text}); 
                    clearInputs(); 
            } 
    
            // Remove a person from the ArrayCollection. 
            public function removePerson():void { 
                // Make sure an item is selected. 
                if (dg.selectedIndex >= 0) { 
                    ac.removeItemAt(dg.selectedIndex); 
            } 
        } 
    
        // Update an existing person in the ArrayCollection. 
        public function updatePerson():void { 
            // Make sure an item is selected. 
            if (dg.selectedItem !== null) { 
                ac.setItemAt({first:firstInput.text, last:lastInput.text, 
                    email:emailInput.text}, dg.selectedIndex); 
            } 
        } 
    
        // The change event listener for the DataGrid. 
        // Clears the text input controls and updates them with the contents 
        // of the selected item. 
        public function dgChangeHandler():void { 
            clearInputs(); 
            firstInput.text = dg.selectedItem.first; 
            lastInput.text = dg.selectedItem.last; 
            emailInput.text = dg.selectedItem.email; 
        } 
    
        // Clear the text from the input controls. 
        public function clearInputs():void { 
            firstInput.text = ""; 
            lastInput.text = ""; 
            emailInput.text = ""; 
        } 
 
        // The labelFunction for the ComboBox; 
        // Puts first and last names in the ComboBox. 
        public function myLabelFunc(item:Object):String { 
            return item.first + " " + item.last; 
        } 
        ]]> 
    </fx:Script> 
    
    <fx:Declarations> 
        <!-- The ArrayCollection used by the DataGrid and ComboBox. --> 
        <mx:ArrayCollection id="ac" 
            collectionChange="collectionEventHandler(event)"> 
            <mx:source> 
                <fx:Object first="Matt" last="Matthews" email="matt@myco.com"/> 
                <fx:Object first="Sue" last="Sanderson" email="sue@myco.com"/> 
                <fx:Object first="Harry" last="Harrison" email="harry@myco.com"/> 
            </mx:source> 
        </mx:ArrayCollection> 
    </fx:Declarations> 
 
    <mx:DataGrid width="450" id="dg" dataProvider="{ac}" 
            change="dgChangeHandler()"> 
        <mx:columns> 
            <mx:DataGridColumn dataField="first" headerText="First Name"/> 
            <mx:DataGridColumn dataField="last" headerText="Last Name"/> 
            <mx:DataGridColumn dataField="email" headerText="Email"/> 
        </mx:columns> 
    </mx:DataGrid> 
 
    <!-- The ComboBox and DataGrid controls share an ArrayCollection as their 
        data provider. 
        The ComboBox control uses the labelFunction property to construct the 
        labels from the dataProvider fields. --> 
    <s:ComboBox id="cb" dataProvider="{ac}" labelFunction="myLabelFunc"/> 
    
    <!-- Form for data to add or change in the ArrayCollection. --> 
    <s:Form> 
       <s:FormItem label="First Name"> 
            <s:TextInput id="firstInput"/> 
       </s:FormItem> 
       <s:FormItem label="Last Name"> 
            <s:TextInput id="lastInput"/> 
       </s:FormItem> 
       <s:FormItem label="Email"> 
            <s:TextInput id="emailInput"/> 
       </s:FormItem> 
    </s:Form> 
    
    <s:HGroup> 
        <!-- Buttons to initiate operations on the collection. --> 
        <s:Button label="Add New" click="addPerson()"/> 
        <s:Button label="Update Selected" click="updatePerson()"/> 
        <s:Button label="Remove Selected" click="removePerson()"/> 
        <!-- Clear the text input fields. --> 
        <s:Button label="Clear" click="clearInputs()"/> 
    </s:HGroup> 
    
    <!-- The application displays event information here --> 
    <s:Label text="Log"/> 
    <s:TextArea id="log" width="100" height="100%"/> 
</s:Application>

Hierarchical data objects

You use hierarchical data objects with the controls that display a nested hierarchy of nodes and subnodes, such as tree branches and leaves, as well as Menu submenus and items. The following controls use hierarchical data objects:

The hierarchical components all use the same mechanism to work with the data provider. The following examples use the Tree control, but the examples apply to the other components.

About hierarchical data objects

The Flex framework, by default, supports two types of hierarchical data objects.

XML

can be any of the following: Strings containing well-formed XML; or XML, XMLList, or XMLListCollection objects, including objects generated by the <fx:XML> and <fx:XMLList> compile-time tags. (These tags support data binding, which you cannot do directly in ActionScript.) Flex can automatically structure a Tree or menu-based control to reflect the nesting hierarchy of well-formed XML.

Objects

can be any set of nested Objects or Object subclasses (including Arrays or ArrayCollection objects) that have a structure where the children of a node are in a children field. For more information, see Creating a custom data descriptor. You can also use the <fx:Model> compile-time tag to create nested objects that support data binding, but you must follow the structure defined in Using the <fx:Model> tag with Tree and menu-based controls.

You can add support for other hierarchical data provider structures, such as nested Objects where the children might be in fields with varying names.

Data descriptors and hierarchical data structure

Hierarchical data used in Tree and menu-based controls must be in a form that can be parsed and manipulated by using a data descriptor class. A data descriptor is a class that provides an interface between the hierarchical control and the data provider object. It implements a set of control-specific methods to determine the data provider contents and structure; to get, add, and remove data; and to change control-specific data properties.

Flex defines two data descriptor interfaces for hierarchical controls:

The Flex framework provides a DefaultDataDescriptor class that implements both interfaces. You can use the dataDescriptor property to specify a custom data descriptor class that handles data models that do not conform to the default descriptor structure.

Data descriptor methods and source requirements

The following table describes the methods of both interfaces, and the behavior of the DefaultDataDescriptor class. The first line of each interface/method entry indicates whether the method belongs to the ITreeDataDescriptor interface, the IMenuDataDescriptor interface, or both interfaces, and therefore indicates whether the method is used for trees, menus, or both.

Method

Returns

DefaultDataDescriptor behavior

hasChildren(node, [model])

A Boolean value indicating whether the node is a branch with children.

For XML, returns true if the node has at least one child element.

For other objects, returns true if the node has a nonempty children field.

getChildren(node, [collection])

A node's children.

For XML, returns an XMLListCollection with the child elements.

For other Objects, returns the contents of the node's children field.

isBranch(node, [collection])

Whether a node is a branch.

For XML, returns true if the node has at least one child, or if it has an isBranch attribute.

For other Objects, returns true if the node has an isBranch field.

getData(node, [collection])

The node data.

Returns the node.

addChildAt(node, child, index, [model])

A Boolean value indicating whether the operation succeeded.

For all cases, inserts the node as a child object before the node currently in the index location.

removeChildAt

(node, index, [model])

A Boolean value indicating whether the operation succeeded.

For all cases, removes the child of the node in the index location.

getType(node)

(IMenuDataDescriptor only)

A String with the menu node type. Meaningful values are check, radio, and separator.

For XML, returns the value of the type attribute of the node.

For other Objects, returns the contents of the node's type field.

isEnabled(node)

(IMenuDataDescriptor only)

A Boolean value indicating whether a menu node is enabled.

For XML, returns the value of the enabled attribute of the node.

For other Objects, returns the contents of the node's enabled field.

setEnabled(node, value)

(IMenuDataDescriptor only)

 

For XML, sets the value of the enabled attribute of the node to true or false.

For other Objects, sets the contents of the node's enabled field.

isToggled(node)

(IMenuDataDescriptor only)

A Boolean value indicating whether a menu node is selected

Returns the value of the node's toggled attribute.

setToggled(node, value)

(IMenuDataDescriptor only)

 

For XML, sets the value of the selected attribute of the node to true or false.

For other Objects, sets the contents of the node's enabled field.

getGroupName(node)

(IMenuDataDescriptor only)

The name of the radio button group to which the node belongs.

For XML, returns the value of the groupName attribute of the node.

For other Objects, returns the contents of the node's groupName field.

The following example Object follows the default data provider structure for a Tree control, and is correctly handled by the DefaultDataDescriptor class:

 [Bindable] 
 public var fileSystemStructure:Object =  
 	{label:"mx", children: [ 
 		{label:"Containers", children: [ 
 			{label:"Accordian", children:[]}, 
 			{label:"DividedBox", children: [ 
 				{label:"BoxDivider.as", data:"BoxDivider.as"},  
 				{label:"BoxUniter.as", data:"BoxUniter.as"}]}, 
 			{label: "Grid", children:[]}]}, 
 		{label: "Controls", children: [ 
 			{label: "Alert", data: "Alert.as"}, 
 			{label: "Styles", children: [ 
 				{label: "AlertForm.as", data:"AlertForm.as"}]}, 
 			{label: "Tree", data: "Tree.as"}, 
 			{label: "Button", data: "Button.as"}]}, 
 		{label: "Core", children:[]} 
 	]};

For objects, the root is the Object instance, so there must always be a single root (as with XML). You could also use an Array containing nested Arrays as the data provider. In this case the provider has no root; each element in the top level array appears at the top level of the control.

The DefaultDataDescriptor can properly handle well-formed XML nodes. The isBranch() method, however, returns true only if the parameter node has child nodes or if the node has an isBranch attribute with the value true. Therefore, if your XML object uses any technique other than a true isBranch attribute to indicate empty branches, you must create a custom data descriptor.

The DefaultDataDescriptor handles collections properly. For example, if a node's children property is an ICollectionView instance, the getChildren() method returns the children as an ICollectionView object.

Using the <fx:Model> tag with Tree and menu-based controls

The <fx:Model> tag lets you define a data provider structure in MXML. The Flex compiler converts the contents of the tag into a hierarchical graph of ActionScript Objects. The <fx:Model> tag has two advantages over defining an Object data provider in ActionScript:

  • You can define the structure by using an easily read, XML-like format.

  • You can bind structure entries to ActionScript variables, so that you can use <fx:Model> to create an object-based data provider that gets its data from multiple dynamic sources.

To use an <fx:Model> tag with a control that uses a data descriptor, the object generated by the compiler must conform to the data descriptor requirements, as discussed in Data descriptors and hierarchical data structure. Also, as with an XML object, the tag must have a single root element.

In most situations, you should consider using an <fx:XML> or <fx:XMLList> tag, as described in XML-based data objects, instead of using an <fx:Model> tag. The XML-based tags support data binding to elements, and the DefaultDataDescriptor class supports all well-structured XML. Therefore you can use a more natural structure, where node names can represent their function, and you do not have to artificially name nodes "children."

To use an <fx:Model> tag as the data provider for a control that uses the DefaultDataDescriptor class, all child nodes must be named "children." This requirement differs from the structure that you use with an Object, where the array that contains the child objects is named "children".

The following example shows the use of an <fx:Model> tag with data binding as a data provider for a menu, and shows how you can change the menu structure dynamically:

<?xml version="1.0"?> 
<!-- dpcontrols\ModelWithMenu.mxml --> 
<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" 
    xmlns="*"> 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
    
    <fx:Script> 
        <![CDATA[ 
 
            import mx.controls.Menu; 
            public var productMenu:Menu; 
 
            public function initMenu(): void { 
                productMenu = Menu.createMenu(null, Products.Department); 
                productMenu.setStyle("disabledColor", 0xCC3366); 
                productMenu.show(10,10); 
            } 
        ]]> 
    </fx:Script> 
 
    <fx:Declarations> 
        <fx:Model id="Products"> 
            <Root> 
                <Department label="Toys"> 
                    <children label="Teddy Bears"/> 
                    <children label="Action Figures"/> 
                    <children label="Building Blocks"/> 
                </Department> 
                <Department label="Kitchen"> 
                    <children label="Electronics"> 
                        <children label="Crock Pot"/> 
                        <children label="Panini Grill"/> 
                    </children> 
                    <children label="Cookware"> 
                        <children label="Grill Pan"/> 
                        <children label="Iron Skillet" enabled="false"/> 
                    </children> 
                </Department> 
                <!-- The items in this entry are bound to the form data --> 
                <Department label="{menuName.text}"> 
                    <children label="{item1.text}"/> 
                    <children label="{item2.text}"/> 
                    <children label="{item3.text}"/> 
                </Department> 
            </Root> 
        </fx:Model> 
    </fx:Declarations> 
 
    <s:Button label="Show Products" click="initMenu()"/> 
    <!-- If you change the contents of the form, the next time you 
        display the Menu, it will show the updated data in the last 
        main menu item. --> 
    <s:Form> 
        <s:FormItem label="Third Submenu title"> 
            <s:TextInput id="menuName" text="Clothing"/> 
        </s:FormItem> 
        <s:FormItem label="Item 1"> 
            <s:TextInput id="item1" text="Sweaters"/> 
        </s:FormItem> 
        <s:FormItem label="Item 2"> 
            <s:TextInput id="item2" text="Shoes"/> 
        </s:FormItem> 
        <s:FormItem label="Item 3"> 
            <s:TextInput id="item3" text="Jackets"/> 
        </s:FormItem> 
    </s:Form> 
</s:Application>

Creating a custom data descriptor

If your hierarchical data does not fit the formats supported by the DefaultDataDescriptor class—for example, if your data is in an object that does not use a children field—you can write a custom data descriptor and specify it in your Tree control's dataDescriptor property. The custom data descriptor must implement all methods of the ITreeDataDescriptor interface.

The following example shows how you can create a custom data descriptor—in this case, for use with a Tree control. This data descriptor correctly handles a data provider that consists of nested ArrayCollection objects.

The following code shows the MyCustomTreeDataDescriptor class, which implements only the ITreeDataDescriptor interface, so it supports Tree controls but not menu-based controls. The custom class supports tree nodes whose children field is either an ArrayCollection or an Object. When getting a node's children, if the children object is an ArrayCollection, it returns the object; otherwise, it wraps the children object in an ArrayCollection before returning it. When adding a node, it uses a different method to add the node, depending on the children field type.

package myComponents 
// myComponents/MyCustomTreeDataDescriptor.as 
{ 
import mx.collections.ArrayCollection; 
import mx.collections.CursorBookmark; 
import mx.collections.ICollectionView; 
import mx.collections.IViewCursor; 
import mx.events.CollectionEvent; 
import mx.events.CollectionEventKind; 
import mx.controls.treeClasses.*; 
 
public class MyCustomTreeDataDescriptor implements ITreeDataDescriptor 
{ 
 
    // The getChildren method requires the node to be an Object 
    // with a children field. 
    // If the field contains an ArrayCollection, it returns the field 
    // Otherwise, it wraps the field in an ArrayCollection. 
    public function getChildren(node:Object, 
        model:Object=null):ICollectionView 
    { 
        try 
        { 
            if (node is Object) { 
                if(node.children is ArrayCollection){ 
                    return node.children; 
                }else{ 
                    return new ArrayCollection(node.children); 
                } 
            } 
        } 
        catch (e:Error) { 
            trace("[Descriptor] exception checking for getChildren"); 
        } 
        return null; 
    } 
 
    // The isBranch method simply returns true if the node is an 
    // Object with a children field. 
    // It does not support empty branches, but does support null children 
    // fields. 
    public function isBranch(node:Object, model:Object=null):Boolean { 
        try { 
            if (node is Object) { 
                if (node.children != null)  { 
                    return true; 
                } 
            } 
        } 
        catch (e:Error) { 
            trace("[Descriptor] exception checking for isBranch"); 
        } 
        return false; 
    } 
 
    // The hasChildren method Returns true if the 
    // node actually has children. 
    public function hasChildren(node:Object, model:Object=null):Boolean { 
        if (node == null) 
            return false; 
        var children:ICollectionView = getChildren(node, model); 
        try { 
            if (children.length > 0) 
                return true; 
        } 
        catch (e:Error) { 
        } 
        return false; 
    } 
    // The getData method simply returns the node as an Object. 
    public function getData(node:Object, model:Object=null):Object { 
        try { 
            return node; 
        } 
        catch (e:Error) { 
        } 
        return null; 
    } 
 
    // The addChildAt method does the following: 
    // If the parent parameter is null or undefined, inserts 
    // the child parameter as the first child of the model parameter. 
    // If the parent parameter is an Object and has a children field, 
    // adds the child parameter to it at the index parameter location. 
    // It does not add a child to a terminal node if it does not have 
    // a children field. 
    public function addChildAt(parent:Object, child:Object, index:int, 
            model:Object=null):Boolean { 
        var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); 
        event.kind = CollectionEventKind.ADD; 
        event.items = [child]; 
        event.location = index; 
        if (!parent) { 
            var iterator:IViewCursor = model.createCursor(); 
            iterator.seek(CursorBookmark.FIRST, index); 
            iterator.insert(child); 
        } 
        else if (parent is Object) { 
            if (parent.children != null) { 
                if(parent.children is ArrayCollection) { 
                    parent.children.addItemAt(child, index); 
                    if (model){ 
                        model.dispatchEvent(event); 
                        model.itemUpdated(parent); 
                    } 
                    return true; 
                } 
                else { 
                    parent.children.splice(index, 0, child); 
                    if (model) 
                        model.dispatchEvent(event); 
                    return true; 
                } 
            } 
        } 
        return false; 
    } 
 
    // The removeChildAt method does the following: 
    // If the parent parameter is null or undefined, 
    // removes the child at the specified index 
    // in the model. 
    // If the parent parameter is an Object and has a children field, 
    // removes the child at the index parameter location in the parent. 
    public function removeChildAt(parent:Object, child:Object, index:int, model:Object=null):Boolean 
    { 
        var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); 
        event.kind = CollectionEventKind.REMOVE; 
        event.items = [child]; 
        event.location = index; 
 
        //handle top level where there is no parent 
        if (!parent) 
        { 
            var iterator:IViewCursor = model.createCursor(); 
            iterator.seek(CursorBookmark.FIRST, index); 
            iterator.remove(); 
            if (model) 
                model.dispatchEvent(event); 
            return true; 
        } 
        else if (parent is Object) 
        { 
            if (parent.children != undefined) 
            { 
                parent.children.splice(index, 1); 
                if (model) 
                    model.dispatchEvent(event); 
                return true; 
            } 
        } 
        return false; 
    } 
 
} 
}

The following example uses the MyCustomTreeDataDescriptor to handle hierarchical nested ArrayCollections and objects. When you click the button, it adds a node to the tree by calling the data descriptor's addChildAt() method. Notice that you would not normally use the addChildAt() method directly. Instead, you would use the methods of a Tree or menu-based control, which in turn use the data descriptor methods to modify the data provider.

<?xml version="1.0" encoding="iso-8859-1"?> 
<!-- dpcontrols\CustDataDescriptor.mxml --> 
<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" 
    xmlns="*" 
    creationComplete="initCollections();"> 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
 
    <fx:Script> 
        <![CDATA[ 
            import mx.collections.*; 
            import mx.controls.treeClasses.*; 
            import myComponents.*; 
    
            /* Variables used to construct the ArrayCollection data provider 
               First top-level node and its children. */ 
            public var nestArray1:Array = [ 
                {label:"item1", children: [ 
                    {label:"item1 child", children:     [ 
                        {label:"item 1 child child", data:"child data"} 
                    ]} 
                ]} 
            ]; 
            /* Second top-level node and its children. */ 
            public var nestArray2:Array = [ 
                {label:"item2", children: [ 
                    {label:"item2 child", children: [ 
                        {label:"item 2 child child", data:"child data"} 
                    ]} 
                ]} 
            ]; 
            /* Second top-level node and its children. */ 
            public var nestArray3:Array = [ 
                {label:"item3", children: [ 
                    {label:"item3 child", children: [ 
                        {label:"item 3 child child", data:"child data"} 
                    ]} 
                ]} 
            ]; 
            /* Variable for the tree array. */ 
            public var treeArray:Array 
            /* Variables for the three Array collections that correspond to the 
               top-level nodes. */ 
            public var col1:ArrayCollection; 
            public var col2:ArrayCollection; 
            public var col3:ArrayCollection; 
            
            /* Variable for the ArrayCollection used as the Tree data provider. */ 
            [Bindable] 
            public var ac:ArrayCollection; 
            
            /* Build the ac ArrayCollection from its parts. */ 
            public function initCollections():void{ 
                /* Wrap each top-level node in an ArrayCollection. */ 
                col1 = new ArrayCollection(nestArray1); 
                col2 = new ArrayCollection(nestArray2); 
                col3 = new ArrayCollection(nestArray3); 
 
                /* Put the three top-level node 
                   ArrayCollections in the treeArray. */ 
                treeArray = [ 
                    {label:"first thing", children: col1}, 
                    {label:"second thing", children: col2}, 
                    {label:"third thing", children: col3}, 
                ]; 
                /* Wrap the treeArray in an ArrayCollection. */ 
                ac = new ArrayCollection(treeArray); 
            } 
 
            /* Adds a child node as the first child of the selected node, 
               if any. The default selectedItem is null, which causes the 
               data descriptor addChild method to add it as the first child 
               of the ac ArrayCollection. */ 
            public function clickAddChildren():void { 
                var newChild:Object = new Object(); 
                newChild.label = "New Child"; 
                newChild.children = new ArrayCollection(); 
                tree.dataDescriptor.addChildAt(tree.selectedItem, newChild, 0, ac); 
            } 
 
        ]]> 
    </fx:Script> 
 
    <mx:Tree width="200" id="tree" dataProvider="{ac}" 
        dataDescriptor="{new MyCustomTreeDataDescriptor()}"/>    
    <s:Button label="Add Child" click="clickAddChildren();"/> 
</s:Application>

XML-based data objects

The data for a tree is often retrieved from a server in the form of XML, but it can also be well-formed XML defined within the <mx:Tree> tag. The DefaultDataDescriptor class can handle well-formed XML data structures.

You can use an <fx:XML> or <fx:XMLList> tag to define an XML or XMLList object in MXML. Unlike the XML and XMLList classes in ActionScript, these tags let you use MXML binding expressions in the XML text to extract node contents from variable data. For example, you can bind a node's name attribute to a text input value, as in the following example:

 <fx:XMLList id="myXMLList"> 
 	<child name="{textInput1.text}"/> 
 	<child name="{textInput2.text}"/> 
 </fx:XMLList>

You can use an XML object directly as a data provider to a hierarchical data control. However, if the object changes dynamically, you should do the following:

  1. Convert the XML or XMLList object to an XMLListCollection object.

  2. Make all updates to the data by modifying the XMLListCollection object.

Doing this ensures that the component represents the dynamic data. The XMLListCollection class supports the use of all IList and ICollectionView interface methods, and adds many of the most commonly used XMLList class methods. For more information on using XMLListCollections, see XMLListCollection objects.

The following code example defines two Tree controls. The first uses an XML object directly, and the second uses an XMLListCollection object as the data source:

<?xml version="1.0"?> 
<!-- dpcontrols\UseXMLDP.mxml --> 
<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" 
    width="650"> 
    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout> 
    
    <fx:Declarations> 
        <fx:XML id="capitals"> 
            <root> 
                <Capitals label="U.S. State Capitals"> 
                    <capital label="AL" value="Montgomery"/> 
                    <capital label="AK" value="Juneau"/> 
                    <capital label="AR" value="Little Rock"/> 
                    <capital label="AZ" value="Phoenix"/>       
                </Capitals> 
                <Capitals label="Canadian Province Capitals"> 
                    <capital label="AB" value="Edmonton"/> 
                    <capital label="BC" value="Victoria"/> 
                    <capital label="MB" value="Winnipeg"/> 
                    <capital label="NB" value="Fredericton"/> 
                </Capitals> 
            </root> 
        </fx:XML> 
 
        <!-- Create an XMLListCollection representing the Tree nodes. 
             capitals.Capitals is an XMLList with both Capitals elements. --> 
        <mx:XMLListCollection id="capitalColl" source="{capitals.Capitals}"/> 
    </fx:Declarations> 
 
    <s:Label text="These two Tree controls appear identical, although their data sources are different."/> 
 
    <s:HGroup> 
        <!-- When you use an XML-based data provider with a tree 
             you must specify the label field, even if it 
             is "label". The XML object includes the root, 
             so you must set showRoot="false". Remember that 
             the Tree will not, by default, reflect dynamic changes 
             to the XML object. --> 
        <mx:Tree id="Tree1" dataProvider="{capitals}" labelField="@label" 
            showRoot="false" width="300"/> 
        
        <!-- The XMLListCollection does not include the XML root. --> 
        <mx:Tree id="Tree2" dataProvider="{capitalColl}" labelField="@label" 
            width="300"/>       
    </s:HGroup> 
</s:Application>

This example shows two important features of using a hierarchical data provider with a Tree control:

  • ECMAScript for XML (E4X) objects must have a single root node, which might not be appropriate for displaying in the Tree. Also, trees can have multiple elements at their highest level. To prevent the tree from displaying the root node, set the showRoot property to false. (The default showRoot value for the Tree control is true.) XMLList collections, however, do not have a single root, and you typically do not need to use the showRoot property.

  • When you use an XML, XMLList, or XMLListCollection object as the tree data provider, you must specify the labelField property, even if it is "label", if the field is an XML attribute. You must do this because you must use the @ sign to signify an attribute.

XMLListCollection objects

XMLListCollection objects provide collection functionality to an XMLList object and make available some of the XML manipulation methods of the native XMLList class, such as the attributes(), children(), and elements() methods. For details of the supported methods, see XMLListCollection in the ActionScript 3.0 Reference for Apache Flex.

The following simple example uses an XMLListCollection object as the data provider for a List control. It uses XMLListCollection methods to dynamically add items to and remove them from the data provider and its representation in the List control. The example uses a Tree control to represent a selection of shopping items and a List collection to represent a shopping list.

Users add items to the List control by selecting an item in a Tree control (which uses a static XML object as its data provider) and clicking a button. When the user clicks the button, the event listener uses the XMListCollection addItem() method to add the selected XML node to the XMLListCollection. Because the data provider is a collection, the List control is updated to show the new data.

Users remove items in a similar manner, by selecting an item in the list and clicking the Remove button. The event listener uses the XMListCollection removeItemAt() method to remove the item from the data provider and its representation in the List control.

<?xml version="1.0"?> 
<!-- dpcontrols\XMLListCollectionWithList.mxml --> 
<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" 
    width="550">    
    
    <fx:Script> 
        <![CDATA[ 
            import mx.collections.XMLListCollection; 
            import mx.collections.ArrayCollection; 
    
            /* An XML object with categorized produce. */ 
            [Bindable] 
            public var myData:XML= 
                <catalog> 
                  <category name="Meat"> 
                      <product name="Buffalo"/> 
                      <product name="T Bone Steak"/> 
                      <product name="Whole Chicken"/> 
                  </category> 
                  <category name="Vegetables"> 
                      <product name="Broccoli"/>                         
                      <product name="Vine Ripened Tomatoes"/> 
                      <product name="Yellow Peppers"/> 
                  </category> 
                  <category name="Fruit"> 
                      <product name="Bananas"/> 
                      <product name="Grapes"/> 
                      <product name="Strawberries"/> 
                  </category> 
              </catalog>; 
 
            /* An XMLListCollection representing the data 
               for the shopping List. */ 
           [Bindable] 
           public var listDP:XMLListCollection = new XMLListCollection(new XMLList()); 
    
           /* Add the item selected in the Tree to the List XMLList data provider. */ 
           private function doTreeSelect():void { 
               if (prodTree.selectedItem) 
               listDP.addItem(prodTree.selectedItem.copy()); 
           } 
 
           /* Remove the selected in the List from the XMLList data provider. */ 
           private function doListRemove():void { 
               if (prodList.selectedItem) 
                   listDP.removeItemAt(prodList.selectedIndex); 
           } 
        ]]> 
    </fx:Script> 
    
    <s:HGroup> 
        <mx:Tree id="prodTree" dataProvider="{myData}" width="200" 
            showRoot="false" labelField="@name"/>           
        <s:VGroup> 
            <s:Button id="treeSelect" label="Add to List" 
                click="doTreeSelect()"/> 
            <s:Button id="listRemove" label="Remove from List" 
                click="doListRemove()"/> 
        </s:VGroup>         
        <s:List id="prodList" dataProvider="{listDP}" width="200" 
            labelField="@name"/>        
    </s:HGroup> 
</s:Application>

Remote data in data provider components

You use Flex data access components: HTTPService, WebService, and RemoteObject, to supply data to Flex data provider components.

To use a remote data source to provide data, you represent the result of the remote service with the appropriate object, as follows:

  • A RemoteObject component automatically returns an ArrayCollection for any data that is represented on the server as a java.util.List object, and you can use the returned object directly.

  • For HTTPService and WebService results, cast the result data to a collection class if the data changes or if you use the same result in multiple places (the latter case is more efficient). As a general rule, use an ArrayCollection for serialized (list-based) objects and an XMLListCollection for XML data.

The following code snippet shows this use, casting a list returned by a web service to an Array in an ArrayCollection:

 	<mx:WebService id="employeeWS" wsdl="http://server.com/service.wsdl" 
 		showBusyCursor="true" 
 		fault="alert(event.fault.faultstring)"> 
 		<mx:operation name="getList"> 
 			<mx:request> 
 				<deptId>{dept.selectedItem.data}</deptId> 
 			</mx:request> 
 		</mx:operation> 
 		... 
 	</mx:WebService> 
  
 	<mx:ArrayCollection id="ac" 
 		source="mx.utils.ArrayUtil.toArray(employeeWS.getList.lastResult)"/> 
 	<mx:DataGrid dataProvider="{ac}" width="100%">

Handling data pages with Spark components

You can use a data collection with a remote data source. Often, you want to access the data as it arrives from the server rather than waiting for all of the remote data to load. Multiple data items in a collection can be grouped into pages. The application can then handle each page as it arrives, rather than waiting for the entire collection.

A collection that supported data pages dispatches an ItemPendingError error when a request for data item is pending. The application can then handle the error as necessary.

The MX List and MX DataGrid controls have built in support for handling ItemPendingError errors. However, the Spark DataGroup and other Spark controls, such as List, do not.

To support data paging with Spark controls, use the AsyncListView as the data provider of the control. The AsyncListView class implements the IList interface, so it can be used as the data provider of a Spark control. AsyncListView handles ItemPendingError errors thrown when items requested by a call to the getItemAt() method are not available.

The following example uses the AsyncListView class with a Spark List control:

<fx:Declarations> 
	// Define an ArrayCollection to hold the data from a DataService. 
	<mx:ArrayCollection id="products"/> 
	<mx:DataService id="ds" destination="inventory"/> 
</fx:Declarations> 
// Define a Button control to populate the ArrayCollection. 
<s:Button label="Get Data" click="ds.fill(products);"/> 
 
// Wrap the ArrayCollection in an AsyncListView class to handle ItemPendingError events. 
<s:List> 
	<mx:AsyncListView list="{products}"/> 
</s:List>

In this example, the AsyncListView class handles any ItemPendingError errors generated when a data item is not yet available.

The AsyncListView class defines two properties, createPendingItemFunction and createFailedItemFunction, that you can use to specify callback functions executed for an ItemPendingError error. The callback function specified by createPendingItemFunction creates a placeholder item in the collection when a request is pending. The callback function specified by createFailedItemFunction creates a placeholder item in the collection when a request fails.

The following example creates these callback functions:

<fx:Script> 
	<![CDATA[ 
		import mx.collections.errors.ItemPendingError; 
	 
		private function createPendingItem(index:int, ipe:ItemPendingError):Object { 
			return "[" + index + " ...]"; 
		} 
 
		private function createFailedItem(index:int, info:Object):Object { 
			return "[" + index + " failed]"; 
		} 
	]]> 
</fx:Script> 
 
<fx:Declarations> 
	<mx:ArrayCollection id="products"/> 
	<mx:DataService id="ds" destination="inventory"/> 
</fx:Declarations> 
 
<s:Button label="Get Data" click="ds.fill(products);"/> 
 
<s:List> 
	<mx:AsyncListView list="{products}" 
		createPendingItemFunction="{createPendingItem}" 
		createFailedItemFunction="{createFailedItem}"/> 
</s:List>

Data providers and the uid property

Flex data provider controls use a unique identifier (UID) to track data items. Flex can automatically create and manage UIDs. However, there are circumstances when you must supply your own uid property by implementing the IUID interface, and there are circumstances when supplying your own uid property improves processing efficiency.

Because the Object and Array classes are dynamic, you normally do not do anything special for data objects whose items belong to these classes. However, you should consider implementing the IUID if your data object items belong to custom classes that you define.

Note: When Flex creates a UID for an object, such as an item in an ArrayCollection, it adds the UID as an mx_internal_uid property of the item. Flex creates mx_internal_uid properties for any objects that are dynamic and do not have bindable properties. To avoid having Flex create mx_internal_uid properties, the object class should do any of the following things: have at least one property with a [Bindable] metadata tag; implement the IUID interface; or have a uid property with a value.

If Flex must consider two or more different objects to be identical, the objects must implement the IUID interface so that you can assign the same uid value to multiple objects. A typical case where you must implement the IUID interface is an application that uses paged collections. As the cursor moves through the collection, a particular item might be pulled down from the server and released from memory repeatedly. Every time the item is pulled into memory, a new object is created to represent the item. If you need to compare items for equality, Flex should consider all objects that represent the same item to be the same "thing."

More common than the case where you must implement the IUID interface is the case where you can improve processing efficiency by doing so. As a general rule, you do not implement the IUID interface if the data provider elements are members of dynamic classes. Flex can automatically create a uid property for these classes. There is still some inefficiency, however, so you might consider implementing the IUID interface if processing efficiency is particularly important.

In all other cases, Flex uses the Dictionary mechanism to manage the uid, which might not be as efficient as supplying your own UID.

The IUID interface contains a single property, uid, which is a unique identifier for the class member, and no methods. Flex provides a UIDUtil class that uses a pseudo-random-number generator to create an identifier that conforms to the standard GUID format. Although this identifier is not guaranteed to be universally unique, it should be unique among all members of your class. To implement a class that uses the UIDUtil class, such as a Person class that has fields for a first name, last name, and ID, you can use the following pattern:

 package { 
 	import mx.core.IUID; 
 	import mx.utils.UIDUtil; 
  
 	[Bindable] 
 	public class Person implements IUID { 
 		public var id:String; 
 		public var firstName:String; 
 		public var lastName:String; 
 		private var _uid:String; 
  
 		public function Person() { 
 			_uid = UIDUtil.createUID(); 
 		} 
  
 		public function get uid():String { 
 			return _uid; 
 		} 
  
 		public function set uid(value:String):void { 
 			// Do nothing, the constructor created the uid. 
 		} 
 	} 
 }

You do not need to use the UIDUtil class in a case where the objects contain a uniquely-identifying field such as an employee ID. In this case, you can use the person's ID as the uid property, because the uid property values uniquely identify the object only in the data provider. The following example implements this approach:

 package  
 { 
 	import mx.core.IUID; 
  
 	[Bindable] 
 	public class Person implements IUID { 
 		public var employee_id:String; 
 		public var firstName:String; 
 		public var lastName:String;  
  
 		public function get uid(): String { 
 			return employee_id; 
 		} 
  
 		public function set uid(value: String): void { 
 			employee_id=value; 
 		} 
 	} 
 } 
Note: Object cloning does not manage or have a relationship with UIDs, so if you clone something that has an internal UID you must also change that internal UID. UIDs are stored on mx_internal_uid only for dynamic Objects. Instances of data classes that implement IUID store their UIDs in a uid property, so that is the property that must be changed after cloning.

Navigation

Using Flex » Using data-driven UI components

Adobe, Adobe Flash and Adobe Flash Platform are either registered trademarks or trademarks of Adobe Systems Incorporated in the United States and/or other countries and are used by permission from Adobe. No other license to the Adobe trademarks are granted.