Update: I’ve written a second part to this article.
I very often need to search a collection of objects. An example would be using a search box connected to a data grid where I want the datagrid to update in realtime as the user types in to the search box.
The catch is I want to be able to search the objects in lots of ways. For example if I have a person object I’d like to be able to search using their first name, last name, phone number as well as other fields.
The problem here is an object with many fields will take a measurable (albeit small) amount of time to search. If you have more than a couple of hundred items things can slow down pretty quickly. We could speed things a bit if we could tell Flex not to search items which failed the last search.
To make this point clearer, here’s an example. Lets says I have three people: Anne, Albert and Bob. If I type ‘A’ it’ll match the first two. When I type ‘An’ it will try to match Bob again even though you and I both now if it didn’t match ‘A’ it definitely won’t match ‘An’.
Here’s one possible solution, it all starts with an interface
package { public interface ISearchable { function getSearchFields():Array function set lastFailedSearchStr( value:String ):void function get lastFailedSearchStr():String } }
Any object that I want to be searchable needs to implement it. The idea is getSearchFields will returns an array containing the the names of all fields which should be searched. The lastFailedSearchStr field is used to help us optimize the search.
Here’s an example in action:
package { import ISearchable; public class Person implements ISearchable { public static const FIELD_FIRST_NAME:String = "firstName"; public static const FIELD_LAST_NAME:String = "lastName"; ... private var _firstName:String; private var _lastName:String; ... private var _lastFailedSearchStr:String; public function getSearchFields():Array { return [ FIELD_FIRST_NAME, FIELD_LAST_NAME ]; } public function set lastFailedSearchStr( value:String ):void { _lastFailedSearchStr = value; } public function get lastFailedSearchStr():String { return _lastFailedSearchStr; } ... (getters and setters for fields) } }
A nice aspect to this approach is it becomes very easy to add a field to search by, you simply need to add it to the array returned in getSearchFields.
The next part of the solution is the class that will actually do the searching. To accomplish this we’ll create a static function which is passed an object which implements ISearchable along with a search string.
package { import ISearchable; public class SearchUtils { public static function isMatch( item:ISearchable, searchStr:String ):Boolean { // check if the current search string starts with the last failed // search string (which means we have no hope of matching) if (item.lastFailedSearchStr && item.lastFailedSearchStr.length != 0) { if (StringUtils.beginsWith( searchStr, item.lastFailedSearchStr) ) { return false; } } // check each of the objects fields to see if we match for each (var field:String in item.getSearchFields()) { var value:String = item[field]; if (StringUtils.beginsWith( value, searchStr )) { item.lastFailedSearchStr = ""; return true; } } item.lastFailedSearchStr = searchStr; return false; } } }
One point about the above code. I reference a function called StringUtils.beginsWith, this is a custom class I created which simply checks if one strings starts with another (making sure to make both strings lower case so the search is case-insensitive).
Now to bring it all together simply use the following function as the filterFunction for your datagrid. In this example I’ve used a textInput with an id of ‘searchTextInput’.
private function filterFunction( item:ISearchable ):Boolean { return SearchUtils.isMatch( item, searchTextInput.text ); }
That’s about it. Keep in mind this will not speed up the first letter typed (as we need to check them all at this point) but beyond that the search speeds get quicker and quicker as we cut our more of the objects we need to search.
Hope you find this helpful,
Hillel
Your article was very captivating and I really enjoyed it man! Jeremy
hye..what have you done is really great.but i just want to do a very simple search application.i have a datagrid,and a search function.when user input a text,it will search through my datagrid.it sound easy.but i don’t know how to this..i’m new in flex development.i hope you can show me how…
thanks in advance
Diana,
Here’s a simple example showing a TextInput used to filter a DataGrid.
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application
xmlns:mx=”http://www.adobe.com/2006/mxml”
verticalAlign=”middle” horizontalAlign=”center”
creationComplete=”init()”>
<mx:Script>
<![CDATA[
import mx.controls.TextInput;
import mx.controls.Alert;
import mx.collections.ArrayCollection;
[Bindable]
private var _data:ArrayCollection;
private function init():void
{
_data = new ArrayCollection(
[
{ “name”:”One” },
{ “name”:”Two” },
{ “name”:”Three” },
{ “name”:”Four” },
{ “name”:”Five” }
]);
_data.filterFunction = filterFunction;
}
private function filterFunction( item:Object ):Boolean
{
var name:String = String( item.name ).toLowerCase();
var searchStr:String = textInput.text.toLowerCase();
return searchStr == name.substr( 0, searchStr.length );
}
private function handleChange():void
{
_data.refresh();
}
]]>
</mx:Script>
<mx:TextInput id=”textInput” change=”handleChange()”/>
<mx:DataGrid dataProvider=”{ _data }”/>
</mx:Application>
Hope this helps,
Hillel
Thank you, this code useful me another way.
Awesome, happy to hear it helped!
wow!!that was nice of you.usually when i post questions in blog,i didn’t get any reply.thanks a lot for your time and effort.if i encounter any problem,i might seek you again.thanks!!
i have implemented some codes from you together with other sources that i gathered.i got some problem with it.i have been searching for the error and correcting it,but neither works.could you help me??where can i post the code so you can take a look at it?thanks again
If the code isn’t too complicated you can just post it here. If you’re dealing with a larger project you can email it to me (my info’s on the contact page).
ok,the code is not so complicated.but it also has another code attach to it.so,i’m emailing the code to you.when you run the code,it gave an error about ‘conflict with namespace’.when i correct it,other error would come out.i have no idea what else to correct.hope you can help.thanks.
:the code have been emailed to you.
I want to creating search string contain. please suggest. example search “BC” in word “ABCDE”
Praem,
You could do something like the following…
var str:String = “ABCDE”;
if (str.indexOf( “BC” ) >= 0)
{
// do something
}
I discovered your website when I was browsing for something entirely different, but this page came up as a top result, your website must be incredibly popular! Keep up the awesome job!
Lula,
Thanks for the kind words 🙂
Best,
Hillel
Thanks for the nice post…