Add "New item Label" to Spark ComboBox - apache-flex

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.

Related

Add feature to spark combobox to allow better binding

What I try todo...
I am trying to implement a more advanced dropDownList component. I added a new property selectedValue which can basically take any value. At the moment the component only tries to match the selectedValue with "id" of dataprovider items. When i debug the sample it looks fine, the selectedIndex gets set based on selectedValue.
Problem...
The selectedItem does not show up in dropDownList after startUp, it only appears if i click the dropdown button. Means it is selected but not represented in view.
After application startup...
click to see image
When i click the arrow button on custom component...
click to see image
And here is the code...
MAIN.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:components="ch.fa.ed.ui.components.*"
width="100%" height="100%" >
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private var _dpList : ArrayCollection;
public function get dpList() : ArrayCollection {
if (_dpList == null) {
_dpList = new ArrayCollection();
// create items
var person1 : Object = new Object();
person1.id = 10;
person1.name = "Bush";
var person2 : Object = new Object();
person2.id = 12;
person2.name = "Obama";
var person3 : Object = new Object();
person3.id = 30;
person3.name = "Clinton";
_dpList.addItem(person1);
_dpList.addItem(person2);
_dpList.addItem(person3);
}
return _dpList;
}
public function set dpList(dpList : ArrayCollection) : void {
_dpList = dpList;
}
]]>
</fx:Script>
<s:VGroup>
<s:DropDownList id="ddList" dataProvider="{dpList}" labelField="name" selectedIndex="2"/>
<components:EdDropDownList id="ddList2" dataProvider="{dpList}" labelField="name" selectedValue="30"/>
</s:VGroup>
</s:Group>
EdDrowDownList.as
package ch.fa.ed.ui.components {
import mx.collections.IList;
import spark.components.DropDownList;
/**
* #author Michael Wittwer <michael.wittwer#falution.ch>
* #date 20.09.2012
*/
public class EdDropDownList extends DropDownList {
/* ******************************************************************************************************
* fields *
****************************************************************************************************** */
private var _selectedValue : *;
/* ******************************************************************************************************
* member variables *
****************************************************************************************************** */
private var selectedValueChanged : Boolean;
private var dataProviderChanged : Boolean;
public function EdDropDownList() {
super();
}
/*
* overriding the commitProperties method to make sure the selectedValue field gets represented in ui
*/
override protected function commitProperties() : void {
super.commitProperties();
if (selectedValueChanged && dataProviderChanged) {
// find the item mathing selectedValue and set index
if (selectedValue != null && dataProvider != null) {
for (var i : int = 0; i < dataProvider.length; i++) {
var item : * = dataProvider.getItemAt(i);
if (item.id == selectedValue) {
selectedIndex = i;
break;
}
}
}
dataProviderChanged = false;
selectedValueChanged = false;
}
if (selectedValueChanged) {
selectedValueChanged = false;
// find the item mathing selectedValue and set index
if (selectedValue != null && dataProvider != null) {
for (var i : int = 0; i < dataProvider.length; i++) {
var item : * = dataProvider.getItemAt(i);
if (item.id == selectedValue) {
selectedIndex = i;
break;
}
}
}
}
if (dataProviderChanged) {
dataProviderChanged = false;
// find the item mathing selectedValue and set index
if (selectedValue != null && dataProvider != null) {
for (var i : int = 0; i < dataProvider.length; i++) {
var item : * = dataProvider.getItemAt(i);
if (item.id == selectedValue) {
selectedIndex = i;
break;
}
}
}
}
}
/* ******************************************************************************************************
* getter and setter methods *
****************************************************************************************************** */
[Bindable]
public function get selectedValue() : * {
return _selectedValue;
}
public function set selectedValue(selectedValue : *) : void {
_selectedValue = selectedValue;
selectedValueChanged = true;
invalidateProperties();
}
[Bindable]
override public function set dataProvider(value : IList) : void {
super.dataProvider = value;
dataProviderChanged = true;
invalidateProperties();
}
}
Any ideas how to fix this?
The finally solution is pretty easy. Just put the super.commitProperties(); at the end of the overriding commitProperties() method.
It makes absolutely sence. Because we are manipulating a property (selectedIndex) in our own commitProperties() which was already handled in the super method. So the updates on selectedIndex will not be visible until commitProperties() gets called for the next time.
so commitProperties() looks like this for a working component:
override protected function commitProperties() : void {
if (selectedValueChanged && dataProviderChanged) {
// find the item mathing selectedValue and set index
updateSelectedIndex();
dataProviderChanged = false;
selectedValueChanged = false;
}
if (selectedValueChanged) {
selectedValueChanged = false;
// find the item mathing selectedValue and set index
updateSelectedIndex();
}
if (dataProviderChanged) {
dataProviderChanged = false;
// find the item mathing selectedValue and set index
updateSelectedIndex();
}
super.commitProperties();
}
private function updateSelectedIndex() : void {
if (selectedValue != null && dataProvider != null) {
for (var i : int = 0; i < dataProvider.length; i++) {
var item : * = dataProvider.getItemAt(i);
if (item.id == selectedValue) {
selectedIndex = i;
break;
}
}
}
}
Hope this helps.
The <s:ComboBox> has several bugs including issues which affect binding (as noted in the topic above) and custom text entry. As a work-around, there is an ActionScript, Flex, spark only combo box which fixes many of these issues, it is available as open source.

Flex 4 - Need help with a custom legend using legendItemClass

I'm creating a custom legend using legendItemClass to highlight each item when the user rolls over, remove it when they roll off and highlight it a little differently when they click. That all works just fine but I also want the pie wedge to explode out when the user click. I have the piece of code but it requires a LegendMouseEvent. I'm also thinking I may be able to re-write the code if I could get the displayName but I'm a loss with that as well. Here's what I have:
Legend:
<mx:Legend id="legend" width="100%" direction="vertical"
labelPlacement="right" markerHeight="10" markerWidth="10"
legendItemClass="CustomPieLegendItem"
itemClick="explode(event)"
color="{_color}" />
Explode:
public function explode(event:LegendMouseEvent) : void {
var len : Number = PieSeries(event.item.source).legendData.length;
var index : Number = 0;
var arr:Array = new Array(len);
if(over){
event.item.alpha = 0.70;
} else {
event.item.alpha = 0.70;
for(var i : Number = 0; i < len ; i++){
if(event.item.label == event.item.source.legendData[i].label){
index = i;
arr[i] = 0.1;
}else {
arr[i] = 0;
}
}
PieSeries(event.item.element).perWedgeExplodeRadius = arr;
}
}
Class:
package
{
import custom.charts;
import flash.display.Sprite;
import flash.events.MouseEvent;
import ilog.core.ilog_internal;
import mx.charts.LegendItem;
import mx.charts.events.LegendMouseEvent;
import mx.charts.series.PieSeries;
import mx.charts.series.items.PieSeriesItem;
public class CustomPieLegendItem extends LegendItem {
public function CustomPieLegendItem(){
super();
}
private var mouseSprite:Sprite;
private var state:int=0;
private static var IDLE_STATE:int=0;
private static var OVER_STATE:int=1;
private static var SELECTED_STATE:int=2;
public function get selected():Boolean{
return (state == OVER_STATE || state == SELECTED_STATE);
}
public function set selected(value:Boolean):void {
if (value)
state=SELECTED_STATE;
else
state=IDLE_STATE;
invalidateDisplayList();
}
private function clickHandler(e:MouseEvent):void {
charts(document).resetSeries();
state=SELECTED_STATE;
// returns the item USED for the displayName, not the actual name
trace((element as PieSeries).displayName);
// does not work because I need to pass a LegendMouseEvent
charts(document).explode("NEEDS LegendMouseEvent HERE");
invalidateDisplayList();
}
private function overHandler(e:MouseEvent):void {
if (state != SELECTED_STATE){
state=OVER_STATE;
invalidateDisplayList();
}
}
private function outHandler(e:MouseEvent):void {
if (state != SELECTED_STATE){
state=IDLE_STATE;
invalidateDisplayList();
}
}
override protected function createChildren():void {
super.createChildren();
mouseSprite=new Sprite();
addChild(mouseSprite);
mouseSprite.addEventListener(MouseEvent.MOUSE_OVER, overHandler);
mouseSprite.addEventListener(MouseEvent.MOUSE_OUT, outHandler);
mouseSprite.addEventListener(MouseEvent.MOUSE_DOWN, clickHandler);
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
super.updateDisplayList(unscaledWidth, unscaledHeight);
mouseSprite.graphics.clear();
mouseSprite.graphics.beginFill(0, 0);
mouseSprite.graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.clear();
if (state == OVER_STATE || state == SELECTED_STATE)
{
graphics.beginFill(0xCCCCCC, 0.2);
if (state == SELECTED_STATE)
graphics.lineStyle(1, 0xCCCCCC);
graphics.drawRect(-2, 0, width+2, height);
}
}
}
}
Why don't you just create a new LegendMouseEvent using below code new LegendMouseEvent(LegendMouseEvent.ITEM_CLICK, e, this) and pass it to explode.
you can use nay types from LegendMouseEvent class , e - MouseEvent passed to clickhandler

Manually dispatch a collection change event

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

Flex: Updating a Tree control

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??

Flex: How do you validate 2 password fields to make sure they match?

I want to use a validator to ensure 2 password fields match in Flex. I want the validator to highlight form fields like a normal flex validation control. Thanks.
enter code hereI created my own custom validator (mostly copied from date validator):
package validators
{
import mx.validators.ValidationResult;
import mx.validators.Validator;
public class PasswordValidator extends Validator
{
// Define Array for the return value of doValidation().
private var results:Array;
public function PasswordValidator()
{
super();
}
public var confirmationSource: Object;
public var confirmationProperty: String;
// Define the doValidation() method.
override protected function doValidation(value:Object):Array {
// Call base class doValidation().
var results:Array = super.doValidation(value.password);
if (value.password != value.confirmation) {
results.push(new ValidationResult(true, null, "Mismatch",
"Password Dosen't match Retype!"));
}
return results;
}
/**
* #private
* Grabs the data for the confirmation password from its different sources
* if its there and bundles it to be processed by the doValidation routine.
*/
override protected function getValueFromSource():Object
{
var value:Object = {};
value.password = super.getValueFromSource();
if (confirmationSource && confirmationProperty)
{
value.confirmation = confirmationSource[confirmationProperty];
}
return value;
}
}
}
the example mxml for using:
<validators:PasswordValidator id="pwvPasswords" required="true" source="{txtPassword}" property="text" confirmationSource="{txtPasswordConfirm}" confirmationProperty="text" trigger="{btnStep2Finish}" />
It's pretty basic, but it's mostly what I need. It only highlights the password box though, would like to get it to highlight both.
Here's a better custom validator that's more universal, clean, and works well with the 'required' field.
import mx.validators.ValidationResult;
import mx.validators.Validator;
public class MatchValidator extends Validator{
private var _matchSource: Object = null;
private var _matchProperty: String = null;
private var _noMatchError: String = "Fields did not match";
[Inspectable(category="General", defaultValue="Fields did not match")]
public function set noMatchError( argError:String):void{
_noMatchError = argError;
}
public function get noMatchError():String{
return _noMatchError;
}
[Inspectable(category="General", defaultValue="null")]
public function set matchSource( argObject:Object):void{
_matchSource = argObject;
}
public function get matchSource():Object{
return _matchSource;
}
[Inspectable(category="General", defaultValue="null")]
public function set matchProperty( argProperty:String):void{
_matchProperty = argProperty;
}
public function get matchProperty():String{
return _matchProperty;
}
override protected function doValidation(value:Object):Array {
// Call base class doValidation().
var results:Array = super.doValidation(value.ours);
var val:String = value.ours ? String(value.ours) : "";
if (results.length > 0 || ((val.length == 0) && !required)){
return results;
}else{
if(val != value.toMatch){
results.length = 0;
results.push( new ValidationResult(true,null,"mismatch",_noMatchError));
return results;
}else{
return results;
}
}
}
override protected function getValueFromSource():Object {
var value:Object = {};
value.ours = super.getValueFromSource();
if (_matchSource && _matchProperty){
value.toMatch = _matchSource[_matchProperty];
}else{
value.toMatch = null;
}
return value;
}
}
Here's an example:
<components:MatchValidator source="{passwordCheckField}" property="text" matchSource="{passwordField}" matchProperty="text"
valid="passwordsMatch = true" invalid="passwordsMatch = false" noMatchError="Passwords do not match"/>
I've done this in a different way using custom validation rules.
Check it out:
http://martypitt.wordpress.com/2009/08/26/rule-based-asynchronous-validation-in-flex-forms/
It shows how to do async validation (eg., checking a username is available on the server), and other validation scenario's that don't fit neatly into flex's out-of-the-box framework, including two passwords match.
Eg:
<mx:Button label="Register" enabled="{ validationRules.isValid }" />
<validation:ValidationRuleCollection id="validationRules">
<validation:UsernameIsUniqueValidationRule username="{ txtUsername.text }" targetComponent="{ txtUsername }" />
<validation:EmailIsUniqueValidationRule email="{ txtEmailAddress.text }" targetComponent="{ txtEmailAddress }" />
<validation:PasswordsMustMatchValidationRule password1="{ txtPassword.text }" password2="{ txtPasswordConfirm.text }" targetComponent="{ txtPasswordConfirm }" />
<mx:StringValidator required="true" source="{ txtUsername }" property="text" requiredFieldError="{ ResourceManager.getInstance().getString( ResourceBundles.ERROR_MESSAGES , 'REQUIRED_FIELD_ERROR' )}" />
</validation:ValidationRuleCollection>
I expanded on Daniel's solution, adding the ability to trigger off of the matched source as well.
import flash.events.Event;
import mx.validators.ValidationResult;
import mx.validators.Validator;
public class MatchValidator extends Validator
{
private var _matchSource: Object = null;
private var _matchProperty: String = null;
private var _noMatchError: String = "Fields did not match";
[Inspectable(category="General", defaultValue="Fields did not match")]
public function set noMatchError( argError:String):void{
_noMatchError = argError;
}
public function get noMatchError():String{
return _noMatchError;
}
[Inspectable(category="General", defaultValue="null")]
public function set matchSource( argObject:Object):void{
removeTriggerHandler();
_matchSource = argObject;
addTriggerHandler();
}
public function get matchSource():Object{
return _matchSource;
}
[Inspectable(category="General", defaultValue="null")]
public function set matchProperty( argProperty:String):void{
_matchProperty = argProperty;
}
public function get matchProperty():String{
return _matchProperty;
}
override protected function doValidation(value:Object):Array {
// Call base class doValidation().
var results:Array = super.doValidation(value.ours);
var val:String = value.ours ? String(value.ours) : "";
if (results.length > 0 || ((val.length == 0) && !required)){
return results;
}else{
if(val != value.toMatch){
results.length = 0;
results.push( new ValidationResult(true,null,"mismatch",_noMatchError));
return results;
}else{
return results;
}
}
}
override protected function getValueFromSource():Object {
var value:Object = {};
value.ours = super.getValueFromSource();
if (_matchSource && _matchProperty){
value.toMatch = _matchSource[_matchProperty];
}else{
value.toMatch = null;
}
return value;
}
override public function set triggerEvent(value:String):void
{
removeTriggerHandler();
super.triggerEvent = value;
addTriggerHandler();
}
private function addTriggerHandler():void
{
if (_matchSource)
_matchSource.addEventListener(triggerEvent,triggerHandler);
}
private function removeTriggerHandler():void
{
if (_matchSource)
_matchSource.removeEventListener(triggerEvent,triggerHandler);
}
private function triggerHandler(event:Event):void
{
validate();
}
}
Unless you have to use a validator, you can simply attach a change event handler and set errorString property of the password field when the passwords don't match. It gives the same highlighting as done by validator.

Resources