Flex DataBinding Drilling Down Through Arrays - apache-flex

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!

Related

JavaFX binding and null values

I was wondering how to bind values where the source of the bind could be null.
I have a property:
private ObjectProperty<Operation> operation = new SimpleObjectProperty<>(null);
I also have a text field:
#FXML
private Text txtCurrentOperation;
I would like to bind the textProperty of the field to the value of the operation object.
My first thought was to use FluentAPI with its when/then/otherwise construct, but it is eagerly evaluated so the solution:
Bindings.when(operation.isNotNull())
.then("null")
.otherwise(operation.get().getName()));
will throw a NPE, because the parameter of otherwise is evaluated no matter what the result of the when.
My next idea was to use lambda somehow:
txtCurrentOperation.textProperty().bind(() ->
new SimpleStringProperty(
operation.isNotNull().get() ? "Null" : operation.get().getName()
));
But the bind has no lambda enabled solution. (Later I realized that it couldn't have, becasue the real work goes backward: the change of the binded object (operation) will trigger the update of the binder (the field text property).)
Some articles I found suggested to use an "extremal" value for the property instead of null. But Operation is a complex and heavy weight component so it is not trivial to construct an artifical instance to represent null. Even more, this seems to me boilercode, something the binding mechanism is designed to help eliminating.
My next try was to logically swap the binding direction and add listener to the operation property and let it update the field programatically. It works and rather simple as long as the need of update only depends the operation object instances:
operation.addListener((e) -> {
txtCurrentOperation.setText(operation.isNull().get() ?
"Null" : operation.get().getName());
});
operation.set(oper);
It is relatively simple, but doesn't work: it throws "A bound value cannot be set." exception and I don't see why is the text property of the control regarded as bound.
I ran out of ideas. After much searching, I still cannot solve the simple problem to update a text field differently based on whether the source is null or not.
This seems so simple and everyday problem, that I am sure I missed the solution.
If a 3rd party library is an option, check out EasyBind. Try something like this:
EasyBind.select(operation)
.selectObject(Operation::nameProperty)
.orElse("null");
There's also a JavaFX JIRA issue for the type of functionality provided by EasyBind. If you don't want to use a 3rd party library, try Bindings.select:
Bindings.when(operation.isNotNull())
.then("null")
.otherwise(Bindings.select(operation, "name"));
Be aware the null checking in Bindings.select isn't super efficient. There's a JIRA issue for it.
Just in case if somebody using not Java itself but Kotlin.
It is a good idea to use wonderful tornadofx library.
There you can just use operation.select{it.name}. Although, this feature seems not to be documented yet, so it took some time to discover it.

How do I make one of a stub's method call the real method in ASMock?

In flex I want to do something similar to the following
var audioPlayerMock:AudioPlayer = AudioPlayer(mockRepository.createStub(mockRepository.createStub(AudioPlayer));
SetupResult.forCall(audioPlayerMock.play).(CALL_ACTUAL_PLAY_METHOD(WITH_ARGUMENT));
AudioPlayer has a lot of methods that I want stubbed, (so I use mockRepository.creatStub()). But there is one method, play(), that I want to call the actual actual method (super.play(argument) if my thinking is right). I'm not sure how to do this?
I know I can use createDynamic(AudioPlayer) then stub out every other method, but that is a bit tedious.
Cheers
You can use IMethodOptions.callOriginalMethod() to call the actual implementation on a stubbed class:
SetupResult.forCall(authatoPlayerMock.play(null))
.ignoreArguments()
.callOriginalMethod();

How to find the number of returned objects from an Actionscript getter function?

In my Flex project, I have a service function getItems() that returns me a collection/array of Item objects.
The function runs an SQL statement like SELECT * FROM table. I therefore do not want to use a SELECT COUNT SQL statement.
I know that if I use a Flex spark:DataGrid, I can easily get the length of the datagrid to know the number of rows (which in my case, would be the number of objects returned by my getItems() function). However, I am using an mx:AdvancedDataGrid and it is not possible to get the length by the same means as with the spark:DataGrid.
Actually, I need to dynamically create a set of labels with text={ItemName}. Using a Vbox and a for loop, I am able to create a list of labels. At the moment, I have a random number for the delimiter in my for loop. I just need to get the number of objects returned by my getItems() function. I can then put that number in the for loop and the job is done.
At least, this is how I plan to do this task.
Is there a better way to do this?
PS: I have Googled extensively, but I could not find any working examples for what I want to do.
Suggestions are welcome and StackOverflow is fabulous!
[EDIT] I eventually used an mx:Repeater to do the task described above.
how about this:
private function getItemsResultHandler(event:ResultEvent):void
{
var items:ArrayCollection = new ArrayCollection();
items = event.result as ArrayCollection;
trace(items.length);
}
Am I missing something? Does this work for you?
var list:IList = getItems();
trace(list.length)
ArrayCollection has a length attribute inherited from ListCollectionView. Is this what you need?
You can simply do
getItemsResult.lastResult.length
or
(getItemsResult.lastResult as ArrayCollection).length
or
ArrayCollection(getItemsResult.lastResult).length
All are essentially the same.

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)

Two-way data binding in actionscript causes stack overflow

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.

Resources