Sort An ArrayCollection By Multiple Fields and Filter An ArrayCollection By Multiple Fields In Flex

This blog entry provides an example (tutorial) on how to sort an ArrayCollection by multiple fields and how to filter an ArrayCollection by multiple fields. ArrayCollections are often used as the dataprovider for a DataGrid component in Flex. Sometimes the Objects contained in the ArrayCollection may not be sorted in the manner you need them to be displayed in the DataGrid. For example, you don't have control over the original data used to build the ArrayCollection such as XML results returned by a Web Service. I'll show you how I sorted an ArrayCollection based on the values stored in two fields of the Objects in the ArrayCollection.

A more common manipulation of an ArrayCollection is to filter the Objects so only certain ones are displayed in the DataGrid. There are several good examples on the web, including ones by Ben Forta (http://www.forta.com/blog/index.cfm/2006/7/13/Filtering-Data-In-Flex) and Scott Stroz (http://www.boyzoid.com/blog/index.cfm/2006/10/19/Filtering-Data-in-Flex), on how to filter an ArrayCollection so that only specific Objects in the ArrayCollection are displayed in the DataGrid. However, their examples show how to filter the ArrayCollection based on one criteria. I'll show you in this example how I filter the ArrayCollection based on the values stored in two fields of the objects in the ArrayCollection.

So we are going to explore two different ways of manipulating an ArrayCollection: sorting the items and filtering the items. You can view a demo (right click to see the source) of an application where I apply these two techniques here:

/flex/datagridmultiplefilter/bin/datagridmultiplefilter.html

The demo uses a Web Service that provides data about a state, including city, zip code, and area code. Sometimes the Web Service runs a little slow, so you may see the busy cursor for 1-2 minutes.

These are my references for sorting and filtering an ArrayCollection:

Class ArrayCollection - http://livedocs.macromedia.com/flex/2/langref/mx/collections/ArrayCollection.html

Class Sort - http://livedocs.macromedia.com/flex/2/langref/mx/collections/Sort.html

Using DataProviders and Collections from the Flex 2 Developer's Guide -

http://livedocs.macromedia.com/flex/2/docs/wwhelp/wwhimpl/js/html/wwhelp.htm?href=00000498.html (specifically look at the section that discusses sorting and filtering)

Example of a multiple sort from the Flex 2 Developer's Guide -

http://livedocs.macromedia.com/flex/2/docs/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00000598.html#409958

Class SortField - http://livedocs.macromedia.com/flex/2/langref/mx/collections/SortField.html

In my demo application, I retrieve XML from a Web Service, The XML includes nodes listing the city, zip, and area code for the state provided. However, the XML is not sorted. I want to display the data sorted by city and then by zip. Though I could use the XML as the data provider for the DataGrid, I don't know of a way to sort the XML. So my first step was to loop through the XML, place the node values in an Object and then add the Object to an ArrayCollection. The ArrayCollection (see dgAryCol) is the data provider for my DataGrid.

Sort the ArrayCollection

After placing all the Objects in the ArrayCollection, I need to sort the ArrayCollection by the value stored in each Object's city field and the by the value stored in each object's zip field. To do this I first create a Sort class object. Then I create two SortField class objects.

sortA = new Sort();

sortByCity = new SortField("CITY", true, false, false);
sortByZip = new SortField("ZIP",false,false,true);

sortA.fields=[sortByCity, sortByZip];
dgAryCol.sort=sortA;
         
         dgAryCol.refresh();

The arguments to the SortField constructor are: the name of the field that I want to sort on (for example "CITY"); compare Strings case sensitive (default is false); arrange items in descending order (default is false, so ascending order is the default), are the values stored in the field numbers (default is false)

You then set the fields property of the Sort object to an array of SortField objects. The order of the SortField objects determines the order when sorting. Lastly, you set the sort property of the ArrayCollection to equal the Sort object. You must call the refresh() method of the ArrayCollection after setting the value for the sort property in order to update the view. The refresh method applies the sort and filter to the view.

In the demo application, after the above code is finished the DataGrid that is using the dgAryCol (the ArrayCollection object) as its data provider shows the information in city order and then by zip code order.

Filter the ArrayCollection

To filter an ArrayCollection you must first determine what is your filter criteria (what values must fields in each Object of the ArrayCollection match to be displayed in the DataGrid). Then you need to provide one or more Flex components to enable the user to specific the filter criteria. In my demo application, I provide a combo box showing the cities for the state selected and a text field where the user can input a zip code. The user can filter the ArrayCollection by selecting a city in the combo box and/or typing a zip code in the text field.

If the user selects just a city, then all Objects in the ArrayCollection that have a matching value in the City field will be returned and visible in the DataGrid. If the user just types in a zip code, then all Objects in the ArrayCollection whose ZIP field contains the numbers the user has typed will be returned and visible in the DataGrid. If the user selects a city and types in a zip code, the only those Object in the ArrayCollection that have a matching value in the City field and whose ZIP field contains the number the user typed in will be returned and visible in the DataGrid. If no city is selected and nothing is typed in to the zip text field then all Objects are returned by the filter and visible in the DataGrid.

The function that does all the filter work is below.

// ArrayCollection Filter function
public function dgAryColFilter(item:Object):Boolean
{
var result:Boolean=false;

/*
    filter on multiple criteria
the two boolean expressions connected by the && (AND) operator must
both be true for the overall if statement to be true
the two sets of inner (within the inner set of ( ) ) boolean expressions are connected by the || (OR)
operator. One of those two expressions must be true for that inner boolean expression to be true

so (T or F) and (T or F) equals T and T which equals true
(F or T) and (F or T) equals T and T which equals true
(F or F) and (T or F) equals F and T which equals false
(T or F) and (F or F) equals T and F which equals false
(F or F) and (F or F) equals F and F which equals false
    */


if ( (city.selectedItem.label == " Select City" || item.CITY == city.selectedItem.label) &&
(zipInput.length == 0 || item.ZIP.indexOf(zipInput.text) >=0 )

) {

result=true;

} //end if
return result;

} //end function dgArycolFilter

The comments in the code explain how the if statement works. To filter on multiple field values, you need to understand Boolean expressions. You can learn more about Boolean expressions in the ActionScript 3.0 guide's discussion of operators and conditionals.

To tell the ArrayCollection what function will provide the sort functionality, you set the filterFunction attribute of the ArrayCollection to equal the function name: dgAryCol.filterFunction = dgAryColFilter. Again, setting the filterFunction does not actually cause the filter function to be applied. Rather, you must call the refresh() method of the ArrayCollection (dgAryCol.refresh(); ).

When the refresh() method is called, each Object in the ArrayCollection is sent to the filter function (in this case to dgAryColFilter). If the filter criteria match for that Object, the filter function returns true, which causes that Object to remain in the view of the ArrayCollection and be displayed in the DataGrid. If the Object does not match the filter criteria, the filter function returns false, which causes that Object to not be in the view of the ArrayCollection. Therefore the Object will not be displayed in the DataGrid.

Note that if the filter function returns false, the Object is not removed (or deleted) from the ArrayCollection. Rather, the Object is removed from the "view" of the ArrayCollection. The "view" are those Objects that will be provided to the DataGrid. If the user changes the filter criteria (for example backspacing over numbers entered in the zip code text field), then the filter function may now return true for that Object. Then that Object will be added back to the view and shown in the DataGrid.

Finally, you need to specify a value for the change event for each of the Flex components that are used to specify the filter criteria. So in my combobox I have change="updateDG()". This means that each time the selected item in the combobox is changed, Flex will call the updateDG() function. In the updateDG() function below:

public function updateDG():void {

//resort the ArrayCollection before refreshing it sortA = new Sort();

sortByCity = new SortField("CITY", true, false, false);
sortByZip = new SortField("ZIP",false,false,true);

sortA.fields=[sortByCity, sortByZip];
dgAryCol.sort=sortA;

//refresh the ArrayCollection to apply the filter dgAryCol.refresh();




} //end function updateDG

I reapply the sort criteria and then refresh() the ArrayCollection. The call to refresh does the sort and filter. Now my DataGrid will now show only the objects in the ArrayCollection that matched the filter criteria and the Objects will be sorted correctly.

Comments
all the macromedia livedocs links are invalid - where can I find them now?
# Posted By casey | 2/13/07 4:36 AM
Casey - I think livedocs are just down temporarily. Livedocs was working on the 12th just fine. Try the livedocs links again later.
# Posted By Bruce | 2/13/07 5:31 AM
Glad to see someone else finally attacking this topic. The examples around the web are great, but most want to go beyond what they do, and the way filterFunction works is not immediately clear.

I had actually successfully gotten this to work with a text input and TWO comboboxes. But, again, there's another thing missing here that I think people might want to see.

What about when the text is filtering a piece of data that has paragraphs of text? What happens, using examples like this one, is "found" searches only show up if the text inputted is in the exact same order as the searched text. In other words, it doesn't work like a typical keywords search, i.e. a web search engine.

Any thoughts on how to do that? Clearly its going to require a loop someplace, but I'm starting to think its impractical as an "as-you-type" function and would need to be processed via a "Search" button.
# Posted By CG | 2/26/07 6:15 PM
CG - I think the first step is to find someone who has already built a key word search for a string. Then you could adapt their algorithm in your own ActionScript class. The key would be to have a function that would return true if the key words are found in the string.

Let everyone know if you get something developed for doing a key word datagrid filter.
# Posted By Bruce | 2/27/07 6:37 AM
Argh! [slap head!]

I finally get the filterFunction thing now. It was about time someone explained that properly. It has taken me a while to realise that the filterFunction could be so dynamic .

Mine looke like this now:

private function platformFilterFunction( item:Object ):Boolean
{
   var result:Boolean = false;

       var matches:int = 0;
       for( var x:String in platformFilterArray){// then loop through the filter list
          
          if(item[platformFilterArray[x]] == true){ // if the filter is in the item and is true
             matches++;
          }// end if   
                  
      }   // end outer loop
      
      // if the company returns a match for ALL of the filters, retur true   
      if (matches == platformFilterArray.length){
         result = true;
      }
   matches = 0;
   return result;      
            
}// end function


Thanks Bruce :-)
# Posted By David Heacock | 3/1/07 5:06 AM
Really helpful post. But what i would really find interesting is if you could (in your example) change the selection in the comboBox based on what the user selects in the dataGrid. I have been looking for a solution to this but don't seem to find an answer.
I mean select an item in the comboBox based NOT on it's index number but rather on one of the object's it hold properties (like zip no.)

Can there be a solution to this or am i looking in vain?

Thanx Bruce :)
# Posted By Doukas Dimitris | 8/24/07 4:33 AM
Thank you!! I've been fighting with filters for about an hour. Your post opened my eyes to what I was missing. Now I've finally got my filter acting correctly.
# Posted By Demian Holmberg | 10/24/07 1:38 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.1.004.