I have a datgrid with two inline item renderers. The dataprovider for my DG is a nested object (objects within objects within objects i.e 3-layered).
Main Object - 1st Level
|
2nd Level Object 1
|
3rd level object '1' => ('name'=>somename,'id'=>someid)
3rd level object '2'
.
.
.
.
3rd level object 'n'
2nd Level Object 2
|
3rd level object '1' => ('name'=>somename,'id'=>someid)
3rd level object '2'
.
.
.
.
3rd level object 'n'
I use 2 item renderers (one for each datagrid column) which loops thro the 2nd level object1 and 2 respectively (the 2nd level object is a dynamic array of objects, in that the number of objects within keep changing).
Within the item renderer I loop thro the 2nd level object using a foreach and then display the data. The data is a linkbutton, which when clicked , calls a remote object function to delete the data from the database
now on the result event of the remote object function call, i call the function to repopulate the DG, so that the updated data is displayed.
When i click on the linkbutton in the first row, the backend works perfectly fine (the data gets deleted from the database and the refreshed data is sent back), but for some reason, the deleted data suddenly appears in the 2nd row.
When i delete it from the second row, it appears on the 3rd row (nothing happens in the backend since the data is already deleted).. and so on, till it appears on the last row and then the DG looks exactly the way it shld have looked after the first delete.
This is just the beginning. The second item renderer also displays a linkbutton, which when clicked, displays that data in the previous column (the one where this data can be deleted). When i click on 1st row, the data gets added in the previous column of the second row .. and so on..
Basically, my DG is acting really weird. I overrided the set data function in the item renderer to refrsh the data and called its invalidateDisplayList. I also call the Datagrid's invalidateDisplayList function after each refresh. The behavior remains the same.
Please help me on this ...
Here's my DB code :
<mx:DataGrid id="privilegesDG" width="100%" variableRowHeight="true" minHeight="500">
<mx:columns>
<mx:DataGridColumn headerText="Roles Assigned">
<mx:itemRenderer>
<fx:Component>
<mx:VBox creationComplete="box1_creationCompleteHandler()">
<fx:Script>
<![CDATA[
import com.pm.modules.events.UpdateDBEvent;
import mx.containers.HBox;
import mx.controls.Alert;
import mx.controls.Label;
import mx.controls.LinkButton;
import mx.events.FlexEvent;
override public function set data(value:Object):void{
super.data = value;
super.invalidateDisplayList();
}
protected function box1_creationCompleteHandler():void
{
for each(var temp:Object in data.roles){
var hgrp:HBox = new HBox();
hgrp.autoLayout = false;
var lbl:Label = new Label();
lbl.text = temp.rname;
var lb:LinkButton = new LinkButton();
lb.label = 'X';
lb.id = temp.rid.toString();
lb.focusEnabled = true;
lb.addEventListener(MouseEvent.CLICK,handleClick);
hgrp.addElement(lbl);
hgrp.addElement(lb);
this.addElement(hgrp);
}
}
protected function handleClick(event:MouseEvent):void{
dispatchEvent(new UpdateDBEvent(UpdateDBEvent.ON_DELETE_PRIVILEGE_ROLE_MAP,0,0,0,event.target.id,0,true));
}
]]>
</fx:Script>
</mx:VBox>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
<mx:DataGridColumn headerText="Roles Not Assigned">
<mx:itemRenderer>
<fx:Component>
<mx:VBox creationComplete="box1_creationCompleteHandler()">
<fx:Script>
<![CDATA[
import com.pm.modules.events.UpdateDBEvent;
import mx.containers.HBox;
import mx.controls.Alert;
import mx.controls.Label;
import mx.controls.LinkButton;
import mx.events.FlexEvent;
override public function set data(value:Object):void{
super.data = value;
super.invalidateDisplayList();
}
protected function box1_creationCompleteHandler():void
{
for each(var temp:Object in data.notroles){
var lb:LinkButton = new LinkButton();
lb.label = temp.rname;
lb.id = temp.rid.toString();
lb.addEventListener(MouseEvent.CLICK,handleClick);
this.addElement(lb);
}
}
protected function handleClick(event:MouseEvent):void{
dispatchEvent(new UpdateDBEvent(UpdateDBEvent.ON_ASSIGN_ROLE_TO_PRIVILEGE,data.ID,event.target.id,0,0,0,true));
}
]]>
</fx:Script>
</mx:VBox>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
The updateDBEvent is responsible for calling the remote function that updates the DB.
Not really sure if this will help, would need to look at your code to be able to say for sure. But if you want the DataGrid to redraw, dont call the
invalidateDisplayList()
instead, call
invalidateList()
Related
I am creating an opensource application for managing environmental resources in my county.
I have a problem on a dataprovider for an itemrenderer (combobox) i am using on a datagrid.
The application works, but I get a warning saying the itemrenderer dataprovider will not be reassigned data on its update with a setter. Even if I do not need to reassign the combobox itemrenderer dataprovider, for a matter of best practice I would like to solve this warning.
This is the usual code I use for getting dataprovider data as an array collection populated from the result of a web service in the parentDocument of the itemrenderer:
//set farmers arrayCollection values for combobox itemrenderer
[Bindable]
private var _acFarmers:ArrayCollection=new ArrayCollection;
public function set acFarmers(acFarmers:ArrayCollection):void{
_acFarmers=acFarmers;
}
//get machines ArrayCollection values
public function get acFarmers():ArrayCollection{
return _acFarmers;
}
This is the code for the datagrid itemrenderer (showing only the interested column of the datagrid):
<mx:DataGridColumn headerText="Agricoltore" dataField="farmerId" width="200" rendererIsEditor="true" editable="false">
<mx:itemRenderer>
<fx:Component id="cmpCmbFarmers">
<mx:HBox>
<s:ComboBox width="200"
id="cmbFarmers"
dataProvider="{outerDocument.acFarmers}"
labelField="companyName"
change="onSelectionChange(event)"
>
<fx:Script>
<![CDATA[
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.events.ListEvent;
private var _ownerData:Object;
private function setSelected():void {
}
override public function set data(value:Object):void{
if(value != null) {
super.data=value;
_ownerData=value;
if(value.collectingMachineId!==null){
for each(var dp:Object in cmbFarmers.dataProvider){
var dpFarmerId:String=dp.farmerId
var dataFarmerId:String=value.farmerId;
if(dpFarmerId==dataFarmerId){
cmbFarmers.selectedItem=dp;
}
}
} else {
cmbFarmers.selectedIndex=0;
data.farmerId=cmbFarmers.selectedItem.farmerId;
}
}
}
import spark.events.IndexChangeEvent;
protected function onSelectionChange(event:IndexChangeEvent):void
{
data.farmerId=cmbFarmers.selectedItem.farmerId;
}
]]>
</fx:Script>
</s:ComboBox>
</mx:HBox>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
This code works if I call the itemrenderer service to get combobox data BEFORE calling the datagrid data service and setting the datagrid arraycollection at re response of the service.
BUT a warning is displayed because the combobox dataprovider will not get changes after a set function on its dataprovider (_acFarmers).
This is the only warning I have on an entire project but i did not manage how to solve it.
I would really appreciate any help.
Thanks
Paolo
You have to put [Bindable] metatag above your setter instead of above _acFarmers. Because right now your acFarmers property is not the source for data binding.
Or better use just public variable instead of getter and setter since you didn't implement other actions in them.
Link about databinding
Try to use dataChangeEvent
you can use the dataChange event with an item renderer or item editor. Flex dispatches the dataChange event every time the data property changes.
<mx:ItemRenderer dataChange="someFunction()">
or
dataToDisplay.addEventListener( FlexEvent.DATA_CHANGE, dataChange );
I have a datagrid within a custom component. This DG accepts a nested object as its dataprovider, and so my datagrid is rendered like this :
<mx:DataGrid id="privilegesDG" dataProvider="{privArray}" width="100%" variableRowHeight="true">
<mx:columns>
<mx:DataGridColumn dataField="Name" />
<mx:DataGridColumn dataField="Alias" />
<mx:DataGridColumn headerText="Roles Assigned" dataField="roles">
<mx:itemRenderer>
<fx:Component>
<mx:VBox creationComplete="box1_creationCompleteHandler(event)">
<fx:Script>
<![CDATA[
import com.pm.modules.events.UpdateDBEvent;
import mx.containers.HBox;
import mx.controls.Label;
import mx.controls.LinkButton;
import mx.events.FlexEvent;
[Bindable]private var prID:int;
protected function box1_creationCompleteHandler(event:FlexEvent):void
{
for each(var temp:Object in data.roles){
prID = temp.rid;
var hgrp:HBox = new HBox();
hgrp.autoLayout = false;
var lbl:Label = new Label();
lbl.text = temp.rname;
var lb:LinkButton = new LinkButton();
lb.label = 'X';
lb.focusEnabled = true;
lb.addEventListener(MouseEvent.CLICK,handleClick);
hgrp.addElement(lbl);
hgrp.addElement(lb);
this.addElement(hgrp);
}
}
protected function handleClick(event:MouseEvent):void{
dispatchEvent(new UpdateDBEvent(UpdateDBEvent.ON_DELETE_PRIVILEGE_ROLE_MAP,0,0,0,prID,0,true));
}
]]>
</fx:Script>
</mx:VBox>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
So i have an inline itemrenderer that displays the nested data. Now eveytime an update operation occurs, i call a function to re-populate this datagrid. But the column which gets populated with the nested data dsnt display anything...
I searched the net and found that mayb i shld call the updateDisplayList function for this component ?
So i tried smthing like this :
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
super.updateDisplayList(unscaledWidth,unscaledHeight);
}
protected function handleResult(event:ResultEvent):void{
if(event.result.toString() == 'false')
Alert.show("Could not perform operation");
else{
RO.getPrivilegesAndRoles(); //re-populates DG
invalidateDisplayList();
}
}
I really dont know what I shld put inside that function. Should i call the re-populating function inside updateDisplayList. I tried that but it dsnt work ...
The creationComplete event of your item renderer occurs only one time on initialization. As far as item renderers are recycling the new data won't populate in your item renderer.
You can solve it two ways:
Create MXML item renderer. As far as its base class is VBox it is the best option here. So you can get rid of data binding to populate data changes automatically. To populate all the roles use Repeater component instead of your loop using {data.roles} as data provider.
The second option is to use ActionScript and to have UIComponent as the base class. In this case you should override override public function set data(value:Object):void and call invalidateDisplayList() from there as far as subscribing on data.roles's collectionChange event and call invalidateDisplayList() from change handler too. Then in updateDisplayList() you should loop your data and create/populate all the controls. The second option can give you better performance but it is not a good way for novices.
So I recommend you to use MXML version with binding.
I havent found quite what I want elsewhere or at least am not accomplished enough to adapt
I'm trying to create a project that shows radio stations with logo, listen button etc
The datasource is an xml file with child tags like 'name','image' and 'listen' for each parent, 'station'.
I access this data via the main application with creationComplete="radioService.send()"
I can then use it on the main app via dataProvider="{radioService.lastResult.stations.station}
However, I want to use the results to populate variables in the custom component in both the components themselves
hard coded examples
mx:Image source="Images/BBC5.gif" id="bbc5Logo" "/>
mx:LinkButton id="bbc5Listen" click="bbc5Listen_clickHandler(event)" />
and in the clickhandler
protected function bbc5Listen_clickHandler(event:MouseEvent):void
{
var url:String = "http://www.bbc.co.uk/iplayer/console/bbc_radio_five_live";
var request:URLRequest = new URLRequest(url);
}
Any help, much appreciated
I done something similar before though I am not sure of what look and feel you wish your app to have but it would be possible to create a Datagrid and bind the datasource to that grid.
Then you can add fields to the datasource like ( img field and use the link in the XML by setting the xpath towards that field) , it would also be possible to create a button and place that in the row of the datagrid for listen.
Obviously this is a slightly different implementation but the same principle applys if you have created a custom component then make sure it makes use of DataSource and DataSource fields which will aquire the data from the right part of the XML. Then it would be possible to map that DataSource field to a String for example.
Hope this helps
You can actually use Custom Events, where you can pass values from your main app to your custom component.
http://www.anujgakhar.com/2009/03/04/flex-passing-data-with-custom-events/
Another approach could be to create Value Objects from your XML and bind your components to these objects, this way you would decouple your components from the XML , which can be handy if at some point in the future, you don't want to use XML, but JSON for instance.
Each Value Object would have the properties defined in your question , i.e , imageSource , id , url etc... and each component would be bound to one Value Object
Edit
Have a look at this video training, it covers data retrieval from an XML , ArrayCollections of ValueObjets etc...
http://www.adobe.com/devnet/flex/videotraining.html
there are two ways to accomplish this:
you should wrap your data structure
from xml source to a [Bindable] actionscript
object (not mandatory, but you can
handle it easier), and bind the array of these data instances (let's say RadioChannel.class) as the dataProvider of your grid. Inside your itemRenderer implementation you can access the current row's RadioChannel instance via the data getter/setter:
RadioChannel.as:
[Bindable]
public class RadioChannel {
private var _name: String;
private var _imageUrl: String;
private var _listenUrl: String;
public function RadioChannel()
{
}
public function get name():String {return _name;}
public function set name(value:String):void {_name = value;}
public function get imageUrl():String {return _imageUrl;}
public function set imageUrl(value:String):void {_imageUrl = value;}
public function get listenUrl():String {return _listenUrl;}
public function set listenUrl(value:String):void {_listenUrl = value;}
public function toString():String
{
return 'RadioChannel ' + name + ' - image: ' + imageUrl +
' | listen at ' + listenUrl;
}
}
RadioList.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:DataGrid xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx" dataProvider="{radioChannels}">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private var _radioChannels:ArrayCollection = new ArrayCollection();
[Bindable]
private function get radioChannels():ArrayCollection {return _radioChannels;}
private function set radioChannels(value:ArrayCollection):void {_radioChannels = value;}
]]>
</fx:Script>
<mx:columns>
<mx:DataGridColumn>
<mx:itemRenderer>
<fx:Component>
<mx:Image source="{data.imageUrl}">
<fx:Script>
<![CDATA[
public override function set data(value:Object):void
{
super.data = value;
trace('current instance: ' + RadioChannel(data).toString());
}
]]>
</fx:Script>
</mx:Image>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
<mx:DataGridColumn dataField="name" />
<mx:DataGridColumn>
<mx:itemRenderer>
<fx:Component>
<mx:HBox>
<fx:Script>
<![CDATA[
protected function onLinkClicked(event:MouseEvent):void
{
var currentChannel:RadioChannel = RadioChannel(data);
if (data)
{
var request:URLRequest = new URLRequest(currentChannel.listenUrl);
[...]
}
}
]]>
</fx:Script>
<mx:Button click="onLinkClicked(event)" label="Listen" />
</mx:HBox>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
The other way is to build up custom
events that deal with an extra
parameter: the url of the channel.
These custom events should cascade back to the list component, where they should be handled.
Since for the second solution you would still need the first's binding as well, i'd choose the first one, and handle the click event locally.
hope this helped.
I think this may be useful, to pass the value from mainApp to custom component you can follow this method.Since you have the value in mainApp,in the custom component you can access the value by using parentDocument object.parentDocument.rootAppVar
While this may be a simple problem, I'm having a heck of a time coming up with a solution.
I have a DataGrid with a ComboBox as an ItemRenderer for one of my columns. When the user selects a row, I want to get the ComboBox's selected value for the selected row.
EDIT: I should have mentioned that the dataField2_Array property in myData is actually an Array is the dataProvider for the ComboBox. Each object in myData could have completely different values in that Array so the ComboBox in each row of the DataGrid could have completely different options to pick from.
Any suggestions?
Some sample code:
<mx:DataGrid id="myGrid"
dataProvider="{myData}">
<mx:columns>
<mx:DataGridColumn headerText="Column 1" dataField="dataField1" />
<mx:DataGridColumn headerText="Column 2" dataField="dataField2_Array">
<mx:itemRenderer>
<mx:Component>
<mx:HBox paddingLeft="5">
<mx:ComboBox id="myComboBox" dataProvider="{data.dataField2_Array}" />
</mx:HBox>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
<mx:DataGrid ="MyDataGrid">
<mx:columns>
<mx:DataGridColumn headerText="Resource" width="200" itemRenderer="com.myClasses.myGridDropdownRenderer"/>
</mx:columns>
</mx:DataGrid>
Here is your itemRenderer for your datagrid.
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox prompt="Please select a Rating" change="stuffChanged()" dataProvider="{data.dataField2_Array}"
xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import flash.events.Event;
import mx.controls.Alert;
import mx.core.Application;
import mx.collections.ArrayCollection;
override public function set data( value:Object ) : void {
super.data = value;
}
public function stuffChanged():void{
var myListData:DataGridListData = DataGridListData(listData);
var r:int=myListData.rowIndex;
var c:int=myListData.columnIndex;
//Application.application.whateverStuff[r+1][c]=this.value;
Application.application.whateverStuff[r+1][c]=
this.selectedItem.data;
}
]]>
</mx:Script>
</mx:ComboBox>
This will be in your main Application which will be holding this value.
[Bindable] public var whateverStuff:ArrayCollection;
Now when your data is changed, it holds the data. You click on the button store this value in a rows object.
[Bindable] public var rows:Object = new Object();
rows=Application.application.whateverStuff;
When your sending the value back to database, send all along with this rows object.
Update:
After i read your comment on the previous riposte, i came to know that each of your combo box has a different options. You should have mentioned it earlier.
When you click on the selected rows, you should able to collect the ID of the row, and this would ensure that only the ID of that row is getting updated in the database regardless even if you update your combo box of other rows.
Once your select a row, click and verify on which row ID you have selected using Alert or trace, then send that rows value alone through an event dispatcher.
Add a function called myGrid_click to the click event of your DataGrid:
<mx:DataGrid id="myGrid" dataProvider="{myData}" click="myGrid_click(event)" >
In this function, store the grid's selectedIndex and use that to get the object out of its dataProvider (let's say it's an array of MyObjects, and we're interested in the dataField2 property of these MyObjects):
public function myGrid_click(event:MouseEvent):void {
var index:int = myGrid.selectedIndex;
var obj:MyObject = myData[index];
var value:String = obj.dataField2;
}
If, as is often the case, the object isn't storing the real value, and is just storing an index to a lookup table (dataField2_Array?), write a for loop to iterate over dataField2_Array looking for that value (actualValue) and assign it to a previously declared variable of greater scope (selectedRowComboBoxValue):
public function myGrid_click(event:MouseEvent):void {
var index:int = myGrid.selectedIndex;
var obj:MyObject = myData[index];
var value:int = obj.dataField2;
for (var i:int = 0; i < dataField2_Array.length; i++) {
if (value == dataField2_Array[index].id) {
selectedRowComboBoxValue = dataField2_Array.actualValue;
break;
}
}
}
Hi I wanted to ask if there is a possibility to have both strings and radiobuttons in one column depending on the value of another column perhaps
|column1 | column 2 |
|r | radiobutton|
|s | string |
If there is an r in column 1 in column2 should appear a radiobutton, otherwise column 2 just shows a string.
Thanks for your answers
Sebastian
You do need to write an item renderer to do this. However, you want to update the state of the render whenever the "data" property is set. This is important since item renderers are recycled. Basically the data property gets set whenever the data for that renderer changes and this happens as you scroll the DataGrid.
Here's a simple Application with a DataGrid:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="onCreationComplete()">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] private var collection:ArrayCollection;
private function onCreationComplete():void
{
collection = new ArrayCollection();
for (var i:uint = 0; i < 20; i++)
collection.addItem({name:'Person #'+i});
}
]]>
</mx:Script>
<mx:DataGrid width="600" dataProvider="{collection}" rowCount="5">
<mx:columns>
<mx:DataGridColumn itemRenderer="com.foo.ItemRenderer"/>
</mx:columns>
</mx:DataGrid>
</mx:Application>
And a simple MXML-based renderer:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
override public function set data(value:Object):void
{
super.data = value;
// only show radio buttons if the "name" property of the data contains "5"; otherwise show a label
radioS.visible = radioS.includeInLayout = radioM.visible = radioM.includeInLayout = radioL.visible = radioL.includeInLayout = data.name.indexOf(5) > -1;
labelName.visible = labelName.includeInLayout = data.name.indexOf(5) < 0;
}
]]>
</mx:Script>
<mx:Label id="labelName" text="{data.name}"/>
<mx:RadioButton id="radioS" label="Small" groupName="radioGroup"/>
<mx:RadioButton id="radioM" label="Medium" groupName="radioGroup"/>
<mx:RadioButton id="radioL" label="Large" groupName="radioGroup"/>
</mx:HBox>
You need to write your own itemRenderer.
From a high-level, what you need to do is tell the column that you will render the column on a per row basis. Then, per row - you check the conditional that you need (like looking at a different column or whatever) and take the action that you want (like adding a radio button vs. some other component).
In the data column do something like this:
<mx:DataGridColumn id="yourColumn"
headerText="Cool Column" editable="false" itemRenderer="SpecialCanvas"/>
Then in a component called 'SpecialCanvas' (let's say he extends a canvas), you can can events or override methods to render as needed... For example:
override protected function initializationComplete():void
{
// check for the conditional that you want and add the component that
// you need to this canvas or what not.
}