When is the right point (from the life cycle view) to set data provier for LIST container on Flex 3 - apache-flex

I'm making a LIST container with my own item renderer to display xml file.
Now, I'm overriding the public override function set data(value:Object):void method in my item renderer, the problem is that this function been called many times(!!) (more then the data provider length).
Maybe I'm not setting the data provider right, here is how I do it:
First declare bindable property:
[Bindable]
private var _listDataProvider:XMLListCollection;
Then, creating LIST object:
<mx:List id="list" dataProvider="{_listDataProvider}" itemRenderer="myItemRenderer" />
Then, loading the xml (with urlLoader) and in the result doing:
_listDataProvider = new XMLListCollection(xml..Person);
The XMLListCollection build-up ok (I can see it in debug).
What am I doing wrong?????
Thanks guys...

It looks right to me, I have a feeling the Flex 3 List and related dataProvider components will set the data a few times for each item renderer the first round (inefficiencies in the framework). The first time, they might set it to null (is that happening?), then the next time they might set it to the value.
To get around this, just do something like:
public function set data(value:Object):void
{
if (super.data == value)
return;
super.data = value;
}
That should do the trick.

Related

Spark ItemRenderer "shorthand" causing strange behaviour

I have a simple ItemRenderer that is causing strange behaviour when I use it in a Spark List of products. It is defined as follows:
<?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"
creationComplete="setProduct()">
<fx:Script>
<![CDATA[
[Bindable]
private var p:MyProduct;
private function setProduct():void
{
p = data as MyProduct;
}
]]>
</fx:Script>
<s:Label text="{p.name}" paddingTop="6" paddingBottom="4"
color="{p.active ? Constants.BLACK : Constants.DISABLED_COLOR}"
fontStyle="{p.active ? 'normal' : FontStyle.ITALIC}"/>
</s:ItemRenderer>
It's basically the same as the default ItemRenderer for a List. I decided to use the variable p so that I wouldn't have to write (data as MyProduct) over-and-over within the Label bindings (this is especially cumbersome in larger, more complex ItemRenderers).
The behaviour it causes seems to "reverse" the items that get displayed, but the actual order of the items remains the same. This happens when a product gets updated with new values. For example, I'll have a list of products A B C D. If I update the properties of any one of them, the List will change the order to show D C B A. But if I click on product D in this reversed list, it will still "load" product A. This post describes a similar problem, but there is no code included, so I'm not sure if it's exactly the same issue.
I tracked this bug down to using the variable p. If I just use (data as MyProduct) within the Label bindings, it works fine. If I take the example from the above link and run the setProduct() method on the dataChange event, the bug goes away and works as expected. However, I'm not convinced that this isn't just coincidental.
Has anyone else experienced something like this? Is the problem directly related to using the creationComplete event? I was thinking that maybe creationComplete was only firing once and throwing everything out of whack. Not sure what any of this has to do with the List reversing the displayed products though.
This is probably due to item renderers being recycled. I would override the data setter instead of using creationComplete, that way you'll be sure you catch changes to data.
override public function set data(value : Object) : void {
super.data = value;
p = value as MyProduct;
}
There are a few things. First off, if you have a value object that you want to be set, then I would generally do it in my set data code.
override public function set data( value:OBject ):void
{
super.data = value;
p = value as MyProduct;
}
But that is relatively small beans compared to what I think the problem really is. The brackets means that the property is bound to that object/object chain. So we have to ask, what is the object bound to in this statement ?
"{p.active ? 'normal' : FontStyle.ITALIC}"
I'm not saying it shouldn't work, but we want to smoke out as many bugs as possible. So if I were you, my code would look like this.
[Bindable] public var fonstStyle:String;
[Bindable] public var color:int;
[Bindable] public var name:String;
private var _p:MyProduct;
override public function set data( value:OBject ):void
{
super.data = value;
_p = value as MyProduct;
fonstStyle = _p.active ? 'normal' : FontStyle.ITALIC;
color = _p.active ? Constants.BLACK : Constants.DISABLED_COLOR;
name = _p.name;
}
I would also get rid of that 'creationComplete' event listener. When working with ItemRenderers, any setting of data-based properties should be done through the 'set data' function.
Now that I look at your code, your renderer is probably setting those variables after being created through the 'creationComplete', and then they aren't being updated.

Data binding across multiple objects in Flex 3

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.

Listen to bindable property in bindable Arraycollection

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. ;)

Having trouble with binding

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.

Flex : How to hide a row in AdvancedDataGrid?

I have an AdvancedDataGrid with a ArrayCollection as its dataProvider. For instance i have a CheckBox that allows me to show or hide certain rows in the AdvancedDataGrid.
Any idea how i could do that?
My suggestion would be to use your data provider's filterFunction property. Basically, you can give your data provider a function that will determine whether a given item in the ArrayCollection is excluded or not (if an item is excluded, it won't be displayed in the AdvancedDataGrid, in essence making it "invisible"). The docs for filterFunction can be found here.
What I'd suggest then is that checking the checkbox sets a property on the object in your data provider, which is then used by your filter function to include/exclude rows. Some (very rough) pseudocode follows:
private function checkboxClickHandler( event:MouseEvent ):void
{
/*
Based on the MouseEvent, determine the row
in the data grid you're dealing with
*/
myDataProvider[someIndex].checkboxFlag = myCheckBox.selected;
myDataProvider.refresh(); // calling refresh() re-applies the filter to
// account for changes.
}
private function myDataProviderFilterFunction( item:Object ):Boolean
{
// assuming we want the item to be filtered if checkboxFlag is true
return !item["checkboxFlag"];
}

Resources