Home > .NET, Vs2010, WCF > Dynamic treeviews with Dynatree in ASP.NET pages

Dynamic treeviews with Dynatree in ASP.NET pages

2012/03/21

I was requested of to implement a treeview in a ASP.NET project, which is highly “ajaxsized” so was not an option the common asp:TreeView that does an Postback at every click.

Searching with Google i have found an official Jquery plugin that is no more maintained , and even the bassistance plugin is no more maintained.

JsTree seems a good solution , but at a first sight i have not found how to change the node icons in a simple manner.

Searching more i have discovered DynaTree: was not a good sensation the website , but the documentation and demos pages are interesting.

The demos are downloadable so i have tried on my own pc, the first approach was difficult because the samples are working if you run them under visual studio 2010 using his own web server, instead under IIS this simple code is not working

$("#tree").dynatree({
  initAjax: {
    url: "sample-data3.json"
  },
 
  onActivate: function(node) {
    $("#echoActive").text("" + node + " (" + node.getKeyPath()+ ")");
  },
 
  onLazyRead: function(node){
    node.appendAjax({
      url: "sample-data2.json",
    });
  }
});

The reason is that the .json files under IIS are not knowed as extension, so the result was


Renaming the .json as .htm (and obviously changing the code) the sample was working under IIS (iis7, in Windows 7 64bit).

Then i have tried to create a real sample, trying to construct the nodes reading from a database , and here was the beginning of the problem.

The samples are very exhaustive and covers an very huge array of situations, but there is not a clear explanation for an ASP.NET developer how to write a web services : is explained only that the lazy loading is expecting a JSON string in the format

[ { “title”: “Node1”, “isLazy”: true, “key”: “BC13B21636CD6D5C”, … }, { … }, … ]

That is a JSON string with the brackets [] as delimiter.

In every case when is needed a JSON serialization is better to write a class , so i wrote TreeNode.cs:

public class TreeNode
{
    public string title { get; set; }
    public bool isFolder { get; set; }
    public bool isLazy { get; set; }
    public string key { get; set; }
    public string addClass { get; set; } 
}

In this class i have implemented some of the options for a DynaTree, it could be completed with other options at will.

The last property addClass serves to customize a node (specific icon, colors, etc.) with a CSS class.

I have tried to write a normal web service (with .asmx extension) with this code:

[WebMethod]
[ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
public List<TreeNode> GetNodes(string key)
{
    List<TreeNode> objLisT = new List<TreeNode>();
    string strNodeTitle = string.Empty;
    if (key.Equals("1"))
    {
        strNodeTitle = "Divisions";
        objLisT.Add(
                new TreeNode()
                {
                    isFolder = true,
                    isLazy = true,
                    key = string.Format("{0}{1}", key, "1"),
                    title = strNodeTitle
                }
        );
    }
    return objLisT;
}

The problem is that this web service returns something as

<?xml version="1.0" encoding="UTF-8"?>
<ArrayOfTreeNode xmlns="http://tempuri.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <TreeNode> 
        <title>Divisions</title> 
        <isFolder>true</isFolder> 
        <isLazy>true</isLazy> 
        <key>11</key> 
    </TreeNode>
    ....

Not exactly the needed json.

Note that is have not defined addClass in my TreeNode, is not the present in the generated XML so will be null.

The solution is to develop an WCF service.

In the Visual Studio 2010 ASP.NET project right clic -> Add New Item -> WCF Service

In my sample i have named my service TS.svc (TS=Tree Service) , by default are created TS.svc and ITS.svc, the ITS.svc is the interface definition (the Contract) that defines by default an DoWork method , inplemented in TS.svc that inherites from ITS, the default declaration is

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TS : ITS

For our quick and dirty solution we can delete the ITS.svc (ok, it should not be done…) and delete the inherits in TS.svc, so it is

public class TS

In order to the web service returns a JSON as needed our implementation is

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
 
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TS
{
    [OperationContract]
    [WebInvoke(Method = "GET",
    ResponseFormat = WebMessageFormat.Json,
    UriTemplate = "GetNodes/{key}",
    BodyStyle = WebMessageBodyStyle.Bare)]
    public List<TreeNode> GetNodes(string key)
    {
        List<TreeNode> objLisT = new List<TreeNode>();
        string strNodeTitle = string.Empty;
        if (key.Equals("1"))
        {
            strNodeTitle = "Divisions";
            objLisT.Add(
                    new TreeNode()
                    {
                        isFolder = true,
                        isLazy = true,
                        key = string.Format("{0}{1}", key, "1"),
                        title = strNodeTitle
                    }
            );
        }
        else
        {
            switch (key)
            {
                case "11":
                    // we need list of divisions  
                    strNodeTitle = "Division A";
                    objLisT.Add(
                            new TreeNode()
                            {
                                isFolder = true,
                                isLazy = true,
                                key = string.Format("{0}-{1}", key, "1"),
                                title = strNodeTitle
                            }
                    );
                    strNodeTitle = "Division B";
                    objLisT.Add(
                            new TreeNode()
                            {
                                isFolder = false,
                                isLazy = false,
                                key = string.Format("{0}-{1}", key, "2"),
                                title = strNodeTitle,
                                addClass = "custom1"
                            }
                    );
                    break;
 
                default:
                    break;
            }
            //strNodeTitle = string.Format("Child Key-{0}", key);
        }
        //
        return objLisT;
    }
}

The key is the BodyStyle defined as WebMessageBodyStyle.Bare

If we use WebMessageBodyStyle.Wrapped testing the service locally


We don’t have in output an xml string in the browser , but we are asked to download a file (in this case named “1”)

that contains

{“GetNodesResult”:[{“addClass”:null,”isFolder”:true,”isLazy”:true,”key”:”11″,”title”:”Divisions”}]}

Which is not the expected format.

Instead with Bare body style we are asked to download a file that contains

[{“addClass”:null,”isFolder”:true,”isLazy”:true,”key”:”11″,”title”:”Divisions”}]

The format we needs.

So we can create a test page as:

<div id="tree">
</div>
<div>Active node: <span id="echoActive">-</span></div>
 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script> 
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js" type="text/javascript"></script>
<script type="text/javascript" src="../Scripts/jquery.cookie.js"></script>
<script type="text/javascript" src="../Scripts/jquery.dynatree.js"></script>
 
<link href="../JQueryRedmondTheme/skin-vista/ui.dynatree.css" rel="stylesheet" type="text/css"/>
 
<script type='text/javascript'>
    $(document).ready(function () {
        $("#tree").dynatree({
            initAjax: {
                url: "TS.svc/GetNodes/1"
            },
            onActivate: function (node) {
                $("#echoActive").text(node.data.title);
            },
            onDeactivate: function (node) {
                $("#echoActive").text("-");
            },
            onLazyRead: function (dtnode) {
                dtnode.appendAjax({
                    url: "TS.svc/GetNodes/" + dtnode.data.key
                });
            }
        });
    });
</script>

In this code we see that we need a reference to JQuery, JQuery UI, cookie and Dynatree as scripting , and to the specific CSS.

For a specific class ,in our sample custom1 defined in the TreeNode instance for the Division B node , we can define our CSS in a custom CSS

span.custom1 span.dynatree-icon{
    background-position: 0 0;
    background-image: url("Images//red.gif");
}

Red.gif is a red point.

The initAjax is called for the initial construction, onLazyRead is called for every node that we defined as isLazy=true; onActivate when we click a node .

Refer to the official documentation for other infos.

When we add a WCF service the web.config is modified with a template that we need to change in order to have our sample working.

In my case i wrote

</system.webServer>
<system.serviceModel>
	<behaviors>
		<endpointBehaviors>
			<behavior name="TSAspNetAjaxBehavior">
				<webHttp/>
			</behavior>
		</endpointBehaviors>
		<serviceBehaviors>
			<behavior name="TestServiceBehavior">
				<serviceMetadata httpGetEnabled="true"/>
				<serviceDebug includeExceptionDetailInFaults="true"/>
			</behavior>
		</serviceBehaviors>
	</behaviors>
	<services>
		<service name="TS" behaviorConfiguration="TestServiceBehavior" >
			<endpoint  behaviorConfiguration="TSAspNetAjaxBehavior"
			 binding="webHttpBinding" contract="TS" />
		</service>
	</services>
</system.serviceModel>
</configuration>

Note that in “service name ” and “contract” i wrote “TS”, if you use a namespace (that should be done…) you must write the complete namespacing.

Ok at this point we can launch our test page:


Clicking on Divisions node


If you put a breakpoint in WCF code you can debug the service.

Advertisements
Categories: .NET, Vs2010, WCF
%d bloggers like this: