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.
function DelayLoadNode(treeid, item)
{
var div=document.getElementById("D" + item);
var xmlhttp = GetXMLHttp();
if (div.innerHTML == "")
{
xmlhttp.open("GET",
"TreeFill.aspx?tree="+treeid+"&id="+item, true);
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4)
{
div.innerHTML = xmlhttp.responseText;
Toggle(item);
}
}
xmlhttp.send(null)
}
else
{
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.
protected override void Render(HtmlTextWriter writer)
{
string treeID = Request["tree"];
Tree tree = (Tree)Session["tree" + treeID];
string nodeID = Request["id"];
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).
| You must Sign In to use this message board. |
|
|
 |
|
 |
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
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!
Dawe (Czech republic) - junior programmer
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
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!
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
 |
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".
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
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.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
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'.
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
 |
Why would I use this treeview over System.Web.UI TreeView? Does this treeview support Events, PostBack etc..
|
| Sign In·View Thread·PermaLink | 2.46/5 |
|
|
|
 |
|
 |
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...
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
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.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
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.
|
| Sign In·View Thread·PermaLink | 1.20/5 |
|
|
|
 |
|
 |
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.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
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); } } xmlhttp.send(null); }
function AddChildrenDivText(xmlhttp) { var Keywords = xmlhttp.responseXML.childNodes(1).childNodes; 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' önClick='ClickExpandNodes(" + KeywordId + ")'><img id='item" + KeywordId + "Minus' src='images/minus.gif' önClick='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 += " " + KeywordText + " "; } return ReturnText; }
XmlFile
="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 
Also have a look at: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/xmlsdk/html/bb76ab1d-abdd-4da6-a476-7d3d7c0f1f0c.asp
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
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.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Parser Error Message: Could not load type 'AjaxTree.Global'.
I'm trying to use the 1.1 version.
Thanks,
Kelly
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
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.
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
 |
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
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
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.
amisett@hotmail.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|