Sunday, November 21, 2010

Nested AdvancedDataGrid

Flex them Grids! (AdvancedDataGrid as a subgrid with flat data)

A grid inside a grid is a common requirement; I thought. Yet, I scoured the internet for a solution and I couldn’t find it anywhere (…at least not something that could be done with “flat data”). The following is something I wanted to achieve.


Adobe LiveDocs show an example here http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf69084-7bf2.html where they put a chart in an itemrenderer. However they show this example with “Hierarchical” collection. Their data structure looks like:
   1: [Bindable]
   2:      private var dpHierarchy:ArrayCollection= new ArrayCollection([
   3:        {name:"Barbara Jennings", region: "Arizona", total:70, children:[  
   4:          {detail:[{amount:5},{amount:10},{amount:20},{amount:45}]}]},
   5:        {name:"Dana Binn", region: "Arizona", total:130,  children:[ 
   6:          {detail:[{amount:15},{amount:25},{amount:35},{amount:55}]}]},
   7:        {name:"Joe Smith", region: "California", total:229,  children:[ 
   8:          {detail:[{amount:26},{amount:32},{amount:73},{amount:123}]}]},
   9:        {name:"Alice Treu", region: "California", total:230, children:[ 
  10:          {detail:[{amount:159},{amount:235},{amount:135},{amount:155}]}
  11:        ]}
  12:      ]);      

However, this is almost never how one would get data from the server. We generally get a flat data-structure as ArrayCollection. The the trick would be to convert this flat structure into a Hierarchy of objects.






   1: private var _BaseData:ArrayCollection =  new ArrayCollection([
   2:     {region:'south', state:'FL', city:'Alachua', population:'6,098'},
   3:     {region:'south', state:'FL', city:'Alford', population:'466'},
   4:     {region:'south', state:'FL', city:'Altha', population:'506'},
   5:     {region:'south', state:'FL', city:'Altamonte', population:'41,200'},
   6:     {region:'south', state:'TX', city:'Addison', population:'14,166'},
   7:     {region:'south', state:'TX', city:'Perezville', population:'5,444'},
   8:     {region:'south', state:'TX', city:'Alamo', population:'14,760'},
   9:     {region:'north', state:'NY', city:'Airmont', population:'7,799'},
  10:     {region:'north', state:'NY', city:'Akron', population:'3,085'},
  11:     {region:'north', state:'NY', city:'Alabama', population:'1,881'},
  12:     {region:'north', state:'NY', city:'Albany', population:'95,658'}
  13: ]);
The converted object should now look like:



   1: private var _HData:ArrayCollection =  new ArrayCollection([
   2:     {region:'south', state:'FL', children:[{detail:[
   3:             {city:'Alachua', population:'6,098'},
   4:             {city:'Altamonte', population:'41,200'},
   5:             {city:'Alford', population:'466'},
   6:             {city:'Altha', population:'506'}
   7:         ]}
   8:     ]},
   9:     {region:'south', state:'TX', children:[{detail:[
  10:             {city:'Perezville', population:'5,444'},
  11:             {city:'Addison', population:'14,166'},
  12:             {city:'Alamo', population:'14,760'}
  13:         ]}
  14:     ]},
  15:     {region:'north', state:'NY', children:[
  16:         {detail:[
  17:                 {city:'Airmont', population:'7,799'},
  18:                 {city:'Akron', population:'3,085'},
  19:                 {city:'Alabama', population:'1,881'},
  20:                 {city:'Albany', population:'95,658'}
  21:             ]
  22:         }
  23:     ]}
  24: ]);

The following function uses a cursor to iterate over this ArrayCollection, read the city and population, creates a child object. Then it discards the repeated rows.




public function setHierarchy():void {
// sort first
var sort:Sort= new Sort();
sort.fields = [new SortField('state',false)];
_BaseData.sort = sort;
_BaseData.refresh();


var HData:ArrayCollection = new ArrayCollection();
var csr:IViewCursor = _BaseData.createCursor();


var previousState:String = "";
var counter:int = 0;
var children:Array = [];

while(!csr.afterLast) {
// flag to remove repeated rows
var addToHierarchy:Boolean = false;
var subGridObj:Object = {city:'',population:''};
subGridObj.city = csr.current.city;
subGridObj.population = csr.current.population;

// find next/previous values and compare with current record
var str:String = csr.current.state; 
if (previousState != str) {
children = [];
previousState = str;
// flag to remove repeated rows
addToHierarchy = true;
}
//create child objects and populate with child-data
children.push(subGridObj);
csr.current.children = [];
csr.current.children[counter] = {detail:children};
// copy to Hierarchy and neglect repeated rows
if (addToHierarchy) {
HData.addItem(csr.current);
}
csr.moveNext();
}

// finally make assignment as HierarchicalData
adg1.dataProvider = new HierarchicalData(HData);
}

The AdvancedDataGrid declaration should now take an ItemRenderer column




<mx:AdvancedDataGrid id="adg1" x="200" width="600" height="400" variableRowHeight="true"
verticalCenter="0">
<mx:columns>
<mx:AdvancedDataGridColumn headerText="Region" dataField="region" />
<mx:AdvancedDataGridColumn headerText="State" dataField="state" />
</mx:columns>

<mx:rendererProviders>            
<mx:AdvancedDataGridRendererProvider
dataField="detail" 
renderer="subGridRenderer" 
columnSpan="0" columnIndex="1"/>         
</mx:rendererProviders>  
</mx:AdvancedDataGrid>

and the itemrenderer would be composed of a grid:




<?xml version="1.0" encoding="utf-8"?>
<s:MXAdvancedDataGridItemRenderer 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">

<s:VGroup paddingBottom="10" paddingLeft="10" paddingTop="10">
<mx:AdvancedDataGrid id="subAdg" dataProvider="{data.detail}">
</mx:AdvancedDataGrid>
</s:VGroup>

</s:MXAdvancedDataGridItemRenderer>

2 comments:

FlashFlexNewbie said...

Techie,
This is very useful. Basically we have to reorganize data

Anonymous said...

Love the Kramer pic on your blog. Nicely done..