Flex: dataProvider variable named "result" makes trouble. Why? - apache-flex

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var result : ArrayCollection = new ArrayCollection([1,2,3]);
]]>
</mx:Script>
<mx:List dataProvider="{result}"/>
</mx:Application>
I have this code. The problem is: if my variable for dataProvider is named "result", then in the running application the List contains the only element "[object Binding]". By if I rename "result" for anything else (for example "res"), the List is displayed as expected - "1", "2", "3". Why?

Short answer: THIS IS A BUG IN FLEX. I HAVE REPORTED IT.
This is odd... If we use the Spark List control, it won't even compile. It tells us that it can't convert Array to IList. result is obviously a variable some place, but where?
So I looked into the code that is generated using the -keep-generated-actionscript=true compiler flag.
Inside the ViewName-generate.as file, you will find an interesting method:
private function _ViewName_bindingsSetup():Array
{
var result:Array = [];
result[0] = new mx.binding.Binding(this,
function():Object
{
return (result);
},
null,
"_ViewName_List1.dataProvider"
);
return result;
}
This is where the Binding objects are making into your result variable.
We can see in the binding object that there is a function that returns (result). In any other case, this would be something else like (results). BUT, in this case, it is returning the local array of Binding objects. That is why this.result works. It is pulling out of the local scope!
So, this is obviously a bug. I have submitted it to Adobe as such: https://bugs.adobe.com/jira/browse/FB-29870

I am just experimenting with Lists and Arrays in Flex. I tried this.result, it worked fine. I assume the result is maybe reserved.
Rob

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.

Check to see if CallResponder is processing

I'm using Flash Builder 4.6. As a simple example, say I have the following application:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:sdk="services.sdk.*">
<fx:Script>
<![CDATA[
private function btnGetValue_clickHandler():void
{
getValueResult.token = sdk.getValue();
}
private function getValueResultHandler():void
{
// ...
}
]]>
</fx:Script>
<fx:Declarations>
<sdk:SDK id="sdk" fault="{Alert.show(event.fault.faultString +'\n\n'+ event.fault.faultDetail, 'SDK ERROR');}" showBusyCursor="false"/>
<s:CallResponder id="getValueResult" result="getValueResultHandler()"/>
</fx:Declarations>
<s:Button id="btnGetValue" click="btnGetValue_clickHandler()" label="Get Value" />
</s:Application>
So when you click on the button, it calls a PHP file and when it gets a result, it calls getValueResultHandler(). Easy enough.
But what if the response from the PHP file takes a second or two and the user clicks the button rapidly? Then the result handler function may not get called every time, since the call responder gets a new token before it received the last response.
Is there a standard way of resolving this issue? I came up with the following workaround, and it works fine, but it seems like this issue would be common enough to have a more built-in solution.
My workaround is:
var getValueResultProcessing:Boolean = false;
private function btnGetValue_clickHandler():void
{
var interval:uint = setInterval(function():void
{
if (!getValueResultProcessing)
{
getValueResultProcessing = true;
getValueResult.token = sdk.getValue();
clearInterval(interval);
}
}, 100);
getValueResult.token = sdk.getValue();
}
private function getValueResultHandler():void
{
getValueResultProcessing = false;
// ...
}
Any better way of resolving this issue?
EDIT:
One of my actual scenarios for which I need a solution is when looping:
private function btnGetValue_clickHandler():void
{
var arr:Array = new Array("value1", "value2", "value3");
for each (var value:String in arr)
getValueResult.token = sdk.getValue(value);
}
So to solve this problem I make arr a global variable and use the setInterval method I described before to shift off the first element and then do the same exact thing in the result function until arr is empty.
Again, looking for a more "standard" solution, if such a thing exists.
Instead of using CallResponder, you might want to manage the responses to service calls yourself. HTTPService (or similar Flex services) return an AsyncToken when you call the HTTPService.send() method. You can then add 'result' and 'fault' handlers by using the AsyncToken.addResponder() method.
If you do it this way, each responder will trigger it's result or fault handler as appropriate. Then use the last approach as suggest by Sam DeHaan, where you store each token in an array when the call is made, and remove the token from the array when the 'result' or 'fault' handler is executed. When removing a token from the array, you can re-enable the button if the array is empty.
You do it something like this (typing code from memory):
var service:HTTPService = new HTTPService();
// configure the service...
var token:AsyncToken = service.send();
// resultHandlerMethod and faultHandler method are functions that you should define
// their signatures look like this:
// resultHandlerMethod(event:ResultEvent)
// faultHandlerMethod(event:FaultEvent)
token.addResponder(new Responder(resultHandlerMethod, faultHandlerMethod);
If the call is button based, disable the button on click. Enable the button at the end of the callback. Simple.

Flex Filterfunction - Filter by Array of values

How to filter an Arraycollection by "array of values" rather than a single value (simple comparision) , the below code snippet is for filtering by single value now I went into scenario like filter out only price [10,4,1,8] (some random values from the master collection). Is there any better way to do the second code snippet
Filter function simple comparision
private function filterForTestData(item:Object):Boolean{
if(item.price < slider.value) return true;
else return false;
}
Filter by array of values
private function filterForTestData(item:Object,filterBy:Array= [10,4,1,8]):Boolean{
for(randomprice in filterBy)
return item.price == randomprice;
}
[Edited]
Apply Filter
testData.filterFunction = filterForTestData;
[Edit]
One point I haven't mentioned is my items in the filterBy array are custom data not the basic datatypes.
I was going to answer this saying use an object as a lookup then use hasOwnProperty to determine if the property is set on the filter Object. From actually testing my method along with these two (creating a mock set of data with strings of numbers (100,000 of them) and the variance between the methods in terms of run-time is negligble (ranges between 130 and 160ms). I think basically all three of these have the same running time.
Here's my full code so you can mess around and point out if I did something that invalidates my testing somehow but this seems to make sense to me, using indexOf it needs to iterate across the set until it finds the object (assuming a purely linear collection not a tree) same with hasOwnProperty (needs to get list of all properties and iterate across them, unless there's a mechanism built-in to find the property more efficiently:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
height="100%"
width="100%"
xmlns:code="http://code.google.com/p/flex-iframe/"
creationComplete="application1_creationCompleteHandler(event)">
<mx:Script>
<![CDATA[
import flash.utils.getTimer;
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
/* private function filterForTestData(item:Object):Boolean{
return filterBy.hasOwnProperty(item);
} */
/* private function filterForTestData(item:Object):Boolean{
return filterBy.indexOf(item) != -1;
} */
private function filterForTestData(item:Object):Boolean{
for(var randomprice in filterBy)
return item == randomprice;
return false;
}
[Bindable]
private var dp:ArrayCollection = new ArrayCollection(['1','2','3','4','5','6','7','8']);
private var filterBy:Object={'10':true,'4':true,'1':true,'8':true};
//private var filterBy:Array= ['10','4','1','8'];
protected function button2_clickHandler(event:MouseEvent):void
{
// TODO Auto-generated method stub
var startTime:int = getTimer();
trace(getTimer());
dp.filterFunction = filterForTestData;
dp.refresh();
var endTime:int = getTimer();
trace("total time: " + (endTime-startTime).toString());
}
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
// TODO Auto-generated method stub
for(var i:int=0;i<100000;i++)
{
dp.addItem(Math.floor(Math.random()*100).toString());
}
theList.dataProvider = dp;
}
]]>
</mx:Script>
<mx:List id="theList" width="100"/>
<mx:Button click="button2_clickHandler(event)"/>
</mx:Application>
I'm going to try and think this through from a pure data perspective and see if it's even possible to get better running time than this or if it's just the nature of the problem that necessitates a certain number of checks (asymptotic analysis).
Good question.
[Edit]
Okay after thinking this through I believe without using a more complex data structure (and incurring the processor time cost up front in creating/re-organizing the data structure... thinking of keeping a balanced tree for search) I'm pretty sure any method will result in an equivalent or worse running time than these methods (that is linear running time or worse O(n)). Using a Tree structure you would get running times of O(log n) for search but incur more processing overhead in keeping the tree balanced (doing rotations as necessary). If anyone can invalidate this statement I would be happy if you did so, but I believe this is all true.
http://www.personal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/binarySearchTree.htm
Why not use the indexof method from Array?
private function filterForTestData(item:Object,filterBy:Array= [10,4,1,8]):Boolean{
return filterBy.indexof(item.price) != -1;
}
(This checks to see if the item's price value is found in the filterBy array. If it is not, the indexof method returns -1. Otherwise, it will return the index of the array.)
I haven't tested this -- you might have to cast the item.price to a Number type, but I'm not certain.

Adobe Air/Flex filtering large datasets

Hi I have an Air app that downloads a data set of around 100`000 objects and puts the objects in an ArrayCollection.
I would like to apply various filters to the data set in numerous screens throughout the app.
I am a little worried about possible performance issues if I make multiple copies of the collection. However, if I don't copy the collection any filters applied will be reflected in all the screens and this is not the behavior I need.
What would be the best way to give multiple views of this large data collection?
Has anyone had any experience with this kind of scenario.
Any help much appreciated.
You could use a class that acts as a proxy for the original ArrayCollection and the data stored in it. You can set the filter function on the proxy and have the proxy delegate most of its other functionality to the referenced ArrayCollection. You can probably start by subclassing ListCollectionView to do this.
You can have single array with original data but also have different ArrayCollections underlaing on same array. Thus, you can apply any filter to instances of ArrayCollections.
As example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
creationComplete="init();">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private var buffer:Array;
[Bindable]
private var listData1:ArrayCollection;
[Bindable]
private var listData2:ArrayCollection;
private function init():void
{
buffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
listData1 = new ArrayCollection();
listData1.source = buffer;
listData2 = new ArrayCollection();
listData2.source = buffer;
listData1.filterFunction = filter1
listData2.filterFunction = filter2;
listData1.refresh();
listData2.refresh();
}
private function filter1(item:Object):Boolean
{
var i:Number = Number(item);
if (i >= 5)
{
return true;
}
return false;
}
private function filter2(item:Object):Boolean
{
var i:Number = Number(item);
if (i <= 5)
{
return true;
}
return false;
}
]]>
</mx:Script>
<mx:HBox>
<mx:List id="list1"
dataProvider="{listData1}"/>
<mx:List id="list2"
dataProvider="{listData2}"/>
</mx:HBox>
</mx:Application>
I guess there are 2 options:
don't worry about performance: even if you have multiple collections, they will all point to the same data objects via references. Just create new ArrayCollections and pass in the objects as an array. You can then apply filters to the individual collections.
process the filters eagerly by applying a filter for each view and then copy the result into a new ArrayCollection. Once you filtered a collection, create a new one with as the source filteredCollection.toArray(). The same applies as the above: the collections will contain references to objects, not value copies.
I have exactly the same situation and developed this tool: surflex. It allows to select, filter and join collections really fast.

flex doesn't seem to bind with custom actionscript object

I have a custom actionscript object defined as bindable with a number of public properties.
[Bindable]
public class MyObject extends Object {
public var mobileNumber:String;
...
In my mxml I have:
<mx:Script><![CDATA[
import mx.binding.utils.BindingUtils;
import org.test.MyObject;
[Bindable]
private var obj: MyObject = new MyObject();
]]></mx:Script>
<mx:Label text="Mobile Number" id="mobileNumberLabel"/>
<mx:TextInput id="mobileNumberText" text="{obj.mobileNumber}" />
<mx:LinkButton label="Load" id="loadButton" enabled="true" click="obj = obj.load();"/>
<mx:LinkButton label="Save" id="saveButton" enabled="true" click="obj.write();"/>
My issue is that when I enter a new value in the field for mobile number and then click the save button, the value typed is not logged out... i.e.:
public function write():void {
var bytes:ByteArray = new ByteArray();
trace("write - mobile:" + this.mobileNumber);
bytes.writeObject(this);
EncryptedLocalStore.setItem(KEY, bytes);
}
I also tried adding in:
private function init():void {
BindingUtils.bindProperty(mobileNumberText, "text", obj, "mobileNumber");
}
but no luck with that either.
I'm probably missing something simple here, but not sure what it is. Hope you can help, thanks.
tst's answer is correct - bindings are one-way. I'm guessing you already knew that though, since you tried to setup the reverse binding in your init() method.
However, there's two problems with your init() method.
First, it's not clear where you put that init() method, or what calls it.
Second, you got the method parameters backwards.
What I typically do in situations like this is either use the mxml tag as the first responder suggested, or if I'm in AS3 code, I do something like this:
private function onCreationComplete(event:Event):void
{
BindingUtils.bindProperty(obj, "mobileNumber", mobileNumberText, ["text"]);
}
Note a couple of points here:
1/ BindingUtils.bindProperty() is "left hand side = right hand side". Thus, it's kinda like writing
obj.mobileNumber = mobileNumberText.text;
Or, closer to what is "actually going on" inside the binding classes:
obj["mobileNumber"] = mobileNumberText["text"];
2/ BindingUtils.bindProperty() actually wants an array as the last param. This is so that you can do "chained properties" logically like:
obj.mobileNumber = mobileNumbersGrid.selectedItem.text;
which would be
BindingUtils.bindProperty(obj, "mobileNumber", mobileNumbersGrid,
["selectedItem", "text"]);
3/ Best practice tip: if you're binding a property chain whose initial member is a property of yourself (this), then write it as:
BindingUtils.bindProperty(obj, "mobileNumber", this,
["mobileNumbersGrid", "selectedItem", "text"]);
This neatly handles the case where your "right hand side object" this.mobileNumbersGrid instance itself is replaced with a new instance object.
4/ If you ever reassign obj ("the left hand side"), you need to create a new binding to the new obj instance. Typically, you do this by turning the local obj property into a getter/setter pair like this:
public function get obj():MyObject
{
return _obj;
}
private var _obj:MyObject = new MyObject();
public function set obj(value:MyObject):void
{
_obj = value;
BindingUtils.bindProperty(_obj, "mobileNumber", mobileNumberText, ["text"]);
}
(Note: to be really careful, you'd stash the returned value from BindingUtils.bindProperty() which is a ChangeWatcher instance, and you'd unwatch() to prevent the old object from receiving property changes)
Hope this helps. Bindings are among the most powerful tools in the Flex framework, but can cause a lot of headaches when used poorly. In particular, be aware of memory leaks and "unexpected updates" when bindings are left "hanging around".
This code:
<mx:TextInput id="mobileNumberText" text="{obj.mobileNumber}" />
makes a one-directional binding between obj.mobileNumber and mobileNumberText.text. What you need is an additional reverse binding - add the following line to your code and it will work:
<mx:Binding source="mobileNumberText.text" destination="obj.mobileNumber"/>
Did you try implementing IEventDispatcher, or extending EventDispatcher?
Bindings work through dispatching of PropertyChangeEvents, so maybe this is the problem?
also, I am not sure, whether bindings work on variables, or whether they require properties (getters/setters).
You should try compiling with -keep-generated-actionscript and see if the resulting code makes any sense, i.e. dispatches any events, if the variable is accessed ...

Resources