How To Filter An ArrayCollection That Is The Data Provider For A Flex 2 Tree Control
When you use an ArrayCollection or XMLListCollection to provide the data for a Flex Tree control, you can filter the items that are displayed. This example shows one way to filter an ArrayCollection that is used as the data provider for a Tree control. For background, see my previous post about using an ArrayCollection as the data provider for a Tree control and also how to filter an ArrayCollection based on multiple criteria.
View an example Flex application (right click on the application to view the source code). Be patient as it may take 15-20 seconds for the application to load the data. (I'm working on getting the data into Flex faster.)
My previous version of the Tree control displayed hierarchical data about consultants, their areas of expertise, and their consulting roles. In this version of the Tree control example, I added two combo boxes that will assist the user in filtering the items displayed in the Tree control. The first combo box provides the areas of expertise for the consultants and the second combo box provides the roles a consultant can perform. So if the user wants to see only those consultants with a specific area of expertise in the Tree, the user selects that area in the combo box. If the user wants to see only those consultants with a specific expertise and a specific role for that area, the user makes a selection in both combo boxes.
When the user makes a change to a combo box, function updateTree is called.
//called when combo box selection changes
private function updateTree():void {
//cause the ArrayCollection to apply the filter
consultantAryCol.refresh();
consultantTree.selectedItem = null;
//resetting the data provider will cause the tree to redraw
//and show only the items that were filtered
consultantTree.dataProvider = consultantAryCol;
}//end function updateTree
The call to refresh() causes the ArrayCollection to call the filter function (which I previously set earlier to the function named treeAryColFilter) for each item in the ArrayCollection. If the filter criteria is found in an item, the filter function returns true and that item is displayed in the Tree. By resetting the Tree's data provider, the Tree control is forced to redraw its items and since the ArrayCollection has been filtered the Tree will only display the items that matched the filter criteria.
The filter function for a Tree's ArrayCollection is slightly different than for a DataGrid's ArrayCollection. The ArrayCollection for the Tree is an array of Objects. Each object also contains an array of objects and each of those objects also contains an array (see my previous post about using an ArrayCollection as the data provider for a Tree control for the ArrayCollection's make up).
In the ArrayCollection's filter function (treeAryColFilter), I use a series of if statements to figure out which combo box the user changed (or if the user has selected an item in both combo boxes). In each if statement I call a separate function that will return true if the value the user selected in the combo box is in a tree item. For example, if the user selected "artificial intelligence" in the areas of expertise combo box, I call the searchForArea function. This function searches through every Object stored in the children attribute of the item in the tree and returns true if a match is found.
//search for the areaID
private function searchForArea(consultant:Object, areaID:int):Boolean {
var found:Boolean=false;
for each (var area:Object in consultant.children) {
if (area.areaID == areaID) {
found = true;
break;
}//end if
}//end for
return found ;
}//end function searchForArea
The point here is that you must loop over each object stored in the array (consultant.children) and compare the object's attribute (area.areaID) value to the value the user selected in the combo box (areaID). The for each statement loops over each Object stored in the consultant.children array (children is the attribute in the consultant object that is an array of Area objects).
Filtering a large number of items displayed in the Tree can assist the user in quickly finding the data she wants without having to manually go through all the items.
You may be able to add some complex programming to keep track of which tree items are opened and if those same items appear again after the filter is applied you could have them open also.
Great post... this really came in handy!
Thanks a lot for a good article.
But here is a problem i am facing now trying to filter DataProvider based on a click event's x_coordinate:
private function DestinationsResultHandler(event:ResultEvent):void {
destinationsList = event.result as ArrayCollection;
trace(destinationsList);
}
[Bindable]
private var currentClickedItem:String;
public function getClickItem(event:Event):void{
var currentClickedItem=event.currentTarget.getRepeaterItem().x_coordinate;
trace('coordinate '+currentClickedItem);
}
public function filterDG(item:Object):Boolean{
var result:Boolean=false;
trace(currentClickedItem);
if(item.x_coordinate==currentClickedItem){
result= true;
}
return result;
}
public function filterAC():void {
destinationsList.filterFunction=filterDG;
/* Refresh the collection view to apply the filter. */
destinationsList.refresh();
test_dg.dataProvider=destinationsList;
}
<mx:Canvas id="map_objects" x="0" y="0">
<mx:Repeater id="destinations_list" dataProvider="{destinationsList}">
<mx:Image x="{destinations_list.currentItem.x_coordinate}" y="{destinations_list.currentItem.y_coordinate}" toolTip="{destinations_list.currentItem.title}" width="30" height="30"
source="../assets/logo_btn.swf" id="location_image" click="getClickItem(event),filterAC()"/>
</mx:Repeater>
</mx:Canvas>
It gives me errors, and i cant make any filtering, because it somehow returns null as x_coordinate in filter, although in trace of getClickItem i can see proper number.
What i am doing wrong? Please help!
[Bindable]
private var currentClickedItem:String;
public function getClickItem(event:Event):void{
var currentClickedItem=event.currentTarget.getRepeaterItem().x_coordinate;
trace('coordinate '+currentClickedItem);
}
public function filterDG(item:Object):Boolean{
if (currentClickedItem==null)
return true
else
return item.x_coordinate == currentClickedItem;
}
public function filterAC(event:Event):void {
currentClickedItem=event.currentTarget.getRepeaterItem().x_coordinate;
destinationsList.filterFunction=filterDG;
/* Refresh the collection view to apply the filter. */
destinationsList.refresh();
test_dg.dataProvider=destinationsList;
}
Thanks for explaining an important issue.
I still find the tree filtering a bit cumbersome. As far as I understand it, I need each node of the tree to hold all the information needed for filtering. This means that I can't just filter by the leafs of the tree - I need the parents to be aware if their children are filtered or not. If not - a parent may be filtered out and then all of its children will be gone even if they hold a needed data.
Is there a way to tell flex to filter only according to leaves?