Resource Bundles in Flex (w/o lots of extra code)

I’ve always thought of resource bundles as one of those things I would implement when I needed them (I’ve never needed to port an application I’ve built to another language). That said, there’s another great benefit to bundles which sometimes gets lost in the crowd. They allow non-developers to change the text displayed in the application. I can’t tell you how many hours\days\weeks I’ve spent making minuscule text changes (only to have them changed back later).

With this new appreciation of the power of resource bundles I’ve started to implement them in the latest application I’m working on. The problem I’ve run into is that they add a tremendous amount of bloat. In this post I’d like to show the technique I’m using to eliminate a lot of the extra code.

Here’s an example of the standard way to use resource bundles (this is a piece of component called MyForm.mxml)

<mx:Metadata>
	[ResourceBundle("MyForm")]
</mx:Metadata> 

<mx:Script>
	<!&#91;CDATA&#91;
		import mx.resources.ResourceManager;
	&#93;&#93;>
</mx:Script>

<mx:Form>
	<mx:FormItem label="{ resourceManager.getString( 'MyForm', 'firstNameTextInput' ) }">
		<mx:TextInput id="firstNameTextInput"/>
	</mx:FormItem>
	<mx:FormItem label="{ resourceManager.getString( 'MyForm', 'lastNameTextInput' ) }">
		<mx:TextInput id="lastNameTextInput"/>
	</mx:FormItem>
	...

</mx:Form>

The main thing that bothers me with the above code is that in most cases the parameters passed to the resourceManager shouldn’t need to be specified. The first parameter, the bundle name, is most likely going to be the same for all of the fields on the page. Additionally, the key for the field can (if you use consistent naming conventions) match the id of the input component.

The heart of this solution is a helper utility class called ResourceUtils.

Here’s our first pass at it.

package
{

import mx.resources.ResourceManager;

public class ResourceUtils
{
	private var _bundleName:String;
	
	public function ResourceUtils( bundleName:String )
	{
		_bundleName = bundleName;
	}
	
	public function getString( key:String ):String
	{
		return ResourceManager.getInstance().getString( _bundleName, key );
	}
}
}

To use this we’ll simply create an instance of it at the top of our component

<mx:Metadata>
	[ResourceBundle("MyForm")]
</mx:Metadata> 

<mx:Script>
	<!&#91;CDATA&#91;
		import ResourceUtils;

		&#91;Bindable&#93;
		private var _rb:ResourceUtils = new ResourceUtils( "MyForm" );
	&#93;&#93;>
</mx:Script>


<mx:Form>
	<mx:FormItem label="{ _rb.getString( 'firstNameTextInput' ) }">
		<mx:TextInput id="firstNameTextInput"/>
	</mx:FormItem>
	<mx:FormItem label="{ _rb.getString( 'lastNameTextInput' ) }">
		<mx:TextInput id="lastNameTextInput"/>
	</mx:FormItem>
	...

</mx:Form>

By using this class we’ve eliminated the need to specify the bundle name every time we want to look up a key. That’s pretty cool but I think we can do even better. The second part of this solution is going to enable us to auto-magically map the key values to the input components based on the component ids.

Here’s the final version of the ResourceUtils class

package
{

import mx.containers.FormItem;
import mx.core.UIComponent;
import mx.resources.ResourceManager;

public class ResourceUtils
{
	private var _bundleName:String;
	
	public function ResourceUtils( bundleName:String )
	{
		_bundleName = bundleName;
	}
	
	public function getString( key:String ):String
	{
		return ResourceManager.getInstance().getString( _bundleName, key );
	}
	
	public static function setLabels( container:UIComponent, bundleName:String = null ):void
	{
		if (!bundleName)
		{
			bundleName = container.className;
		}
		
		setChildLabels( container, bundleName );
	}
	
	private static function setChildLabels( container:UIComponent, bundleName:String ):void
	{ 
		for (var x:uint; x < container.numChildren; x++)
		{
			var child:Object = container.getChildAt( x );
			
			if (child is UIComponent)
			{
				ResourceUtils.setChildLabels( child as UIComponent, bundleName );
				
				var label:String = ResourceManager.getInstance().getString( bundleName, child.id );
				
				if (!label)
				{
					continue;
				}
				
				if (UIComponent( child ).parent is FormItem)
				{
					var formItem:FormItem = UIComponent( child ).parent as FormItem;
					formItem.label = label;
				}					
				else if (child.hasOwnProperty( "prompt" ))
				{
					child.prompt = label;
				}
				else if (child.hasOwnProperty( "label" ))
				{
					child.label = label;
				}
				else if (child.hasOwnProperty( "text" ))
				{
					child.text = label;
				}
			}
		}
	}
}
}
&#91;/sourcecode&#93;

In the final version of the class we've added a new public static function called setLabels. This will recursively loop through all of the forms children and check for any components for which a key has been set in our resource bundle file. If we find a value we'll check if it looks like something we can set the label for.

Here's an example of how we'd use this class

&#91;sourcecode language='jscript'&#93;
<mx:Metadata>
	[ResourceBundle("MyForm")]
</mx:Metadata> 

<mx:Script>
	<!&#91;CDATA&#91;
		import ResourceUtils;

		// call this function in the initializer handler for the component
		private function init():void
		{
			ResourceUtils.setLabels( this );
		}
	&#93;&#93;>
</mx:Script>

<mx:Form>
	<mx:FormItem>
		<mx:TextInput id="firstNameTextInput"/>
	</mx:FormItem>
	<mx:FormItem>
		<mx:TextInput id="lastNameTextInput"/>
	</mx:FormItem>
	...

</mx:Form>

If you scroll back to the top of the page you’ll see that we’ve removed the need to reference not just the ResourceManager but the labels as well.

In practice you’d probably want to use a combination of the static setLabels function as well instantiating an instance of ResourceUtils depending on whether it makes sense for the labels to be auto-magically mapped. Also, be careful to only use the setLabels functions on your lower level child components. If you used it in your main application class (and you have a large application) it’s going to recurse it’s way through every component in your application which probably won’t be too efficient.

Hope you find this approach useful

Best,
Hillel

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

First post… hello world

Is this thing on…

So I finally did it, I started a blog. I guess this is just my way of trying to give something back. I’ve gotten very excited about Flex over the past year and I can’t tell you how many times a blog post has saved my ass.

The plan is simple, as I discover new things about Flex (which aren’t posted on a million other blogs) I’ll post it here.

Hope this blog helps you at some point…

Best,

Hillel