How do I ensure a Flex dataProvider processes the data synchronously? - apache-flex

I am using an component, and currently have a dataProvider working that is an ArrayCollection (have a separate question about how to make this an XML file... but I digress).
Variable declaration looks like this:
[Bindable]
private var _dpImageList : ArrayCollection = new ArrayCollection([
{"location" : "path/to/image1.jpg"},
{"location" : "path/to/image2.jpg"},
{"location" : "path/to/image3.jpg"}
]);
I then refer to like this:
<s:List
id="lstImages"
width="100%"
dataProvider="{_dpImageList}"
itemRenderer="path.to.render.ImageRenderer"
skinClass="path.to.skins.ListSkin"
>
<s:layout>
<s:HorizontalLayout gap="2" />
</s:layout>
</s:List>
Currently, it would appear that each item is processed asynchronously.
However, I want them to be processed synchronously.
Reason: I am displaying a list of images, and I want the leftmost one rendered first, followed by the one to its right, and so on.
Edit:
I just found this answer.
Do you think that could be the same issue?

Instead of declaring the variable and using it as the binding source, declare two collections. Then, onCreationComplete call loadNext() which shifts an object out of the second array and pushes it into the first. When the item has been rendered (custom event dispatched by itemRenderer and caught) call loadNext() again until such time as your source array is empty and your bound dataProvider has all the images.
I can write it in code if this doesn't make any sense. ;)
<?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:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768" creationComplete="init()">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var _source : ArrayCollection = new ArrayCollection([
{"location" : "path/to/image1.jpg"},
{"location" : "path/to/image2.jpg"},
{"location" : "path/to/image3.jpg"}
]);
[Bindable] private var dataProvider:ArrayCollection;
protected function init():void
{
this.lstImages.addEventListener( "imageLoaded", handleImageLoaded);
loadImage()
}
protected function loadImage():void
{
if(this._source.length<=0)
return;
var image:Object = this._source.getItemAt(0);
dataProvider.addItem(image);
this._source.removeItemAt(0);
}
protected function handleImageLoaded(event:Event):void
{
loadImage()
}
]]>
</fx:Script>
<s:List
id="lstImages"
width="100%"
dataProvider="{_dpImageList}"
itemRenderer="path.to.render.ImageRenderer"
skinClass="path.to.skins.ListSkin"
>
<s:layout>
<s:HorizontalLayout gap="2" />
</s:layout>
</s:List>
</s:Application>
Your item renderer's image's complete handle will dispatch:
protected function handleImageLoaded(event:Event):void
{
owner.dispatch(new Event("imageLoaded"));
}
And that should load your images in a clean sequence.

Related

Flex Mobile Project Warning "warning: unable to bind to property 'myData' on class 'com.amec.controls.Text::TextListView'"

So on a Flex Mobile Project I am trying to bind the results from a Select SQLLite query to a view. But it is not displaying results and I get the following
warning: unable to bind to property 'myData' on class 'com.amec.controls.Text::TextListView'
From FLash Builder. Below is my view class. What is it I am missing or doing wrong?
<?xml version="1.0" encoding="utf-8"?>
<amec:BaseAddView xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:amec="com.amec.Components.*"
creationComplete="vw_creationCompleteHandler(event)" title="TextListView">
<amec:layout>
<s:VerticalLayout horizontalAlign="center" paddingLeft="5" paddingRight="5" paddingTop="5" paddingBottom="5"/>
</amec:layout>
<fx:Script>
<![CDATA[
import com.amec.BaseSql;
import com.amec.MenuMessage;
import mx.events.FlexEvent;
[Bindable]private var resultArr:ArrayCollection = new ArrayCollection();
import mx.collections.ArrayCollection;
protected function vw_creationCompleteHandler(event:FlexEvent):void
{
}
private function get myData():ArrayCollection
{
var conn:BaseSql = new BaseSql();
conn.stmt.text = "SELECT DMV_VALUE_1 FROM DOMAIN_VALUE WHERE DMV_DMN_ID = :id";
x.parameters[":id"] = id;
x.sqlConnection = conn.stmt.sqlConnection;
var x:SQLStatement = new SQLStatement;
x.text = conn.stmt.text;
x.execute();
var result:Array = conn.stmt.getResult().data;
var r:ArrayCollection = new ArrayCollection();
if (result)
{
r.source = result;
return r;
} else {
return null;
}
}
]]>
</fx:Script>
<s:List id="list" top="0" bottom="0" left="0" right="0"
dataProvider="{myData}" labelField="DMV_VALUE_1">
</s:List>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
Also I am not getting any results to show up in the view.
First, set the property to be Bindable:
[Bindable(event="myDataChanged")]
private function get myData():ArrayCollection{
...
}
Since you do not have a 'set' method defined. As such, whenever the 'myData' changes you will have to manually dispatch the myDataChanged event in order for Bindable to have an affect.
I suspect that you will also have to make myData() a public property; otherwise how will the classes that make binding work be able to introspect into your custom component in order to access the property.
Overall, it looks like you are choosing a very bad implementation approach. Is it is unusual to use a get method to make a database call and process the results. It much more common to encapsulate such code out of your view; and pass the result data into the view. There are many different ways to do this. You could do it with a singleton approach, or a dependency injection framework, or dispatching events from a service class with the result data.

Handling mouse click in Flex 4 List to find the selected item (since itemClick is gone)

I have prepared a simplified test case for my question. It will run instantly in your Flash Builder if you put the 2 files below into a project.
I'm trying to display a List of strings and a confirmation checkbox in a popup:
In the real application I dispatch a custom event with the string selected in the list, but in the test code below I just call trace(str);
My problem: if I use click event, then the window closes, even if I click at a scrollbar (the !str check below doesn't help, when an item had been selected in previous use). And if I use change event, then the window doesn't close, when I click on the same item as the last time. And the itemClick event seems not to be present in spark.components.List anymore.
Any suggestions please on how to handle this probably frequent problem?
Writing a custom item renderer and having a click event handler for each item seems to be overkill for this case, because I have strings in the list.
Test.mxml: (please click myBtn few times - to see my problems with click and change)
<?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:mx="library://ns.adobe.com/flex/mx"
minWidth="400" minHeight="300">
<fx:Script>
<![CDATA[
import mx.managers.PopUpManager;
private var _popup:Popup = new Popup();
private function showPopup(event:MouseEvent):void {
PopUpManager.addPopUp(_popup, this, true);
PopUpManager.centerPopUp(_popup);
}
]]>
</fx:Script>
<s:Button id="myBtn" right="5" bottom="5"
label="Open window" click="showPopup(event)" />
</s:Application>
Popup.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="240" height="240"
creationComplete="init(event)"
close="close()">
<fx:Script>
<![CDATA[
import mx.collections.ArrayList;
import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.events.CloseEvent;
import mx.events.ItemClickEvent;
import mx.managers.PopUpManager;
private var myData:ArrayList = new ArrayList();
private function init(event:FlexEvent):void {
// XXX in the real app data is updated from server
myData.removeAll();
for (var i:uint = 1; i <= 10; i++)
myData.addItem('Item #' + i);
}
public function close(event:TimerEvent=null):void {
PopUpManager.removePopUp(this);
}
private function handleClick(event:MouseEvent):void {
var str:String = myList.selectedItem as String;
if (!str)
return;
if (myBox.selected) {
Alert.show(
'Select ' + str + '?',
null,
mx.controls.Alert.YES | mx.controls.Alert.NO,
null,
handleConfirm,
null,
mx.controls.Alert.NO
);
} else {
sendEvent();
}
}
private function handleConfirm(event:CloseEvent):void {
if (event.detail == mx.controls.Alert.YES)
sendEvent();
}
private function sendEvent():void {
close();
// XXX in the real app dispatchEvent() is called
trace('selected: ' + (myList.selectedItem as String));
}
]]>
</fx:Script>
<s:VGroup paddingLeft="20" paddingTop="20"
paddingRight="20" paddingBottom="20" gap="20"
width="100%" height="100%">
<s:List id="myList" dataProvider="{myData}"
click="handleClick(event)"
width="100%" height="100%" fontSize="24" />
<s:CheckBox id="myBox" label="Confirm" />
</s:VGroup>
</s:TitleWindow>
Also I wonder, why do I get the warning above:
Data binding will not be able to detect assignments to "myData".
The Spark List dispatches an 'IndexChangeEvent.CHANGE'. You can listen for this event to know when the selection in the List has changed.
<s:List id="myList" dataProvider="{myData}"
change="handleIndexChange()"
width="100%" height="100%" fontSize="24" />
That event is only dispatched whenever the selected index actually changes, which means that when you reopen the window a second time an item might still be selected and when you click on that one, no CHANGE event will be fired. To fix this just deselect the selection before you close the window:
public function close():void {
myList.selectedIndex = -1;
PopUpManager.removePopUp(this);
}
Also make sure to dispatch your event with the selected item before you close the window (and deselect it).
As for your question about the binding warning: you get that message because you didn't mark 'myData' to be bindable. To fix this just use the [Bindable] tag:
[Bindable]
private var myData:ArrayList = new ArrayList();
or skip the binding altogether if you don't need it and just assign the dataprovider to the list in ActionScript:
myList.dataProvider = myData;
I'd recommend two solutions if you absolutely wnat to display what item was selected. Otherwise, the solution provided by RIAStar would do the trick.
Listen to rendererAdd and rendererRemove events within your PopUp
As explained here, you can easily access to your list's renderers without interfering with its virtualLayout business.
Use a custom renderer
I know. But as long as you keep your code clean, itemRenderers won't blow up your application's memory. They're made to render huge amount of items without memory leaks.
In your Test.mxml, modify the codes like:
<fx:Script>
<![CDATA[
import mx.managers.PopUpManager;
private var _popup:Popup;
private function showPopup(event:MouseEvent):void {
_popup = new Popup();
PopUpManager.addPopUp(_popup, this, true);
PopUpManager.centerPopUp(_popup);
}
]]>
</fx:Script>
And in your Popup.mxml, I am not sure why you have the TimerEvent in the close function.
Also the trace won't be shown, as you are calling the close() function immediately after the alert's YES button has been clicked..

Data Grid not displaying data in array collection

My data grid is displaying stale data, rather than the real time data available in it's data provider (array collection). I've tried refeshing the data in the collection, but that has no effect. Below is my code, does anyone see what could be the problem?
<mx:Accordion/>
<fx:Script>
<![CDATA[
private var _gridData:ArrayCollecton = new ArrayCollection;
[Bindable] public function get gridData():ArrayCollection { return _gridData; }
public function set gridData(value:ArrayCollection):void { _gridData = value; }
public function loadGridData():void {
// imgCollection is the data returned from the server
var tempCollection:ArrayCollection = new ArrayCollection();
for (var i:int = 0; i < imgCollection.length; i++)
{
var img:Object = new Object();
img.id = imgCollection.getItemAt(i).imgId;
img.url = "http://..." + imgCollection.getItemAt(i).imgId;
img.caption = (imgCollection.getItemAt(i).imgCaption == null) ? "": imgCollection.getItemAt(i).imgCaption;
img.group = images;
tempCollection.addItem(new ObjectProxy(img));
}
gridData = tempCollection;
<!-- Use http service to get data and save it in grid data array collection, this is run on accordion create completion and whenever data is added or removed from the array collection -->
}
]]>
</fx:Script>
<!-- NOTE: There is a cyclic binding between the data grid and the gridData array collection -->
<fx:Binding source="dg.dataProvider as ArrayCollection" destination="gridData"/>
...
...
<s:NavigatorContent>
<s:Panel>
<mx:DataGrid dataProvider="{gridData}" ...>
...
...
</mx:DataGrid>
</s:Panel>
</s:NavigatorContent>
UPDATE:
I tried the suggestions mentioned below, however, they do not resolve the issue. The data grid has custom item renderers, could that be the problem?
<?xml version="1.0" encoding="utf-8"?>
<s:MXDataGridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
focusEnabled="true">
<mx:Image id="image" source="{data.url}" height="65" maintainAspectRatio="true" scaleContent="true"/>
</s:MXDataGridItemRenderer>
You don't need "cyclical" binding, because in your datagrid you do not change the collection, but you change its items. The collection stays intact. the dataprovider of the DataGrid and your _gridData point to the same collection.
If I'm not mistaking you should have [Bindable] on the setter as well because the datagrid has not other way of knowing when your data has changed.
Regards, Alin
Looks to me like you are overthinking this. Since you aren't doing anything in your getter/setters you could get rid of them and just mark your ArrayCollection as Bindable, then set it as the dataProvider for the DataGrid and be done:
<fx:Script>
<![CDATA[
[Bindable]
public var gridData:ArrayCollecton = new ArrayCollection;
public function loadGridData():void {
// Whenever you change the gridData, the DataGrid will update appropriately.
}
]]>
</fx:Script>
<mx:DataGrid dataProvider="{gridData}"></mx:DataGrid>
The problem with your existing code is likely that you are not dispatching a change event in your setter. Getting rid of the getter/setters allows the ArrayCollection to handle dispatching that event for you. Hope that helps.
EDIT: Based on the updated question, you may want to try changing your renderer to look like this, which would help if your custom data object is not bindable.
<?xml version="1.0" encoding="utf-8"?>
<s:MXDataGridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
focusEnabled="true">
<fx:Script>
<![CDATA[
override public function set data(value:Object):void {
super.data = value;
image.source = value.url;
}
]]>
</fx:Script>
<mx:Image id="image" source="{data.url}" height="65" maintainAspectRatio="true" scaleContent="true"/>

Cannot bind data to DropDownList control in Flex 4

I'm fetching some data from a PHP application using Zend AMF. However I can't get the data to bind to a simple DropDownList control. The PHP method is:
class Test
{
public function myMethod()
{
$res = array();
$res[] = array('NAME' => 'ThisIsATest', 'ID' => 1);
return $res;
}
}
Network Monitor reports the method is returning results. It's returning the following as an array:
Array
(
[0] => Array
(
[NAME] => Property
[ID] => 1
)
)
Below is the Flex code:
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="500" height="286"
creationComplete="initApp()">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
private function myMethodResult(e:ResultEvent):void
{
searchType.dataProvider = e.result as ArrayCollection;
}
protected function initApp():void
{
service.myMethod();
}
protected function faultHandler(event:FaultEvent):void
{
trace(event.fault.faultString);
}
]]>
</fx:Script>
<fx:Declarations>
<s:RemoteObject id="service"
destination="zend"
source="Test"
showBusyCursor="true"
fault="faultHandler(event)">
<s:method name="myMethod" result="myMethodResult(event)"/>
</s:RemoteObject>
</fx:Declarations>
<s:DropDownList id="searchType" labelField="NAME"/>
</s:WindowedApplication>
Any help would be greatly appreciated. Thanks in advance.
You ask about binding, but I don't think that's what you want to know about. I believe the answer is this line in the result handler:
searchType.dataProvider = e.result as ArrayCollection;
I'm assuming that you are getting back an Array from ColdFusion. If memory serves me, you cannot cast an array as an ArrayCollection. The result will, most likely, be null. Have you stepped through code in debug mode to verify?
Instead try this:
searchType.dataProvider = new ArrayCollecection(e.result as Array);
Since e.result is a generic object, you'll need to cast it as an array.
To address the binding portion of your answer. Binding has a source and a value. When the source changes, the value is automatically updated. You have a value ( dropDownList.dataProvider ) that you want to change, but you do not have a source for that. Nothing in your code makes use of binding. You're just manually setting the value when the results come back. To make use of binding I might modify your code like this:
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="500" height="286"
creationComplete="initApp()">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
// create a variable taht can be used as the source for a binding operation
[Bindable]
public var mySource : ArrayCollection;
private function myMethodResult(e:ResultEvent):void
{
// searchType.dataProvider = e.result as ArrayCollection;
// change the value of your binding source
mySource = new ArrayCollection(e.result);
}
protected function initApp():void
{
service.myMethod();
}
protected function faultHandler(event:FaultEvent):void
{
trace(event.fault.faultString);
}
]]>
</fx:Script>
<fx:Declarations>
<s:RemoteObject id="service"
destination="zend"
source="Test"
showBusyCursor="true"
fault="faultHandler(event)">
<s:method name="myMethod" result="myMethodResult(event)"/>
</s:RemoteObject>
</fx:Declarations>
<!-- and finally, specify your dataProvider as the target for binding -->
<s:DropDownList id="searchType" labelField="NAME" dataProvider="{this.mySource }"/>
</s:WindowedApplication>
I wrote all code in a browser and it may not be "compile perfect"
#Flextras
searchType.dataProvider = new ArrayCollecection(e.result);
...resulted in...
1118: Implicit coercion of a value with static type Object to a possibly unrelated type Array.
Instead I tried...
searchType = ArrayCollection(e.result);
But this resulted in...
Error #1034: Type Coercion failed: cannot convert []#812a1c9 to mx.collections.ArrayCollection
Then I tried...
typeArray.source = e.result as Array;
...and...
<s:DropDownList labelField="NAME">
<s:ArrayCollection id="typeArray"/>
</s:DropDownList>
This works! \o/

using an image item render in a flex data grid

I'm attempting to add an image to a datagrid item render dynamically in flex.
Here is my DataGrid code
The value of "str" in the getImagePath function is correct.
<?xml version="1.0" encoding="utf-8"?>
<mx:DataGrid xmlns:mx="http://www.adobe.com/2006/mxml"
doubleClickEnabled="true">
<mx:Script>
<![CDATA[
private function userLabelFunction(item:Object, column:DataGridColumn):String
{
return item.user.username;
}
private function getImagePath(item:Object, column:DataGridColumn):String
{
var str:String=item.track["artwork-url"]
if (str == "")
{
str=item.user["avatar-url"];
}
return str;
}
]]>
</mx:Script>
<mx:columns>
<mx:DataGridColumn dataField="artwork-url"
headerText="Art"
itemRenderer="components.content.contents.datagrids.ImageRenderer"
labelFunction="getImagePath"/>
<mx:DataGridColumn dataField="title"
headerText="Title"
minWidth="100"/>
<mx:DataGridColumn dataField="user"
headerText="User"
labelFunction="userLabelFunction"/>
<mx:DataGridColumn dataField="bpm"
headerText="BPM"/>
</mx:columns>
</mx:DataGrid>
I cant manage to get that image url value into my item renderer
I've tried overriding the set data property like so
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml"
height="60"
verticalAlign="top"
creationComplete="init()">
<mx:Script>
<![CDATA[
import components.content.contents.containers.ContentContainerSoundCloud;
import mx.core.Application;
import mx.controls.dataGridClasses.DataGridColumn;
import com.adobe.viewsource.ViewSource;
[Bindable]
public var imgPath:String;
private function init():void
{
}
override public function set data(value:Object):void
{
super.data = value.track["artwork-url"];
imgPath =super.data.valueOf()
trace(imgPath)
}
]]>
</mx:Script>
<mx:Image source="{imgPath}" id="rowimage"/>
</mx:HBox>
But in doing so the trace output looks like so
<artwork-url>
<mx_internal_uid>7C98E149-1984-584C-7600-AD8940BF2A9C</mx_internal_uid>
</artwork-url>
I was expecting the "value" property in the set data to recieve the string I sent it from the get imagePathFunction but it fact it returns my entire XMLList.
What am I doing wrong?
Based on your trace output it looks like you are retrieving the wrong value from your dataProvider to set as the source. Without seeing your actual data, it's hard to know what the exact issue may be.
That said, I would re-work your itemRenderer a bit. First, you don't need to put a single image in an HBox. Just use an Image. Also, you should not need to specify the height in the Renderer, the DataGrid should take care of such positioning.
I also removed the creationComplete listener, since no code was in it. And instead of using binding, I just set the soruce property on the component in the data set method. I also set the super.data to the value, not a processed value; and I performed the processing when setting the value's source. The updated code is like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:Image xmlns:mx="http://www.adobe.com/2006/mxml"
>
<mx:Script>
<![CDATA[
import components.content.contents.containers.ContentContainerSoundCloud;
import mx.core.Application;
import mx.controls.dataGridClasses.DataGridColumn;
import com.adobe.viewsource.ViewSource;
[Bindable]
public var imgPath:String;
override public function set data(value:Object):void
{
super.data = value;
this.source =value.track["artwork-url"];
trace(imgPath)
}
]]>
</mx:Script>
</mx:Image>
My preferred method in itemRenderers is to listen to the dataChange event, however that's just personal preference. There is nothing wrong w/ overriding the data set method.

Resources