Let me try to explain this the best I can.
I have a component containing a data grid with the following data grid column
<mx:DataGridColumn dataField="email" headerText="Email Address">
<mx:itemRenderer>
<mx:Component>
<mx:VBox width="100%" horizontalScrollPolicy="off" verticalScrollPolicy="off" horizontalAlign="center">
<mx:TextInput id="tiEmailAddress"
width="95%"
text="{data.email}"
invalid="{data.isNotValidEmail(event);}"
valid="{data.isValidEmail(event);}"/>
<mx:EmailValidator id="validatorEmailAddress"
source="{tiEmailAddress}"
property="text"
required="true"
requiredFieldError="Email address is required."/>
</mx:VBox>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
Here's the guts of my value object:
[Bindable(event="contactChange")]
public class ContactVO extends EventDispatcher
{
private var _email:String = "default email";
public var selected:Boolean = false;
public var edited:Boolean = false;
public var isNew:Boolean = false;
public var isValid:Boolean = false;
public function ContactVO()
{ }
public function isNotValidEmail(e:Event):void
{
isValid = false;
email = e.target.text;
}
public function isValidEmail(e:Event):void
{
isValid = true;
email = e.target.text;
}
public function get email():String
{
return _email;
}
public function set email(value:String) : void
{
if (value != _email) {
_email = value;
edited = true;
}
dispatchEvent(new Event("contactChange", true));
}
}
Then back in the component, I have this which gets called at creationComplete
addEventListener("contactChange", processContactChange);
Through the Flex debugger, I can see the addEventListener statement called when the component is created, I can see the event fired from the value object when the validation is performed and the value changes, but processContactChange is never called, so I assume the event is never making it to my component.
Any idea what I've gotten wrong here? Thanks!
[SOLUTION]
The conversation with #Flextras below helped to deepen my understanding of this process and figure out where the disconnect was in my understanding. Basically, I changed the innards of my component's data column entry to the following:
<mx:TextInput id="tiEmailAddress"
width="95%"
text="{data.email}"
invalid="{data.isNotValidEmail(event);}"
valid="{data.isValidEmail(event);}"
creationComplete="{addListener(data)}"/>
<mx:Script>
<![CDATA[
private function addListener(data:Object):void
{
var eventDispatcher:EventDispatcher = data as EventDispatcher;
eventDispatcher.addEventListener("contactChange", outerDocument.processContactChange);
}
]]>
</mx:Script>
and removed the event listener from my creationComplete method
When you add the event listener you have to add it to the class that fires the event. In your code, you're adding it to the component that contains the DataGrid. Neither the itemRenderer, DataGrid, nor component containing the DataGrid fire the event.
IF the component containing the DataGrid has access to the ContactVO event, you can listen directly on that.
Otherwise, you can add an event listener in your itemRenderer.
If you absolutely need to execute code in the component containing the DataGrid, then the itemRenderer should listen on the Value Object for the event, then fire an event of it's own. Make sure that the 'itemRenderer' event bubbles; and it will move up the display hierarchy.
Related
I would like to programmatically change a selected item, in a tree or list, to the item currently "marked/focused" under the mouse pointer .
I'm working with an Flex Air standalone application.
I was thinking in the lines of:
myTree.selectedItem = EVENT.TARGET (where EVENT could be a mouseover/rightclick/rollOver event, and TARGET should be the node/item currently under the mouse pointer).
Is there a way of doing this (or in any other way)?
Ahh, and i want to do it without left clicking ;-)
Thank you in advance,
Sebastian
I found this interesting enough so I am asking if this is the easiest way to achieve this. First off, instead of the list, you need to add the rollOver-listener to the ItemRenderer, not to the list itself (as the event.target and event.currentTarget will just show your list).
So lets create a custom ItemRenderer and add a rollOver listener
<xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="true" height="20" rollOver="itemrenderer1_rollOverHandler(event)">
<fx:Script>
<![CDATA[
protected function itemrenderer1_rollOverHandler(event:MouseEvent):void
{
this.dispatchEvent(new CustomEvent(CustomEvent.SELECT_ITEM, data, true));
}
]]>
<s:Label id="label1" text="{data.label}"/>
</s:ItemRenderer>
You need to somehow get the value of the selected item (which is the data on the itemRenderer) so I created a CustomEvent-class just to do so.
package
{
import flash.events.Event;
public class CustomEvent extends Event
{
public var selectedItem:Object;
public static const SELECT_ITEM:String = "selectItem";
public function CustomEvent(type:String, selectedItem:Object, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
this.selectedItem = selectedItem;
}
}
}
then I added a eventListener to the main class and set the list.selectedItem property accordingly:
//for the main MXML initializer:
this.addEventListener(CustomEvent.SELECT_ITEM, rollOverChangeSelected);
//and the function:
protected function rollOverChangeSelected(ce:CustomEvent):void{
list.selectedItem = ce.selectedItem;
}
Another way: bindable variable
The list:
s:List id="list" allowMultipleSelection="true" selectionColor="red" rollOverColor="red" itemRenderer="customItemRenderer" selectedItem="{_rollOverSelectedItem}">
The variable and set / get methods:
[Bindable] public var _rollOverSelectedItem:Object;
public function get rollOverSelectedItem():Object
{
return _rollOverSelectedItem;
}
public function set rollOverSelectedItem(value:Object):void
{
_rollOverSelectedItem = value;
}
and the ItemRenderer's rollOver-method:
protected function itemrenderer1_rollOverHandler(event:MouseEvent):void
{
this.parentApplication.rollOverSelectedItem = data;
}
What is the best/proper way?
i have a component which gets the data selected by the radio button
<mx:itemRenderer >
<fx:Component id="radio">
<mx:RadioButton selected="false" useHandCursor="true" change="item_changeHandler(event)">
<fx:Script>
<![CDATA[
private var data_id:int;
private var data_name:String;
protected function item_changeHandler(event:Event):void
{
data_id=data.id;
data_name=data.name;
}
]]>
</fx:Script>
</mx:RadioButton>
</fx:Component>
</mx:itemRenderer>
here there are 2 variables data_id and data_name and i want to use this component variables in the main application which get called when i click a button so what to do how do i call the variables values into this function i tried this
protected function delete_clickHandler(event:MouseEvent):void
{
// TODO Auto-generated method stub
dispatchEvent( new Event( Event.CHANGE ) );
deleteTaxonomy(data.id, data.name);
}
If I understand you correctly, then I would dispatch an event when the id and name changes and catch it in the main application. E.g. you could create a CustomEvent class YouDataEvent.as with the variables id and name. I often use this with my own objects and then send the object with the event. Lets call your object containing the id and name YourObject
import flash.events.Event;
public class YourObjectEvent extends Event
{
public static const YOURDATA_UPDATED:String = "yourdataUpdated";
public var yourObj:YourObject
public function YourObjectEvent(type:String, yourObjInput:YourObject, bubbles:Boolean = true, cancelable:Boolean = false)
{
this.yourObj = yourObjInput;
super(type, bubbles, cancelable);
}
}
Then you can dispatch the event when the data changes e.g. in the *item_changeHandler* method like so:
dispatchEvent(new YourObjectEvent(YourObjectEvent.YOURDATA_UPDATED, yourObjVar));
And catch it at the main application with e.g.:
FlexGlobals.topLevelApplication.addEventListener(YourObjectEvent.YOURDATA_UPDATED, yourObjUpdatedHandler);
private function yourObjUpdatedHandler(event:YourObjectEvent): void {
//DO something
var id:Number = event.yourObj.id;
var name:String = event.yourObj.name
}
Hope this helps!
I have noticed an unexpected behaviour with binding in Flex, my code is as follow :
Application code
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" horizontalAlign="center" xmlns:Model="Model.*">
<mx:Script>
<![CDATA[
import Model.DataDummy;
]]>
</mx:Script>
<mx:HBox width="100%" horizontalGap="30">
<mx:Button id="buttonChange1" label="Change property value" click="myDummy._resetMyProperty();" />
<mx:Label id="labelRaw" text="{'My Property=' + myDummy.MyProperty}" opaqueBackground="#DDDDDD" />
<mx:Label id="labelFormatted" text="{'My Property formatted=' + myDummy.MyPropertyFormatted}" opaqueBackground="#DDDDDD" />
</mx:HBox>
<Model:MyDummy id="myDummy" />
<mx:DataGrid id="dataGrid"
width="100%" dataProvider="{DataDummy.Dummies}">
<mx:columns>
<mx:DataGridColumn dataField="MyProperty" headerText="My property" />
<mx:DataGridColumn dataField="MyPropertyFormatted" headerText="My property Formatted" />
</mx:columns>
</mx:DataGrid>
<mx:Button id="buttonChange2" click="{for each ( var d:MyDummy in DataDummy.Dummies ){d._resetMyProperty();}}" label="Change property value in DataGrid" />
</mx:Application>
Model.MyDummy class code
package Model
{
import flash.events.EventDispatcher;
import mx.formatters.NumberFormatter;
import mx.utils.StringUtil;
[Bindable]
public class MyDummy extends EventDispatcher
{
/*** Constructor ***/
public function MyDummy()
{
this._resetMyProperty();
}
/*** Properties ***/
private var _myProperty:Number;
public function get MyProperty():Number
{
return _myProperty;
}
public function set MyProperty(value:Number):void
{
if ( value !== _myProperty )
{
_myProperty = value;
//var event:Event = new Event("ID_Changed");
//this.dispatchEvent(event);
}
}
//[Bindable (event="ID_Changed", type="flash.events.Event")]
public function get MyPropertyFormatted():String
{
var idFormatted:String = "";
if ( ! isNaN(this.MyProperty) )
{
var formatter:NumberFormatter = new NumberFormatter();
formatter.precision = 2;
idFormatted = formatter.format(this.MyProperty);
}
else
idFormatted = MyProperty.toString();
return StringUtil.substitute( "{0} (My property has been formatted)", idFormatted );
}
/*** Methods ***/
public function _resetMyProperty():void
{
this.MyProperty = Math.round(Math.random() * 1000000000);
}
}
}
Model.DataDummy class code
package Model
{
import mx.collections.ArrayCollection;
public class DataDummy
{
private static var _dummies:ArrayCollection;
public static function get Dummies():ArrayCollection
{
if ( _dummies == null )
{
_dummies = new ArrayCollection();
_dummies.addItem(new MyDummy());
_dummies.addItem(new MyDummy());
}
return _dummies;
}
}
}
The behaviour is as follow :
When I click on buttonChange1, _resetMyProperty is called on the instance myDummy.
The result is that the label "labelRaw" has its text changed and the label "labelFormatted" does not have its text changed. This does happen because MyPropertyFormatted is a readonly property and that readonly properties are binded only at the initialisation of the application and not afterwards, according to Flex documentation. With this, I agree.
When I click on buttonChange2, resetMyProperty method is called on every MyDummy element of the ArrayCollection Model.DataDummy.Dummies (this static property is binded to the DataGrid).
The result is that both columns of the DataGrid have their values changed, despite the fact that the DataGrid's second column is linked to the same readonly property MyPropertyFormatted of the MyDummy objects. I find this inconsistent with the previous behaviour I described.
My point is that :
1. On one hand, because I'm binding my controls to a single instance of an certain object, binding won't trigger on his readonly properties.
2. On the other hand, when I'm binding a control on a collection of the same certain objects, binding will trigger on every properties (readonly or not).
If I want binding to be triggered on readonly properties in point 1, I have to dispatch an event and precise on the readonly properties' MetaTag that their binding will be triggered according to this event (as show the commentaries in the code of the class Model.MyDummy class).
Why is this behaviour different ? I would like to precisely understand what an ArrayCollection instance's binding does that a single instance's binding does not.
Thank you for your help.
I suppose the right code is something like the following.
First, our model.MyDummy class:
package model
{
import flash.events.EventDispatcher;
import mx.events.PropertyChangeEvent;
import mx.formatters.NumberFormatter;
import mx.utils.StringUtil;
public class MyDummy extends EventDispatcher
{
//------------------------------------------------------------------------------
//
// Constructor
//
//------------------------------------------------------------------------------
public function MyDummy()
{
resetMyProperty();
}
//------------------------------------------------------------------------------
//
// Properties
//
//------------------------------------------------------------------------------
//--------------------------------------
// myProperty
//--------------------------------------
private var _myProperty:Number;
[Bindable(event="propertyChange")]
public function get myProperty():Number
{
return _myProperty;
}
public function set myProperty(value:Number):void
{
if (_myProperty == value)
return;
var oldPropertyValue:Number = _myProperty;
var oldFormatted:String = myPropertyFormatted;
_myProperty = value;
dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "myProperty", oldPropertyValue, value));
dispatchEvent(PropertyChangeEvent.
createUpdateEvent(this, "myPropertyFormatted", oldFormatted, myPropertyFormatted));
}
[Bindable(event="propertyChange")]
public function get myPropertyFormatted():String
{
var idFormatted:String = "";
if (!isNaN(myProperty))
{
var formatter:NumberFormatter = new NumberFormatter();
formatter.precision = 2;
idFormatted = formatter.format(myProperty);
}
else
idFormatted = myProperty.toString();
return StringUtil.substitute("{0} (My property has been formatted)", idFormatted);
}
//------------------------------------------------------------------------------
//
// Methods
//
//------------------------------------------------------------------------------
public function resetMyProperty():void
{
myProperty = Math.round(Math.random() * 1000000000);
}
}
}
We're firing propertyChange event to have possibility to fire collectionChangeEvent from our ArrayCollection (it listens propertyChange event automatically).
Then our model.DataDummy class:
package model
{
import mx.collections.ArrayCollection;
import mx.events.CollectionEvent;
public class DataDummy
{
//------------------------------------------------------------------------------
//
// Constructor
//
//------------------------------------------------------------------------------
public function DataDummy()
{
dummies = new ArrayCollection();
dummies.addItem(new MyDummy());
dummies.addItem(new MyDummy());
}
//------------------------------------------------------------------------------
//
// Variables
//
//------------------------------------------------------------------------------
[Bindable]
public var dummies:ArrayCollection;
}
}
We don't use statics to have advantage of data binding with [Bindable] metatag.
And finally our main class with minimal changes:
<mx:Application horizontalAlign="center" layout="vertical" xmlns:model="model.*" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
//------------------------------------------------------------------------------
//
// Event Handlers
//
//------------------------------------------------------------------------------
protected function buttonChange2_clickHandler(event:MouseEvent):void
{
for each (var d:MyDummy in dataProvider.dummies)
{
d.resetMyProperty();
}
}
]]>
</mx:Script>
<mx:HBox horizontalGap="30" width="100%">
<mx:Button click="myDummy.resetMyProperty();" id="buttonChange1" label="Change property value" />
<mx:Label id="labelRaw" opaqueBackground="#DDDDDD" text="{'My Property=' + myDummy.myProperty}" />
<mx:Label id="labelFormatted" opaqueBackground="#DDDDDD"
text="{'My Property formatted=' + myDummy.myPropertyFormatted}" />
</mx:HBox>
<model:MyDummy id="myDummy" />
<model:DataDummy id="dataProvider" />
<mx:DataGrid dataProvider="{dataProvider.dummies}" id="dataGrid" width="100%">
<mx:columns>
<mx:DataGridColumn dataField="myProperty" headerText="My property" />
<mx:DataGridColumn dataField="myPropertyFormatted" headerText="My property Formatted" />
</mx:columns>
</mx:DataGrid>
<mx:Button click="buttonChange2_clickHandler(event)" id="buttonChange2" label="Change property value in DataGrid" />
</mx:Application>
As you can see all the bindings works as expected.
P.S. [Bindable(event="propertyChange")] is an equivalent of simple [Bindable] but this way you can avoid compiler warnings on myPropertyFormatted getter. Actually, using simple [Bindable] form causes mxmlc compiler to generate dispatchEvent code by itself. And you can use pointing a particular event in [Bindable] tag to have more control. For example in our case we can fire event for myPropertyFormatted.
P.P.S. I've changed your C#-like naming conventions to reflect actual ActionScript/MXML ones.
Can't seem to bind to data from within a custom component. I've tried BindUtilis and {} but can't seem to fathom it out. Here's what I've got:
I have a class DataModel which has been made bindable
Within Mainn.mxml I have two components: DataGrid (used for testing) & CustomComponent (which extends Canvas)
When the data within DataModel.somelist is updated the DataGrid reflects the changes but the CustomComponent doesn't appear to.
I was expecting to see the trace (CustomComponent.dataProvider) fired whenever this._dataModel.itemList is changed. What am I doing wrong?
Main.mxml looks something like this:
<mx:Script>
<![CDATA[
import model.DataModel;
[Bindable]
private var _dataModel:DataModel = DataModel.getInstance();
]]>
</mx:Script>
<mx:VBox width="100%" height="100%">
<views:ItemDisplayList width="100%" height="300" id="idl" >
<views:dataProvider>
{this._dataModel.itemList}
</views:dataProvider>
</views:ItemDisplayList>
<mx:DataGrid id="dg" width="100%" height="300" >
<mx:dataProvider>
{this._dataModel.itemList}
</mx:dataProvider>
</mx:DataGrid>
</mx:VBox>
The CustomComponent has this AS class:
package code{
import model.DataModel;
import mx.containers.Canvas;
public class CustomComponent extends Canvas{
[Bindable]
private var _dataModel:DataModel = DataModel.getInstance();
private var _dataProvider:ArrayCollection ;
public function CustomComponent(){
super();
_dataProvider = new ArrayCollection();
trace("CustomComponent start");
}
public function get dataProvider() : ArrayCollection {
trace("get dataProvider");
return _dataProvider;
}
public function set dataProvider(value: ArrayCollection) : void {
trace("set dataProvider");
this._dataProvider = value;
invalidateProperties();
invalidateSize();
invalidateDisplayList();
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
...
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number ) : void{
trace("updateDisplayList");
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
}
}
Are you resetting the dataProvider in your custom component, or by updating you mean adding/removing an item from dataModel.itemList?
If you're adding an item to dataModel.itemList, then your custom component wont update but the DataGrid will. This is because in the DataGrid (maybe it's in the core ListBase component in Flex?), when you set dataProvider, it adds an event listener for CollectionEvent.COLLECTION_CHANGE. When it hears that (when something in your ArrayCollection/IList changes, from remove/add/refresh/etc), it runs a few methods to update the DataGrid's itemRenderers. You'll have to code that manually if you create your own dataProvider property.
If you set the dataProvider explicitly on your custom component (custom.dataProvider = myArrayCollection), it will update. But that's not the most efficient thing to do. I recommend extending one of the List classes that has already implemented a dataProvider property, it's a lot to tackle.
Check out the source for the mx ListBase's dataProvider method to see what they did.
Hope that helps,
Lance
I have a custom component made up of a selectable control (radio button)
and a text input. I want to perform some logic in response to the
change events from both of those controls, but after that I want
anything that is registered on the composite component's change handler
to have a change to handle the events as well. The problem is, when I
re-dispatch the events the event target has changed to my custom
component, losing the originating event's target.
Here's my custom component:
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" label="{listItem.#text}" data="{listItem.#level.toString()}">
<mx:Script>
<![CDATA[
import mx.controls.RadioButtonGroup;
[Bindable]
public var selected: Boolean;
[Bindable]
public var text: String;
[Bindable]
public var listItem: XML;
[Bindable]
public var group: RadioButtonGroup;
private function onSelectionChange(event: Event): void {
selected = event.target.selected;
dispatchEvent(event);
}
private function onTextChange(event: Event): void {
text = event.target.text;
dispatchEvent(event);
}
]]>
</mx:Script>
<mx:RadioButton group="{group}" label="{label}" selected="{selected}" change="onSelectionChange(event)"/>
<mx:TextInput width="100%"
maxChars="{listItem.specify.#entryLength}"
enabled="{selected}"
visible="{listItem.hasOwnProperty('specify')}"
includeInLayout="{visible}"
change="onTextChange(event)"/>
</mx:HBox>
In the event handler that receives change events from this component, I
see that event.target is an instance of SpecifyRadioButton, not the
TextInput or RadioButton, as I'd expect. How should I propagate the
event to get what I want here?
Getting event [Event type="change" bubbles=false cancelable=false eventPhase=2]
from question0.tabSurvey.questionForm.questionContainer.Single94.VBox95.SpecifyRadioButton111
Instead of re-dispatching the original event, create a new event and pass the original event as a origEvent property. The new event which the SpecifyRadioButton dispatches can either be a custom event class which extends Event, or you can be lazy and just use mx.events.DynamicEvent.
Example:
import mx.events.DynamicEvent;
private function onSelectionChange(event: Event): void {
selected = event.target.selected;
var newEvent:DynamicEvent = new DynamicEvent(Event.CHANGE);
newEvent.origEvent = event;
dispatchEvent(newEvent);
}
Then, in your handlers for the SpecifyRadioButton.change event, reference the event.origEvent property.
It makes sense that the target of the event would be SpecifyRadioButton, because that's what is dispatching the event.
The TextInput component's "change" event is set to not bubble up, meaning that it can be listened to by listeners in the same component as it, but nowhere else. If you want the change event to bubble up, you're going to have to extend the TextInput class (or use something nifty like Mate).
package {
import flash.events.Event;
import mx.controls.TextInput;
public class CarbonatedTextInput extends TextInput {
public function CarbonatedTextInput () {
super();
addEventListener(Event.CHANGE, forceBubbles);
}
/*
We have to remove the event listener before we
dispatch the new event (with bubbles!) because
otherwise our listener in the constructor would
catch us and put us in an endless loop...
resulting in a **STACK OVERFLOW**
*/
protected function forceBubbles(e:Event):void {
removeEventListener(Event.CHANGE, forceBubbles);
dispatchEvent(new Event(Event.CHANGE, true));
addEventListener(Event.CHANGE, forceBubbles);
}
}
}