Monday, October 02, 2006

Flex Custom Combobox Component

We needed a country combobox that used an array collection as its dataprovider, however would accept a text value (i.e. "Canada") and select the right item based on the text. We extended the combobox control and embedded our countries array collection inside the control for the purpose of this blog entry. Ultimately we will be using an array collection returned by a dataservice.



Several challenges:
  • searching a specific property of all objects in the arraycollection
  • binding the selectedText property of our control to a function
  • setting the default value of the combobox to nothing

I asked around and it was recommended that I loop through the underlying array of the arraycollection. That sounded very microsoft like and I figured there had to be a better way. It was also suggested I add a dummy record to my countries collection with a "Select Country" string and make that element 0. Again, though this would work, it seemed very ms-ugly.

I struggled with this for quite some time but finally got it working. This custom combobox exposes a property called selectedText. I use ChangeWatcher to keep an eye on the property and trigger a function (setCountry) when it changes. I use a sort on the array collection and the use a cursor on the sort to do my searching... much nicer than a nasty loop. I discovered the handy little "prompt" property which I set to "Select Country".

I'll spare you the gory details of my adventure. Here's the final product:

<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.adobe.com/2006/mxml" initialize="initApp()"
xmlns:util="com.atrexis.util.*" change="selectedText=this.text">
<mx:ArrayCollection id="countries">
<mx:Object code="BDA" description="Bermuda"/>
<mx:Object code="CAN" description="Canada"/>
<mx:Object code="USA" description="United States"/>
<mx:Object code="UK" description="United Kingdom"/>
</mx:ArrayCollection>
<mx:Script>
<![CDATA[
import mx.events.FlexEvent;
import mx.binding.utils.ChangeWatcher;
import mx.collections.*
import mx.collections.ArrayCollection;

[Bindable]
public var selectedText:String;
private var countryCursor:IViewCursor;

private function initApp():void{
mx.binding.utils.ChangeWatcher.watch(this,"selectedText",setCountry);
var sort :Sort = new Sort();
sort.fields = [ new SortField( "description", false ) ];
countries.sort = sort;
countries.refresh();
countryCursor = countries.createCursor();
dataProvider=countries;
labelField="description";
setCountry();
prompt = "Select Country";
}

private function setCountry(event:Event = null):void{
if (selectedText != null && selectedText != "") {
countryCursor.findFirst({description:selectedText});
this.selectedItem=countryCursor.current;
}
}
]]>
</mx:Script>
</mx:ComboBox>

Things to watch out for:

  • the function called by changewatcher must take an event argument
  • remember to update the selectedText property on Change event of combobox

3 comments:

Rogerio Gonzalez said...

tell me, why don´t use the filterFunction of the ArrayCollection to do the search?

Regards!
Rogerio

Victor Rubba said...

It's less efficient, since the filter function will actually run through the entire collection evaluating each value against your filter function. Also, if I apply a filterFunction, the combobox will only have the one value in it, and I'd have to capture the click event to set the filterFunction of the Arraylist back to null, refresh the collection and updateDisplay of combobox. I actually spoke to the Flex team about this last night and they confirmed that using a cursor is the most efficient way of doing this.

Chad said...

No sample code on how to use it / visual example to play with?