Tuesday, February 24, 2009

Custom DHTML Tree View

Introduction
This document is divided into five sections
The problem statement
Constraints
The resolution
The code
Generated data

The problem statement
The requirement involves building an expandable and collapsible tree view on a web page for an indefinite number of nodes and inner nodes, similar to the folders and files in a file system.
The depth into which a particular branch goes can also vary from node to node. Hence the data is not in the form of normalized set of tables but in the form of an array of strings each of which specifies one complete branch.
The nodes in a branch are separated by ‘/’s and each branch may or may not have nodes in common with other branches (see generated sample data in the end)
Constraints
The Microsoft tree view is not acceptable because it does not meet the required performance rps and requires a postback to the server for every click on a node.
Other commonly available VB and DHTML options involve complex implementation methods running into many hundred lines of client and server side code and also require creation of memory intensive XmlDocument objects and such which is again not acceptable.
The resolution
Creation of hierarchical HTML structure during run time using tables and DIV tags which can be manipulated by a single JavaScript function.
This essentially is creating of single string of HTML using a StringBuilder and placing it on the web page during page load.
No XML or other heavy objects are necessary and the entire process is accomplished in a few lines of code. The rest is taken care of by the JavaScript function.
The code
Given below is the C# function which generates the required html on the server and the JavaScript function which works on that HTML for every click by the user.
As can be seen
The whole job is accomplished in less than 100 lines
No XML or other heavy objects are used
The algorithm in itself is simple, yet efficient. No more than 1 nested loop is ever used.

Please note that as per the problem statement and the generated, there are three kinds of nodes.
Only expandable (nodes with inner nodes)
Only selectable (nodes without inner nodes)
Expandable and selectable
This last one is due the nature of the data. Every single string in the data needs to be available for the user to select. But as can be seen, there are branches which have inner nodes in certain other branches.
For example, the 4th string in the sample data has Child00 as the selectable and final node
But in strings 5 and 6, the same Child00 has 1 inner node each hence making this particular node, Child00, both expandable and selectable.
The C# (Server side code): -
private void buildTree()
{
string[] Branches = generateBranches();
foreach (string OneString in Branches)
Response.Write(OneString + “<br>”);
StringBuilder TreeBuilder = new StringBuilder(“<TABLE border=\”0\” width=\”100%\” cellpadding=\”1\”
cellspacing=\”0\”><TR><TD width=\”20\”> </TD><TD class=\”\”><A onclick=\”Toggle(this, “)\”><IMG src=\”Images/minus.gif\”>Root node</A><DIV>”);
int OuCount = Branches.Length; int CurrentNodePosition = 0; for (int i=0; i<OuCount; i++)
{
string[] ThisStringParts = Branches[i].Split(new Char[] {’/’});
string[] NextStringParts = new string[0];
if (i<(OuCount-1))
NextStringParts = Branches[i+1].Split(new Char[] {’/’}); int PartsCount = ThisStringParts.Length; string NextString = “”; string ThisString = “”; int NextNodePosition = 0; bool NewNodeFound = false; for (int j=0; j<PartsCount; j++)
{
ThisString += ThisStringParts[j];
if (j<NextStringParts.Length)
NextString += NextStringParts[j]; if (!NewNodeFound)
{
if (ThisString.CompareTo(NextString) != 0)
{
NewNodeFound = true;
NextNodePosition = j;
}
}
if (j>=CurrentNodePosition)
{
TreeBuilder.Append(“<TABLE border=\”0\” cellpadding=\”1\” cellspacing=\”0\”><TR><TD
width=\”10\”></TD><TD class=\”\”>”);
if ((j == (PartsCount - 1)) && (ThisString.CompareTo(NextString) != 0))
TreeBuilder.Append(“<IMG border=\”0\” src=\”Images/leaf.gif\”><A onclick=\”javascript:showSelection(‘” + Branches[i] + “’);\” href=\”#\”>” + ThisStringParts[j] + “</a><DIV>”);
else if (j == (PartsCount - 1))
TreeBuilder.Append(“<A onclick=\”Toggle(this,’” + Branches[i] + “’)\” href=\”#\”><IMG
border=\”0\” src=\”Images/minus.gif\”>” + ThisStringParts[j] + “</a><DIV>”);
else
TreeBuilder.Append(“<A onclick=\”Toggle(this,”)\”><IMG border=\”0\” src=\”Images/minus.gif\”>” + ThisStringParts[j] + “</a><DIV>”);
}
}
if (NewNodeFound)
{
CurrentNodePosition = NextNodePosition; for (int j=NextNodePosition; j<=(PartsCount-1); j++)
TreeBuilder.Append(“</DIV></TD></TR></TABLE>”);
}
else
{
CurrentNodePosition = PartsCount;
}
}
TreeBuilder.Append(“</DIV></TD></TR></TABLE>”); wclblTree.Text = TreeBuilder.ToString();
}

The JavaScript: -
function Toggle(node, Branch)
{
if (node.nextSibling.style.display == ‘none’)
{
if (node.children.length > 0)
{
if (node.children.item(0).tagName == “IMG”)
{
node.children.item(0).src = “Images/minus.gif”;
}
}
node.nextSibling.style.display = “;
}
else
{
if (node.children.length > 0)
{
if (node.children.item(0).tagName == “IMG”)
{
node.children.item(0).src = “Images/plus.gif”;
}
}
node.nextSibling.style.display = ‘none’;
}
}

Generated data
InnerNode00
InnerNode00/Child00/GrandChild01
InnerNode00/Child00/GrandChild02
InnerNode00/Child01
InnerNode00/Child01/GrandChild04
InnerNode00/Child01/GrandChild05
InnerNode00/Child02
InnerNode00/Child02/GrandChild07
InnerNode00/Child02/GrandChild08
InnerNode01
InnerNode01/Child03/GrandChild10
InnerNode01/Child03/GrandChild11
InnerNode01/Child04
InnerNode01/Child04/GrandChild13
InnerNode01/Child04/GrandChild14
InnerNode01/Child05
InnerNode01/Child05/GrandChild16
InnerNode01/Child05/GrandChild17
InnerNode02
InnerNode02/Child06/GrandChild19
InnerNode02/Child06/GrandChild20
InnerNode02/Child07
InnerNode02/Child07/GrandChild22
InnerNode02/Child07/GrandChild23
InnerNode02/Child08
InnerNode02/Child08/GrandChild25
InnerNode02/Child08/GrandChild26
InnerNode03
InnerNode03/Child09/GrandChild28
InnerNode03/Child09/GrandChild29
InnerNode03/Child10
InnerNode03/Child10/GrandChild31
InnerNode03/Child10/GrandChild32
InnerNode03/Child11
InnerNode03/Child11/GrandChild34
InnerNode03/Child11/GrandChild35
InnerNode04
InnerNode04/Child12/GrandChild37
InnerNode04/Child12/GrandChild38
InnerNode04/Child13
InnerNode04/Child13/GrandChild40
InnerNode04/Child13/GrandChild41
InnerNode04/Child14
InnerNode04/Child14/GrandChild43
InnerNode04/Child14/GrandChild44
InnerNode05
InnerNode05/Child15/GrandChild46
InnerNode05/Child15/GrandChild47
InnerNode05/Child16
InnerNode05/Child16/GrandChild49
InnerNode05/Child16/GrandChild50
InnerNode05/Child17
InnerNode05/Child17/GrandChild52
InnerNode05/Child17/GrandChild53
InnerNode06
InnerNode06/Child18/GrandChild55
InnerNode06/Child18/GrandChild56
InnerNode06/Child19
InnerNode06/Child19/GrandChild58
InnerNode06/Child19/GrandChild59
InnerNode06/Child20
InnerNode06/Child20/GrandChild61
InnerNode06/Child20/GrandChild62
InnerNode07
InnerNode07/Child21/GrandChild64
InnerNode07/Child21/GrandChild65
InnerNode07/Child22
InnerNode07/Child22/GrandChild67
InnerNode07/Child22/GrandChild68
InnerNode07/Child23
InnerNode07/Child23/GrandChild70
InnerNode07/Child23/GrandChild71
InnerNode08
InnerNode08/Child24/GrandChild73
InnerNode08/Child24/GrandChild74
InnerNode08/Child25
InnerNode08/Child25/GrandChild76
InnerNode08/Child25/GrandChild77
InnerNode08/Child26
InnerNode08/Child26/GrandChild79
InnerNode08/Child26/GrandChild80

No comments:

Post a Comment