Monday, October 31, 2011

Flex4: Automatic Data grid generation using reflection in Action Script 3

When building web applications in flex 4 using blazeds services, the most straightforward option to present raw datas is to use a Datagrid (or AdvancedDatagrid). Usually the client side developer must implement some data visualization and manipulation component, which heavily relies on datagrid usage. I found myself doing every time the same things: cut and paste, change String values, rename some variables. Such a process is time consuming and do not really add some values to the programmer work, it is just mechanic action quitting time to real problem solving activities.
Using the reflection capabilities of Actionscript 3, you can forget about repetitive task such the one above still retaining a certain degree of collaboration with your designer. The following tutorial shows how you can write your own little framework for data-grids automatic generation and visualization. The example can be further modified to implement also more complex visualization requirement using the same reflection capability.
Reflection in as3 does not provides the same functionalities of the Reflection API in Java, still provides some tools useful to automate some tasks such that automatic Datagrid generation and visualization. In this example, we have a simple web application using remote services provided by a blazeds server. We have this simple class replying an imaginary db table named "Employee" (I omit remoting specific metadata definition for now):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package
{
  public class Employee
  {
    public function Employee()
    {
    }
    [Column(name="Id")]
    public var id:int;
    [Column(name="Name")]
    public var name:String;
    [Column(name="Surname")]
    public var surname:String;
    [Column(name="Department")]
    public var department:String;
  }
}
We can note the usage of a custom metadata tag named Column. We will use such a information to initialize Datagrid's headerText and to inform our routine that the specific property must be included in a column. We create now a custom component in flex 4, containing just a Datagrid and a PopUpMenuButton as follow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<s:Group 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="632" height="300"
     creationComplete="init()">
  <fx:Declarations>
    <!-- Place non-visual elements (e.g., services, value objects) here -->
  </fx:Declarations>
  <mx:DataGrid x="124" y="29" width="400" height="95" id="grid">
  </mx:DataGrid>
  <mx:PopUpMenuButton x="124" y="0" label="Hide/Show columns"/>
 
  <fx:Script>
    <![CDATA[
      public var cls:Object;
 
      private function init():void {
        //More to come later
      }
    ]]>
  </fx:Script>
</s:Group>
Note that we define a public variable of a generic type Object. Such a variable must be istanziated by the containing component to a object of the type Employee (if we want to display Employee data, but the point is that we can pass any object in here).
1
2
3
4
5
6
7
8
9
10
11
<?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="955" 
         minHeight="600"  xmlns:ns1="*">
        <fx:Declarations>
    <!-- Place non-visual elements (e.g., services, value objects) here -->
    <ns1:Employee id="badulo"/>
  </fx:Declarations>
  <ns1:customGrid horizontalCenter="0" verticalCenter="0" cls="{badulo}">
  </ns1:customGrid>
</s:Application>
At this point, we can make use of the Reflection capability of as3, using the describeType function of the flash.utils package. I'm not going to give a deep description of this function as internet is full of references, I just say that it returns an XML object containg a full bunch of informations about the type Class of its parameter. We focus on the XMLList containg the variables description and with a bit of xml processing we are able to automatically create columns defining headerTexts and dataFields without a line of mxml code. As always, we want to generate a drop-in component reusable in any project which can free us from annoying tasks. So, let's define an as3 Class responsible of doing all the background processing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package
{
  import flash.utils.describeType;
 
  import mx.controls.dataGridClasses.DataGridColumn;
 
  public class ColumnsGenerator
  {
    private var cls:Object;
 
    public function ColumnsGenerator(cls:Object)
    {
      this.cls = cls;
    }
 
    public function generateColumns():Array {
      var desc:XML = describeType(cls);
      var vars:XMLList = desc.variable;
      var cols:Array = new Array();
      for each (var v:XML in vars) {
        var mdValue:String;
        if ((mdValue = getHeaderText(v.metadata)) != null) {
          var col:DataGridColumn = new DataGridColumn();
          col.headerText = mdValue;
          col.dataField = v.@name;
          cols.push(col);
        }        
      }
      return cols;
    }
 
    private function getHeaderText(md:XMLList):String {
      for each (var v:XML in md) {
        if (v.@name == "Column") {
          return v.arg[0].@value;
        }
      }
      return null;
    }
  }
}
The generateColumns function process the xml and looks for our custom metadata informations. If they are present, the DatagridColumn is initialized and added to the array. Note that we are almost done, it remains to call the function in our custom component and update the columns of the datagrid:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<s:Group 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="632" height="504"
     creationComplete="init()">
  <fx:Declarations>
    <!-- Place non-visual elements (e.g., services, value objects) here -->
  </fx:Declarations>
  <mx:DataGrid x="124" y="29" width="400" height="95" id="grid">
  </mx:DataGrid>
  <mx:PopUpMenuButton x="124" y="0" label="Hide/Show columns"/>
  <s:TextArea x="0" y="123" width="632" height="177" id="ta"/>
  <s:TextArea x="0" y="308" width="632" height="196" id="ta1"/>
  <fx:Script>
    <![CDATA[
      public var cls:Object;
 
      private function init():void {
        var cg:ColumnsGenerator = new ColumnsGenerator(cls);
        grid.columns = cg.generateColumns();
      }
    ]]>
  </fx:Script>
</s:Group>
At this point, you can use the preceding tutorial to add automatic columns hide/show functionality with a little modification. The result is a totally automated datagrid generation and visualization logic embedded into a custom component, which can be reused in any part of your project just passing to it a specific Class instance. You can also add more custom metadatas to your as3 Classes to address some issues as columns order and renderer definition using Reflection. As a final comment, note that you must add the directive
-keep-as3-metadata “Column”
to your compiler to retain custom metadata information at runtime.