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

DHTML Tree View of Arbitrary Depth using AJAX

By , 25 Aug 2005
 

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.

/// <summary>
/// Render the nodes attached to a node at a 
/// specific level in the tree
/// </summary>
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

About the Author

Adrian Holland
Architect Webefinity
Australia Australia
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionLicense agreementmembernpcmeister9 Jan '12 - 1:39 
Hi,
 
The reason I'm posting this is to see if we can get a different license for this project rather than the open license published by you. We would like to use the software in one of our commercial product.
If you have any commercial license or willing to confirm to us that this can be distributed under different license – it would be much appreciated.

Thank you
QuestionAsking for Help using database driven data with the current code???memberMadi Me Me25 May '10 - 22:54 
Hi,
I would like to use the data from database for the tree. Could you please suggest me how I should modify the existing source? I am new to the tree control. Thank you so much in advance!
GeneralSession ProblemmemberMattsterP26 Nov '07 - 13:45 
This is a neat bit of code, however I keep running into a problem when it tries to fetch the tree out of the Session Object, it seems like the AJAX call initiated from js has a different session id than the session from the page that requested the original TreeTest.aspx page, therefore it barfs on the line: Tree tree = (Tree)Session["tree" + treeID];
 
Is this how it is supposed to work, I suppose I can keep the tree in the Application object and that would work fine, just wanted to know how it was intended to work.
 
-Matt
 

QuestionBIG tree?memberDacman9 Aug '07 - 23:35 
This article is very interesting but have a BIG problem ... building the whole tree in memory of the server can have some unexpecting problems. For example I'am working on web portals for many users and if I have for example 1000 online users and I build for each of them this tree in memory it will cost me 9GB of memory and thats a lot ... But this code have very interesting possibilities ... If somebody can tell me how to improve this program that every request from user can make a "Select" from database it will be very useful in my work .. otherwise I must program my own AJAX Tree for that purpose. But nice work! Big Grin | :-D
 
Dawe (Czech republic) - junior programmer
QuestionCreating Tree Problem-Questionmemberdhmason24 Feb '07 - 15:33 
In your code, you initially create the tree in the TreeTest's code-behind (TreeTest.aspx). The code refers to the ID of the control 'TreeView1" (TreeView1.DisplayTree = tree).
 
My problem is the code which I need to have generate the tree is in the web service C# code, where it is not aware of the client-side control with the 'TreeView1' ID. I can have the web service return an array of TreeNodeCollection objects. But, I don't know how to have javascript then somehow create the client-side tree and load it with the nodes returned by the web service. I would greatly appreciate advice or possible examples. Thanks!
AnswerRe: Creating Tree Problem-Questionmemberjulianhollingsworth24 Sep '07 - 18:00 
Hi,
 
I am experiencing the same problem. How did you get around this problem?
GeneralRe: Creating Tree Problem-Questionmemberdhmason26 Sep '07 - 7:31 
It has been so long and the code I created has changed/evolved a lot since I posted my question. So, I am sorry if I can't recall the details.
 
In the current version of my code, the page_load (of the user control on the web page) creates the tree with just an 'emty' root node and stores the tree id in a session scope variable for later use. In my case, the tree will contain nodes based on the result of a search. When the search finds data (returned from a remote web service), the 1st level of nodes below the root are created, and the root node of the tree is used to display number of matches found, such as "25 matches found". If the search fails to find any matches, the root node of the tree is used to display a message "no matches found".
Generalinserting nodes into treememberdhmason24 Feb '07 - 15:11 
Great code - thanks for sharing it! I plan to see if I can use/modify it for my needs. Please read below and let me know if you have any advice or know of possible examples. Thanks again!
 
I need to call a web service from javascript (async), and use the return/results from the web service to generate my tree. The web service performs a search of a data source. However, due to the nature/volume of the data, this will only return the first level nodes. When the user expands a node, another call to the web service is performed, which returns children. So, I will need to 'insert' nodes into the tree.
 
Currently this tree is done via an asp treeview and it's events (when user expands a node). I am trying to create a version which is more client-based, where I use javascript to initiate the user's search as an async request. This is needed as there are multiple data sources being searched with each source's results displayed on the same screen. So, simultaneous async requests are triggered.
QuestionCompiling This?memberquakeguy4 Apr '06 - 6:10 
Newbie alert! I know next to nothing about ASPX.
I wanted to play with this code. Can I just drop it into IIS and set as an application, or do I need to compile it? I have Visual Studio 2003 for C++, but not other languages, but should be able to get the others.
 
Actually I did drop it into IIS and set as app, but now I get the dreaded:
Parser Error Message: Could not load type 'WebApplication1.Global'.

QuestionCan I use this Tree for java ?memberel_nio13 Feb '06 - 14:31 
Hi ...
 
Can I use this Tree for java ? What I need to do for this?
 

Thank you,
 

pablo
GeneralDHTML TreeView vs ASP.NET 2.0 TreeViewmembermykone22 Dec '05 - 12:38 
Why would I use this treeview over System.Web.UI TreeView? Does this treeview support Events, PostBack etc..
AnswerRe: DHTML TreeView vs ASP.NET 2.0 TreeViewmemberMember 263282630 Sep '08 - 22:29 
Because it is a DHTML\DOM Treeview as opposed to the monolithic ASP.Net 2.0 Treeview.
 
Basically, the tree nodes are populated on demand rather than on page load. This means you dont need to download say a 2MB tree to view 20KB worth of nodes. This applies to scenarios like browsing a file system to find say a particular MP3. If you have say 50 000 MP3s, you dont really want to download 50 000 tree nodes when you only need to go through say music->artist->album->song. While the ASP tree view also supports on demand loading, it does so by reloading the page every time, which is very cumbersome and unelegant.
 
The above DHTML TreeView can support events and post backs, if you add them...
GeneralNice treeview control - have questionmemberlarmister15 Dec '05 - 13:54 
I came across your treeview control while searching for one that can be used with .Net. It looks like it would suit my needs. I do have a novice question.
 
In your example there is only one root node with many children. I need to have several root nodes (much like a typical tree navigation) and am not sure how to modify your code to allow for that.
 
Any pointers would be grateful.
GeneralRe: Nice treeview control - have questionmemberAdrian Holland15 Dec '05 - 20:32 
Hi,
 
Really all you need to do is supress the display of the content section of the root node, and make sure that the root node has expanded=true so it writes out the child DIV's for each first level node correctly.
 
Once done, the root node will still be the root of the tree in memory, but it will be invisible through the browser..
 
Regards,
Adrian.

GeneralAmazon Catalogue Treememberveerendrashivhare10 Nov '05 - 23:48 
I modified JS Cook Tree to make it use AJAX,
support n level hierarchy. I used this to Display
Amazon Catalogue in a new "innovative" way.
URL :- http://lmap.co.nr/Amazon1.htm
 
In the tree, one can browse amazon catalogues based
on the browse id. On reaching the item level (marked
with red dots) one can click on it to view details such
as price, image etc.
GeneralUsing XML in this examplememberJulzAU1 Nov '05 - 14:36 
Great article! It definatly headed me in the right direction. I required similar functionality to retrieve a tree of some 1600 nodes (and growing) this is ideal. One extension that i required was using XML to do the data transfer.
 
For this to work you MUST have a content type of "text/xml" (i'll include the xml file too)
 
function GetChildrenNodes(ParentNodeId)
{
  var xmlhttp = GetXMLHttp();
  var ThisItemDiv = document.getElementById("Item"+ParentNodeId);
							
  xmlhttp.open("GET", "GenericPageControls/AJAXKeywordResult.aspx?parent_node="+ParentNodeId+"&command=getchildren", true);
  xmlhttp.onreadystatechange=function() 
  {
     if (xmlhttp.readyState==4)
     {
        ThisItemDiv.innerHTML = AddChildrenDivText(xmlhttp); //This is changed to work on the object, so we can work on the XML
     }
  }
  xmlhttp.send(null);
}
 
//Deals with the XML
function AddChildrenDivText(xmlhttp)
{
  var Keywords = xmlhttp.responseXML.childNodes(1).childNodes; //root's child nodes (Keyword)
  var ReturnText = "";
						
  for (Counter = 0; Counter < Keywords.length; Counter++)
  {
    var Keyword = Keywords(Counter);
    var KeywordId = Keyword.getAttribute("Id");
    var KeywordText	= Keyword.getAttribute("Text");
    var KeywordChildCount = Keyword.getAttribute("ChildCount");
								
    if (KeywordChildCount > 0)
      ReturnText += "<img id='item" + KeywordId + "Plus' src='images/plus.gif' onClick='ClickExpandNodes(" + KeywordId + ")'><img id='item" + KeywordId + "Minus' src='images/minus.gif' onClick='ClickContractNodes(" + KeywordId + ")' style='display:none;'>";
    else
      ReturnText += "<img src='images/blank.gif'>";
 
														
    ReturnText += "<input type='checkbox' id='check" + KeywordId + "' "
    if (isDBSelected(KeywordId))
      ReturnText += "selected";
    ReturnText += ">";
								
    ReturnText += " <span class='NormalText'>" + KeywordText + "</span><br><div id='item" + KeywordId + "' style='margin-left:15px; display:none;'></div>";
  }
							
  return ReturnText;
}
 
 

XmlFile

<?xml version="1.0"?>
<root>
<Keyword Id="1" ChildCount="3" Text="Heart Condition" />
<Keyword Id="2" ChildCount="0" Text="Lemming issues" />
<Keyword Id="6" ChildCount="0" Text="Cabbages" />
<Keyword Id="7" ChildCount="0" Text="stuff" />
<Keyword Id="8" ChildCount="0" Text="more stuff" />
<Keyword Id="9" ChildCount="0" Text="things" />
<Keyword Id="10" ChildCount="0" Text="other things" />
<Keyword Id="11" ChildCount="3" Text="people" />
<Keyword Id="15" ChildCount="5" Text="things" />
<Keyword Id="25" ChildCount="0" Text="Departmental Files" />
<Keyword Id="26" ChildCount="28" Text="Drugs" />
<Keyword Id="859" ChildCount="25" Text="Medical terms" />
<Keyword Id="1297" ChildCount="29" Text="Pharmaceutical terms" />
<Keyword Id="1556" ChildCount="9" Text="Geography" />
<Keyword Id="1659" ChildCount="1" Text="Devices & Techniques" />
</root>

 
Awesome work!
 
btw. nice to see another melbournite Smile | :)
 
Also have a look at:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/xmlsdk/html/bb76ab1d-abdd-4da6-a476-7d3d7c0f1f0c.asp
 

QuestionHow to expanded all the node.sussumashankariv13 Oct '05 - 7:55 
This article is very useful for me to display all the data
in hierahical fashion. But i HAVE A PROBLEM I want to display all the node when the tree is loaded (should be expanded in the begin itself and it should work normal)
when the user clicks the - symbol it should be colapsed and clicking on the plus sign it should work normally.
 
Please can anyone suggest me a solution for it.
 
Thanks for the author.
 

GeneralError when loadingmemberspook12 Oct '05 - 8:14 
Parser Error Message: Could not load type 'AjaxTree.Global'.
 
I'm trying to use the 1.1 version.
 
Thanks,
 
Kelly
GeneralComfortASP.NETmemberdazei22 Sep '05 - 6:37 
Very nice article. Maybe you are looking for a complete framework providing AJAX features like in this article while keeping ASP.NET server-side programming model, please visit:
 
www.comfortasp.de
 
Daniel.

GeneralWIHT A XML FILE NOT BuildTreememberwindlan1 Sep '05 - 7:33 
How should I do if loading a big xml file, not by subroutine BuildTree? Thank you.
AnswerRe: WIHT A XML FILE NOT BuildTreememberAdrian Holland1 Sep '05 - 15:59 
One way would be to build the tree inside the current Tree & TreeNode by traversing your XML using a parser.
 
Since it sounds like your tree is static (has the same content for all users) you should also move its storage from the session in Session[] (as presented) to Application[]. This will stop you replicating the structure for each user when you dont need to. The only reason we stored it in session is because we build the tree dynamically based on the users context (who they are, what they have access to).
 
The expanded property of the treenode only captures the initial expansion state, the actual expansion state of the tree is managed by the users browser, so you can safely store one instance of the tree on the server.
 
You could also have Tree and TreeNode return nodes by reading the contents of the XML each time a query is made, but this could be quite slow if your XML is of any reasonable size.
 
Let us know how you go.
 
Adrian Holland.
www.rewrittensoftware.com
General.net v1.1memberamiSett26 Aug '05 - 1:17 
Sorry but I don't understand the changes req'd to convert the code in tree.cs to comply with framework 1.1. Please post the changes. Thanks.Sniff | :^)
 
amisett@hotmail.com
AnswerRe: .net v1.1memberAdrian Holland26 Aug '05 - 4:09 
No problems, the article has been updated to include the 1.1 source code.
 
Adrian.
www.rewrittensoftware.com
GeneralRe: .net v1.1memberamiSett26 Aug '05 - 4:26 
working really well. Trying to get to grips with AJAX. Thanks lots.
 
ami

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

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