Since I found the webpages explaning the bindable propety quite confusing,so I would like to post my question here,which is quite simple,if I declare a variable to be bindable,does that mean whenever I changed the value of this variable in another class,all appearence of this variable will be synchronized to be the same value at the same time?
Say,if boolean variable "select" is declared to be bindable in Class A and default to be false,and we have an if statement in class A like if(select).
Then in another class,we changed the value of "select" to be true,will that if(select) statement pass the test ?
Also,how about the following setter method that is defined to be bindable:
[Bindable]
public function set isShowingAvg(b:Boolean):void
{
_isShowingAvg = b;
hasChanged();
}
Does this code imply that changing the value of _isShowingAvg is also going to be broadcasted?
Thanks in advance.
Thanks for your idea.
Declaring a property as Bindable means that when you change the value, an event will get broadcasted. This event enables data binding, but it's not necessarily automatic.
If the consuming class is MXML and you use brackets, like this:
<mx:Button enabled="{selected}" />
Then the MXML compiler will generate the appropriate binding code and anytime selected changes, enabled will also get changed.
If you're using it outside MXML then you'll either subscribe to the event to detect changes or use BindingUtils.
In your example I think you need to mark the getter [Bindable] and not the setter.
example:
public static const SHOWING_AVG_CHANGED:String = "showingAvgChangedEvent";
[Bindable(event="showingAvgChangedEvent")]
public function get isShowingAvg():Boolean
{
return _isShowingAvg;
}
public function set isShowingAvg(isShowing:Boolean):void
{
_isShowingAvg = isShowing;
dispatchEvent(new Event(SHOWING_AVG_CHANGED));
}
Related
I am new to Flex (got assigned to maintain an old project at work) and am having some trouble getting data binding to work correctly. I have a popup form class, AddOffer.mxml which uses a model AddOfferModel.as. On my popup form, I have the following component:
<mx:FormItem label="{getResource('addOffer.form.OFFER_DATE')}:"
labelWidth="90">
<views:OfferWindowDatesFragment
id="offerWindowField"
start="{model.offerStartDate}"
stop="{model.offerStopDate}" />
</mx:FormItem>
My AddForm.mxml file also has some embedded actionscript where I define my 'model' variable:
[Bindable]
public var model:AddOfferModel;
The model variables I am trying to bind to are standard getters/setters and look like this inside AddOfferModel.as:
[Bindable]
public function set offerStartDate(val:EditableInstant):void
{
_offerStartDate = val;
}
public function get offerStartDate():EditableInstant
{
return _offerStartDate;
}
private var _offerStartDate:EditableInstant;
[Bindable]
public function set offerStopDate(val:EditableInstant):void
{
_offerStopDate = val;
}
public function get offerStopDate():EditableInstant
{
return _offerStopDate;
}
private var _offerStopDate:EditableInstant;
Inside the OfferWindowDatesFragment component class, the start and stop variables look like this:
[Bindable]
public function set start(val:EditableInstant):void
{
_start = val;
}
public function get start():EditableInstant
{
return _start;
}
private var _start:EditableInstant;
[Bindable]
public function set stop(val:EditableInstant):void
{
_stop = val;
}
public function get stop():EditableInstant
{
return _stop;
}
private var _stop:EditableInstant;
Basically, I just want to bind the start and stop variables in my OfferWindowDatesFragment class to the offerStartDate and offerStopDate variables in the AddOfferModel.as file. Whenever I access the start/stop variables in functions inside the OfferWindowDatesFragment class, they are null.
I have an event listener function that gets triggered in OfferWindowDatesFragment anytime a user selects a new date, it looks like this:
private function changeOfferDate():void
{
start.currentValue = offerDateEditor.start;
stop.currentValue = offerDateEditor.stop;
}
Every time I reach this function, it throws up an error because 'start' and 'stop' are both null ... but should have been initialized and bound already. If I look at the variables in the debugger, I can confirm that values on the right side of the assignment expression are valid, and not what is causing the error.
I am not real familiar with how initialization works in Flex, and I assumed as long as I instantiated the component as seen in the first code snippet at the top of my post, it would initialize all the class variables, and setup the bindings. Am I missing something? Perhaps I am not properly initializing the model or class data for AddForm.mxml or AddFormModel.as, thereby binding null references to the start/stop fields in my OfferWindowDatesFragment class?
Any help would be greatly appreciated. Thanks!
EDIT:
I looked into this further and tried using Mate to inject the 'model' variable inside AddOffer.mxml with a valid AddOfferModel object:
<Injectors target="{AddOffer}" debug="{debug}">
<ObjectBuilder generator="{AddOfferModel}" constructorArguments="{scope.dispatcher}" cache="local"/>
<PropertyInjector targetKey="model" source="{lastReturn}" />
</Injectors>
I load the AddOffer.mxml dialog as the result of a button click event on another form. The function that pops it up looks like this:
public function addOffer():void
{
var addOfferDialog:AddOffer = new AddOffer();
addOfferDialog.addEventListener("addOffer", addOfferFromDialog);
modalUtil.popup(addOfferDialog);
}
It doesn't seem to be assigning anything to the 'model' variable in AddOffer.mxml. Does loading a view/dialog this way not trigger an injection from Mate by chance? (I realize this last part might belong in the Mate forums, but I'm hoping somebody here might have some insight on all of this).
In AddOffer.mxml, you have this code:
[Bindable]
public var model:AddOfferModel;
Is there something outside AddOffer.mxml that is setting this to a valid AddOfferModel? There should be. The nature of how the Flex component life cycle means that you can expect that things may be null at times as a View builds. So you should build your components to be able to "right themselves" after receiving bad data, if the data eventually comes good.
Data binding is one way to do this, but it may not paper over everything depending on what else is going on.
Have you verified that the model value you're getting is not null at the point where the user selects the date and that its offerStartDate and offerEndDate properties have been populated with valid EditableInstants? If both of those are correct, I'd start looking for pieces of the Views that expect to have stuff at a given instant and then don't recover if it is provided later.
I've got a bindable model class (lets call it myModel) with two properties, label and value. The value gets updated frequently, so it is marked as bindable.
Works fine so far, the data is updated and the standard property change event is dispatched.
Now I have to make an ArrayCollection from the object instances of this model to use it as a data provider in a data group. The data gets then passed to a custom itemRenderer in which I access the myModel properties via data.label and data.value.
The only problem I've got now is that the myModel value property doesn't change any more (I suppose because I stored the objects in the ArrayCollection).
The ArrayCollection is marked bindable as well btw, because new object instances of myModel can be added during runtime.
Is there any way to make this work? Any help regarding this would be much appreciated!
Edit: I almost forgot, the value object in the myModel class is updated by another bindable class. Yes, I know that's bonkers but that's why I'm here, to get some input on a simpler (and in fact working) way to solve this problem.
2nd edit: Allright guys, a little bit of code to illustrate the issue;
Lets start with the first bindable class;
[Bindable]
public class FirstClass
{
public var name:String;
public var firstValue:Number;
public var secondValue:Number;
public var thirdValue:Number;
public function FirstClass()
{ }
}
The values (first to third) get updated by a controller class. So far so good.
Now to the second model class (for matters of consistency, lets keep the MyClass name)
[Bindable]
public class MyClass
{
public var label:String;
public var value:Number;
public function FirstClass()
{ }
}
These are the two model classes. Background behind this is that I need a String value (a label) for each property of an instance of FirstClass. I'd like to make this simpler, so I'm really not settled on this "solution" cough ;).
Anyhow, we've got the two models, now to my .mxml class;
[Bindable] private var firstClassInstance:FirstClass;
I create a new ArrayCollection and add objects like this;
myArrayCollection.addItem(new MyClass("This is a label", firstClassInstance.firstValue));
And again, the DataGroup uses this ArrayCollection as a data provider.
As we already established (thank you #Windowns), the ArrayCollection looks only for objects being added or removed, not property changes of these objects.
Call itemUpdated on your ArrayCollection when you update a "piece" of an item stored in it.
There could be many issues with binding. Please post code to help us see what is happening. Here are some "high level" things to watch out for that might answer your question
When using an bindable arraycollection of objects, it's important to note that the binding for the arraycollection only looks at each object instance and if it's added or removed from the collection. It will not know about any property changes that occur to your object. Commonly when you use an itemrenderer, the properties are bound to display elements. Like maybe the "value" property bound to a label in the itemrenderer. Now when your object instance (myModel) changes it's "value" property the label should pick it up. Also note that you need to mark any properties you intend to bind to visual elements with the [Bindable] meta-tag.
public class myModel
{
[Bindable]
public var label:String;
[Bindable]
public var value:String;
public function myModel() {}
}
Answer after code post:
When you do the following:
myArrayCollection.addItem(new MyClass("This is a label", firstClassInstance.firstValue));
You are taking the value of firstClassInstance.firstValue and supplying it as a hard value (as in not passing value by reference). So if you do the following:
myArrayCollection.getItemAt(addedClassIndex).value = 5;
Will not cause any changes to be noted in the firstClassInstance.firstValue as there is no "referening information" stored. We are only working with the basic type of Number which is never passed by reference like all other objects are in Flex.
Maybe try this:
[Bindable]
public class MyClass
{
public var label:String;
[Bindable] //might be redundant due to global [Bindable] flag, been a while since i've used a global one
public function get value():Number{
return m_objRef.firstValue;
}
public function set value(value:Number):void{
m_objRef.firstValue = value;
}
private var m_objRef:FirstClass;
public function MyClass(_label:String, _valueObj:FirstClass) {
m_objRef = _valueObj;
label = _label;
}
}
Allright guys (and gals ;)) after two hours of messing around with BindingUtils, I finally found the solution to my problem.
The two model classes can remain the way they are, so passing the instance of FirstClass isn't necessary.
Simply binding the value properties of FirstClass to the value field of MyClass works as expected and the values in the ArrayCollection get updated as well.
So the solution;
myClassObject = new MyClass();
myClassObject.label = "This is a label";
BindingUtils.bindProperty(myClassObject, "value", firstClassObject, "firstValue");
And then simply add the myClassObject to the ArrayCollection.
Keep in mind that all the code here is pseudo code, so never mind any typos.
Still, #Windowns suggesting with passing the FirstClass object to the MyClass will be incorporated into my final solution as it makes switching between properties a lot easier (FirstClass has got lots of them, not just the 4 in my first post). Many thanks for that!
I took Amy's advice and researched a little further on the itemUpdated method. Turns out, the solution was right there.
See here: http://flex4examples.wordpress.com/2009/08/28/1st/
I applied this methodology (with little variations) to my code and it works quite good. Performance on the iPad2 is good and so is the memory usage of my component.
Let's hope that Amy is fine with this solution as well. Fingers crossed. ;)
Can we put [Bindable] on functions/methods? I know that bindable is used to change the value of the source property to destination property. But not sure if we can use that for methods. Can you guys give me reason why we cannot put/ if we can then what will be the outcome?
Can you guys give me reason why we cannot put/ if we can then what
will be the outcome?
You can use Bindable on get/set properties; which are implemented as methods. Sort of like this:
private var _myValue : Boolean;
[Bindable(event='myValueChanged']
public function get myValue():Boolean{
return _myValue;
}
public function set myValue(value:Boolean):void{
_myValue = value;
dispatchEvent(new Event('myValueChanged'));
}
[Disclaimer I wrote this code in the browser]
The purpose of Binding is to 'magically' link two properties together. So, when the source property changes, the destination property also changes.
How are you expecting to apply this concept to a function?
I'm not sure if I'm misunderstanding the binding in Flex. I'm using Cairngorm framework. I have the following component with code like:
[Bindable]
var _model:LalModelLocator = LalModelLocator.getInstance();
....
<s:DataGroup dataProvider="{_model.friendsSearchResults}"
includeIn="find"
itemRenderer="com.lal.renderers.SingleFriendDisplayRenderer">
<s:layout>
<s:TileLayout orientation="columns" requestedColumnCount="2" />
</s:layout> </s:DataGroup>
in the model locator:
[Bindable]
public var friendsSearchResults:ArrayCollection = new ArrayCollection();
Inside the item renderer there is a button that calls a command and inside the command results there is a line like this:
model.friendsSearchResults = friendsSearchResults;
Putting break points and stepping through the code I confirmed that this like gets called and the friendsSearchResults gets updated.
To my understanding if I update a bindable variable it should automatically re-render the s:DataGroup which has a dataProvider of that variable.
There's nothing obviously wrong in the code sample. It should work so I think there's a problem elsewhere.
I would recommend setting a breakpoint where the dataProvider is assigned and also where model.friendsSearchResults is assigned. Make sure they're both pointing to the same object instance. Then step through the property assignment and corresponding event.
To make debugging easier you can switch to using a named event instead of the default. With a named event, only event listeners interested in your particular property are triggered instead of any listeners listening for any property change. This is easier to debug and will run faster. For example, change:
[Bindable]
public var results:ArrayCollection;
to
[Bindable("resultsChanged")]
private var _results:ArrayCollection;
public function get results():ArrayCollection {
return _results;
}
public function set results(value:ArrayCollection):Void {
_results = value;
dispatchEvent(new Event("resultsChanged"));
}
Another thing to keep in mind is that bindings hide certain errors like null reference exceptions. They assume the value simply isn't available yet and suppress the error. Stepping through the assignment and related bindings will help find a problem like this.
Consider the following code:
[Bindable(event="ReportHeaderVO_effectiveFromDateJulian_updated")]
public function set effectiveFromDateJulian ( value:Number ) : void
{
_effectiveFromDateJulian = value;
dispatchEvent( new FlexEvent("ReportHeaderVO_effectiveFromDateJulian_updated") );
}
public function get effectiveFromDateJulian () : Number
{
return _effectiveFromDateJulian;
}
public function get effectiveFromDate () : Date
{
return DateUtil.convertJDEJulianToDate(_effectiveFromDateJulian);
}
There is a setter and a getter for the effectiveFromDateJulian which is a number representation of the date. I have provided a seperate getter which retrieves the same value, only converted to a proper date. It is a getter only though and relies on the setter for the numeric property to get its data from; so the effectiveFromDate property is effectively read-only.
Data binding works on the effectiveFromDateJulian property; any updates work fine and notify everything properly. But when binding to the effectiveFromDate (getter only) property, I get a warning from the compiler:
warning: unable to bind to property 'effectiveToDate' on class 'com.vo::ReportHeaderVO'
Is there a way to make it possible to bind to this read-only property? I would assume I would have to dispatch an event on the setter that effects the read-only property, but I don't know what that would look like.
This is a simple example, you could imagine a read-only property that depends on several setters to function and when any of those setters are updated the read-only property would need to fire a propertyChanged event as well. Any ideas? Please let me know if I need to clarify anything.
Update:
From the Adobe documentation here:
http://livedocs.adobe.com/flex/3/html/help.html?content=databinding_8.html
Using read-only properties as the
source for data binding
You can automatically use a read-only
property defined by a getter method,
which means no setter method, as the
source for a data-binding expression.
Flex performs the data binding once
when the application starts.
Because the data binding from a
read-only property occurs only once at
application start up, you omit the
[Bindable] metadata tag for the
read-only property.
And this makes sense for constant values, but in this case the value does change, it just doesn't get set directly.
Make the readonly getter Bindable and dispatch the corresponding event from the original setter method.
[Bindable(event="ReportHeaderVO_effectiveFromDateJulian_updated")]
public function set effectiveFromDateJulian ( value:Number ) : void
{
_effectiveFromDateJulian = value;
dispatchEvent( new FlexEvent("ReportHeaderVO_effectiveFromDateJulian_updated") );
dispatchEvent( new FlexEvent("ReportHeaderVO_effectiveFromDate_updated") );
}
[Bindable(event="ReportHeaderVO_effectiveFromDate_updated")]
public function get effectiveFromDate (date:Date) : Date
{
return DateUtil.convertJDEJulianToDate(_effectiveFromDateJulian);
}