DevX HomePage

Build an XML-based Tree Control with JavaScript

Tree controls provide a hierarchical view of data and XML provides a way to structure data hierarchically, so viewing XML data as a tree structure is a natural fit. But browsers don't provide a tree control. Instead, use this mix of XML, XSLT, JavaScript, and CSS to produce an extensible HTML tree control.
fter writing the Build an Object-Oriented Tree Control Using JavaScript article, it occurred to me that using XML as the data source for the tree would be a natural fit. I wanted to create a solution that was both maintainable and extensible. That is, I wanted to create an automated system whereby any XML document that adhered to a given grammar would easily become an HTML tree control. Essentially, I wanted to be able to view the XML document in a browser and have the browser render it as a tree similar to the TreeView control in Windows Explorer. Further, the same control should work in both Internet Explorer 6 (IE) and Netscape/Mozilla (N7).


Tree controls provide a hierarchical view of data and XML provides a way to structure data hierarchically, so it would seem that viewing XML data as a tree structure would be a natural fit. The problem is, how can you build an HTML tree control that uses XML for its data model?


The solution is to brew a mix of XML, XSLT, JavaScript, and CSS to produce an extensible HTML tree control.




Getting Started
Initially, I set about designing a JavaScript-only solution and immediately ran into problems. While N7 parses XML according to the W3C's DOM, IE uses a proprietary ActiveX-based parser to parse XML documents. It felt like I was traveling backward in time when developers had to support different flavors of the major browsers—each with its own DOM. Rather than build a script for each browser, I decided to attack the problem from a different level.

The problem was really pretty simple. I wanted to take an XML representation of a tree and transform it into an HTML representation. Transformation was the key! One XML technology—XSLT (Extensible Stylesheet Language Transformations)—is perfect for this task. To provide an XSLT implementation you must include a number of different components in the solution:

Although you have more files to manage, the modularity helps to make the code both maintainable and extensible. And both major browsers support XSLT!

Create an XML Grammar
For this solution to work properly, you have to accept some limitations on the content in the XML document. I'll start by writing a Document Type Definition (DTD). DTDs define the content model for instances of the document type. Including a DTD makes it easier to create the XSLT file. Any document that adheres to the DTD can then be transformed by the same stylesheet. That way, should you choose to change the view of the data, you'll only need to modify the stylesheet—not each document instance. Open your favorite text editor and enter the following:

 <!ELEMENT tree (branch+)>
<!ELEMENT branch (branchText,(branch|leaf)*)>
<!ATTLIST branch id CDATA #REQUIRED>
<!ELEMENT branchText (#PCDATA)>
<!ELEMENT leaf (leafText,link)>
<!ELEMENT leafText (#PCDATA)>
<!ELEMENT link (#PCDATA)>
Save the file as tree.dtd.

The DTD specifies that <tree> elements must contain 1 or more <branch> elements (the '+' indicates 'one or more'). Each <branch> element must contain a single, required <branchText> element as the first child and then zero or more of either <branch> elements or <leaf> elements (the '*' indicates 'zero or more' and the '|' indicates 'or'). In addition, all <branch> elements must have an id attribute. The DTD defines the <branchText> element as containing PCDATA—parsed character data. PCDATA is plain text that adheres to the rules for writing well-formed XML. The PCDATA also specifies that the element will contain no child elements. <leaf> elements contain one each of <leafText> and <link> elements, in that order (the ',' indicates 'and' and 'in order'). The <leafText> and <link> elements also contain PCDATA, so they can't have any child elements either.

Create an XML Source Document
In most cases, it's easier to understand the XML grammar by looking at a document instance. Grab a new file and enter the following XML:

 <?xml version="1.0" ?>
<tree>
<branch id="html">
<branchText>HTML</branchText>
<leaf>
<leafText>Tags, Tags, Tags</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Hyperlinks</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Images</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Tables</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Forms</leafText>
<link>#</link>
</leaf>
</branch>
<branch id="css">
<branchText>CSS</branchText>
<leaf>
<leafText>Inline Styles</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Document Wide Styles</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>External Style Sheets</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Formatting Text</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Positioning Text</leafText>
<link>#</link>
</leaf>
</branch>
<branch id="javascript">
<branchText>JavaScript</branchText>
<leaf>
<leafText>The Basics</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Working with Images</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Controlling Frames</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Browser Windows</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Form Validation</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Handling Events</leafText>
<link>#</link>
</leaf>
</branch>
<branch id="dhtml">
<branchText>DHTML</branchText>
<leaf>
<leafText>Object Detection</leafText>
<link>#</link>
</leaf>
<branch id="animation">
<branchText>Animation</branchText>
<leaf>
<leafText>Path Animation</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>
Point To Point Animation</leafText>
<link>#</link>
</leaf>
</branch>
<leaf>
<leafText>Menus</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Tabbed User Interface</leafText>
<link>#</link>
</leaf>
<leaf>
<leafText>Trees</leafText>
<link>#</link>
</leaf>
</branch>
</tree>
Save the file as tree.xml. The preceding file is called an instance of the tree DTD because it adheres to the grammar specified in the DTD. To validate the document—that is, to make sure that it adheres to the grammar—add a DOCTYPE declaration to the document just beneath the <?xml version="1.0"?> prolog:

 <!DOCTYPE tree SYSTEM 'tree.dtd'>
You'll need a validating parser to validate the document. I've been working on an XML editor, written in Java, which includes validation services. You can download the editor here.

JXEd is freeware but, be warned; it is a work in progress. To validate documents in JXEd, open the file and choose Validate from the Tools menu.

It's important to validate your XML documents so that no errors will occur when the transformation is applied.




Create JavaScript and CSS Files
You'll need to create the JavaScript and CSS files next. The JavaScript file contains the variables and functions to make the tree work in a browser and the CSS stylesheet controls how the browser formats the text in the tree control.

Here's the content of the JavaScript file:

 var openImg = new Image();
openImg.src = "open.gif";
var closedImg = new Image();
closedImg.src = "closed.gif";
function showBranch(branch){
var objBranch =
document.getElementById(branch).style;
if(objBranch.display=="block")
objBranch.display="none";
else
objBranch.display="block";
swapFolder('I' + branch);
}
function swapFolder(img){
objImg = document.getElementById(img);
if(objImg.src.indexOf('closed.gif')>-1)
objImg.src = openImg.src;
else
objImg.src = closedImg.src;
}
Save the completed JavaScript file as xmlTree.js.

Here's the code for the CSS file:

 body{
font: 10pt Verdana,sans-serif;
color: navy;
}
.trigger{
cursor: pointer;
cursor: hand;
display: block;
}
.branch{
display: none;
margin-left: 16px;
}
a{
text-decoration: none;
}
a:hover{
text-decoration: underline;
}
Save the completed CSS file as xmlTree.css.




Create the XSLT Stylesheet
XSLT—as the "Transformations" aspect of the name suggests—is a mechanism whereby your XML data is transformed into some other form. That form could be HTML, WML, SOAP, or another XML structure. This article focuses on converting the XML document containing the data to HTML. The concept is simple: you create a set of rules for the elements in your XML document (aka a stylesheet) that manipulates those elements to make them presentation-friendly. Then you apply the stylesheet to the document with an XSLT processor. The transformation can happen in place when XSLT support is built into the client, or externally, for example on your Web server. If you perform the transformation externally, you send the transformation's result to the client. However, both IE and N7 can be used to process the XSLT instructions in place, so if you're using one of those two browsers, you don't need a Web server to use the tree control presented in this article.

In a nutshell, XSLT transforms your XML into a more viewable form. The original XML content is known as the source tree in the XSLT process. The output of the transformation is known as the result tree. Your stylesheet contains the rules for making the switch from source tree to result tree happen. You define those rules in XSLT templates. The XSLT engine applies the rules you define.

Here is the XSLT Stylesheet:

 <?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html"></xsl:output>
<xsl:template match="/">
<html>
<head>
<title>XML Tree Control</title>
<link rel="stylesheet" type="text/css"
href="xmlTree.css"/>
<script type="text/javascript"
src="xmlTree.js"></script>
</head>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="tree">
<body>
<xsl:apply-templates/>
</body>
</xsl:template>
<xsl:template match="branch">
<span class="trigger">
<xsl:attribute name="onClick">
showBranch
('<xsl:value-of select="@id"/>');
</xsl:attribute>
<img src="closed.gif">
<xsl:attribute name="id">I
<xsl:value-of select="@id"/>
</xsl:attribute>
</img>
<xsl:value-of select="branchText"/>
<br/>
</span>
<span class="branch">
<xsl:attribute name="id">
<xsl:value-of select="@id"/>
</xsl:attribute>
<xsl:apply-templates/>
</span>
</xsl:template>
<xsl:template match="leaf">
<img src="doc.gif"/>
<a>
<xsl:attribute name="href" >
<xsl:value-of select="link"/>
</xsl:attribute>
<xsl:value-of select="leafText"/>
</a><br/>
</xsl:template>
<!-- avoid output of text node
with default template -->
<xsl:template match="branchText"/>
</xsl:stylesheet>
Save the file as xmlTree.xsl.

XSLT is written in XML, so it enjoys all the benefits inherent in XML—including namespace support as shown in the preceding code. The <xsl:stylesheet> element is a container for all of the templates in the stylesheet. The <xsl:output> element is optional and can take several parameters that give you better control over the result tree. The method attribute takes as a value either xml, html, or text. XML is case-sensitive, so be careful how you enter names and parameter values.




How the XSLT Stylesheet Works
To begin a transformation, an XSLT processor first looks for a template that matches the root node of the document. It's customary to use the root template as the container for all the output that will follow. In the preceding stylesheet, the template matching the root is declared as follows (note that the root node sits one level higher in the document than the root element):

 <xsl:template match="/">
<html>
<head>
<title>XML Tree Control</title>
<link rel="stylesheet" type="text/css"
href="xmlTree.css"/>
<script type="text/javascript"
src="xmlTree.js"></script>
</head>
<xsl:apply-templates/>
</html>
</xsl:template>
Each template provides two kinds of instructions: literals that the stylesheet will write without alteration directly to the result tree, and instructions that inform the XSLT processor to perform some action. You may have noticed the <xsl:apply-templates/> element above. That's an instruction to the XSLT processor to move forward and process the children of the current node. As the processor processes the node's children, it searches for templates that match those children and executes the instructions in the matching template.

There are a few things you must keep in mind when writing XSLT stylesheets.Each match attribute value in a given template is an identifier for a given node or node set. The identifier is actually an XPath expression. You build XPath expressions around the concept of a current node being processed. So, for example, when the first or root template encounters the <xsl:apply-templates/> instruction, it looks for a template that matches children of the root node. The XPath expression that matches the child of the root node is "tree" so that is the next template processed.

Similarly, when the processor encounters the <xsl:apply-templates/> instruction in the tree template, it will go off and evaluate the children of the <tree> node. The XPath expression that matches those children in the sample document is "branch", so the stylesheet applies the branch template to each <branch> node that is a child of a <tree> node. The template will not, however, be applied to any <branch> node that is NOT a child of a <tree> node (at least not yet!). The process continues until all the stylesheet has processed all the nodes in the source tree.

When writing XSLT stylesheets, bear in mind that XSLT processors apply a default template to any nodes that don't have a specific matching template. The default template automatically processes the child nodes of the given node or outputs the text content of a node if it contains no children. That's why the stylesheet for the tree control has a blank template for the <branchText> nodes above. The stylesheet already outputs the value of the <branchText> node (using the statement <xsl:value-of select="branchText"/>) and I didn't want the text duplicated in the output. The empty template tells the XSLT processor to do nothing when it encounters a <branchText> node.

The interesting parts of the stylesheet occur in the branch template:

 <xsl:template match="branch">
<span class="trigger">
<xsl:attribute name="onClick">
showBranch
('<xsl:value-of select="@id"/>');
</xsl:attribute>
<img src="closed.gif">
<xsl:attribute name="id">I
<xsl:value-of select="@id"/>
</xsl:attribute>
</img>
<xsl:value-of select="branchText"/>
<br/>
</span>
<span class="branch">
<xsl:attribute name="id">
<xsl:value-of select="@id"/>
</xsl:attribute>
<xsl:apply-templates/>
</span>
</xsl:template>
The branch template creates a <span> element and adds an onClick attribute. The stylesheet gets the value for the onClick attribute from the id attribute of the <branch> node (the current node). Next, the template creates an image for each <span> element (trigger) with a unique id—the sample code uses an 'I' as a prefix and appends the id attribute value. The id attribute ensures that JavaScript (which acts on the result of the transformation) will be able to determine which trigger a user clicked so it can swap the src attribute value to display the correct image. The final <span> represents the branch to expand in the browser. Once again, sometimes it's easier to analyze what's happening in a transformation by looking at the results:

 <html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<title>XML Tree Control</title>
<link href="xmlTree.css" type="text/css"
rel="stylesheet">
<script src="xmlTree.js"
type="text/javascript">
</script>
</head>
<body>
<span class="trigger" onclick="
showBranch('html'); ">
<img src="closed.gif" id="Ihtml">HTML<br>
</span>
<span class="branch" id="html"><img
src="doc.gif">
<a href="#">Tags, Tags, Tags</a><br>
<img src="doc.gif">
<a href="#">Hyperlinks</a><br>
<img src="doc.gif"><a href="#">Images</a><br>
<img src="doc.gif"><a href="#">Tables</a><br>
<img src="doc.gif"><a href="#">Forms</a><br>
</span>
<span class="trigger" onclick="
showBranch('css'); ">
<img src="closed.gif" id="Icss">CSS<br>
</span>
<span class="branch" id="css"><img
src="doc.gif">
<a href="#">Inline Styles</a><br>
<img src="doc.gif"><a href="#">Document
Wide Styles</a><br>
<img src="doc.gif"><a href="#">External
Style Sheets</a><br>
<img src="doc.gif"><a href="#">Formatting
Text</a><br>
<img src="doc.gif"><a href="#">Positioning
Text</a><br>
</span>
<span class="trigger" onclick="
showBranch('javascript'); ">
<img src="closed.gif"
id="Ijavascript">JavaScript<br>
</span>
<span class="branch" id="javascript"><img
src="doc.gif">
<a href="#">The Basics</a><br>
<img src="doc.gif"><a href="#">Working
with Images</a><br>
<img src="doc.gif"><a href="#">Controlling
Frames</a><br>
<img src="doc.gif"><a href="#">Browser
Windows</a><br>
<img src="doc.gif"><a href="#">Form
Validation</a><br>
<img src="doc.gif"><a href="#">Handling
Events</a><br>
</span>
<span class="trigger" onclick="
showBranch('dhtml'); ">
<img src="closed.gif" id="Idhtml">DHTML<br>
</span>
<span class="branch" id="dhtml"><img
src="doc.gif">
<a href="#">Object Detection</a><br>
<span class="trigger" onclick="
showBranch('animation'); ">
<img src="closed.gif"
id="Ianimation">Animation<br>
</span>
<span class="branch" id="animation"><img
src="doc.gif">
<a href="#">Path Animation</a><br>
<img src="doc.gif"><a href="#">Point To
Point Animation</a><br>
</span>
<img src="doc.gif"><a href="#">Menus</a><br>
<img src="doc.gif"><a href="#">Tabbed User
Interface</a><br>
<img src="doc.gif"><a href="#">Trees</a><br>
</span>
</body>
</html>
The only thing left to do is to attach the XSLT stylesheet to the xmlTree.xml XML document. Just beneath the <?xml version="1.0"?> prolog, add the following processing instruction:

 <?xml-stylesheet type="text/xsl" href="xmlTree.xsl"?>
After altering the xmlTree.xml file (and saving the changes), you can load it into either IE or N7 to see the tree control work. I tested the sample code with IE 6 and Mozilla 1.3 and both browsers rendered the tree beautifully. Just make sure that all the files are in the same directory.




Embed the Tree in an HTML Document
In most cases, the tree control will be a component in another Web page. The transformation above creates the tree for viewing in a browser but, usually, you'll want to surround the tree with other content.

Both IE and N7 use the same mechanism for embedding external content—the <iframe> element. Here's a simple method for embedding the tree in another page:

 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Embedded XML Tree Control</title>
<style type="text/css">
#tree{
border-right:1px black solid;
border-top: none;
border-bottom: none;
border-left:none;
position: absolute;
top:10;
left:0;
width: 150;
}
#content{
position:absolute;
top:0;
left: 160;
font-family: sans-serif;
color: navy;
}
</style>
<script type="text/javascript">
function setHeight(){
var availableHeight = 0;
if(document.all)
availableHeight = document.body.clientHeight;
else
availableHeight = innerHeight;
var tree = document.getElementById('tree').style;
tree.height = availableHeight-20;
}
</script>
</head>
<body onload="setHeight()">
<iframe id="tree" src="tree.xml"></iframe>
<div id="content">
<h2>Page Content Here</h2>
</div>
</body>
</html>
In my opinion, Mozilla renders this HTML file correctly but IE doesn't. IE insists on displaying a scrollbar for the <iframe>—even when it isn't necessary.

I've barely scratched the surface of XSLT and XPath. There are tomes of information on the subject. I advise you to inform yourself through whatever means available to you on XML and its accompanying technologies.

The good news is that the solution I've implemented here will work for any XML document that adheres to the tree DTD. Because the implementation breaks the tree into smaller, more manageable pieces, the tree control is both maintainable and extensible. The solution also allows you to concentrate on the content of the tree—freeing you from the underlying complexity. Finally, the solution works in both of the major browsers currently available.

Tom Duffy , DevX's JavaScript Pro, is an Associate Professor at Norwalk Community College, Norwalk, CT where he teaches Web Development and Java. Tom is also the Manager of the NCC Ventures Lab, the college's in-house Web design studio. You can reach him via e-mail .


DevX is a division of Internet.com.
© Copyright 2010 Internet.com. All Rights Reserved. Legal Notices