Flex/AS3: Data binding through a refenced property - apache-flex

I have:
class A: with a property "data:ArrayCollection". It's my data source (data provider) and has bitmaps.
class B: has an instance of A (called "Ainst") and calls the following method: C.init( Ainst.data). To pass the data provider to Object C.
class C: has a reference of the data provider "data" (because of "init" method). It shows the arrayCollection into Images as: Image.source = data.getItemAt(0).
But C never updates its images, that is, data binding doesn't work with this scheme. I've put [Bindable] meta-tag in all properties and even classes.
class A:
public class A{ [Bindable]public var data:ArrayCollection; }
class B:
public class B{ [Bindable]public var Ainst:A; public var Cinst:C; public function init(){ Cinst = new C(); Cinst.init(A.data) } }
class C: An image menu with 3 items (bitmaps)
<s:VGroup 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:local="*"
horizontalAlign="center"
paddingTop="10"
paddingBottom="10"
gap="10">
<fx:Script>
<![CDATA[
[Bindable] public var _images:ArrayCollection;
public function init( images:ArrayCollection ):void{
_images = images;
}
]]>
</fx:Script>
<ms:Image id="PreviousButton" smoothBitmapContent="true" width="55" height="55" source="#Embed(source='/../figures/upArrow.png')"/>
<ms:Image id="TopItem" smoothBitmapContent="true" maintainAspectRatio="true" x="12.5" source="{_images.getItemAt(0)}" />
<ms:Image id="MiddleItem" smoothBitmapContent="true" maintainAspectRatio="true" x="12.5" source="{_images.getItemAt(1)}"/>
<ms:Image id="BottomItem" smoothBitmapContent="true" maintainAspectRatio="true" x="12.5" source="{_images.getItemAt(3)}"/><!-- getItemAt(2).image -->
<ms:Image id="NextButton" smoothBitmapContent="true" width="55" height="55" source="#Embed(source='/../figures/downArrow.png')"/>
</s:VGroup>
Any thought? Thanks.

Your problem is that the function getItemAt (_images.getItemAt(0)) is NOT Bindable.

Take a look at your class A:
public class A{
[Bindable]
public var data:ArrayCollection;
}
The property data here isn't static. So it is related to instance of A but not A itself. Now take a look at constructor of B:
public function init(){
Cinst = new C();
Cinst.init(A.data);
}
And its field declaration:
[Bindable]
public var Ainst:A;
As you can see in a line
Cinst.init(A.data);
you're referring data as a static property. You should use:
public function init(){
Cinst = new C();
Cinst.init(Ainst.data);
}
instead.
And please keep ActionScript naming and coding conventions. Place statements in a separate lines and start identifiers with lowercase letter. It allows to read your code easier.

Related

Flex Binding : Unexpected behaviour

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.

Flex List ItemRenderer with image loses BitmapData when scrolling

Hi i have a mx:List with a DataProvider. This data Provider is a ArrayCollection if FotoItems
public class FotoItem extends EventDispatcher
{
[Bindable]
public var data:Bitmap;
[Bindable]
public var id:int;
[Bindable]
public var duration:Number;
public function FotoItem(data:Bitmap, id:int, duration:Number, target:IEventDispatcher=null)
{
super(target);
this.data = data;
this.id = id;
this.duration = duration;
}
}
my itemRenderer looks like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" >
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
]]>
</fx:Script>
<s:Label text="index"/>
<mx:Image source="{data.data}" maxHeight="100" maxWidth="100"/>
<s:Label text="Duration: {data.duration}ms"/>
<s:Label text="ID: {data.id}"/>
</mx:VBox>
Now when i am scrolling then all images that leave the screen disappear :(
When i take a look at the arrayCollection every item's BitmapData is null.
Why is this the case?
I changed Datatype of data in Class FotoItem from Bitmap to BitmapData
in the ItemRenderer i do the following:
override public function set data( value:Object ) : void {
super.data = value;
pic.source = new Bitmap(value.image);
}
this works now. No idea why it is not working with bitmaps
I think it might be something with your use of data.data - I believe data is a reserved keyword in Actionscript, and it might be best to name your image property something else, such as data.imageData.
I'm also not sure why you're importing ArrayCollection into your item renderer as you don't appear to be using it in your itemRenderer.
You may also be running into problems with itemRenderer recyling. You may want to override public function set data() and handle setting the individual item properties there instead of relying on binding.
Where are you looking at the arrayCollection to see that the bitmapData is null?

AS: How to handle custom event in parent component

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?

Flex 3 Binding problem

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 :)

How do I implement data binding in an ActionScript Class?

I am having a problem with binding values in my ActionScript components. I basically want to set the value of a a variable in my component to a value in the model, and have the component variable automatically update when the model value is updated. I think that I just don't fully understand how data binding works in Flex - this is not a problem when using MXML components, but, when using ActionScript classes, the binding does not work.
This is the code I'm using, where the values are not binding:
package
{
public class Type1Lists extends TwoLists
{
public function Type1Lists()
{
super();
super.availableEntities = super.composite.availableType1Entities;
super.selectedEntities = super.composite.selectedType1Entities;
}
}
}
package
{
public class Type2Lists extends TwoLists
{
public function Type2Lists()
{
super();
super.availableEntities = super.composite.availableType2Entities;
super.selectedEntities = super.composite.selectedType2Entities;
}
}
}
/* TwoLists.mxml */
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
public var __model:ModelLocator = ModelLocator.getInstance();
public var composite:Composite =
__model.selectedComposite;
[Bindable]
public var availableEntities:ArrayCollection;
[Bindable]
public var selectedEntities:ArrayCollection;
]]>
</mx:Script>
<mx:List id="availableEntitiesList" dataProvider="{availableEntities}" />
<mx:List id="selectedEntitiesList" dataProvider="{selectedEntities}" />
</mx:HBox>
To use binding by code you should use mx.binding.utils.*
Take a look and the BindingUtils.bindProperty and bindSetter methods.
Also, be careful with manual databinding, it could lead you to memory leaks.
To avoid them, save the ChangeWatcher returned by bindProperty and bindSetter and call watcher's unwatch method when is no more used (i.e, in the dipose or destructor method)
You need to add the [Bindable] tag either to the class itself (making all properties bindable) or the properties you want to be [Bindable]. Marking properties or objects as [Bindable] in your MXML is not sufficient.
To fix this, I simply converted the classes to MXML components, and added a private variable for my ModelLocator.
/* Type1Lists.mxml */
<?xml version="1.0" encoding="utf-8"?>
<TwoLists xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
availableEntities="{__model.selectedComposite.availableType1Entities}"
selectedEntities="{__model.selectedComposite.selectedType1Entities}">
<mx:Script>
<![CDATA[
import model.ModelLocator;
[Bindable]
private var __model:ModelLocator = ModelLocator.getInstance();
</mx:Script>
</TwoLists>
/* Type2Lists.mxml */
<?xml version="1.0" encoding="utf-8"?>
<TwoLists xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
availableEntities="{__model.selectedComposite.availableType2Entities}"
selectedEntities="{__model.selectedComposite.selectedType2Entities}">
<mx:Script>
<![CDATA[
import model.ModelLocator;
[Bindable]
private var __model:ModelLocator = ModelLocator.getInstance();
</mx:Script>
</TwoLists>

Resources