ExtJS and ASP.NET Part 9- TreeGrid and WCF

In Extjs apart from combo box, another very useful control is Treegrid. I am going to show you how to populate a Treegrid using wcf json data service in an asp.net web form project.

On daily basis we need to present hierarchical data in a tree control, there is hardly any powerful and high quality ones that are available in javascript, I highly recommend jstree if you are constrained to jquery, coming into extjs, there are so many of those tree controls that you can use straight away. But the lack of documentation usually makes the initial learning curve too steep to climb over. When you work in a special server language, to figure out how to connect the controls to your server services might be even harder.

Good news is in a minute you are going to see how easy and simple it is to get a tree grid up and running.

Here is a screen shot of the control

Treegrid

Treegrid

The control I used is called Ext.ux.tree.TreeGrid it is in ux namespaces so it is not in the default suite of ext-all. This is the link of treegrid sample project from sencha, you can download the necessary libraries from there as well.

1.Set up the style sheet

<link rel="Stylesheet" type="text/css" href="lib/ext-3.1.1/resources/css/ext-all.css" />
  <link rel="stylesheet" type="text/css" href="lib/ext-3.1.1/ux/treegrid/treegrid.css"
        rel="stylesheet" />

2.Set up the libraries

  <script type="text/javascript" src="lib/ext-3.1.1/adapter/ext/ext-base-debug.js"></script>
    <%-- <script type="text/javascript" src="lib/ext-3.1.1/adapter/jquery/ext-jquery-adapter.js"></script>--%>
<script type="text/javascript" src="lib/ext-3.1.1/ext-all-debug.js"></script>
<script type="text/javascript" src="lib/ext-3.1.1/ux/treegrid/TreeGridSorter.js"></script>
<script type="text/javascript" src="lib/ext-3.1.1/ux/treegrid/TreeGridColumnResizer.js"></script>
<script type="text/javascript" src="lib/ext-3.1.1/ux/treegrid/TreeGridNodeUI.js"></script>
<script type="text/javascript" src="lib/ext-3.1.1/ux/treegrid/TreeGridLoader.js"></script>
<script type="text/javascript" src="lib/ext-3.1.1/ux/treegrid/TreeGridColumns.js"></script>
<script type="text/javascript" src="lib/ext-3.1.1/ux/treegrid/TreeGrid.js"></script>

They are all necessary for the treegrid control which is an extension of Ext.tree.TreePanel. I am not going into the details of this.

3.Set up the columns for the tree grid

var taskColumn ={
                header: 'Task',
                dataIndex: 'task',
                width: 230
             }
var ageColumn ={
                header: 'Age',
                dataIndex: 'age',
                tpl: new Ext.XTemplate('{age:this.formatnumber}',
               { formatnumber: formatnumber_shared }), // this is embeding functions in    template
               width: 100
                }

var durationColumn = 
            {   
                header: 'Duration',
                width: 100,
                dataIndex: 'duration',
                align: 'center',
                sortType: 'asFloat',
                tpl: new Ext.XTemplate('{duration:this.formatHours}', {
                        formatHours: function (v) {
                                        if (v < 1) {
                                            return Math.round(v * 60) + ' mins';
                                        } else if (Math.floor(v) !== v) {
                                            var min = v - Math.floor(v);
                                            return Math.floor(v) + 'h ' + Math.round(min * 60) + 'm';
                                        } else {
                                            return v + ' hour' + (v === 1 ? '' : 's');
                                        }
                                    }
                                })
                            }
                            
                            
 var assignedToColumn={
                 header: 'Assigned To',
                 width: 150,
                 tpl: new Ext.XTemplate(
                      '<tpl if="user!=null">',
                         '{user}',
                      '</tpl>',
                      '<tpl if="user==null">',
                         'user is null',
                      '</tpl>'),
                  dataIndex: 'user'
      }

So you can see I have 4 columns for my tree grid, it is slightly different from the sencha’s example just for the purpose of exploring the control a little bit.

The key fields for a column are:
Header – the column header, dataIndex is the field name of row data object, the other thing is I have provided 2 different ways of formatting the column, using Ext.XTemplate and a template formatting function.

4.Set up the tree grid

// static
 var  statictree = new Ext.ux.tree.TreeGrid({
                        title: 'Core Team Projects',
                        id: 'statictreeid',
                        rootVisible: false,
                        width: 500,
                        height: 300,
                   
                        selModel: new Ext.tree.MultiSelectionModel({
                            listeners: {
                                selectionchange: TreeSelectionChange
                            }
                        }),

                        renderTo: 'div2',

                        enableDD: false,
                        enableSort: false, // disable sorting
                        enableHdMenu: false, // disable hiding columns
                        columns: [taskColumn,ageColumn, durationColumn,assignedToColumn]
                    });

It is easy, is not it? the first column and only the first column is in the tree structure that are having parent and children relationship, and you can expand and collapse, other columns are just tabular data rendered in the same row. selModel is only needed when you need to handle the row selection of treegrid. rootVisible will be explained later. You can see I called it a static tree, it does not mean that we can not get data from a remote server, it just means that the tree is static, compared with a dynamic tree, the static tree will have all its nodes loaded at one time, while dynamic tree can allow the nodes being loaded on fly when you try to expand.

5.Get the data from wcf service and populate the tree

  //status display
 var mask = new Ext.LoadMask(Ext.getBody(), {
                msg: "Loading..."
            });
 mask.show();

var myparams = { 'pid': 1 };
 Ext.Ajax.request({
                url: 'Service1.svc/getStaticTreeData',
                params: Ext.encode(myparams),
                method: 'POST',
                headers: this.header || { 'Content-Type': 'application/json;charset=utf-8' },
                success: function (response, options) {
                    // response callback

                    //update status display
                    mask.hide();

                    // display literal output from response
                    var s = response.responseText;

                    // decode text into Json object
                    treedata = (Ext.decode(response.responseText)).d;

                      // populate tree
                     
                        var root = new Ext.tree.AsyncTreeNode({
                            text: 'Dummy',
                            id: 'Dummy',
                            children: treedata
                        });

                         statictree.setRootNode(root);
              

                },
                failure: function (response, options) {
                    mask.hide();
                    Ext.MessageBox.alert('Failed', 'Unable to get data');
                }

            });

If you have read part 2 of my extjs series, this is a standard post ajax call, tree is populated in the call back of ajax call.

The only trick here is : create an Ext.tree.AsyncTreeNode, and populate the children node with the server data, then shove the root to the statictree instance. The text and id field of root object is to do with the top node of treegrid, as I want to display from the second level which is my treedata rather than the single top node ‘dummy’, that is why I set rootVisible as false in the firt place.

The other key thing here is to contruct the data in right format which should be easy peasy for a seansoned server side programmer.

Let us have a look of server side code

6.The interface of wcf service method

[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json)]
    public TreeNode[] getStaticTreeData()
    {
} 

The important thing is to declare the treenode class in a composite way(composite pattern)

public class TreeNode
  {
      
        public string task { get; set; }
        public float duration { get; set; }
     
        public string user { get; set; }
        
        // system fields
        //this will be sent to server as node:id in dynamic mode
        public string id { get; set; }
        // to indicate end of node
        public bool leaf { get; set; }
        // to initially expand or not
        public bool expanded { get; set; }
        // to use different icon
        public string iconCls { get; set; }

        public int age { get; set; }
        public TreeNode[] children { get; set; } 
    }

A few fields are necessary like id,leaf,expanded,iconCls and children as they will be used by TreeGrid control . Task field has been dedicated to the first column, and you can add as many other fields as possible to suit the need of your project, and they do not have to be all rendered in the columns, and those invisible fields are accessible through
node.attributes.fieldname on client side.

The children field is the essence of composite structure, and it can be as many levels deep as you like.

An example of populating the data on server

[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json)]
    public TreeNode[] getStaticTreeData(int pid)
    {
List<TreeNode> topnodes = new List<TreeNode>();

        //first main node
        TreeNode node = new TreeNode();
        node.id ="m_100";
        node.task = "ColumnTree Example";
        node.age = 10; 
        node.duration = 1.25f;
        node.user = "user1";
        node.leaf = false;
        node.iconCls = "application-icon1";
        List<TreeNode> subnodes = new List<TreeNode>();

        TreeNode subnode = new TreeNode();
        subnode.id = "m_101";
  	subnode.task = "Abstract rendering in TreeNodeUI";
        subnode.age = 0; 
        subnode.duration = 0.2f;
        subnode.user = "user2";
        subnode.leaf = true;
        subnode.iconCls = "application-icon1";
        subnodes.Add(subnode);

  return topnodes.ToArray();

}

Id field is not visible on client side, but important to identify the row, leaf field tells the control if this is a leaf node or not, expanded field is to do with the initial rendering of the node status. iconCls is a hook that you can apply different css for nodes to suit your business need.

To finish the whole example, now back to client side.

An example of formatting function used by previous column template

     function formatnumber_shared(v) {
                if (v > 0) {
                    return v;
                }
                else {
                    return "";
                }
            }

An example of selection event handling of previous code

    function TreeSelectionChange(treeob, nodes) {
                if (nodes.length > 0) {

                    Ext.Msg.alert('You Selected' + nodes[0].attributes.task);

                  var  parentnode = treeob.tree.getNodeById("m_101");



                }
            }

This example shows how to retrieve data from tree node, and how to search a node through id.

Last but not least, a few advanced concepts of treegrid:

If you have a big tree, then you probably need to dynamic load tree nodes through Ext.tree.TreeLoader, wire it up with a wcf service, what it does is when you click a tree node, if it is not a leaf node, then an ajax call will be made to server with ‘node’ as the parameter name and node id as its value in the query, returned data will be used as child nodes of the node just clicked.

Tags: ,

This entry was posted on Friday, September 3rd, 2010 at 12:09 am and is filed under ASP.NET, Javascript. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

10 Responses to “ExtJS and ASP.NET Part 9- TreeGrid and WCF”

  1. How can I implement it?

  2. jacob says:

    is it possible for you to provide the source code for the TreeGrid and WCF project. maybe a solution file in zip?? Thanks in advance!!

    • CleanCodeNZ says:

      Sorry, it is not that simple to just post the source code. The best way to learn it is to try it out, if you are stuck, then feel free to ask.Cheers

  3. jacob says:

    I’m trying to use treepanel and not TreeGrid as you have. Do I need to extend the loader to load data from Json. do you have any examples for treepanel. Thanks for the reply

  4. jacob says:

    sorry for double replys..

    looking at the wcf ‘getStaticTreeData’, i don’t see anything added to the topnodes list (which is being returned) and the subnodes list being added to the ‘node.children’ (i assume thats where it would go?). Also the TreeNode class has the ‘children’ property as an array rather than a list, is it correct? I’m trying to do same thing, but my tree doesn’t load the children nodes. even if my webservice return i hardcoded simple json text. i’ve tried to return array of TreeNode as well as try serializing the parent treenode object, list and dictionary.

    sorry for a big reply.. its the only help i’ve come around. Thanks!!

    • CleanCodeNZ says:

      The result returned by wcf does not have top node, and it is used by root.children, and you set rootVisible: false in the config of tree telling tree to start rendering from root.children which is the data from wcf.

      What could possible go wrong here:

      Have you got this line?

      loader: new Ext.tree.TreeLoader({ preloadChildren: true }),
      

      Your json data can only be loaded through a treeloader, and children needs to be preloaded as it is a static tree.

      The other thing is in your json object, you must use ‘children’ as the name of a list of next level objects, you also need to ensure that treedata must be ready before the construction of your tree, normally it is done in the success callback of wcf ajax call.

      As the ‘children’ should be a list or an array, to be honest I do not know the difference between two, from json serialization perspective, there might not be any difference(not sure about this).

      As you are using treepanel, to me the difference of treegrid and treepanel is that treegrid is using a special Ext.tree.TreeNodeUI implementation on top of treepanel, from the data point of view it should be all similar.

  5. jacob says:

    hey thanks for keeping up. Much-Much appreciated.

    here is a piece of my webservice:

    [WebMethod]
    [ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
    public string Get()
    {
    string x = “[{” +
    “text: ‘Menu Option 1’,” +
    “leaf: true” +
    “}, {” +
    “text: ‘Menu Option 2’,” +
    “leaf: true” +
    “}, {” +
    “text: ‘Menu Option 3’,” +
    “leaf: true” +
    “}];”;
    return x;
    }

    and here is the output i’m getting:
    http://img813.imageshack.us/img813/7308/screenshottg.png

    rest of the code is same as your example. Define the tree, in the success of the ajax call, create the root and set to the tree.
    why is my tree getting childrens as single letter of the json string and not nodes with menu option1, 2….

    Thanks!!

    • CleanCodeNZ says:

      Well a couple of points:
      First: have you set up the first column dataIndex to be field of ‘text’
      Second: this is a string, have you used Ext.decode(yourstring) to turn it into json array before it is used?
      Third: I always use json serializer in wcf to serialize array of objects, rather than code up a json string format of which is hardly human readable, but to test this string, you can Ext.decode your string in browser end into json and feed it to your treeroot.children, the screenshot looks to me the string is not in correct format.
      Good luck.

  6. jacob says:

    thanks a lot, got it working!! really embarrassed to tell what i did wrong.. all the property name in the TreeNode class were starting with uppercase letter, they NEED to be in lowercase as you have in the example. Just didn’t realize that may be the issue. Note to anyone coming here for help. Much Much Appreciated!

  7. Thilakraj says:

    Will you please try to provide a example ExtJs4.1 Grid load with Web service.

Leave a Reply

*