Two-way data binding in actionscript causes stack overflow - apache-flex

I'm binding two AutoCompleteModified objects to one another; meaning you type
in one and it selects the correct object in the other. It works fine when I
define it in MXML:
However, a user can add a new row to a Grid and then I set up the binding and
objects via actionscript and it gives an 'undefined' error: ChangeWatcher line 427/wrapHandler.
var innerHBox:HBox = new HBox();
var dtc_acm:AutoCompleteModified = new AutoCompleteModified();
dtc_acm.dataProvider = data2;
dtc_acm.labelField = 'id';
var cp_acm:AutoCompleteModified = new AutoCompleteModified();
cp_acm.dataProvider = data2;
cp_acm.labelField = 'name';cp_acm.width = this.CP1.width;
BindingUtils.bindProperty( dtc_acm,'selectedIndex',cp_acm,'selectedIndex' );
BindingUtils.bindProperty( cp_acm,'selectedItem',dtc_acm,'selectedItem' );
innerHBox.addChild( dtc_acm );
innerHBox.addChild( cp_acm );
I don't understand what may be happening here. Can anyone see any potential
problems in my code? If I only keep it 1-way binding then it works fine. But both throw this error. Is there something about not only doing it 2-way in actionscript, but adding components that aren't on the stage yet?
Thank you kindly for any helpful tips,
Matt

I'm trying to do the same thing. It works in MXML but not in AS. For example, this works:
<mx:TextArea id="t1" verticalScrollPosition="{t2.verticalScrollPosition}" height="200"/>
<mx:TextArea id="t2" verticalScrollPosition="{t1.verticalScrollPosition}" height="200"/>
If I scroll one of the TextAreas then the other one scrolls too. However, trying to do the same thing in actionscript causes a stack overflow (infinite loop)
BindingUtils.bindProperty( _t1, 'verticalScrollPosition', _t2, 'verticalScrollPosition' );
BindingUtils.bindProperty( _t2, 'verticalScrollPosition', _t1, 'verticalScrollPosition' );
I used the -keep-generated-actionscript compiler option and looked at the generated asctionscript for the mxml example and it creates a couple mx.binding.Binding objects and it looks like the key is setting the twoWayCounterpart property. I haven't tried mimicking that code yet but it might help you.

Since the two components are bound to each other like this I'm not surprised you are seeing this, I'm more surprised that it worked via mxml.
Have you tried changing the (optional) 5th parameter in bindProperty to true? That parameter is commitOnly and defaults to false. That may fix your problem.
Another approach could be to have an intermediary variable to store the selected item and bind your components to that variable.
Hope that helps.

Related

Flex: NPE and Proper way to access component properties before creation complete

I think I'm missing something important about flex sdk component lifecycle, but cannot sort it out, although read a lot of tutorials. Could you please share your experience on how do you operate with flex visual object's properties and how do you avoid NPE when accessing them before component creation complete.
Let's say we have a simple component MyTitleWindow.mxml:
<s:TitleWindow>
<s:DataGrid id="myDataGrid" />
</s:TitleWindow>
Another component got data from a remote object, and wants to apply the data into title window's datagrid and show it via PopUpManager:
private function handleDataReceived(data : ArrayCollection) : void {
var myTitleWindow : TitleWindow = new MyTitleWindow();
PopUpManager.addPopUp(myTitleWindow);
myTitleWindow.myDataGrid.dataProvider = data;
}
Ofcourse, the line myTitleWindow.myDataGrid.dataProvider = data will throw an NPE because we're trying to access myDataGrid that haven't been rendered yet.
Currently I can see only 2 options how to avoid NPE:
Create a setter for data in titleWindow, put the data into some
cache. Listen to creationComplete event, in it's handler apply data
from the cache to the datagrid. This approach works fine, but I'm
tired of adding this safe-guards across the application.
Make a
bindable property - works for me only with simple datatypes
(numbers, strings...)
Is there anything I'm missing in the area of using flex validation/invalidation cycle that could help to avoid excessive code?
The problem you are having and Crusader is reporting is because in Flex components are initialized lazy. This is generally a good thing but in your case it is what's causing your problems.
In general I wouldn't suggest to set the dataProvider on a view component from outside the component as you have no way of knowing if all is setup and ready to use.
What I usually do. In simple cases I simply add a public propberty which I make [Bindable]. The (i think) cleaner way would be to create a setter (and getter) and so save the dataProvider in a local variable (in your case probably ArrayCollection). In the setter I usually check if the "myDataGrid" exists and if it exists, to additionally set the dataProvider property. I would then add a CreationComplete callback in my component and in that I would also set the dataProvider.
So when setting the dataProvider before the component is finished initializing, the value would simply be saved in the local variable and as soon as it is finished setting up the dataProvider is automatically set. If the component is allready setup (you are changing the dataProvider) the setter would automatically update the dataProvider of "myDataGrid"
<s:TitleWindow creationComplete="onCreationComplete(event)">
...
private var myDataProvider:ArrayCollection;
private function onCreationComplete(event:FlexEvent):void {
myDataGrid.dataProvider = myDataProvider;
}
public function set myDdataProvider(myDataProvider:ArrayCollection):void {
myDataProvider = myDataProvider;
// Only update the dataProvider if the grid is available.
if(myDataGrid) {
myDataGrid.dataProvider = myDataProvider;
}
}
....
<s:DataGrid id="myDataGrid" />
</s:TitleWindow>
Yeah, this can be an annoyance, and it's not just with popups. It can also be a pain when using a ViewStack components with a default creationpolicy, which I tend to do fairly often.
I might get flamed for this, but I usually just use bindings. I'm not sure what you mean by "simple datatypes" - it works fine with custom types too. You'd have to provide an example.
One thing you could do (and I'll probably get flamed for this :p ) is create your popup component instance early on and re-use it rather than creating a new one each time.
No, I don't think you're "missing something" in the component lifecycle.
I always try to invert the responsibility for setting a dataProvider and prefer to have components observe a [Bindable] collection.
In simple examples such as yours I avoid giving my components an id. This prevents me from breaking encapsulation by referring to them externally.
<s:TitleWindow>
<s:DataGrid dataProvider="{data}" />
</s:TitleWindow>
The consumers of your MyTitleWindow component should not know that it has a DataGrid with id 'myDataGrid', or be required to set the dataProvider property of 'myDataGrid'.
Considering components declared in MXML require a no-arguement constructor (and that we're unable to declare multiple constructors) - one approach that has worked well for me in the past is to offer a static 'newInstance' method. I give this method a relevant name based on the domain I'm working in, and also any required parameters.
public static function withData(data : ArrayCollection) : MyTitleWindow
{
var myTitleWindow : MyTitleWindow = new MyTitleWindow();
myTitleWindow.data = data;
return myTitleWindow;
}
This clearly communicates the 'contract' of my component to any and all consumers. (Obviously things become clearer with more relevant naming).
private function handleDataReceived(data : ArrayCollection) : void
{
PopUpManager.addPopUp(MyTitleWindow.withData(data));
}

Flex: select tree node right after the dataProvider is been assigned / updated / replace

i have a Flex tree control and im trying to select a tree node 3 levels down right after the dataProvider is assigned with a collection object like the following.
basically treeItem1, treeItem2, treeItem3 are the nodes in the tree and treeitem3 is a child of treeItem2 which is a child of treeItem1. Assume these treeItem(1,2,3) are referenced correctly from the collection items.
my problem is that if i wait for the whole component to load completely then select the nodes, it open/select/scrolltoIndex correctly. However, if i were to select the node right after the dataProvider is assigned, then it doesn't even open or select (basically the this.treeService.selectedItem is always null).
can anyone point out what i did wrong? is there anything needs to happen after the dataProvider is assigned?
thanks
this.treeService.dataProvider = oPricingHelper.getCurrentPricingSercicesTreeSource();
this.treeService.expandItem(treeItem1, true);
this.treeService.expandItem(treeItem2, true);
this.treeService.selectedItem = treeItem3;
this.treeService.scrollToIndex(this.treeService.selectedIndex);
I have used the updateComplete event to know when a component (such as a DataGroup or List) has completed rendering after performing a simple task (such as updating the dataProvider reference). Of course, you have to be careful and remove listening to updateComplete because it can run a lot, unless you have a need for it to run.
Something like:
//...some function...
this.treeService.addEventListener(FlexEvent.UPDATE_COMPLETE, onTreeUpdateComplete);
this.treeService.dataProvider = oPricingHelper.getCurrentPricingSercicesTreeSource();
//...rest of some function...
private function onTreeUpdateComplete(event:FlexEvent):void {
this.treeService.removeEventListener(FlexEvent.UPDATE_COMPLETE, onTreeUpdateComplete);
this.treeService.expandItem(treeItem1, true);
this.treeService.expandItem(treeItem2, true);
this.treeService.selectedItem = treeItem3;
this.treeService.scrollToIndex(this.treeService.selectedIndex);
}
I'm not positive your experiencing the same issue but I seem to have the same type of problem with using the advanced data grid, it appears in these cases where the dataprovider is acceptable as multiple types, the components do some extra work in the background to wrap things up into something Hierarchical (HierarchicalData or HierarchicalCollectionView) and in doing so the dataprovider setter call is not synchronous (so it will return before actually having assigned the internal property storing the dataprovider). I've used callLater in this case with moderate success, callLater is generally a bad practice but basically adds a function to a list of functions to call once background processing is done, so this is assuming that something in the dataprovider setter called UIComponent.suspendBackgroundProcessing() and that it will subsequently call UIComponent.resumeBackgroundProcessing() and then it will execute the list of functions added by using callLater. Alternatively you could use setTimeout(someFunction,1000).
These are both "hacks" the real solution is to dig into the framework code and see what it's really doing when you tell it to set the dataprovider. Wherever you see that it actually has set the dataprovider you could extend that class and dispatch an event that you could listen for to run the function to do the selections after this point.
If anyone has a better solution please by all means correct me (I would love to have a better answer than this)

How to bind background of a column in silverlight during runtime?

I have a grid with bunch of columns in it. To start with this grid is build during run time in the code behind and not much code in XAML except the generic grid, so I can't bind the column background during design time. I have been reading various blogs, questions and found the following as the closest answer.
SolidColorBrush backgroundBrush = new SolidColorBrush();
Binding b = new Binding("BackGroundColor");
b.Converter = new ColorConverterForReadOnly(); //This converter return color based on parameter
b.ConverterParameter = data;
BindingOperations.SetBinding(backgroundBrush, SolidColorBrush.ColorProperty, b);
column.Background = backgroundBrush;
When I ran the code, the binding did not happen, I put a break point (in the first line) inside the converter to see if debug hits the converter at all and it did not hit the converter at all. If I would put
column.Background = new SolidColorBrush(Colors.Blue)
I can see the the column colors set to blue.
What am I missing in the binding that is not letting converter invoked?
Thanks,
The binding is targeting a "BackGroundColor" property. For the binding to be hit, the DataContext of the column control would need to be an object that has a "BackGroundColor" property.

Disable databinding in MXML

When the Flex SDK converts MXML to actionscript it generates a lot of databinding code. Sometimes, however, I don't want to bind a variable, for example if I know the variable will not change.
I can't seem to find a work around in Flex to disable the autogenerated databinding.
Also, I was hoping this might also help with some of the runtime warnings thrown by databinding.
To get around them, I sometimes use the following, which only throws syntax warnings (and don't appear in my console at runtime).
Syntax warning:
Data binding will not be able to detect changes when using square bracket operator. For Array, please use ArrayCollection.getItemAt() instead.
The following tag will tell Flex SDK that variable do not really change and remove "Unable to bind ..." warnings:
[Bindable("__NoChangeEvent__")]
private var model:MyModel = MyModel.instance;
Next, move array[i]-like expressions to a separate function in order to remove warnings. If you had this:
<mx:Button label="{array[i]}"/>
Then create a function:
private function buttonLabel(i):String
{
return array[i];
}
And the MXML:
<mx:Button label="{buttonLabel(i)}"/>
P.S: If button label changes in runtime then you can add [Bindable(...)] metatags to the function:
[Bindable("stringsChange")]
private function buttonLabel(i):String
{
return array[i];
}
dispatchEvent(new Event("stringsChange"));

Flex DataBinding Drilling Down Through Arrays

The help page on the BindUtils.bindProperty function:
http://livedocs.adobe.com/flex/3/langref/mx/binding/utils/BindingUtils.html
Has this to say:
"For example, to bind the property host.a.b.c, call the method as: bindProperty(host, ["a","b","c"], ...)."
But what if I need to bind to host.a.b[2].c? How do I do that?
There are often binding issues when you drill down into objects, at least under most normal binding sitautions.
Not sure if this is what you're after, but:
[Bindable] public var myObject = a.b[2];
And later in your code:
<myComp myValue="{myObject.c}" />
However, I would consider it highly unusual to bind to a specific element of an array. If you could expand on what you're trying to do; maybe we can point you in a different direction.
It turns out, flex lets me do this:
bindProperty(host, ["a","b","2","c"], ...);
Hazzah!

Resources