简体   繁体   中英

Null in Flex ComboBox

How can you make a ComboBox where the user can select null ?

If you simply create a combobox with null in the dataprovider, the value appears but the user cannot select it:

<mx:ComboBox id="cb" dataProvider="{[null, 'foo', 'bar']}" />

Is there a way to make that null selectable?

A workaround is to add an item into the dataProvider that is not null but 'represents' null; and then map between null and that object every time you access the combobox. But that's not really an elegant solution; you would always have to keep this mapping in mind in all code that accesses a 'nullable' combobox...

Edit: expanding on why I don't like the workaround: It could be done in a subclass of course, but either I introduce new attributes (like nullableSelectedItem ); but then you have to be careful to always use these attributes. Or I override ComboBoxes selectedItem ; but I'm affraid that is going to break the base class: it might not like something changing its idea of what the current selected item is from within. And even this fragile hack works, on top of selectedItem and dataProvider this nullItem then also needs to be handled special in data and listData for renderers, in labelFunction , and then it's probably still being exposed in events the ComboBox sends... It might work, but it's quite a hack just to fix the problem that if the user clicks on the item it isn't activated (for the rest the ComboBox handles null fine). (Another alternative is to have a ui component delegate to a ComboBox, but that's even much more code just to avoid this small problem)

It seems that the only way to correctly manage an empty item is to actually add an item to the dataprovider of the combobox. The following subclass will handle this automatically.

The implementation is a little tricky in order to support changes on dataprovider, in terms of items add/remove and of complete reassignment of the dataprovider itself (just think about arraycollections bindings to remote services' response).

package {

  import flash.events.Event; import flash.events.FocusEvent; import mx.collections.ArrayCollection; import mx.collections.ICollectionView; import mx.collections.IList; import mx.containers.FormItem; import mx.controls.ComboBox; import mx.events.CollectionEvent; import mx.events.ListEvent; import mx.validators.Validator; public class EmptyItemComboBox extends ComboBox { protected var _emptyItem:Object = null; protected var _originalDataProvider:ICollectionView = null; public function EmptyItemComboBox() { super(); addEmptyItem(); addEventListener(Event.CHANGE, onChange); } private function onChange(event:Event):void { dispatchEvent(new Event("isEmptySelectedChanged")); } [Bindable] public function get emptyItem():Object { return _emptyItem; } public function set emptyItem(value:Object):void { if (_emptyItem != value) { clearEmptyItem(); _emptyItem = value; addEmptyItem(); } } [Bindable(event="isEmptySelectedChanged")] public function get isEmptySelected():Boolean { return (selectedItem == null || (_emptyItem != null && selectedItem == _emptyItem)); } override public function set selectedItem(value:Object):void { if (value == null && emptyItem != null) { super.selectedItem = emptyItem; } else if (value != selectedItem) { super.selectedItem = value; } dispatchEvent(new Event("isEmptySelectedChanged")); } override public function set dataProvider(value:Object):void { if (_originalDataProvider != null) { _originalDataProvider.removeEventListener( CollectionEvent.COLLECTION_CHANGE, onOriginalCollectionChange); } super.dataProvider = value; _originalDataProvider = (dataProvider as ICollectionView); _originalDataProvider.addEventListener( CollectionEvent.COLLECTION_CHANGE, onOriginalCollectionChange) addEmptyItem(); } private function clearEmptyItem():void { if (emptyItem != null && dataProvider != null && dataProvider is IList) { var dp:IList = dataProvider as IList; var idx:int = dp.getItemIndex(_emptyItem); if (idx >=0) { dp.removeItemAt(idx); } } dispatchEvent(new Event("isEmptySelectedChanged")); } private function addEmptyItem():void { if (emptyItem != null) { if (dataProvider != null && dataProvider is IList) { var dp:IList = dataProvider as IList; var idx:int = dp.getItemIndex(_emptyItem); if (idx == -1) { var newDp:ArrayCollection = new ArrayCollection(dp.toArray().concat()); newDp.addItemAt(_emptyItem, 0); super.dataProvider = newDp; } } } dispatchEvent(new Event("isEmptySelectedChanged")); } private function onOriginalCollectionChange(event:CollectionEvent):void { if (_emptyItem != null) { dataProvider = _originalDataProvider; addEmptyItem(); } } } } 

Some notes about the class:

  • It will insert the empty object automatically on the list.. this was a strong requirements for my scenario: the dataproviders was returned by remote services and they couldn't include additional elements just to support the Flex UI, neither I could manually watch every collection to create empty items on each collection refresh.

  • Since it has to work with the contents of the collection, it will internally create a copy of the original one with the same items' instances AND the empty one, so the instance of the original collection will not be touched at all and can be reused in other context.

  • It will listen for changes on the original dataprovider, allowing to work on it and even to assign a competely new one: the empty item will be recreated automatically.

  • You are in control providing the actual instance to use as the "empty item" with the emptyItem attribute: this allows to keep a consistent data type with the rest of the collection (if you use typed objects), or to define a custom label on it.

Some example code using it...

  <mx:Script> <![CDATA[ import mx.controls.Alert; ]]> </mx:Script> <mx:ArrayCollection id="myDP"> <mx:source> <mx:Array> <mx:Object value="1" label="First"/> <mx:Object value="2" label="Second"/> <mx:Object value="3" label="Third"/> </mx:Array> </mx:source> </mx:ArrayCollection> <local:EmptyItemComboBox id="comboBox" dataProvider="{myDP}" labelField="label"> <local:emptyItem> <mx:Object value="{null}" label="(select an item)"/> </local:emptyItem> </local:EmptyItemComboBox> <mx:Button label="Show selected item" click="Alert.show(comboBox.selectedItem.value)"/> <mx:Button label="Clear DP" click="myDP.removeAll()"/> <mx:Button label="Add item to DP" click="myDP.addItem({value: '4', label: 'Fourth'})"/> <mx:Button label="Reset DP" click="myDP = new ArrayCollection([{value: '1', label: 'First'}])"/> <mx:Label text="{'comboBox.isEmptySelected = ' + comboBox.isEmptySelected}"/> </mx:Application> 

The following solution is likely the simpliest one:

<mx:ComboBox id="cb" dataProvider="{[{label:"", data:null}, {label:'foo', data:'foo'}, {label:'bar', data:'bar'}]}" />

and access the data using cb.selectedItem.data

However, this simple solution is not binding-friendly, as Wouter mentioned.

So here is a more tricky solution that will allow selecting null objects :

<mx:ComboBox id="cb" dataProvider="{[null, 'foo', 'bar']}" dropdownFactory="{new ClassFactory(NullList)}" />

Where NullList is the following class:

package test
{
import mx.controls.List;

public class NullList extends List
{
    public function NullList()
    {
        super();
    }

    override public function isItemSelectable(data:Object):Boolean
    {
        if (!selectable)
            return false;
        return true;
    }

}
}

Unfortunately, this is impossible. How ever, a good solution that will not make you "have to keep this mapping in mind" is to create a class inherits from ComboBox with its own DataProvider property.

this property setter will handle the null values and have a representation for it on the super ComboBox class.

requireSelection =“false”将允许空值和提示允许您输入该空白值的文本用途(如果您愿意)。

A really simple, but also very limited solution is to add a prompt="" attribute..

This will prevent the ComboBox from selecting the first item in the dataProvider automatically, but as soon as the user select an item, the empty row will not be displayed anymore.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM