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
Like this:
Like Loading...