One of the things I like most about ColdFusion's <CFSELECT> tag is the SELECTED attribute, give it a value and it figures out which one to pre-select. The Flex ComboBox allows you to set selectedIndex (the relative element position) but not selectedValue. Actually, ComboBox does have a selectedValue property, but that is used to read the value of the selected item, and can't be used to set the value of the item to be selected.
Coincidentally, while I was working on this, both Scott Stroz and Ray Camden posted solutions to the exact same problem. Scott's solution involved calling a method to set the value, and I really think that the selectedValue property should be used to be consistent with how ComboBox itself works. Ray's solution allows for a property to be set (he defined a new property for this), but his solution would not work for me as my dataProvider was being populated after the control had been created (it is being populated by a call to a CFC).
So, here is my solution. It's bit more complex than what Scott and Ray suggested, but it does support selectedValue, it'll also allow that property to be changed as needed (even after control creation), and it also properly handles delayed dataProvider population.
Here is the code:
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
private var _selectedValue:String;
private var bSelectedValueSet:Boolean = false;
private var bDataProviderSet:Boolean = false;
// Override committ, this may be called repeatedly
override protected function commitProperties():void
{
// invoke ComboBox version
super.commitProperties();
// If value set and have dataProvider
if (bSelectedValueSet && bDataProviderSet)
{
// Set flag to false so code won't be called until selectedValue is set again
bSelectedValueSet=false;
// Loop through dataProvider
for (var i:int=0;i<this.dataProvider.length;i++)
{
// Get this item's data
var item:String = this.dataProvider[i].data;
// Check if is selectedValue
if(item == _selectedValue)
{
// Yes, set selectedIndex
this.selectedIndex = i;
break;
}
}
}
}
// Trap dataProvider being set
override public function set dataProvider(o:Object):void
{
// invoke ComboBox version
super.dataProvider = o;
// This may get called before dataProvider is set, so make sure not null and has entries
if (o!=null && o.length)
{
// Got it, set flag
bDataProviderSet = true;
}
}
// set for selectedValue
public function set selectedValue(s:String):void
{
// Set flag
bSelectedValueSet = true;
// Save value
_selectedValue = s;
// Invalidate to force commit
invalidateProperties();
}
]]>
</mx:Script>
</mx:ComboBox>
And here is a simple test case:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:ns1="*">
<mx:Script>
<![CDATA[
[Bindable]
private var comboData:Array=
[{label:"Item1", data:"1"},
{label:"Item2", data:"2"},
{label:"Item3", data:"3"}];
]]>
</mx:Script>
<ns1:ComboBox2 dataProvider="{comboData}" selectedValue="3" />
</mx:Application>
--- Ben
Here's the code:
http://www.tristanhauser.com/flex/ExtendedComboBox...
I noticed in your example you used an Array with static data to set the combo box values. Would it be possible to show an example using mx:WebService?
public function initCombobox(obj:Object):void{
for(var i:int=0;i<obj.dataProvider.length;i++){
var selected:String = obj.dataProvider[i].selected;
if(selected != null){
if(selected == "true"){
obj.selectedIndex = i;
break;
}
}
}
}
<area>
<item label="??" data="-1" />
<item label="??" data="0570" />
<item selected="true" label="??" data="0571" />
<item label="??" data="0572" />
<item label="??" data="0573" />
<item label="??" data="0574" />
<item label="??" data="0575" />
<item label="??" data="0576" />
<item label="??" data="0577" />
<item label="??" data="0578" />
<item label="??" data="0579" />
<item label="??" data="0580" />
</area>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
public function initCombobox(obj:Object):void{
for(var i:int=0;i<obj.dataProvider.length;i++){
var selected:String = obj.dataProvider[i].selected;
if(selected != null){
if(selected == "true"){
obj.selectedIndex = i;
break;
}
}
}
}
]]>
</mx:Script>
<mx:ArrayCollection id="ac">
<mx:Array>
<mx:Object label="A" data="1"/>
<mx:Object label="B" data="2"/>
<mx:Object label="C" data="3"/>
<mx:Object label="D" data="4"/>
<mx:Object label="E" data="5" selected="true"/>
<mx:Object label="F" data="1"/>
<mx:Object label="G" data="1"/>
</mx:Array>
</mx:ArrayCollection>
<mx:ComboBox id="testComb" dataProvider="{ac}" x="37" y="64"></mx:ComboBox>
<mx:ComboBox id="testComb2" dataProvider="{ac}" creationComplete="initCombobox(testComb2)" x="37" y="94"></mx:ComboBox>
</mx:Application>
Just my 2 cents. I feel better now. :-)
I have duplicated this control - THANKS - by extending mx:List. Because this list is necessarily several thousand items long (everyone in our firm), I have added the following to the logic in order to show the highlighted dp item:
...
this.selectedIndex = i;
//added:
this.verticalScrollPosition = i;
...
It works great, except for the first time that it's called - the correct item is selected, but the verticalScrollPosition doesn't take effect. If you scroll down the list you can see that the correct item is selected - it just doesn't scroll down to that position. I'm assuming this is because the parent component hasn't been completely rendered yet (this is a Cairngorm app where selectedValue property is bound to a property of a value object in the modelLocator), but the creationComplete event is dispatched before the selectedValue setter is called so I'm at a loss. Each subsequent view of that screen correctly scrolls to the item in the list.
Any advice on how to force this to scroll on the first call?
Thanks in advance,
Joe
thanks for this... but how do i 'bind' the selected index to a datagrid selection?
ie, something like, ideally:
<comp:ComboBox2 x="112" y="39" dataProvider="{rstClients}" selectedValue="<b>{surveyGrid.selectedItem.SCLIENTNAME}</b>" labelField="SCLIENTNAME" width="205" />
compared to textinput controls' bindings, triggering combobox updates through datagrid's itemfocusin seems 'dirty'.
I can get everything but cannot get the value of the selectedItem from the cfselect combo box.
Any suggestions here as I know nothign about flex
Thanks
Jim
You could try this.scrollToIndex(i);
In theory these should be pretty darned identical in behavior, but I think what might have gotten it to work was setting the creationPolicy="all" attribute on the application.
Not sure whether this was definitely the fix, but it sure did seem to help with a lot of other little gotcha's throughout the application and eliminated a bunch of null checking, etc.
Just thought I would add this note to the thread incase it helps someone else.
Jaqueline
Another newbie to Flex here..
I'm trying to Bind this to a selectedItem from a DataGrid to a Combo box please can someone tell me if this is possible with this Combobox as Flex does no support SelectedItem..
I see you can use a static value in the SelectedValue and it works fine, but if you put a Dynamic Value in ie. selectedValue="{datagrid.selectedItem.column}" it doesn't seem to work..
Any adive would be great..
Matt
I'm new to Flex and recently committed to using it to develop a large portion of my small startup company's product, but this ComboBox nonsense is making me wonder if I made the right choice. How many more wasted days will I experience chasing solutions to problems that should be part of the OOB product.
Thanks again Ben!
Tom
Thanks for this great extension to the ComboBox! Thanks mostly for commenting the code because I was having so much trouble getting this thing to work. It turned out I was setting the dataProvider with databinding, BEFORE I populated the dataProvider.
Making the call to set dataProvider right after populating my label data object did the trick. I'm planning on always using this ComboBox.
Question, why doesn't the Flex framework already define a selectedValue method for mx:ComboBox in the first place, does it have anything to do with data binding?
thanks,
Derek
Let's hope that Adobe will add this into Flex 3 .... it's really not to have this very basic functionality in a combobox!
thnx
public function findItemIndex( dp:IList, data:String ):int
{
for( var i:int = 0; i < dp.length; ++i )
{
if( dp.getItemAt(i).data == data )
return i;
}
return -1;
}
<mx:ComboBox dataProvider="{myDP}" selectedIndex="{findItemIndex(myDP, selectedItem) }" />
In case anyone wants to make their comboBox default to the size of the DataProvider (it defaults to a drop down of only a few items and you have to scroll to the rest) then you can add code like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.adobe.com/2006/mxml"
rowCount="{myDataProvider.length}">
var item:String = this.dataProvider[i].data; to
var item:String = this.dataProvider[i].DATA;
:)
Thanks Ben.
flex is open source that doesn't mean Adobe should leave to developer but basic properties are their responsibility. Anyways new comers like us face more problems because other part of world has all methods for doing simple work like selecting index where value = ?
if (o!=null && o.length)
{
// Got it, set flag
bDataProviderSet = true;
bSelectedValueSet = true;
}
I have another view with the navigation that has tree component that offers a secondary option to change the workflowState. I am missing a step though, because if I click on a node in the tree, the view successfully changes, but the comboBox in the header view does not "automagically" adjust.
How can I set the comboBox view to "listen" to see if the workflowSate has changed, and adjust accordingly? If it is too much to explain in the comments, if anyone could forward me to a relevant post, I would be very grateful! TIA!
package flex.components.util {
import mx.collections.IList;
import mx.controls.ComboBox;
public class ComboBoxExt extends ComboBox {
public function findItemIndex(data:String):int {
var list:IList = IList(this.dataProvider);
for (var i:int = 0; i<list.length; ++i) {
if (list.getItemAt(i).data == data) return i;
}
return -1;
}
}
}
And you can call something like this in your script section
myView.myCombo.selectedIndex = myView.myCombo.findItemIndex("somedata");
Hope this sheds another light ;-)