Faster searching in Flex (part 1)

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

Tags: , , ,

14 responses to “Faster searching in Flex (part 1)”

  1. Jeremy Walderson says :

    Your article was very captivating and I really enjoyed it man! Jeremy

  2. dieyana says :

    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

    • Hillel says :

      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

  3. dieyana says :

    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!!

  4. dieyana says :

    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

    • Hillel says :

      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).

  5. dieyana says :

    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.

  6. Praem says :

    I want to creating search string contain. please suggest. example search “BC” in word “ABCDE”

    • Hillel says :

      Praem,

      You could do something like the following…

      var str:String = “ABCDE”;

      if (str.indexOf( “BC” ) >= 0)
      {
      // do something
      }

  7. Lula Domansky says :

    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!

  8. Batman Lot says :

    Thanks for the nice post…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: