Nested Data in Flex DataGrid by Extending DataGridColumn

Skill

Nested Data in Flex DataGrid by Extending DataGridColumn

Posted in:

There are several technologies that I use on a very regular basis, among these are XML and Flex - which shouldn't come as a surprise to anyone. And anyone who has played with Flex a good deal knows that outputting nested XML or object data to a DataGrid isn't as easy as it should be. So today I am going to show off some code to make this much easier.

Below you see a typical DataGrid that is showing off some data. In this particular case it looks like a class schedule of sorts. It doesn't look like anything special, but that is the point. We have a normal working DataGrid. The interesting part is the data that is being used as the dataprovider and the syntax used for the columns of the DataGrid. You may notice you can't sort the columns, this is a side-effect of our new DataGridColumn but can be fixed in individual cases.

Get Adobe Flash player

The first thing we will take a glance at is the XML data that we are pulling in. The data below is pulled in using a HTTPService as XML and then used as the data for the dataprovider for our grid. Bask in the glory of the XML:

<?xml version="1.0"?>
<classes>
  <class id="ECON 500" name="Microeconomic Analysis">
    <professor name="Carol Rankin">
      <email>fake@xu.edu</email>
    </professor>
    <days>T</days>
    <time>6:00 pm - 9:30 pm</time>
    <location>
      <building name="Hailstone Hall" />
      <room>17</room>
    </location>
  </class>
  <class id="ACCT 500" name="Financial Accounting">
    <professor name="Gary Motl">
      <email>fake@xu.edu</email>
    </professor>
    <days>R</days>
    <time>6:00 pm - 9:05 pm</time>
    <location>
      <building name="Hailstone Hall" />
      <room>17</room>
    </location>
  </class>
  <class id="ECON 501" name="Macroeconomic Analysis">
    <professor name="Carol Rankin">
      <email>fake@xu.edu</email>
    </professor>
    <days>T</days>
    <time>6:00 pm - 9:30 pm</time>
    <location>
      <building name="Hailstone Hall" />
      <room>17</room>
    </location>
  </class>
  <class id="MKTG 500" name="Marketing">
    <professor name="John Kerr">
      <email>fake@xu.edu</email>
    </professor>
    <days>R</days>
    <time>6:00 pm - 9:30 pm</time>
    <location>
      <building name="Hailstone Hall" />
      <room>3</room>
    </location>
  </class>
</classes>

Alright next we are going to take a look at the code that makes up the main application. I will step through the code right after.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application width="500" xmlns:mx="http://www.adobe.com/2006/mxml"
   xmlns:components="com.pfp.components.*" layout="absolute"
   creationComplete="classService.send()" height="200">
  <mx:Script>
   <![CDATA[
     import mx.rpc.events.ResultEvent;
     
     private function updateData(event:ResultEvent):void
     {
       classesDG.dataProvider = XML(event.result).descendants("class");
     }
   ]]>
 </mx:Script>
  <mx:HTTPService id="classService" url="data/classes.xml"
     result="updateData(event)" resultFormat="xml" />
  <mx:DataGrid id="classesDG" width="100%" height="100%">
    <mx:columns>
      <components:DataGridColumnNested width="75"
         dataField="@id" headerText="Class" />
      <components:DataGridColumnNested
         dataField="@name" headerText="Name"/>
      <components:DataGridColumnNested width="90"
         dataField="professor@name" headerText="Professor" />
      <components:DataGridColumnNested width="45"
         dataField="days" headerText="Days" />
      <components:DataGridColumnNested width="90"
         dataField="location.building@name" headerText="Building" />
      <components:DataGridColumnNested width="50"
         dataField="location.room" headerText="Room" />
    </mx:columns>
  </mx:DataGrid>
</mx:Application>

Above you can see we have our data being pulled in when the creationComplete event is fired on the application. We ask that the data be returned in XML format from our HTTPService and is processed by the updateData function. The updateData function simply casts the resulting data as XML and then sets the dataProvider on our DataGrid to the XMLList that is the classes (the class descendants).

The only other component we have is our DataGrid, which has 6 columns but you will notice that these aren't the normal columns. These columns are instances of the custom DataGridColumn component we are about to build. You can see that we use the @ symbol to return the attribute values and also . (dot) notation to retrieve nested children. Our column will take care of the rest of the work to make sure the label has the correct output in it.

Finally we have our new custom component. The code for this class follows.

package com.pfp.components
{
  import mx.controls.dataGridClasses.DataGridColumn;

  public class DataGridColumnNested extends DataGridColumn
  {
    public function DataGridColumnNested(columnName:String=null)
    {
      super(columnName);
      this.sortable = false;
    }
   
    override public function itemToLabel(data:Object):String
    {
      var fields:Array;
      var attribute:String;
      var label:String;
     
      var dataFieldSplit:String = dataField;
      var currentData:Object = data;
     
      if(dataField.indexOf("@") != -1)
      {
        fields = dataFieldSplit.split("@");
        dataFieldSplit = fields[0];
        attribute = fields[1];
      }
     
      if(dataField.indexOf(".") != -1)
      {
        fields = dataFieldSplit.split(".");
       
        for each(var f:String in fields)
          currentData = currentData[f];
         
        if(currentData is String)
          return String(currentData);
      }
      else
      {
        if(dataFieldSplit != "")
          currentData = currentData[dataFieldSplit];
      }
     
      if(attribute)
      {
        if(currentData is XML || currentData is XMLList)
          currentData = XML(currentData).attribute(attribute);
        else  
          currentData = currentData[attribute];
      }
     
      try
      {
        label = currentData.toString();
      }
      catch(e:Error)
      {
        label = super.itemToLabel(data);
      }
     
      return label;
    }
  }
}

First I have to acknowledge this post on Extending DataGrid Column to Use on Nested Data for helping me with the starting point. I took what that person had done and expanded and modified it.

Above you will see all the work is done inside the ItemToLabel function, which takes in the current data object and returns the string to display in the cell. We override this function and parse apart the dataField property of the column to get the appropriate object to show. We start by pulling off the attribute if there was an @ in the dataField. Then we loop through any . (dots) to go through nested children, as this is done we change the current object we are paying attention to. Then in the end we check to see if we need to grab an attribute from that final object. The last piece simply calls toString() on the object and if it doesn't work we do the default DataGridColumn behavior.

That pretty much takes care of it. To make a sort function work you will need to add the sortable property and also give the column a sortCompareFunction, you can learn more about these from the Flex 3 DataGridColumn docs. I know this was a quick one but I just wanted to show everyone an easy way to accomplish this tedious task. If you have any questions or concerns leave a comment.

Crawford
04/01/2008 - 13:21

Good article.

reply

Jeff
04/13/2008 - 18:06

Goo article, but how do you make the nested data editable so that the bound data is updated when you have the "editable" property set to true?

reply

Fabio Kehyaian
08/08/2008 - 12:59

Good post.
I have the same question as Jeff.
Anybody can help me out with this.

thanks.
Fabio

reply

D
05/05/2008 - 10:05

where in all this can you null check so that you dont get this if the object is null?

TypeError: Error #1009: Cannot access a property or method of a null object reference. at components::DataGridColumnNested/itemToLabel()
at mx.controls.dataGridClasses::DataGridBase/makeListData()

reply

SD.Plox
06/05/2008 - 15:54

i found another good solution here:

http://www.cflex.net/showFileDetails.cfm?ObjectID=603

instead they use an label function that it's more simple but maybe you don't need a solution like that.

therefore, know it is ever a good idea.

PD: thanks 'The Fattest I' was very useful for me : )

reply

The Fattest
06/05/2008 - 17:09

Thanks for the link SD.Plox, I have written about labelfunctions before. I was simply providing an alternative solution, which can be used in a general solution.

reply

Kiall / Managed I.T.
06/09/2008 - 15:33

You literally just saved my life. I was all of 30 seconds away from trashing the office.

Thanks!

reply

Nested data and barchart
07/17/2008 - 17:04

this was very helpful for my datagrid. How can I get my nested data to work with a BarChart?

reply

The Fattest
07/17/2008 - 18:46

Sorry but I don't know very much about the charting in Flex.

reply

Ravi
09/16/2008 - 06:57

How can i use this for editable grid and save the value to the corresponding attribute

reply

Aaron Hardy
09/17/2008 - 15:22

Thanks for the post and good solution. It makes me wonder why Adobe didn't anticipate this...or if they did, why they didn't implement something similar.

reply

Nate Ross
10/13/2008 - 19:22

Nice solution... I recently made a DataGrid that supports nested properties by extending DataGrid instead of DataGridColumn. It allows for the dataField property of DataGridColumn to contain values like object1.object2.object3.property or object1.objectList2[2].object3.property without adding any new properties. Feel free to check it out at http://nateross.wordpress.com/2008/10/11/nested-data-grid/

reply

Nate Ross
01/07/2009 - 21:37

The link above is broken, instead use this one to access the NestedDataGrid solution http://natescodevault.com/?p=61

reply

Shannon Pettit
02/21/2009 - 14:58

You may also want to checkout:

http://learn.adobe.com/wiki/display/Flex/DataGrid

You can use ItemRender to created nested datagrids that are still sortable.

reply

Rohit kotecha.
11/26/2009 - 05:18

hi ,
very helpful post. my problem got solved of displaying nested property of object in datagrid. I have kept editable="true" of datagrid so that user can edit data.

But on click event of submit button i can't get edited data column of datagrid for which i have set datafield to nested object property. so is there any extra function need to override?

Thank and regards,
Rohit kotecha.
kotecharohit@gmail.com

reply

S Nichols
12/09/2009 - 19:21

Below is an example that should support traditional getter methods: example: getName().lastName;

Traditional getter are sometimes used in DTO and value add objects.

                public static function invokeMethod(objectContainingMethod:*, methodName:String, parms:Array):* {
                        var method:Function = objectContainingMethod[methodName];
                        var returnValue:* = method.apply(objectContainingMethod, parms);
                        return returnValue;
                }
               
                override public function itemToLabel(data:Object, withFormatting:Boolean=true):String
                {
                if (dataField.indexOf(".") != -1)
                {
                       
                        var fields:Array = dataField.split(".");
                        var currentData:Object = data;
                        // snichols: Added null check.
                        if(data == null) {
                                return "";
                        }

                        for(var i:int = 0; i < fields.length - 1; i++) {
                                var classInfo:XML = describeType(currentData);
                                        var methods:ArrayCollection = new ArrayCollection();
                                        for each (var a:XML in classInfo..method) {
                                                methods.addItem(String(a.@name));
                                        }                              
                                var methodName:String = String(fields[i]).replace(/(\()|(\))/gi, "");
                                if(methods.contains(methodName))
                                        currentData = NestedColumn.invokeMethod(currentData, methodName, null);
                        }
                       
                        var label:String = currentData[fields[fields.length - 1]];

                        if (withFormatting)
                        return applyFormatting(label);
                        return label;
                }
               
                return super.itemToLabel(data, withFormatting);
                }

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.

Sponsors