rss
twitter
    Find out what I'm doing, Follow Me :)

Filtering Data in Flex

One very powerful, and useful, feature of Flex is the ability to filter data inside the Flex application itself.  One such example would be if you were displaying a datagrid that contained many rows of data and wanted to give your users the ability to filter the data based on certain criteria.

The MXML below creates a small Flex appliction the displays a datagrid with information (name and city) about hospitals in New Jersey.

view plain print about
1<?xml version="1.0" encoding="utf-8"?><mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="initData()">    <mx:Script>        <![CDATA[            import mx.collections.ArrayCollection;           [Bindable]           private var dataList:ArrayCollection ;                       private function initData():void{            dataList= new ArrayCollection([            {name:"Atlantic City Medical Center-City Division", city:"Atlantic City"},            {name:"Atlantic City Medical Center-Mainland Division", city:"Pomona "},            {name:"Barnert Hospital", city:"Paterson"},            {name:"Bayonne Medical Center", city:"Bayonne"},            {name:"Bayshore Community Hospital", city:"Holmdel"},            {name:"Bergen Regional Medical Center. L.P.", city:"Paramus"},            {name:"Burdette Tomlin Memorial Hospital", city:"Cape May"},            {name:"Capital Health System - Fuld Campus", city:"Trenton"},            {name:"Capital Health System - Mercer Campus", city:"Trenton"},            {name:"CentraState Healthcare System", city:"Freehold"},            {name:"Chilton Memorial Hospital", city:"Pompton Plains"},            {name:"Christ Hospital", city:"Jersey City"},            {name:"Clara Maass Medical Center", city:"Belleville"},            {name:"Columbus Hospital", city:"Newark"},            {name:"Community Medical Center", city:"Toms River"},            {name:"East Orange General Hospital", city:"East Orange"},            {name:"Englewood Hospital and Medical Center", city:"Englewood"},            {name:"Hackensack University Medical Center", city:"Hackensack"},            {name:"Hackettstown Community Hospital", city:"Hackettstown"},            {name:"Holy Name Hospital", city:"Teaneck"},            {name:"Hospital Center at Orange", city:"Orange"},            {name:"Hunterdon Medical Center", city:"Flemington"},            {name:"Irvington General Hospital", city:"Irvington"},            {name:"Jersey Shore University Medical Center", city:"Neptune"},            {name:"JFK Medical Center", city:"Edison"},            {name:"Kennedy Memorial Hospitals/UMC Cherry Hill", city:"Cherry Hill"},            {name:"Kennedy Memorial Hospitals/UMC Stratford", city:"Stratford"},            {name:"Kennedy Memorial Hospitals/UMC Washington Twp", city:"Turnersville"},            {name:"Kessler Memorial Hospital", city:"Hammonton"},            {name:"Kimball Medical Center", city:"Lakewood"},            {name:"LibertyHealth-Greenville Hospital Campus", city:"Jersey City"},            {name:"LibertyHealth-Jersey City Medical Center Campus", city:"Jersey City"},            {name:"LibertyHealth-Meadowlands Hospital Campus", city:"Secaucus"},            {name:"Lourdes Medical Center of Burlington County", city:"Willingboro"},            {name:"Monmouth Medical Center", city:"Long Branch"},            {name:"MONOC", city:"Eatontown"},            {name:"Morristown Memorial Hospital", city:"Morristown"},            {name:"Muhlenberg Regional Medical Center", city:"Plainfield"},            {name:"Newark Beth Israel Medical Center", city:"Newark"},            {name:"Newton Memorial Hospital", city:"Newton"},            {name:"Ocean Medical Center", city:"Brick"},            {name:"Our Lady of Lourdes Medical Center", city:"Camden"},            {name:"Overlook Hospital", city:"Summit"},            {name:"Palisades Medical Center-New York Presbyterian ", city:"North Bergen"},            {name:"Pascack Valley Hospital ", city:"Westwood"},            {name:"PBI Regional Medical Center", city:"Passaic"},            {name:"Raritan Bay Medical Center - Old Bridge", city:"Old Bridge"},            {name:"Raritan Bay Medical Center - Perth Amboy", city:"Perth Amboy"},            {name:"Riverview Medical Center", city:"Red Bank"},            {name:"Robert Wood Johnson Univ Hospital", city:"New Brunswick"},            {name:"Robert Wood Johnson Univ Hospital at Hamilton", city:"Hamilton"},            {name:"Robert Wood Johnson Univ Hospital at Rahway", city:"Rahway"},            {name:"Saint Barnabas Medical Center", city:"Livingston"},            {name:"Saint Clares Hospital/Boonton Township", city:"Boonton"},            {name:"Saint Clares Hospital/Denville", city:"Denville"},            {name:"Saint Clares Hospital/Sussex", city:"Sussex"},            {name:"Saint James Hospital", city:"Newark"},            {name:"Saint Michaels Medical Center", city:"Newark"},            {name:"Saint Peters University Hospital", city:"New Brunswick"},            {name:"Shore Memorial Hospital", city:"Somers Point"},            {name:"Somerset Medical Center", city:"Somerville"},            {name:"South Jersey Healthcare-Bridgeton Hospital", city:"Bridgeton"},            {name:"South Jersey Healthcare-Elmer Hospital", city:"Elmer"},            {name:"South Jersey Healthcare-Newcomb Hospital", city:"Vineland"},            {name:"Southern Ocean County Hospital", city:"Manahawkin"},            {name:"St. Francis Medical Center", city:"Trenton"},            {name:"St. Josephs Regional Medical Center", city:"Paterson"},            {name:"St. Josephs Wayne Hospital", city:"Wayne"},            {name:"St. Mary Hospital", city:"Hoboken"},            {name:"St. Marys Hospital Passaic", city:"Passaic"},            {name:"Summit Hospital", city:"Summit"},            {name:"The Cooper Health System", city:"Camden"},            {name:"The Memorial Hospital of Salem County", city:"Salem"},            {name:"The Mountainside Hospital", city:"Montclair"},            {name:"The Valley Hospital", city:"Ridgewood"},            {name:"Trinitas Hospital", city:"Elizabeth"},            {name:"Underwood-Memorial Hospital", city:"Woodbury "},            {name:"Union Hospital", city:"Union"},            {name:"University Medical Center at Princeton", city:"Princeton"},            {name:"University of Medicine &amp; Dentistry of NJ-Univ Hosp", city:"Newark"},            {name:"Virtua Memorial Hospital Burlington County", city:"Mount Holly"},            {name:"Virtua West Jersey Hospital-Berlin", city:"Berlin"},            {name:"Virtua West Jersey Hospital-Marlton", city:"Marlton"},            {name:"Virtua West Jersey Hospital-Voorhees", city:"Voorhees"},            {name:"Warren Hospital", city:"Phillipsburg"}           ])            }                      private function filterDemo():void{               dataList.filterFunction = searchDemo;               dataList.refresh();           }                      private function searchDemo(item:Object):Boolean{               var isMatch:Boolean = false               if(item.name.toLowerCase().search(search.text.toLowerCase()) != -1){                   isMatch = true               }                              return isMatch;                          }                      private function clearSearch():void{               dataList.filterFunction = null;               dataList.refresh();               search.text = '';           }        ]]>    </mx:Script>        <mx:Form>            <mx:FormItem label="Search" direction="horizontal">               <mx:TextInput id="search" change="filterDemo()" />                <mx:Button label="Clear Search" click="clearSearch()" />            </mx:FormItem>          </mx:Form>         <mx:DataGrid dataProvider="{dataList}" width="400" height="400">            <mx:columns>               <mx:DataGridColumn headerText="Name" dataField="name" />               <mx:DataGridColumn headerText="City" dataField="city" />             </mx:columns>          </mx:DataGrid>   </mx:Application>

I admit, not a huge dataset, but big enough for our purposes.  The application itself is very simple, it displays a form with a text filed and a button, and a datagrid.  The code inside the method initData() merely populates an ArrayCollection, named dataList, with data.  For those CFers who might not understand exactly what an ArrayCollection is, think 'Array of Structs'.

Let's look at the pertinent code.

view plain print about
1<mx:TextInput id="search" change="filterDemo()" /> <mx:Button label="Clear Search" click="clearSearch()" />

These are pretty simple.  In the <mx:TextInput> we have the "change" attribute set to filterDemo().  All this does is tell Flex that when the value of the text box changes, call the method filterDemo().  In the <mx:Button> we are telling Flex to call the clearSearch() method when the button is clicked.

Next, let's look at these methods, first filterDemo().

view plain print about
1          private function filterDemo():void{               dataList.filterFunction = searchDemo;               dataList.refresh();           }                      private function searchDemo(item:Object):Boolean{               var isMatch:Boolean = false               if(item.name.toLowerCase().search(search.text.toLowerCase()) != -1){                   isMatch = true               }                              return isMatch;                          }

I know, there are two methods here, but they are related.  The method filterDemo() sets the filterFunction of our ArrayCollection, named dataList to "searchDemo".  This tells Flex, that for each item in the ArrayCollection, call the searchDemo() method. Then we tell Flex to refresh the ArrayCollection.  Using the refresh() method of an ArrayCollection will tell Flex to go through each item in the ArrayCollection, and filter it using the method specified by filterFunction.

When you are filtering data, and specify a filterFunction, each time the function is called, in our case searchDemo(), is called, Flex passes the entire 'row' into the function as an object.  What is important to keep in mind here is that if your ArrayCollection contains 12 'columns', yet you are only displaying 3, all 12 'columns' are passed to the filterFunction.

Now, to break down searchDemo().( Note that the function returns a Boolean.)  The first thing we do is set a variable, named isMatch, that we will use to tell Flex whether the item matches or not.  Next, we check to see if the value of item.name, which is what is displayed under the Name column in the data grid, mathces what has been typed into the search box (search.text). In this case, 'name' is merely the name we used when setting up the ArrayCcollection.  

Remember, Flex is case sensitive, and that includes when comparing strings, so we need to add toLowerCase() to item.name and search.text or else we would get some unexpected results.  In Flex, the search() method of a string is similar to Find() in ColdFusion, only that you pass one argument, the sub-string you are looking for.  

If the value of search.text.toLowerCase() is contained within item.name.toLowerCase(), we set isMatch to true.  We then return isMatch.  If the value retured from searchDemo() is true, the item is included in the new results, it the value is false, it is not included in the new results.

The last method we use, clearSeacrh().

view plain print about
1          private function clearSearch():void{               dataList.filterFunction = null;               dataList.refresh();               search.text = '';           }

This is used to restore the ArrayCollection to its original state, with all default data.  In the method, called when the 'Clear Search' button is clicked, sets the filterFunction to null and then refreshes the ArrayCcollection.  Setting a filterFunction to null tells Flex there is no filter.  The last thing we do is set the text of the search box to an empty string.

A demo is available here.

8 comments

(Comment Moderation is enabled. Your comment will not appear until approved.)
todd sharp said...
I'm seeing some weirdness when trying to use this. My dataProvider is returned as an Array of Structs from a remoting call - when I try to convert it to an ArrayCollection it is not populating my grid. If I don't convert to ArrayCollection, the refresh() method does not work. I have some suspicions as to what the cause is, but I'm not sure if you had any ideas as to what it could be.
todd sharp said...
If you care, when returning an array of structs from CF:

This works: myGrid.dataProvider = new ArrayCollection(event.result);

This don't work: myGrid.dataProvider = event.result as ArrayCollection;
Josh Johnson said...
So I love this explanation. It's helped me understand the filter function a lot better. But one question... If you were to take it to the next logical level, you'd create a selector with the list of columns, and allow the user to specify the search column, rather than just hard coding the name. I started doing this, ran into an issue where even though I have the column name stored in a variable, I'm unable to 'inject' that variable into the searchDemo IF statement. Any thoughts?
vicent said...
It's a good explanation and a useful component to search words in datagrid fields. I have attempting to use your code in my datagrid of two fields: author and title. However, the use of the title field search perfectly, whereas author field do'nt produce a filter in the datagrid. I do'nt understand this situation. Both field are creates with a HTTPService in a ArrayCollection, through a php call. The datagrid shows both fields, but only title field is good.


Any ideas,

Sorry and thanks in advance
Paul Feakins said...
Thanks for the tutorial :) One point - an ArrayCollection isn't really like an Array of structs. I suppose you're refering to the fact that an ArrayCollection is often used to store objects which can be compared to structs.

But you can also store those in a normal Array in Flex. An ArrayCollection is more like a wrapper for an Array which gives you additional methods, such as the refresh() method and is like a "view" of the data. You can filter and sort this "view" with the ArrayCollection methods but the underlying Array is not modified. It's always best to use ArrayCollections for dataProviders as Arrays used as dataProviders get converted to them anyway :)

Hope that helps.
Paul.
sudarshan said...
does the filterFunction work when the dataProvider has nested array collection, this i am using to render the data using Advanced Data Grid
Core000 said...
I am trying to filter a datagrid by multiple criteria. I would like to have several types of components (checkbox, slider, textfield, etc) to filter, each one set to filter a particular datafield (asset_status, serial, tag....see data structure) in the arraycollection.

http://s256908546.onlinehome.us/datagrid/am_post.h...

Appreciate any help.
Core000 said...
I want to filter a datagrid by multiple criteria. I would like to have several types of components (checkbox, slider, textfield, etc) to filter, each one

set to filter a particular datafield (asset_status, serial, tag....see data structure) in the arraycollection.

The filterfunction does not work. When I click type in the textbox or check any checkboxes, it does not retreive the intended results. My link is below.

Appreciate any help.