Data Grid not displaying data in array collection - apache-flex

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"/>

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.

Flex: DataGrid column formatting of numbers

I'm trying to format some numbers in a column of a DataGrid. I'm getting an error in my simplified test program below when I run it. All the examples I've seen so far have column data that are strings. Is there a way to do it using numbers? How to modify the code below to format the checking values?
<?xml version="1.0" encoding="utf-8"?>
<mx: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">
<fx:Script>
<![CDATA[
[Bindable]
public var checking:Array = new Array(1000000.2222, 0, 1000);
private function myLabelFunction(item:Array, column:DataGridColumn):String {
var result:String;
result = myFormatter.format(item);
return result;
}
]]>
</fx:Script>
<fx:Declarations>
<s:NumberFormatter id="myFormatter"
fractionalDigits="2"
decimalSeparator="."
groupingSeparator=","
useGrouping="true"
negativeNumberFormat="0"
/>
</fx:Declarations>
<mx:DataGrid id="dg1" dataProvider="{checking}" >
<mx:columns>
<mx:DataGridColumn dataField="checking" headerText="Checking"
labelFunction="myLabelFunction" />
</mx:columns>
</mx:DataGrid>
</mx:Application>
Change filter function signature (item should be Object)
private function myLabelFunction(item:Object, column:DataGridColumn):String
Remove dataField="checking" from column.
While the label function will certainly work -- I usually prefer an ItemRenderer for things like this. You override the render function and then you can display whatever it is in the grid view "box" however you like.
A decent example is here. Scroll down about 1/4 the way down for a DataGrid example.
In case of object you must use/No caso de objeto, deve-se usar:
private function myLabelFunction(item:Object, column:GridColumn):String {
var result:String;
result = myFormatter.format(item[column.dataField]);
return result;
}

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/

in Flex DropDownList, is there a way to bind to a property of an item in dataProvider?

I have following code.
<s:DropDownList dataProvider={_dataProvider}/>
<fx:Script>
private var _dataProvider:ArrayCollection = new ArrayCollection([{label:"one", data:1}, {label:"two", data:2}]);
</fx:Script>
I want to bind the data property of the selectedItem in the DropDownList. Is there a way to do this?
Not sure if this another approach to the question... but I created a custom dropdownlist and binded the selectedItem to incoming changes. When the value of my desired data changes it will trigger the dropdownlist to change its selection.
DropDownListBindable.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:DropDownList xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
[Bindable] public var valueField:String = "";
override public function set selectedItem(value:*):void{
try{
for(var i:uint=0;i<this.dataProvider.length;i++){
if(this.dataProvider[i][this.valueField]==value){
this.selectedIndex=i;
break;
}else{
this.selectedIndex=-1;
}
}
}catch(e:Error){}
}
]]>
</fx:Script>
</s:DropDownList>
On the application I import the custom dropdownlist and bind the valuefield with whatever needs to be binded... in your case its 'data'. I also created an object called 'mydata' for the dropdownlist to listen to for changes. When mydata changes the list will too. I've added a button to demonstrate how the list changes.
main.mxml
<?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"
xmlns:components="com.components.*">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] private var myData:Object = new Object();
[Bindable] private var _dataProvider:ArrayCollection = new ArrayCollection([{label:"one", data:1}, {label:"two", data:2}]);
]]>
</fx:Script>
<s:HGroup>
<components:DropDownListBindable
dataProvider="{_dataProvider}"
prompt="--select one--"
selectedItem="{this.myData}"
id="ddl"
valueField="data"
labelField="label"/>
<s:Button label="change datafield" click="this.myData=1"/>
</s:HGroup>
</s:Application>
I'm pretty sure the answer is no, but just to be clear; I'm confused.
If your dataProvider contains objects like this:
{label:"one", data:1}
First off, this syntax will create a generic object with no customization. If none of the properties on that object are explicitly defined, none of them can implement the Bindable metadata tag, and therefore when used as the source for data binding, the target will never update.
Second off, even if you created your own non-generic object with properties being bindable, binding doesn't usually go multiple levels deep into an object's properties of an array.
The selectedItem will point to an object like are in your _dataProvider, or possibly null, based on user interaction with the dropDownList. Binding the selectedItem to a property inside the item doesn't make sense; because you'd be comparing an literal to an object and nothing would ever be selected.
I'm unclear, without looking, what happens in the DropDownList when you try to set selectedItem to an item not in your dataProvider. I imagine it resets the selection, though.
If you can expand on what exactly you're trying to accomplish we may be able to help more.
<s:DropDownList id="ddl" dataProvider="{_dataProvider}"/>
<s:Label text="{ddl.selectedItem.data.toString()}"/>
Yes. You can do this. It is quite simple, actually. I do it all the time:
<s:DropDownList dataProvider="{_dataProvider}" selectedItem="#{_selectedItem}" />
With ActionScript that looks like this:
private var _dataProvider:ArrayCollection = new ArrayCollection([{label:"one", data:1}, {label:"two", data:2}]);
[Bindable] private var _selectedItem;
Every time the user selects an item in the drop down list, the _selectedItem will get set.

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

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.

Resources