I've a tree control with checkboxes that uses the control from http://www.sephiroth.it/file_detail.php?id=151#
Somehow I can't get the control to update when I change the dataProvider (i.e. by clicking a checkbox) the only way I can get it to update is to use the scrollbar. How do I force the update? I've tried all possible methods I can figure out? (see update below)
Also how can I reset the Tree (collpasing all nodes, scroll to the top in a large tree)?
package offerta.monkeywrench.components
{
import offerta.monkeywrench.components.componentClasses.TreeCheckBoxItemRenderer;
import mx.collections.ArrayCollection;
import mx.events.TreeEvent;
public class WatchTree extends TreeCheckBox
{
public var idProperty:String;
public var watchFactory:Function;
private var _wSet:Boolean = false;
/* clientId: */
private var _clientId:String;
[Bindable]
public function get clientId():String
{
return _clientId;
}
public function set clientId(value:String):void
{
this._clientId = value;
}
/* //clientId */
/* watching: */
private var _watching:ArrayCollection;
[Bindable]
public function set watching(value:ArrayCollection):void
{
this._watching = value;
}
public function get watching():ArrayCollection
{
return this._watching;
}
/* //watching */
override public function initialize() :void
{
super.initialize();
addEventListener("itemCheck", onItemCheck, false, 0, true);
}
private function isWatching(id:String):Boolean
{
for each(var w:Object in this._watching)
{
if(w[this.idProperty]==id) return true;
}
return false;
}
private function onItemCheck(event:TreeEvent):void
{
var item:Object = event.item as Object;
var currentValue:uint = (event.itemRenderer as TreeCheckBoxItemRenderer).checkBox.checkState;
if(item.children==null)
{
currentValue==2 ? addWatch(item.Id) : removeWatch(item.Id);
}
else
{
for each(var x:Object in item.children)
{
currentValue==2 ? addWatch(x.Id) : removeWatch(x.Id);
}
}
updateParents(item, currentValue);
updateChilds(item, currentValue);
this.dataProvider.refresh();
super.invalidateProperties();
super.invalidateDisplayList();
super.updateDisplayList(this.unscaledWidth, this.unscaledHeight);
}
private function updateParents(item:Object, value:uint):void
{
var checkValue:String = (value == ( 1 << 1 | 2 << 1 ) ? "2" : value == ( 1 << 1 ) ? "1" : "0");
var parentNode:Object = item.parent;
if(parentNode)
{
for each(var x:Object in parentNode.children)
{
if(x.checked != checkValue)
{
checkValue = "2"
}
}
parentNode.checked = checkValue;
updateParents(parentNode, value);
}
}
private function updateChilds(item:Object, value:uint):void
{
var middle:Boolean = (value&2<<1)==(2<<1);
if(item.children!=null && item.children.length>0&&!middle)
{
for each(var x:Object in item.children)
{
x.checked = value == (1<<1|2<<1) ? "2" : value==(1<<1) ? "1" : "0";
updateChilds(x, value);
}
}
}
private function addWatch(id:String):void
{
if(isWatching(id)) return;
this._watching.addItem(this.watchFactory(id, this.clientId));
}
private function removeWatch(id:String):void
{
for(var i:int=0, n:int=this._watching.length; i<n; ++i)
{
if(this._watching[i][this.idProperty]==id)
{
this._watching.removeItemAt(i);
return;
}
}
}
public function update(__watching:ArrayCollection, __clientId:String):void
{
clientId = __clientId;
watching = __watching;
if(this.dataProvider!=null)
{
var ws:ArrayCollection = ArrayCollection(this.dataProvider);
for each(var group:Object in ws)
{
var count:int = 0;
for each(var child:Object in group.children)
{
if(isWatching(child.Id))
{
child.checked = "1";
count++;
}
}
group.checked = (count==0 ? "0" : (count==group.children.length ? "1" : "2"));
}
this._wSet = false;
var dp:ArrayCollection = ArrayCollection(this.dataProvider);
dp.refresh();
super.invalidateProperties();
super.invalidateDisplayList();
super.updateDisplayList(this.unscaledWidth, this.unscaledHeight);
//scroll up the list???
//collapse? (doesn't work)
this.expandItem(null, false);
}
}
}
}
I've found the Tree control a little touchy in Flex. The way I ended up forcing a redraw was to disconnect the dataProvider and reconnect it, then force validation, something a bit like this :
private function forceRedraw(tree:Tree, dataProvider:Object):void
{
var scrollPosition:Number = tree.verticalScrollPosition;
var openItems:Object = tree.openItems;
tree.dataProvider = dataProvider;
tree.openItems = openItems;
tree.validateNow();
tree.verticalScrollPosition = scrollPosition;
}
I guess this incidentally answers the second part of your question since all you'd have to do is null out the openItems collection and set the verticalScrollPosition to 0.
You might have another problem: whenever you check an item the tree scrolls to the top and this is just annoying. To solve this problem you should update the TreeCheckBox.as file this way:
in function checkHandler:
private function checkHandler( event: TreeEvent ): void;
comment the commitProperties(); call.
Now it should work well.
Cheers.
I've had some minor problem with this solution, var scrollPosition:Number = tree.verticalScrollPosition; is constantly 0??
Related
I'm creating a custom component which is a list of Buttons.
When the user clicks on a button, i change its css class and then i would like to add it in a custom "selectedItems" property to retrieve it in my ViewModel.
When I do a push on the "selectedItems" array property, no event is raised and I don't get the information.
Also, I tried to re-set the entire array but not better.
I don't know how to achieve this.
Here is the code of my component :
import {WrapLayout} from "ui/layouts/wrap-layout";
import {EventData} from "data/observable";
import {ValueButton} from "./value-button";
import dependencyObservableModule = require("ui/core/dependency-observable");
export class ValuesSelector extends WrapLayout {
public static itemsProperty = new dependencyObservableModule.Property(
"items",
"ValuesSelector",
new dependencyObservableModule.PropertyMetadata(
[],
dependencyObservableModule.PropertyMetadataSettings.None,
function(data: dependencyObservableModule.PropertyChangeData) {
if (data.newValue) {
let instance = <ValuesSelector>data.object;
instance.items = data.newValue;
}
}));
public static deleteOnClickProperty = new dependencyObservableModule.Property(
"deleteOnClick",
"ValuesSelector",
new dependencyObservableModule.PropertyMetadata(
false,
dependencyObservableModule.PropertyMetadataSettings.None));
public static selectedItemsProperty = new dependencyObservableModule.Property(
"selectedItems",
"ValuesSelector",
new dependencyObservableModule.PropertyMetadata(
[],
dependencyObservableModule.PropertyMetadataSettings.None));
public static singleSelectionProperty = new dependencyObservableModule.Property(
"singleSelection",
"ValuesSelector",
new dependencyObservableModule.PropertyMetadata(
false,
dependencyObservableModule.PropertyMetadataSettings.None));
public get selectedItems() {
return this._getValue(ValuesSelector.selectedItemsProperty);
}
public set selectedItems(value: any[]) {
this._setValue(ValuesSelector.selectedItemsProperty, value);
}
public get deleteOnClick() {
return this._getValue(ValuesSelector.deleteOnClickProperty);
}
public set deleteOnClick(value: boolean) {
this._setValue(ValuesSelector.deleteOnClickProperty, value);
}
public get singleSelection() {
return this._getValue(ValuesSelector.singleSelectionProperty);
}
public set singleSelection(value: boolean) {
this._setValue(ValuesSelector.singleSelectionProperty, value);
}
public get items() {
return this._getValue(ValuesSelector.itemsProperty);
}
public set items(value: any) {
this._setValue(ValuesSelector.itemsProperty, value);
this.createUI();
}
private _buttons: ValueButton[];
constructor() {
super();
this.orientation = "horizontal";
this._buttons = [];
}
private createUI() {
this.removeChildren();
let itemsLength = this.items.length;
for (let i = 0; i < itemsLength; i++) {
let itemButton = new ValueButton();
itemButton.text = this.items[i].label;
itemButton.value = this.items[i];
itemButton.className = "values-selector-item";
if (this.deleteOnClick) {
itemButton.className = "values-selector-selected-item";
}
itemButton.on(ValueButton.tapEvent, (data: EventData) => {
let clickedButton = <ValueButton>data.object;
if (this.deleteOnClick) {
let itemIndex = this.items.indexOf(clickedButton.value);
if (itemIndex > -1) {
let newSelectedItems = this.items;
newSelectedItems.splice(itemIndex, 1);
this.items = newSelectedItems;
}
return;
}
let internalSelectedItems = this.selectedItems;
if (clickedButton.className === "values-selector-item") {
if (this.singleSelection && this.selectedItems.length > 0) {
internalSelectedItems = [];
for (let i = 0; i < this._buttons.length; i++) {
this._buttons[i].className = "values-selector-item";
}
}
internalSelectedItems.push(clickedButton.value);
clickedButton.className = "values-selector-selected-item";
} else {
let itemIndex = internalSelectedItems.indexOf(clickedButton.value);
if (itemIndex > -1) {
internalSelectedItems.splice(itemIndex, 1);
}
clickedButton.className = "values-selector-item";
}
this.selectedItems = internalSelectedItems;
}, this);
this._buttons.push(itemButton);
this.addChild(itemButton);
}
}
}
Can you help me ?
Thanks
Ok I made a mistake by databinding my property.
In fact, in the XML I use the component like this :
<vs:ValuesSelector items="{{ criterias }}" selectedItems="{{ myObject.selectedCriterias }}" />
But in the ViewModel, I never initialized the selectedCriterias property because I thought that the default value [] specified in the component would create it.
So in the ViewModel, here is the fix :
Before
this.myObject = {
id : 0
};
After
this.myObject = {
id : 0,
selectedCriterias: []
};
I have a checkbox in AdvancedDataGrid as GroupItemrenderer.I have selected certain child nodes in tree and closed its parent,later when I reopen the same parent node,selected child nodes are not those that i selected.How to retain the correct selections in checkbox.??
Also am unable to set the value for checkbox (GroupItemRenderer) by default from component and also unable to access the value for checkbox in Data property.
package
{
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;
import mx.collections.ICollectionView;
import mx.collections.IHierarchicalCollectionView;
import mx.collections.IHierarchicalData;
import mx.collections.IViewCursor;
import mx.controls.AdvancedDataGrid;
import mx.controls.Alert;
import mx.controls.CheckBox;
import mx.controls.Image;
import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;
import mx.controls.advancedDataGridClasses.AdvancedDataGridGroupItemRenderer;
import mx.controls.advancedDataGridClasses.AdvancedDataGridListData;
import mx.core.FlexGlobals;
import mx.core.mx_internal;
use namespace mx_internal;
public class CheckADGRenderer extends AdvancedDataGridGroupItemRenderer
{
protected var myImage:Image;
public var status:String = "false";
// set image properties
private var imageWidth:Number = 6;
private var imageHeight:Number = 6;
private var inner:String = "inner.png";
protected var myCheckBox:CheckBox;
static private var STATE_SCHRODINGER:String = "schrodinger";
static private var STATE_CHECKED:String = "checked";
static private var STATE_UNCHECKED:String = "unchecked";
public function CheckADGRenderer ()
{
super();
mouseEnabled = false;
}
private function toggleParents(item:Object,adg:AdvancedDataGrid,state:String):void
{
if (item == null)
{
return;
}
else
{
item.#state = false;
toggleParents(adg.getParentItem(item), adg, getState (adg, adg.getParentItem(item)));
}
}
private function toggleChildren (item:Object, adg:AdvancedDataGrid, state:String):void
{
if (item == null)
{
return;
}
else
{
//item.#state = state;
var adgCollection:IHierarchicalCollectionView = adg.dataProvider as IHierarchicalCollectionView;
var adgHD:IHierarchicalData = adgCollection.source;
if (adgHD.hasChildren(item))
{
var children:ICollectionView = adgCollection.getChildren (item);
var cursor:IViewCursor = children.createCursor();
while (!cursor.afterLast)
{
toggleChildren(cursor.current, adg, state);
cursor.moveNext();
}
}
}
}
private function getState(adg:AdvancedDataGrid, parent:Object):String
{
var noChecks:int = 0;
var noCats:int = 0;
var noUnChecks:int = 0;
if (parent != null)
{
var adgCollection:IHierarchicalCollectionView = adg.dataProvider as IHierarchicalCollectionView;
var cursor:IViewCursor = adgCollection.getChildren(parent).createCursor();
}
if ((noChecks > 0 && noUnChecks > 0) || (noCats > 0))
{
return STATE_SCHRODINGER;
}
else if (noChecks > 0)
{
return STATE_CHECKED;
}
else
{
return STATE_UNCHECKED;
}
}
private function imageToggleHandler(event:MouseEvent):void
{
myCheckBox.selected = !myCheckBox.selected;
checkBoxToggleHandler(event);
}
var selectArr:Array = new Array();
private function checkBoxToggleHandler(event:MouseEvent):void
{
if (data)
{
var myListData:AdvancedDataGridListData = AdvancedDataGridListData(this.listData);
var selectedNode:Object = myListData.item;
var adg:AdvancedDataGrid = AdvancedDataGrid(myListData.owner);
var toggle:Boolean = myCheckBox.selected;
if (toggle)
{
toggleChildren(data, adg, STATE_CHECKED);
}
else
{
toggleChildren(data, adg, STATE_UNCHECKED);
}
var parent:Object = adg.getParentItem (data);
toggleParents (parent, adg, getState (adg, parent));
//Alert.show(selectArr.toString()+"\t\t"+selectArr.length+"\t\t"+selectArr[0].length+"\t\t"+adg.selectedIndices.length);
}
}
override protected function createChildren():void
{
super.createChildren();
myCheckBox = new CheckBox();
myCheckBox.setStyle( "verticalAlign", "middle" );
myCheckBox.addEventListener( MouseEvent.CLICK, checkBoxToggleHandler );
addChild(myCheckBox);
}
private function setCheckState (checkBox:CheckBox, value:Object, state:Boolean):void
{
if (state == STATE_CHECKED)
{
checkBox.selected = state;
}
else if (state == STATE_UNCHECKED)
{
checkBox.selected = false;
}
else if (state == STATE_SCHRODINGER)
{
checkBox.selected = false;
}
checkBox.selected = state;
}
override public function set data(value:Object):void
{
super.data = value;
var myListData:AdvancedDataGridListData = AdvancedDataGridListData(this.listData);
//var adg:AdvancedDataGrid = AdvancedDataGrid(myListData.owner);
var selectedNode:Object = myListData.item;
myCheckBox.selected = AdvancedDataGridListData(super.listData).item.show;
//var adg:AdvancedDataGrid = AdvancedDataGrid(myListData.owner);
//setCheckState (myCheckBox, value, Boolean(value.state));
}
override protected function commitProperties():void
{
super.commitProperties();
var dg:AdvancedDataGrid = AdvancedDataGrid(listData.owner);
var column:AdvancedDataGridColumn =
dg.columns[listData.columnIndex];
label.wordWrap = dg.columnWordWrap(column);
}
/**
* #private
*/
override protected function measure():void
{
super.measure();
var w:Number = data ? AdvancedDataGridListData(listData).indent : 0;
if (disclosureIcon)
w += disclosureIcon.width;
if (icon)
w += icon.measuredWidth;
if (myCheckBox)
w += myCheckBox.measuredWidth;
// guarantee that label width isn't zero because it messes up ability to measure
if (label.width < 4 || label.height < 4)
{
label.width = 4;
label.height = 16;
}
if (isNaN(explicitWidth))
{
w += label.getExplicitOrMeasuredWidth();
measuredWidth = w;
}
else
{
label.width = Math.max(explicitWidth - w, 4);
}
measuredHeight = label.getExplicitOrMeasuredHeight();
if (icon && icon.measuredHeight > measuredHeight)
measuredHeight = icon.measuredHeight;
if (myCheckBox && myCheckBox.measuredHeight > measuredHeight)
measuredHeight = myCheckBox.measuredHeight;
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(super.data)
{
if (super.icon != null)
{
myCheckBox.x = super.icon.x;
myCheckBox.y = (unscaledHeight - myCheckBox.height) / 2;
super.icon.x = myCheckBox.x + myCheckBox.width + 17;
if (icon.x + icon.width > unscaledWidth)
icon.setActualSize(0, unscaledHeight);
super.label.x = super.icon.x + super.icon.width + 3;
super.label.setActualSize(Math.max(unscaledWidth - super.label.x, 4), unscaledHeight);
}
else
{
myCheckBox.x = super.label.x;
myCheckBox.y = (unscaledHeight - myCheckBox.height) / 2;
super.label.x = myCheckBox.x + myCheckBox.width + 17;
super.label.setActualSize(Math.max(unscaledWidth - super.label.x, 4), unscaledHeight);
}
if (myCheckBox.x + myCheckBox.width > unscaledWidth)
myCheckBox.visible = false;
}
trace(label.width);
}
}
}
I am using 2D array as dataprovider to ADG.
My post :
how to add an attribute to an existing array dynamically in Flex , is associated with the above post.
I suspect you haven't written any code to set the default state of the Checkbox based on data.
Generically, your itemRenderer's Checkbox state should be stored as part of the data. When the
itemRenderer is initialized, set the checkbox to checked--or not--based on the property in your data element. When the Checkbox is clicked; change that same property in your data element.
This approach will sync your itemRenderer's display state up with your data.
If you want a more specific answer, you'll have to share some code so we can see what you're doing.
I have a Custom Spark ComboBox which contains a list of items. I want to Add a new Item to the ComboBox stating the text "Add new Item", which on selecting popup a window to do some operations.
I have achieved this by creating a new object of the same type of the entities in the data provider, with the LabelField of the new object having the text "Add new Item". I have override the set dataProvider method and in the custom combobox.
But this adds the values to the Actual List which is binded to the DataProvider. The list is used in the business logics. So i do not want this to happen. I have lot of Entity Classes. i could not change all the objects.
All i want is to achieve the same functionality in my custom component without changing the other code. I have also tried to create a new instance of dataProvier but i noticed that the binding of the List and dataprovider is lost when i created a new instance.
Kindly help.!!
Edited:
ExtendedComboBox.as
package components
{
import flash.utils.getDefinitionByName;
import mx.collections.ArrayCollection;
import mx.collections.IList;
import spark.components.ComboBox;
import spark.events.DropDownEvent;
public class ExtendedComboBox extends ComboBox
{
private var _addItem:Boolean = false;
private var _addItemLabel:String = "Create New Item" ;
private var _dropDownClass:String = null ;
private var originalDP:IList ;
private var dpEdited:Boolean = false;
public function ExtendedComboBox()
{
super();
this.addItem = true;
this.addEventListener(DropDownEvent.CLOSE, dropDownCloseEventListner );
this.addEventListener(DropDownEvent.OPEN, openDropDownEvent );
}
public function get dropDownClass():String
{
return _dropDownClass;
}
public function set dropDownClass(value:String):void
{
_dropDownClass = value;
}
public function get addItemLabel():String
{
return _addItemLabel;
}
public function set addItemLabel(value:String):void
{
_addItemLabel = value;
}
public function get addItem():Boolean
{
return _addItem;
}
public function set addItem(value:Boolean):void
{
_addItem = value;
}
private function dropDownCloseEventListner(event:DropDownEvent):void{
}
protected function openDropDownEvent(event:DropDownEvent):void{
if(addItem)
{
// if(value) value = new ArrayCollection();
var value:IList ;
if(originalDP == null) value = new ArrayCollection ;
else value = new ArrayCollection( originalDP.toArray() ) ;
var tempObj:Object;
var createItemPresent:Boolean =false ;
if(dropDownClass != null)
{
var TempClass = flash.utils.getDefinitionByName(dropDownClass) as Class;
tempObj = new TempClass();
if(value.length >0)
{
// trace(value.getChildAt(0)[this.labelField]) ;
if(value.getItemAt(0)[this.labelField] == addItemLabel)
createItemPresent = true ;
}
if(!createItemPresent)
{
tempObj[this.labelField] = addItemLabel ;
var sort = (value as ArrayCollection).sort ;
value.addItemAt(tempObj, 0);
(value as ArrayCollection).sort = sort ;
dpEdited = true;
}
}
}
super.dataProvider = value;
}
override public function set dataProvider(value:IList):void{
if(!dpEdited)
{
originalDP = value;
dpEdited = true;
}
/*if(addItem)
{
// if(value) value = new ArrayCollection();
var tempObj:Object;
var createItemPresent:Boolean =false ;
if(dropDownClass != null)
{
var TempClass = flash.utils.getDefinitionByName(dropDownClass) as Class;
tempObj = new TempClass();
if(value.length >0)
{
if(value.getItemIndex(0)[this.labelField] == addItemLabel)
createItemPresent = true ;
}
if(!createItemPresent)
{
tempObj[this.labelField] = addItemLabel ;
var sort = (value as ArrayCollection).sort ;
value.addItemAt(tempObj, 0);
(value as ArrayCollection).sort = sort ;
}
}
}*/
super.dataProvider = value;
}
}
}
MyEntityObj.as
package entity
{
public class MyEntityObj
{
private var _name:String ;
private var _age:int ;
private var _company:String;
public function MyEntityObj()
{
}
public function get company():String
{
return _company;
}
public function set company(value:String):void
{
_company = value;
}
public function get age():int
{
return _age;
}
public function set age(value:int):void
{
_age = value;
}
public function get name():String
{
return _name;
}
public function set name(value:String):void
{
_name = value;
}
}
}
And Implementation Sample - ComboImpl.mxml
<?xml version="1.0" encoding="utf-8"?>
import mx.collections.ArrayCollection;
import mx.events.CollectionEvent;
import mx.events.FlexEvent;
[Bindable]
private var samplDP:ArrayCollection;
protected function application1_initializeHandler(event:FlexEvent):void
{
samplDP = new ArrayCollection ;
samplDP.addEventListener(CollectionEvent.COLLECTION_CHANGE, changeHandlerFunc );
var sampVO:MyEntityObj;
for(var i:int = 0; i<5;i++)
{
sampVO = new MyEntityObj;
sampVO.name = "Name " + i;
sampVO.age = i;
sampVO.company = "Company " + i;
samplDP.addItem(sampVO);
}
}
protected function changeHandlerFunc(event:CollectionEvent):void{
var nameList:String = "" ;
for each(var myObj:* in samplDP)
{
nameList += myObj.name + ", " ;
}
changeHandler.text = nameList ;
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:VGroup>
<s:Label id="changeHandler" />
<components:ExtendedComboBox dataProvider="{samplDP}" labelField="name" addItem="true" dropDownClass="entity.MyEntityObj" addItemLabel="Create Sample" />
</s:VGroup>
As commented in the set DataProvider Overridden method and the in the DropDownClose events addition of the new item directly affects the original List. i do not want this to happen.
Note that its just a sample implementation. The component creation in my project actually happens in the action script class dynamically.
Kindly help.!!
It sounds to me like:
You're going to have to extend the ComboBox (If you drill down into how it's implemented, possibly the DataGroup) to include your extra "Add New Item" item w/o it being in the dataProvider.
I expect this to be very difficult, but have not reviewed the code for this purpose.
I have a standard combobox that dispatches a collection event when the dataprovider finishes initializing:
my_cb.addEventListener( CollectionEvent.COLLECTION_CHANGE, getMyStuff );
Then I have a custom component that also has a dataProvider. How do I get it to dispatch a collection change event when its dataprovider finishes loading?
From what I've read, I can't do it. Will dispatching a propertychangeevent work?
Thanks for any helpful tips!
UPDATE:
I have a custom component that I call 'SortingComboBox' but it is not a ComboBox at all; it extends Button and I set is dataProvider property to my arraycollection, model.product (which is an arraycollection).
And here is how I use the dataProvider in that component:
code
[Bindable]
private var _dataProvider : Object;
public function get dataProvider() : Object
{
return _dataProvider;
}
public function set dataProvider(value : Object) : void
{
_dataProvider = value;
}
code
In the createChildren() method of this component, I use this:
BindingUtils.bindProperty(dropDown, "dataProvider", this, "dataProvider");
The dropDown is a custom VBox that I use to display labels.
When you call the setter, you have to make sure
1) that you actually are changing the value with the setter. So even if you are inside the class, call this.dataProvider = foo instead of _dataProvider = foo
2) The binding will not trigger unless you actually change the value. If you trace you'll see that the setter actually calls the getter, if the values of what you pass into the setter and the getter are the same, the binding will not occur.
Your other option is to put an event on the getter, then just call it to trigger the binding.
[Bindable( "somethingChanged" )]
public function get dataProvider() : Object
{
return _dataProvider;
}
dispatchEvent( new Event( "somethingChanged" ) );
Make your dataprovider bindable
[Bindable]
protected var _dataProvider:ArrayCollection ;
Data binding is something unique to ActionScript/Flex.
Among other things it will dispatch change events.
Maybe if you post your code for the custom component I can be more specific.
Actually can you explain what your goal is you are trying to achieve?
All I can tell is you are trying to make a button have a drop down.
Why?
this is the custom component just to give you a better idea.
code
package com.fidelity.primeservices.act.components.sortingcombobox
{
import com.fidelity.primeservices.act.events.component.ResetSortEvent;
import com.fidelity.primeservices.act.events.component.SortEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import mx.binding.utils.BindingUtils;
import mx.controls.Button;
import mx.core.UIComponent;
import mx.effects.Tween;
import mx.events.FlexMouseEvent;
import mx.managers.PopUpManager;
import mx.events.PropertyChangeEvent;
import mx.events.PropertyChangeEventKind;
public class SortingComboBox extends Button
{
private const MAX_LABEL_LENGTH : int = 400;
private const ELIPSES : String = "...";
[Bindable]
private var _dataProvider : Object;
private var dropDown : SortingDropDown;
private var inTween : Boolean;
private var showingDropdown : Boolean;
private var openCloseTween : Tween;
public var noSelectionLabel : String = "No Filter";
public var noSelectionData : String = "ALL";
public function get dataProvider() : Object
{
return _dataProvider;
}
public function set dataProvider(value : Object) : void
{
_dataProvider = value;
}
private function collectionEvent(e : Event):void
{
trace(new Date(), e);
}
public function SortingComboBox()
{
super();
this.buttonMode = true;
this.useHandCursor = true;
inTween = false;
showingDropdown = false;
addEventListener(Event.REMOVED_FROM_STAGE, removedFromStage);
}
override protected function createChildren() : void
{
super.createChildren();
dropDown = new SortingDropDown();
dropDown.width = 240;
dropDown.maxHeight = 300;
dropDown.visible = false;
BindingUtils.bindProperty(dropDown, "dataProvider", this, "dataProvider");
dropDown.styleName = "sortingDropDown";
dropDown.addEventListener(SortEvent.CLOSE_SORT, closeDropDown);
dropDown.addEventListener(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, dropdownCheckForClose);
dropDown.addEventListener(FlexMouseEvent.MOUSE_WHEEL_OUTSIDE, dropdownCheckForClose);
dropDown.addEventListener(SortEvent.UPDATE_SORT, onSortUpdate); //this event bubbles
dropDown.addEventListener(ResetSortEvent.RESET_SORT_EVENT, onSortUpdate);
PopUpManager.addPopUp(dropDown, this);
this.addEventListener(MouseEvent.CLICK, toggleDropDown);
// weak reference to stage
systemManager.addEventListener(Event.RESIZE, stageResizeHandler, false, 0, true);
}
private function stageResizeHandler(evt : Event) : void
{
showingDropdown = false;
dropDown.visible = showingDropdown;
}
private function toggleDropDown(evt : MouseEvent) : void
{
if(!dropDown.visible)
{
openDropDown(evt);
}
else
{
closeDropDown(evt);
}
}
private function openDropDown(evt : MouseEvent) : void
{
if (dropDown.parent == null) // was popped up then closed
{
PopUpManager.addPopUp(dropDown, this);
}
else
{
PopUpManager.bringToFront(dropDown);
}
showingDropdown = true;
dropDown.visible = showingDropdown;
dropDown.enabled = false;
var point:Point = new Point(0, unscaledHeight);
point = localToGlobal(point);
point = dropDown.parent.globalToLocal(point);
//if the dropdown is larger than the button and its
//width would push it offscreen, align it to the left.
if (dropDown.width > unscaledWidth && point.x + dropDown.width > screen.width)
{
point.x -= dropDown.width - unscaledWidth;
}
dropDown.move(point.x, point.y);
//run opening tween
inTween = true;
// Block all layout, responses from web service, and other background
// processing until the tween finishes executing.
UIComponent.suspendBackgroundProcessing();
dropDown.scrollRect = new Rectangle(0, dropDown.height, dropDown.width, dropDown.height);
openCloseTween = new Tween(this, dropDown.height, 0, 250);
}
private function closeDropDown(evt : Event) : void
{
//dropDown.visible = false;
showingDropdown = false;
//run closing tween
inTween = true;
// Block all layout, responses from web service, and other background
// processing until the tween finishes executing.
UIComponent.suspendBackgroundProcessing();
openCloseTween = new Tween(this, 0, dropDown.height, 250);
}
private function dropdownCheckForClose(event : MouseEvent) : void
{
if (event.target != dropDown)
// the dropdown's items can dispatch a mouseDownOutside
// event which then bubbles up to us
return;
if (!hitTestPoint(event.stageX, event.stageY, true))
{
closeDropDown(event);
}
}
public function refresh():void
{
onSortUpdate(null);
}
private function onSortUpdate(evt1 : Event) : void
{
//update the label
var dpLength : int = this.dataProvider.length;
var nextLabel : String = "";
var nextData : String = "";
for (var i : int = 0; i < dpLength; i++)
{
if (this.dataProvider[i].selected == true)
{
nextLabel += this.dataProvider[i].label + ", ";
if (this.dataProvider[i].data != null)
{
nextData += this.dataProvider[i].data + ", ";
}
}
}
if (nextLabel.length > 0)
{
// remove extra comma at end
nextLabel = nextLabel.substr(0, nextLabel.length - 2);
}
if (nextData.length > 0)
{
nextData = nextData.substr(0, nextData.length - 2);
}
if (nextLabel.length > MAX_LABEL_LENGTH)
{
// limit label to MAX_LABEL_LENGTH + ... REASON: tooltips with lots of characters take a long time to render
nextLabel = nextLabel.substr(0, MAX_LABEL_LENGTH) + ELIPSES;
}
if (nextLabel.length == 0)
{
nextLabel = noSelectionLabel;
//nextLabel = "No Filter";
}
if (nextData.length == 0)
{
nextData = noSelectionData;
//nextData = "ALL";
}
label = nextLabel;
data = nextData;
toolTip = label;
if (evt1 is SortEvent)
{
trace("sort event");
var temp:Object = this.dataProvider;
this.dataProvider = null;
this.dataProvider = temp;
this.refresh();
}
else
{
trace("not dispatching");
}
}
public function onTweenUpdate(value:Number):void
{
dropDown.scrollRect = new Rectangle(0, value, dropDown.width, dropDown.height);
}
public function onTweenEnd(value:Number) : void
{
// Clear the scrollRect here. This way if drop shadows are
// assigned to the dropdown they show up correctly
dropDown.scrollRect = null;
inTween = false;
dropDown.enabled = true;
dropDown.visible = showingDropdown;
UIComponent.resumeBackgroundProcessing();
}
private function removedFromStage(event:Event):void
{
if(inTween)
{
openCloseTween.endTween();
}
// Ensure we've unregistered ourselves from PopupManager, else
// we'll be leaked.
PopUpManager.removePopUp(dropDown);
}
}
}
Ok this code here
[Bindable]
private var _dataProvider : Object;
public function get dataProvider() : Object
{
return _dataProvider;
}
public function set dataProvider(value : Object) : void
{
_dataProvider = value;
}
is no different then
[Bindable]
public var _dataProvider : Object;
Since objects are passed by reference you are not protecting it in anyway and the setter and getter are pointless.
On the other hand you made the source _dataProvider Bindable so anytime the data changes it will dispatch a CollectionEvent.COLLECTION_CHANGE
Is it possible to make an item in a List control not selectable? If so, how would this be accomplished?
I've tried one thing so far. What I did was use a custom item renderer that checks for a value in the data property upon a FlexEvent.DATA_CHANGE event. If that value is not set, I tried setting the item renderer's selectable property to false. This, unfortunately, does not seem to work.
Any ideas?
So I came upon a solution of my own. Its similar to yours, and seems to do the trick and cover all the bases apart from hacking the page up and page down keys. I say hack, because I'm not sure it handles the increase or decrease in the caretIndex the same way as the List control. Basically it just manually sets the caretIndex to an index before what the next selectable item is and changes the keycode to a simple up or down.
protected function disabledFilterFunction( data:Object ):Boolean
{
return ( data != null && data.data == null );
}
override protected function mouseEventToItemRenderer( event:MouseEvent ):IListItemRenderer
{
var item:IListItemRenderer = super.mouseEventToItemRenderer( event );
if( item && item.data && disabledFilterFunction( item.data ) )
return null;
return item;
}
override protected function moveSelectionVertically( code:uint, shiftKey:Boolean, ctrlKey:Boolean ):void
{
var i:int;
var newIndex:int;
switch( code )
{
case Keyboard.UP:
newIndex = getPreviousUnselectableIndex( caretIndex - 1 );
break;
case Keyboard.DOWN:
newIndex = getNextUnselectableIndex( caretIndex + 1 );
break;
case Keyboard.HOME:
newIndex = getFirstSelectableIndex();
code = Keyboard.UP;
break;
case Keyboard.END:
newIndex = getLastSelectableIndex();
code = Keyboard.DOWN;
break;
case Keyboard.PAGE_UP:
{
newIndex = Math.max( getFirstSelectableIndex(), getPreviousUnselectableIndex( caretIndex - ( rowCount - 2 ) ) );
code = Keyboard.UP;
break;
}
case Keyboard.PAGE_DOWN:
{
newIndex = Math.min( getLastSelectableIndex(), getNextUnselectableIndex( caretIndex + ( rowCount - 1 ) ) );
code = Keyboard.DOWN;
break;
}
}
if( newIndex > -1 && newIndex < collection.length )
{
caretIndex = newIndex;
super.moveSelectionVertically( code, shiftKey, ctrlKey );
}
}
private function getFirstSelectableIndex():int
{
var result:int = -1;
for( var i:int = 0; i < collection.length; i++ )
{
if( !disabledFilterFunction( collection[i] ) )
{
result = i + 1;
break;
}
}
return result;
}
private function getLastSelectableIndex():int
{
var result:int = -1;
for( var i:int = collection.length - 1; i > -1; i-- )
{
if( !disabledFilterFunction( collection[i] ) )
{
result = i - 1;
break;
}
}
return result;
}
private function getPreviousUnselectableIndex( startIndex:int ):int
{
var result:int = -1;
for( var i:int = startIndex; i > -1; i-- )
{
if( !disabledFilterFunction( collection[i] ) )
{
result = i + 1;
break;
}
}
return result;
}
private function getNextUnselectableIndex( startIndex:int ):int
{
var result:int = collection.length;
for( var i:int = startIndex; i < collection.length; i++ )
{
if( !disabledFilterFunction( collection[i] ) )
{
result = i - 1;
break;
}
}
return result;
}
I was able to fix the problem of first/last items being non-selectable by simply doing this verticalScrollPosition++ and verticalScrollPosition-- right before caretIndex++ and caretIndex-- respectively (in the example that Michael linked to up above) . I couldn't believe that the fix was so easy, but it was!
I was able to do this to add a separator component, following this ComboBox example. Here's an example with the renderer logic stripped out and the selectability logic left in:
package com.example.ui {
import flash.events.MouseEvent;
import flash.ui.Keyboard;
import mx.controls.List;
import mx.controls.listClasses.IListItemRenderer;
public class MyList extends List
{
public function MyList()
{
super();
}
/** Override mouse navigation */
protected override function mouseEventToItemRenderer(event:MouseEvent):IListItemRenderer {
var row:IListItemRenderer = super.mouseEventToItemRenderer(event);
if (row != null && isSelectable(row.data)) {
return null;
}
return row;
}
/** Override keyboard navigation */
protected override function moveSelectionVertically(code:uint, shiftKey:Boolean, ctrlKey:Boolean):void {
super.moveSelectionVertically(code, shiftKey, ctrlKey);
if (code == Keyboard.DOWN && isSeparatorData(selectedItem)) {
caretIndex++;
}
if (code == Keyboard.UP && isSeparatorData(selectedItem)) {
caretIndex--;
}
finishKeySelection();
}
/**
* Define this mechanism in a way that makes sense for your project.
*/
protected function isSelectable(data:Object):Boolean {
return data != null && data.hasOwnProperty("type") && data.type == "separator";
}
}
}
An alternative (still imperfect) that deals better with scrollable lists and consecutive separators:
protected override function moveSelectionVertically(code:uint, shiftKey:Boolean, ctrlKey:Boolean):void {
super.moveSelectionVertically(code, shiftKey, ctrlKey);
var newCode:uint = singleLineCode(code);
var item:Object = selectedItem;
var itemChanged:Boolean = true;
while (!isNaN(newCode) && itemChanged && isSeparatorData(item)) {
super.moveSelectionVertically(newCode, shiftKey, ctrlKey);
itemChanged = (item === selectedItem);
item = selectedItem;
}
}
private function singleLineCode(code:uint):uint {
switch (code) {
case Keyboard.UP:
case Keyboard.PAGE_UP:
return Keyboard.UP;
break;
case Keyboard.DOWN:
case Keyboard.PAGE_DOWN:
return Keyboard.DOWN;
break;
default:
return NaN;
break;
}
return code;
}
Just thought I'd add my two sense. I was wondering the same thing (how to I set a list to not selectable) and I realized that the spark component datagroup would do exactly that. of course you need to be using flex 4, but if you are, and are wondering can I set my list to not selectable, I'd suggest using the datagroup.
I was looking for a solution myself and here's the solution I came up with. Note that I'm using a spark list. I hope someone finds this helpful.
Implement both event handlers for change and changing
Set the selection to -1 and requireSelection to false so that nothing is selected
When you build your dataprovider, enable/disable items as desired
Provide some logic in the changing handler to call 'preventDefault' on the event if it isn't enabled, or shouldn't be selected.
Example: cribbed from my implementation where I build my own items and use a Tile Layout
<s:List id="myListView"
itemRenderer="spark.skins.spark.DefaultComplexItemRenderer"
horizontalCenter="0"
verticalCenter="0"
borderVisible="false"
dataProvider="{myItems}"
change="changeHandler(event)" changing="changingHandler(event)"
requireSelection="false"
selectedIndex="-1" >
<s:layout>
<s:TileLayout verticalGap="0" />
</s:layout>
</s:List>
<fx:script>
<![CDATA[
import mx.collections.ArrayCollection;
import spark.events.IndexChangeEvent;
[Bindable]
public var myItems = new ArrayCollection;
protected function startup():void {
// Here's where you'd build up your items if they
// need to be built dynamically.
}
protected function changeHandler(event:IndexChangeEvent):void
{
var currentIndx:int = event.currentTarget.selectedIndex;
var selectedItem:UIComponent = event.currentTarget.selectedItem as UIComponent;
// Do whatever you need to do on selection here
}
protected function canMicrophoneChange(event:IndexChangeEvent):void
{
var currentIndx:int = event.currentTarget.selectedIndex;
var selectedItem:UIComponent = event.currentTarget.selectedItem as UIComponent;
// This will cancel the select if the item was not enabled.
if (selectedItem.enabled == false) event.preventDefault();
}
]]>
</fx:script>