Pages

February 22, 2010

Binding a Value to a ComboBox

Posted by Wade
When working with the ComboBox component, I have found that I pretty much always want to bind a value to the ComboBox so that it automatically selects a corresponding record in the dataProvider. The most straight forward example of this is with a ComboBox on an address form to select a state. If you load an address into the form, the ComboBox should automatically select the state associated to that address.

To accomplish this I followed a post by Ben Forta to extend the ComboBox, and have made some changes over the years to fine tune it. The principle idea of the approach is to add a selectedValue property to the ComboBox and then loop through the dataProvider to look for a match whenever the selectedValue or the dataProvider is changed. I added dataProperty for specifying the property in the dataProvider that the value should map to, and an additional override to the collectionChangeHandler to catch any updates to the dataProvider.

I settled on naming the component VBComboBox, with VB standing for Value Bound. I have it implemented as an actionscript class in my projects, but wrapped in mxml for this blog post to avoid having to specify a package name. I've included an example of the component at the bottom of this post with viewable source, so I'll just walk though some of the highlights.

All the "magic" happens in the commitProperties method. This method is executed anytime the properties of the class are invalidated. The setter for the dataProvider in the ComboBox calls invaidateProperties() as does the setter for the selectedValue in VBComboBox. Each setter also sets a flag that the value has changed. The following is a rough breakdown of the functionality that was added.
  • The first if statement in the commitProperties method checks these flags to see if the dataProvider or selectedValue have changed.
  • If a change is detected, then loop through all the objects in the dataProvider.
  • The if statement in the loop checks the dataProperty (defaulted to "data") of the current object against the selectedValue. If they match the idx var is set with the current position and the loop is broken.
  • The selectedIndex is set to the position that was found or -1. Setting it to -1 if no match is found will cause the prompt value assigned to the ComboBox to be displayed.
override protected function commitProperties():void
{
    super.commitProperties();

    //If the selectedValue and dataProvider are set, find the selectedIndex of the value.
    if ((selectedValueChanged == true && dataProvider != null) ||
        (dataProviderChanged == true && selectedValue != undefined))
    {
        dataProviderChanged=false;
        selectedValueChanged=false;

        var idx:int=-1;

        //Loop through data provider until a record with the value is found.
        for (var i:int=0; i < dataProvider.length; i++)
        {
            if (this.dataProvider[i] != null &&
                this.dataProvider[i].hasOwnProperty(dataProperty) &&
                this.dataProvider[i][dataProperty] == selectedValue)
            {
                idx=i;
                break;
            }
        }

        //Set the selectedIndex with the index found or -1 if not found.
        this.selectedIndex=idx;
    }
}
There is also an override of the collectionChangeHandler to catch changes to the dataProvider that happen outside of the setter. This can happen records are added, removed, or if the source of an ArrayCollection is reset for example. In this case, the dataProviderChanged flag is set to true, and the properties are invalidated to re-check the selectedValue against the dataProvider.
override protected function collectionChangeHandler(event:Event):void
{
    super.collectionChangeHandler(event);

    dataProviderChanged=true;
    invalidateProperties();
}
To view the entire source, right click on the example and select "View Source".



4 comments:

Cтанислав Заярский said...

Solution is good to some extent.

When you will have dictionary of some typed objects as a dataprovider, and as a selected item - the same type object but not from that dictionary, it won't work.

Wade Vogt said...

That is the purpose of the dataProperty. It is defaulted to 'data', but it can be changed to work with the objects in the dataProvider. For example, if you wanted to bind to a property called stateId in the objects in the dataProvider, you would just set the dataProperty='stateId'.

flex developers said...

Great post. Thanks for sharing.

Anonymous said...

This post is helpfull but I want to check if user entered value in combobox is not null.
In case where combobox is editable.
How can I achieve this?