Flex Custom Components and Custom Methods

I have been following Ray Camden's series of posts on a Flex project he is working on.  During this project, he discovered that sometimes functionality you might expect does not exist (Though, in his example, the functionality he was looking for does exist).  I ran into a issue like this with <mx:ComboBox> on a recent project.

Let's say you have a form that allows you to update information about widgets, and one piece of information is the type of widget.  The type of widget is chosen from a comboBox, however, the value stored in the database is different than what you see in the combo box (think of an HTML select box - often we display a nice user friendly label, but store the 'value' which is some kind of an ID). There is no native method (at least not that I could find), that allows you to set the selected item based on the value of that item.  I could, however, loop over the data or dataprovider for the combo box and compare the value to that data and set the selectedIndex accordingly, however, this seemed...ugly, and could cause issues should I need to use this over and over.  The solution was actually quite simple, custom components.

Flex allows you to build custom components, based on native Flex containers or controls, that can each contain their methods, or even overwrite native methods.  I decided to build a custom component with the logic necessary to select an item based on the value.

Before we begin, create a new Flex project.  For this demo, you can just leave all the default values.

One of the first things I do when starting a Flex project is to add a 'components' directory to my Flex project.  You can name this anything you want, I prefer 'components'. 

To create a new component, right click on the 'components' folder and choose 'New --> MXML Component'. Give the component a name with a .mxml file extension.  In this demo, we will name it 'customComboBox.mxml'.  In the 'Based On:' form field, select 'ComboBox', and then click 'Finish'.

Congratulations, you just created a custom Flex component!

In order to use you component, you first need to add a namespace the <mx:Application> tag. You need to add xmlns:comp="components.*". Assuming you did n ot make any changes acfter creating the Flex project, your main application page should look like this:

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:comp="components.*">

</mx:Application>

To use our custom component, simply add the following between the opening and closing <mx:application> tags:

<?xml version="1.0"?>
<!-- Simple example to demonstrate the ViewStack layout container. -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:comp="components.*">

    <comp:customComboBox id="myCombo"/>
</mx:Application>

Now if you run the application, you will see an empty combo box on the page.  Before we get into modifying the component, let's add some data that can be used to populate the combo box.

<?xml version="1.0"?>
<!-- Simple example to demonstrate the ViewStack layout container. -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:comp="components.*">
    <mx:Script>
        <![CDATA[
           import mx.collections.ArrayCollection;
           [Bindable]
           private var comboData:Array=
                [{label:"Item1", data:"1"},
                {label:"Item2", data:"2"},
                {label:"Item3", data:"3"}];
        ]]>
    </mx:Script>
    <comp:customComboBox dataProvider="{comboData}" id="myCombo"/>
</mx:Application>

Here we created an variable as an Array with an item that have a label and data (Think of labels as what is in between <option></option> tags, and think of data as the value attribute of the <option> tag.  We then set the dataProvider for our custom combo box to this variable.  If you view the application now, you will see that our combo box now has data associated with it.

We can now modify our custom component to give us the custom functionality we desire.  Open up '/components/customComboBox.mxml' if it is not already open and add the following code between the opening and closing <mx:ComboBox> tags.

<mx:Script>
        <![CDATA[
           public function selectedItemByValue(val:int):void{
                    for (var i:int=0;i<this.dataProvider.length;i++){
                        var item:int = this.dataProvider[i].data;
                      
                        if(item == val){
                            this.selectedIndex = i;
                            break;
                        }
                        else{ this.selectedIndex = 0;}
                    }
            }
        ]]>

    </mx:Script>

Let's break down this code.  First, we are creating a public function named selectedItemByValue.  it needs to be public so that you can call this method from outside of the component.  This method takes one argument, which in this case must be an integer.  Nothing is returned from the method.

Next, we loop over the dataprovider and check to see if the value passed in equals the 'data' property of our dataprovider, if it does, we set the selectedcIndex of the comboBox to the current iteration of our loop.  If it doesn't match, we set the selectedindex to 0.  Id the value passed in does not match any items in the dataprovider, the forst item in the dataprovider will be shown.

If you compile and view the application, you will see nothing changes in what we can see.  Now we need to trigger the new method we just created.  For this demo, we will create 3 buttons and each button, when clicked, will change the comboBox to show the item with the matching 'data' property.

Add this code to your main application file after our custom comboBox.

<mx:HBox>
    <mx:Button label="Choose Item 1" click="myCombo.selectedItemByValue(1);" />
    <mx:Button label="Choose Item 2" click="myCombo.selectedItemByValue(2);"/>
    <mx:Button label="Choose Item 3" click="myCombo.selectedItemByValue(3);"/>
</mx:HBox>

You can see here that all we are doing is calling the selectedItemByValue() method of our custom combo box and passing in different values depending on which button is clicked.

Save and compile your application and then view it.  You will see that as you click each button, the appropriate item displays in the combo box.

You may want to keep this custom component in a place where you can easily copy it into other applciations.  I find that I use it frequently.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
todd sharp's Gravatar Nice post Scott! The same technique/function could be used to "bind" the comboBox to a dataGrid on change of the grid, couldn't it?
# Posted By todd sharp | 11/22/06 4:46 PM
Scott Stroz's Gravatar Todd - Yes, you can use this for what you described. This was a (fairly) simple example of not only how to create and use custom components, but how to add custom method and properties to them.
# Posted By Scott Stroz | 11/22/06 4:52 PM
mamadlin's Gravatar Thanks for the comment, I faced the same problem, and was hoping that there is a cleaner way than this, but I guess will have to leave at this.

ML.
# Posted By mamadlin | 12/18/06 3:32 PM
Jared Rypka-Hauer's Gravatar Scott,

Given the following snippet:

var myData:Array = [{label:'data1',data:1},{label:'data2',data:2},{label:'data3',data:3}]

Doing something like this works fine:

this.dataProvider.selectedItem = myData[2];

So you don't actually need to compare an invidual VALUE and set the selectedItem, all you have to do is pass in the object itself. That means that you can, in combination with the filterFunction of an ArrayCollection (which is what queries and structs are when passed from CF to Flex) to find the item you want and set the selectedItem to that instance. That is:

private function getMyRecord(obj:Object):boolean { obj.ColName == this.myId; }

In your remoting result handler, set the filterFunction on the result, call reset and get a handle on the correct element in the collection (which should be the only one available). Then reset the result, set it to the dataprovider and set the selectedItem to the handle you got from the filterFunction.

Be it known that there are probably gotchas here because I haven't actually TESTED this, just looked over the docs on the subject... but using the filterFunction property will get you the one item that matches your value, and setting the object to the selectedItem from the get-go is a bit more "elegant". Play with it... blog what you find out. :)

J
# Posted By Jared Rypka-Hauer | 12/28/06 5:44 PM
Craig Kaminsky's Gravatar Scott:

Just wanted to say thanks for this post! I've been trying to figure out a reliable and good way to do this and your post really helped me on a project I'm wrapping up!!

Also, I'm a big golfer and if you ever get a chance, come play Fairway Pines outside Ridgway, Colorado (we're in the Southwest Corner of the state not too far from Telluride). It's a great mountain course (elevation ~8000 feet) with views of the San Juan mountains (several 14,000+ peaks to see from each hole).

Thanks, again!
Craig
# Posted By Craig Kaminsky | 4/5/07 4:18 PM
George's Gravatar I got this to work fine but am having trouble making the combobox change using a data grid. Can you help me with this?
Thanks
George
# Posted By George | 1/21/08 12:44 PM
George's Gravatar Well I got the thing combo to change when I select a row in my grid. But when I go to Add a new record. I cannot get my add statement to take the selection from the combobox..

private function sendFormdata4():void {
var myobj:Object = new Object();
myobj.AccNum = AccNumLabel.text;
myobj.DBA = DBALabel.selectedLabel;

The DBALabel is the id of the combobox.
I have a handle function for the combo data.

public function handleAccountsResult(event:ResultEvent):void{
myAccounts = Accounts.Accounts.lastResult;
var prompt:Object = new Object();
prompt.data = null;
prompt.label = "Select Account";
myAccounts.addItemAt( prompt, 0 );
DBALabel.selectedIndex = 0;
}

All that happens when I send a new record is that the label "Selected Account" goes into the DBA column.
Any thoughts?

Thanks
# Posted By George | 1/22/08 3:57 PM
Anuya's Gravatar Scott:

Thanks a lot for your post. I was facing the same problem and your post helped me a lot.
# Posted By Anuya | 4/22/08 3:38 AM
pastis's Gravatar A bug has been submited to flex team for this issue. You can vote for it here : http://bugs.adobe.com/jira/browse/SDK-14751
# Posted By pastis | 5/27/08 1:14 PM
Fabio Kehyaian's Gravatar Great!!

Thank you very much.
# Posted By Fabio Kehyaian | 7/24/08 3:34 PM
James's Gravatar Likewise, THANK YOU for a good solution and well-written post. Just what I needed.
# Posted By James | 8/23/08 1:06 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9. original design by tri-star web design