Click here to Skip to main content
Click here to Skip to main content

DHTML Tree View of Arbitrary Depth using AJAX

, 25 Aug 2005
Rate this:
Please Sign up or sign in to vote.
This article provides a gentle introduction to AJAX by applying that technology to significantly enhance a tree previously rendered using JavaScript.

Introduction

In a previous article from our team, Bryon Baker provides a JavaScript solution to present a TreeView in any web page. You can find that article here. This article is intended to build on that solution by providing a server side equivalent control, and to give a gentle introduction to AJAX.

While the code presented here is developed in a type-safe manner using Generics from .NET Framework 2.0, it can be easily ported back to 1.1 by using an ArrayList instead of List<> in Tree.cs. The code for .NET 1.1 has also been included.

AJAX

AJAX is a technique for progressively building web content, most notably used for:

  • Building pages progressively (the technique used here).
  • Making server requests, for example, for real-time validation.

In this example, a tree is built progressively by downloading children of a node when they are required, i.e., the user has expanded that portion of the tree.

Build the Tree

Fundamentally, we need to track the contents of the tree, and that contents need to exist within the session, and be available when a new portion of the tree is requested. To do this, we build a hierarchy in memory of tree nodes. Rather than reproducing the tree code here I have shown a simple recursive method that progressively builds up a big tree.

protected void BuildTree(int depth, TreeNode node)
{
    if (depth > 4)
        return;

    for (int i=0; i<10; i++)
    {
        node.Children.Add(new TreeNode("Node " + depth + i, "",
            "img/folder_open.gif", "img/folder_closed.gif"));
    }

    foreach (TreeNode child in node.Children)
        BuildTree(depth + 1, child);
}

This will build a tree of 11110 nodes, a tree 4 levels deep, with 10 nodes below each other node.

For your interest, downloading a tree of this size results in HTML that is around 9 MB in size, not something you would ask your user to download!

Each node

Each node in the tree has a similar structure as follows, making use of tables:

plus/minus image open icon closed icon text/link/etc.

div (empty initially) to hold child nodes

Some things to note:

  • Both open and closed images are included but one is visible depending upon the expanded state.
  • The text in this example is a link, but you could generate any content.

The Div below each node will begin its life empty, to be filled at some point when the user expands the node. This is filled in by making an HTTP Request to the server to retrieve the child nodes. For this reason, the plus/minus and open/closed icons all link to the following JavaScript.

This code performs the following actions:

  • Find the DIV to place the children into, based on the ID passed in (item). This is a unique ID for the TreeNode that is being expanded.
  • Create an object to make an HTTP request with.
  • Submit the request to a specific URL, namely "TreeFill.aspx?tree=&id=".
  • Place the received HTML into the DIV that holds the children.
  • Toggle the node to show the child DIV.
///
/// Perform an AJAX style request for the contents 
/// of a node and put the contents
/// into the empty div.
///
function DelayLoadNode(treeid, item)
{
    var div=document.getElementById("D" + item);
    var xmlhttp = GetXMLHttp();

    // Make sure the node is empty really, and if so fill it
    if (div.innerHTML == "")
    {
        xmlhttp.open("GET", 
            "TreeFill.aspx?tree="+treeid+"&id="+item, true);
        xmlhttp.onreadystatechange=function() 
        {
            if (xmlhttp.readyState==4)
            {
                // DEBUG: alert(xmlhttp.responseText);
                div.innerHTML = xmlhttp.responseText;
                Toggle(item);
            }
        }
        xmlhttp.send(null)
    }
    else // The node is already populated
    {
        Toggle(item);
    }
}

Filling out the Tree

When the AJAX call comes in, the TreeFill.aspx page answers the call by finding the node being expanded, and returning the HTML for its children. There is no magic here, just trawl through the tree structure on the server until we find the right node, and render its children in the response.

In order to do this, we need the tree itself on the server side. This is located by pulling the tree structure out of the session based on the ID of the tree that we are expanding. The Tree structure was placed into the Session using the treeID when the tree was first rendered.

/// <span class="code-SummaryComment"><summary>
</span>
/// Render the nodes attached to a node at a 
/// specific level in the tree
/// <span class="code-SummaryComment"></summary>
</span>
protected override void Render(HtmlTextWriter writer)
{
    string treeID = Request["tree"];
    // ..snip..
    Tree tree = (Tree)Session["tree" + treeID];
    // ..snip..
    string nodeID = Request["id"];
    // ..snip..
     
    // Locate and output the requested node
    bool found = false;
    OutputTreeNode(nodeID, treeID, writer, 
        tree.Root, ref found);
}

Currently, OutputTreeNode() does a brute force search, but a more elegant search algorithm could easily be applied.

Conclusion

The basic steps for proper operation of the tree are:

Initial setup

  • Build a tree in memory and pass it to the TreeView control.
  • Store the tree in memory so that TreeFill can find it.
  • Send the Root node to the browser, including smarts to download children.

User expands a node in the tree

  • Request the children by asking for a node from a specific tree.
  • Respond with the HTML from the child nodes.
  • Populate the DIV below the node with the content for the children.
  • Expand the node by showing the DIV.

So, the whole tree is not downloaded at once, it is downloaded progressively; this allows even very large navigation trees to be very responsive. AJAX is used to do the progressive download, and the page is updated using DHTML. And finally, this control can be hosted without frames (as was not the case with the JavaScript version).

Thanks again to D. D. de Kerf for the original inspiration!

Note: This code is supplied "as is" without any form of warranty. Rewritten software shall not be liable for any loss or damage to person or property as a result of using this code. Use this code at your own risk! You are licensed to use this code free of charge for commercial or non-commercial use providing you do not remove the copyright notice or disclaimer from the comment section in the code.

Enjoy!

(Source is included for both .NET 1.1 and 2.0).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Adrian Holland
Architect Webefinity
Australia Australia
Adrian is current the Solution Architect at CubeBuild.com.
 
The core of CubeBuild is a website and application platform that is pluggable into ASP.NET MVC. Any MVC application can have content authoring added to its pages with little effort, and new content types are created using IronPython.NET open source components.
 
We are currently deploying a Point of Service (Web based POS) built on CubeBuild which allows a single web channel for face-to-face sales, and sales through your online store. All from a single inventory base, and from any device.

Comments and Discussions

 
QuestionCompiling This? Pinmemberquakeguy4-Apr-06 7:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 26 Aug 2005
Article Copyright 2005 by Adrian Holland
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid