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.
Related
I am using a component to display the popup and using an event listener to get popover properties and remove the popup in the Parent. The poup var, however, in the listeners popup var is nul so it throws an error.
Any suggestions would be greatly appreciated.
John
Here is my EditStudentLogInForm.mxml component..
<?xml version="1.0"?>
<!-- containers\layouts\myComponents\MyLoginForm.mxml -->
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="handleCreationComplete();">
<mx:Script>
<![CDATA[
import mx.managers.PopUpManager;
[Bindable] public var studentLoginEmail:String;
]]>
</mx:Script>
<mx:Form width="333">
<mx:FormItem label="Email">
<mx:TextInput id="username" width="207"/>
</mx:FormItem>
<mx:FormItem label="Password">
<mx:TextInput id="password"
width="205"/>
</mx:FormItem>
</mx:Form>
<mx:HBox>
<mx:Button id="okButton" label="OK"/>
<mx:Button id="cancelButton" label="Cancel" />
</mx:HBox>
</mx:TitleWindow>
Here is the Parent...
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:s="library://ns.adobe.com/flex/spark">
<mx:Script>
<![CDATA[
import flash.events.Event;
import mx.managers.PopUpManager;
import mx.core.IFlexDisplayObject;
import EditStudentLogInForm;
import mx.containers.TitleWindow;
public var helpWindow:EditStudentLogInForm;
public function showLogin():void {
// Create the TitleWindow container.
var helpWindow:EditStudentLogInForm = EditStudentLogInForm(
PopUpManager.createPopUp(this, EditStudentLogInForm, true));
helpWindow.username.text = "johnbdh#myserver.com";
helpWindow["cancelButton"].addEventListener("click", removeMe);
helpWindow["okButton"].addEventListener("click", submitData);
}
// OK button click event listener.
private function submitData(event:Event):void {
testText.text = helpWindow.username.text;
//*********helpWindow is nul*******
removeMe(event);
}
// Cancel button click event listener.
private function removeMe(event:Event):void {
PopUpManager.removePopUp(helpWindow);
}
]]>
</mx:Script>
</mx:Application>
When you do
public function showLogin():void {
var helpWindow:EditStudentLogInForm = ...
}
you're declaring and instantiating a new variable helpWindow inside the scope of the showLogin method. This means that the instance you assigned to this locally scoped variable can not be accessed outside the showLogin method.
You did declare another variable helpWindow on the class scope (your class being the main application in this case), but you're never assigning any instance to it (since you're assigning this popup instance to the helpWindow variable that lives only in showLogin.
Hence when you try to access this variable in another method, it's value is null.
The solution is simple enough: just assign the popup instance to the class-scoped variable:
public function showLogin():void {
helpWindow = EditStudentLogInForm(
PopUpManager.createPopUp(this, EditStudentLogInForm, true)
);
...
}
On a side note: if you have a variable of the same name on the class and inside a method, the most locally scoped one always takes precedence:
public var s:String = 'class';
public function myMethod():void {
var s:String = 'method';
trace(s); // prints method
trace(this.s); // prints class
}
public function myOtherMethod():void {
trace(s); // prints class
trace(this.s); // prints class
}
I'm doing a project using Java + Flex. I created Java class and used Flex remote object to invoke the method. When I write all the code in an mxml, it runs well. But when I wrap the script in an as file, there's something curious. I need to click twice on Flex button to get the result which returned by the remote object. I think there's something wrong with my as file.
Below is my MXML:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="500" minHeight="600">
<fx:Declarations>
<mx:RemoteObject id="Control" destination="Control" showBusyCursor="true" />
</fx:Declarations>
<fx:Script>
<![CDATA[
import com.wntime.ControlUtil;
import mx.rpc.events.ResultEvent;
private var resultOfCmd:String;
private var cmdStr:String;
private var ct:ControlUtil = new ControlUtil();
/* invoke as method */
private function test():void
{
cmdStr = cmdTxt.text;
resultOfCmd = ct.exec(cmdStr);
cmdConsole.text = resultOfCmd;
}
/* */
private function exec():void{
cmdStr = cmdTxt.text;
Control.execCmd(cmdStr);
Control.addEventListener(ResultEvent.RESULT,execCmd_clickHandler);
}
private function execCmd_clickHandler(event:ResultEvent):void
{
cmdConsole.text = event.result.toString();
}
private function clearCmdConsole():void
{
cmdConsole.text = "";
}
]]>
</fx:Script>
<s:Panel id="CmdPanel" x="70" y="50" width="501" height="350" title="Command Execute Panel">
<s:RichText x="20" y="33" fontSize="14" text="Cmd:"/>
<s:TextInput id="cmdTxt" x="60" y="30" width="239"/>
<s:Button id="execCmd" x="312" y="30" width="68" label="execute" click="exec()"/>
<s:Button x="400" y="30" label="CmdTest" click="test()"/>
<s:TextArea id="cmdConsole" x="20" y="85" width="450" editable="false"/>
<s:Button x="400" y="250" label="clear" click="clearCmdConsole()"/>
</s:Panel>
</s:Application>
Here is the as file which named ControlUtil:
package com.wntime{
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;
public class ControlUtil
{
private var cmd:String = null;
private var result:String = null;
private var roControl:RemoteObject = new RemoteObject();
public function ControlUtil()
{
roControl.destination = "Control";
}
public function exec(_cmd:String):String{
this.cmd = _cmd;
roControl.execCmd(cmd);
roControl.addEventListener(FaultEvent.FAULT, execCmd);
roControl.addEventListener(ResultEvent.RESULT, execCmd);
return result;
}
public function execCmd(event:ResultEvent):void
{
setResult(event.result.toString());
}
public function setResult(_result:String):void
{
this.result = _result;
}
}
}
If I click the execute button. The result will show in the console(the textarea) directly.
But I need to click twice on CmdTest button to get the result to show in the console.
Give me a hand plz.Thanks in advance.
This is just a wild guess, but I think the method you invoke at the Java side returns faster than you add your listeners, hence no event handler is called. The second time all listeners are in place and your call succeeds. Try adding your listeners before you invoke remote method.
Your code has various errors/problem in my opinion, I would do something like that:
package com.wntime{
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;
public class ControlUtil
{
private var cmd:String = null;
private var result:String = null;
private var roControl:RemoteObject = new RemoteObject();
// the callBack function is the function that is called when the
// remoteobject successfully or not complete the request...
// you can set as parameters anything you want...
private var callBack:Function = null;
public function ControlUtil()
{
roControl.destination = "Control";
}
public function exec(callBack:Function, _cmd:String):void{
this.cmd = _cmd;
this.callBack = callBack;
roControl.addEventListener(FaultEvent.FAULT, errorCmd);
roControl.addEventListener(ResultEvent.RESULT, execCmd);
roControl.execCmd(cmd);
}
private function execCmd(event:ResultEvent):void
{
callBack(true,event.result.toString());
}
private function errorCmd(event:FaultEvent):void
{
callBack(false, event.error); // call the callBack function passing the value you need
}
}
}
the callBack function is something like that:
private function name(b:Boolean, s:String = null){....}
* EDIT *
from your main code you invoke the exec command...
// function invoked when the button is clicked!
private function buttonClick():void
{
var tmp:ControlUtil = new ControlUtil();
//exec(callBack:Function, _cmd:String)
//you pass the function as a reference so when the async request is terminated the function is invoked and you can parse the result....
tmp.exec(getResult, "cmqString");
}
// callBack function for the method ControlUtil.exec
private function getResult(b:Boolean, result:String = ""):void
{
if (b)
{
// the call returned correctly and the result variable contains the value.
}
else
{
// the call failed and the result variable contains the error
}
}
Both the boolean and the result value are returned because I specified it in the ControlUtil when i used callBack(true/false, result/error)
You can create the function as you prefer...
I defined a simple event class:
public class NewMoveEvent extends Event
{
public function NewMoveEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=true)
{
super(type, bubbles, cancelable);
}
}
}
Then in custom mxml component, I defined a button which triggers it:
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:ns1="ui.*" layout="absolute" width="682" height="412" title="Board">
<mx:Metadata>
[Event(name="newformevent", type="events.NewMoveEvent")]
</mx:Metadata>
<mx:Script>
<![CDATA[
import events.NewMoveEvent;
import config.Config;
private function addNewUIComponent(event:Event):void
{
var e:NewMoveEvent = new NewMoveEvent("newformevent");
dispatchEvent(e);
}
]]>
</mx:Script>
<ns1:ChessBoard x="8" y="9" width="350" height="350" backgroundColor="0x99CCCC" moveId="0" name="chessboard" themeColor="#FFFFFF"/>
<mx:Button id="next" x="507" y="127" label="Next" click="addNewUIComponent(event)"/>
<ns1:PieceContainer x="363" y="10" width="292" height="51" items="{Config.piecesWhite}" id="white"/>
<ns1:PieceContainer x="362" y="69" width="292" height="51" items="{Config.piecesBlack}" id = "black"/>
<ns1:PasteBin x="363" y="306" width="292" height="53" backgroundColor="0x99CCCC" id="paste"/>
<mx:Button x="445" y="127" label="Save" name="save" enabled="false"/>
No from the main application file I want to set the event handler, to this event.
I can easily do it from mxml
e.g.
But cant do it in actionscript (e.g. this code don't work):
private function addNewUIContainer(event:Event):void
{
var newBoard:UIContainer = new UIContainer();
newBoard.addEventListener(NewMoveEvent.NEWFORMEVENT, addNewUIContainer);
}
Compiler gives me an error. Don't understand why.
ERROR
Access of possibly undefined property NEWFORMEVENT through a reference with static type Class.
And yes, UIContainer is mxml class
The function addNewUiContainer is defined in main file (project.mxml)
It doesn't look like you have defined the public static const NEWFORMEVENT:String = "newformevent";
public class NewMoveEvent extends Event
{
public static const NEWFORMEVENT:String = "newformevent";
public function NewMoveEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=true)
{
super(type, bubbles, cancelable);
}
}
Was that it?
I have a custom ActionScript class:
package EntityClasses
{
import mx.collections.ArrayCollection;
[RemoteClass(alias="tkatva.tt.entities.CompanyInfo")]
[Bindable]
public class CompanyInfo
{
public var companyInfoId:int;
public var companyName:String;
public var streetAddress:String;
public var postNumber:String;
public var city:String;
public var country:String;
public var email:String;
public var businessIdentityCode:String;
public var intBic:String;
public var homepageUrl:String;
public var lastUpdatedBy:String;
public var lastUpdateDate:Date;
public var version:int;
public var settingGroupList:ArrayCollection;
public var bankAccountList:ArrayCollection;
public var personList:ArrayCollection;
}
}
And I want to bind these values to textBoxes, so that when user types information to the textBoxes it is populated in the class too. Is Flex 3 bidirectional so that marking the class with [Bindable] I can bind the values to a textbox for example?
This is my mxml file in which I try to bind the class:
import mx.rpc.events.ResultEvent;
import EntityClasses.CompanyInfo;
[Bindable]
public var company:CompanyInfo;
public var uuid:String;
private function init():void {
company = new CompanyInfo();
}
private function getCompanyInfo():void {
try {
company = TtRo.getCompanyInfo(uuid);
} catch (Exception) {
}
}
private function handleClick():void {
this.txtInfo.text = "COMPANY:" + company.companyName;
TtRo.addCompanyInfo(company,uuid);
}
private function handleAdding(e:ResultEvent):void {
var res:Boolean;
res = e.result as Boolean;
if (res) {
this.txtInfo.text = "OK";
} else {
this.txtInfo.text = "NOTOK";
}
}
]]>
</mx:Script>
<mx:TextInput x="194" y="10" id="txtCname" text="{company.companyName}"/>
<mx:TextInput x="194" y="40" id="txtStreet" text="{company.streetAddress}"/>
<mx:TextInput x="194" y="70" id="txtPostnumber" text="{company.postNumber}"/>
<mx:TextInput x="194" y="100" id="txtCity" text="{company.city}"/>
<mx:TextInput x="194" y="130" id="txtCountry" text="{company.country}"/>
<mx:TextInput x="194" y="160" id="txtEmail" text="{company.email}"/>
<mx:TextInput x="194" y="190" id="txtBic" text="{company.businessIdentityCode}"/>
<mx:TextInput x="194" y="220" id="txtIntBic" text="{company.intBic}"/>
<mx:TextInput x="194" y="250" id="txtUrl" text="{company.homepageUrl}"/>
Whats wrong with this? The flex compiler shows me this kind of warnings: Data binding will not be able to detect assignments to "company".
Im new to Flex and any help would be appriciated... Thanks...
No, Flex data binding is not bidirectional. You have to explicitly bind the text property of text fields back to the corresponding properties of the company object using BindingUtils.bindProperty()
private function init():void
{
company = new CompanyInfo();
BindingUtils.bindProperty(company, "companyName", this ["txtCname", "text"]);
}
If you're using it in MXML components it might be more readable if you add the reverse binding in one place using the mx:Bindable tag
eg.
<mx:Binding destination="company.companyName" source="txtCname.text" />
<mx:Binding destination="company.streetAddress" source="txtStreet.text" />
...
etc.
Flex 3 Cookbook has a lot of recipes for this sort of work in chapter 11.9 & ch 14
You need to mark the variables themselves as bindable:
[Bindable]public var companyInfoId:int;
Welcome to Flex data binding, its lovely :)
I just ran into a strange binding problem. In the mini app below, the Flex Label component is updated when 'someText' changes, but my boundSetter won't be called after the first, initial call.
In short: Why is the boundSetterForSomeText() function not called, while the label does update?
Could anybody please shed some light onto this fundamental issue? Thanks a million!
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" minWidth="1024" minHeight="768"
initialize="onInitialize()"
>
<mx:Panel>
<mx:Label text="{this.someText}" />
<mx:Button label="Set random text" click="generateRandom()" />
</mx:Panel>
<mx:Script>
<![CDATA[
import mx.binding.utils.ChangeWatcher;
import mx.binding.utils.BindingUtils;
[Bindable(event="xxx")]
public var someText : String;
public function onInitialize() : void
{
var cw:ChangeWatcher = BindingUtils.bindSetter(boundSetterForSomeText, this, ['someText']);
}
public function generateRandom() : void
{
this.someText = String( Math.round(Math.random() * 10000) );
this.dispatchEvent(new Event("xxx"));
}
public function boundSetterForSomeText(obj:Object) : void
{
trace( obj );
}
]]>
</mx:Script>
</mx:Application>
You can use this code to create a get/set pair or "property":
private var _someText:String;
[Bindable(event="xxx")]
public function get someText():String
{
return _someText;
}
public function set someText(value:String):void
{
if (_someText != value)
{
_someText = value;
this.dispatchEvent(new Event("xxx"));
}
}
It does work when event is default. (Default event is propertyChange)
[Bindable]
public var someText : String;
I did some debugging and I have no clue why it doesn't work with custom event. I think it should.