Flex AutoComplete Component: a new take on an old standard…

While there are a couple of other AutoComplete components out there (most notably the ones from the Flex Team and kuwamoto.org), neither of them had the features we were looking for. I struggled for a while trying to extend them but it just wasn’t working.

One of the main features we needed was for the component to show the part of the string which matches the search term. We also needed it to support selecting multiple items as well as creating an ordered list.

Latest version

As you can see in the demo, there are four main boolean properties which control how the chooser works:

  • isBrowseable: This will display a ‘Browse’ button to the right of the TextInput. If the user clicks it they will be presented with either a searchable DataGrid or a list builder (see below).
  • isMultiSelect: This enables selecting multiple values.
  • isOrderable: This makes the items in the list orderable. It provides buttons to reorder them as well as enabling drag-and-drop.
  • useListBuilder: If this is set to true, when clicking the browse button the user gets a List Builder rather than the DataGrid (note: this requires isBrowseable and isMultiSelect to be true).

Here are the other main properties:

  • labelField: Which property of the data objects to use when displayed in the TextInput and the Lists.
  • labelFunction: In place of the labelField you can specify a function for determining the string to display.
  • dropDownLabelFunction: A reference to the function used to display the matched items in the dropdown. It supports using HTML (which can be used to highlight the part of the string which matches the search pattern).
  • prompt: The message which is initially displayed in the TextInput.
  • filterFunciton: A reference to the function used to search when entering text into the TextInput. The function is passed the object being checked along with the search string
  • isEqualFunction: A reference to a function which can be used to determine if the search string should be considered equal to the object (if it returns true it will automatically select the item).

The bare minimum required to use the component is to set the dataProvider property to an ArrayCollection of objects and specify a value for labelField.

The code for the demo is pretty straight forward.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
	xmlns:mx="http://www.adobe.com/2006/mxml"
	xmlns:hc="com.hillelcoren.components.*"
	width="550" height="400"
	initialize="init()">

	<mx:Script>
		<!&#91;CDATA&#91;
			import mx.collections.ArrayCollection;

			&#91;Bindable&#93;
			public var colors:ArrayCollection;

			private function init():void
			{
				colors = new ArrayCollection(
				&#91;
					{ "name":"Almond", "hex":"#C5E17A" },

					...

					{ "name":"Yellow Orange", "hex":"#FFAE42" }
				&#93; );
			}			

			private function handleChange():void
			{
				var color:Object = chooser.chosenItem;

				if (color != null)
				{
					setStyle( "backgroundColor", color.hex );
				}
			}
		&#93;&#93;>
	</mx:Script>

	<mx:Panel width="100%" height="100%" title="Chooser Demo"
			paddingBottom="20" paddingTop="20" paddingLeft="20" paddingRight="20">
		<mx:HBox>		

			<mx:VBox horizontalAlign="left">
				<mx:CheckBox id="browesable" label="Browesable"/>
				<mx:CheckBox id="multiselect" label="Multiselect"/>
				<mx:CheckBox id="orderable" label="Orderable" enabled="{ multiselect.selected }"/>
				<mx:CheckBox id="listBuilder" label="List Builder" enabled="{ multiselect.selected }"/>
			</mx:VBox>		

			<hc:Chooser id="chooser" dataProvider="{ colors }" labelField="name"
				prompt="Choose your favorite Crayola crayon" width="300" change="handleChange()"
				isBrowseable="{ browesable.selected }" isMultiSelect="{ multiselect.selected }"
				isOrderable="{ orderable.selected }" useListBuilder="{ listBuilder.selected }"/>					

		</mx:HBox>
	</mx:Panel>
</mx:Application>

I consider this to be a version 0.9. It seems to work pretty well but I’m sure there’s a bug or two hiding in there. There are also a couple of more features I’d like to implement.

There’s no copyright… please use the code freely. I just ask that if you come up with any improvements you email them back to me.

Hope you find this component useful,
Hillel

Flex Error #1009: Cannot access a property or method of a null object reference

An update to this post can be found here

This was my first “bang my head against the wall for a really long time” Flex error. Here’s a simplified version of what I was doing wrong. I had a created a custom component which contained another Flex component.

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
	<mx:Button id="button"/>
</mx:Canvas>

Then in my application I was using it as follows:

var myComp:MyComp = new MyComp();
myComp.button.label = "Button Label";
addChild( myComp );

The problem here has to do with what’s called the “component instantiation life cycle”. The basic issue is that when I was setting the label for the button it hadn’t yet been created. In the rest of the post I’ll cover three ways to fix this.

Solution 1 (don’t actually do this, just showing what’s possible)

The easiest way to fix this is to just add the component to the stage before setting the label.

var myComp:MyComp = new MyComp();
addChild( myComp );
myComp.button.label = "Button Label";

As you can see we’ve simply swapped lines 2 and 3. This is definitely not best practice. While it does work it means that you need to display your component before making any changes to it. This will be pretty inefficient as it will require the Flash player to update the component a second time.

Solution 2 (this one’s pretty good)

A better way to do this would be to add a variable to the custom component which stores the label. Then when the component is added to the stage it sets the button’s label to the value set. Here’s what it would look like

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" initialize="init()">

	<mx:Script>
		<![CDATA[
			
			private var _buttonLabel:String;
			
			private function init():void
			{
				button.label = _buttonLabel;
			}
			
			public function set buttonLabel( value:String ):void
			{
				_buttonLabel = value;
			}
					
		]]>
	</mx:Script>
	
	
	<mx:Button id="button"/>
</mx:Canvas>

One thing worth pointing out is we’re using the initialize event rather than the creationComplete event. Setting the label for the button will cause the Flash player to recalculate the size of the button. If we used the creationComplete event it will have already measured the button. This means we’ll be forcing the player to measure it again.

To use this component we’d change the code slightly to use the new buttonLabel parameter:

var myComp:MyComp = new MyComp();
myComp.buttonLabel = "Button Label";
addChild( myComp );

Solution 3 (the real way to do it)

The catch with the 2nd solution is it only enables you to set the button label before you add the component to the stage. We could work around that by checking if the button already exists when setting the value for buttonLabel but we’re going to skip past that change and just implement it the real way.

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">

	<mx:Script>
		<![CDATA[
			
			private var _buttonLabel:String;
			private var _buttonLabelChanged:Boolean;
			
			public function set buttonLabel( value:String ):void
			{
				_buttonLabel = value;
				_buttonLabelChanged = true;
				
				invalidateProperties();
				invalidateSize();
			}
			
			override protected function commitProperties():void
			{
				super.commitProperties();
				
				if (_buttonLabelChanged)
				{
					button.label = _buttonLabel;
					_buttonLabelChanged = false;
				}
			}
					
		]]>
	</mx:Script>

	<mx:Button id="button"/>
</mx:Canvas>

By overriding the commitProperties function we enable the component to be far more efficient. The code which changes the button label will execute much quicker that the change will be displayed on screen. Using this approach even if we change the label many times, the Flash player will only recalculate the component once.

The purpose of the post was to show you how to resolve the “null object” error. While discussing this problem we’ve gotten in to the area of creating custom components but we’ve really just skimmed the surface. There are a ton of great tutorials online covering this topic in great depth. If you’d like to learn more a quick google search should find you everything you ever wanted to know (and probably more).

Best,
Hillel