I have a special fondness for DHTML tree widgets. It was my very first STRANGE CODE episode, and was even actively debugged by potential users. So why am I revisiting it here and now?
The answer is actually rather simple. The original version of that code was written in early 2001. At the time of this writing (summer 2003), it's been over two years since I wrote it.
Version 2.0 of the tree widget used custom tag properties to control display of the tree nodes. This worked great with IE 6 and any Gecko-based browser, but caused nothing but heartache for lesser versions of IE (I know that's a redundancy, thank you...). The quick hack to fix that problem was what mutated into version 3.0 of the tree widget, which is now in use throughout this website.
Version 3.0 also makes extensive use of my "DHTML Aikido" technique from several recent STRANGE CODES. For those too lazy to review, DHTML Aikido is where you do as much as you can via CSS definitions, then just switch relevant class names via a simple Javascript to activate style changes.
The following is one type of markup you can use for your tree widget:
<ul class="tree" onclick="test(event)">
<li class="leaf">
<div class="branch">Header 1</div>
<ul class="node">
<li class="leaf"><a href="">item</a></li>
<li class="leaf"><a href="">item</a></li>
<li class="leaf">
<div class="branch">Header 11</div>
<ul class="node">
<li class="leaf"><a href="">item</a></li>
<li class="leaf"><a href="">item</a></li>
</ul>
</li>
</ul>
</li>
</ul>
If you View Source on the STRANGE CODE index page, you will find that I use nested <DIV>s to achieve the same look and feel. It's all a matter of preference.
Since most of Version 3.0's work is being done with styles, we're going to spend a lot of time and detail on them.
.tree { height: 30em;
padding-left: 0em;
border: inset 2px #ccc;
background: #fff;
font: 10pt Geneva,Verdana,Helvetica;
cursor: pointer; }
This is the overall defining style for the tree widget. At this point in the style cascade, we set its dimensions and appearance. Of special interest is the last rule, where we reset the cursor from its default appearance over text. This gives users the impression that they're dealing with some sort of sophisticated page applet rather than a bunch of CSS rules and a small Javascript. Unless they View Source, they shouldn't be any the wiser.
.branch { border: outset 2px #999;
padding: 0em 0.25em;
background: #999;
color: #fff;
font-weight: bold; }
This class serves as the title for each node in the tree. When nodes are collapsed, this will be the only part visible. It also serves as the event target for Gecko-based browsers.
.node { padding-left: 0.5em;
margin-bottom: 0.5em;
display: none; }
This class coexists at the same level of the DOM node tree as branch. There will be one node for each branch in the tree.
ul.tree li { list-style: none;
border-left: solid 1px #000;
border-bottom: solid 1px #000; }
This sets the overall style for the leaves of the tree widget. I don't assign these style rules to the class leaf because if I did I'd have to go through the extra work of defining these rules for the active class as well. Since I will be toggling this level of object between the leaf and active classes, I simplify my class declarations by defining them this way.
.leaf a { text-decoration: none; }
This style is a matter of personal preference on my part.
.active ul.node { display: block; }
This style makes a selected node visible.
.node li.leaf ul.node
{ display: none; }
This style has to be included to handle the case of nested elements. If it weren't here, every time I click to expand a tree leaf, all nested leaves would expand with it. Not exactly the look and feel that we're all accustomed to from tree widgets now, is it?
0 function test(e) {
1 var el = window.event? window.event.srcElement: e.target.parentNode;
2 if (el.className == "branch") el = el.parentNode;
3 switch (el.className) {
4 case "leaf": el.className = "active"; break;
5 case "active": el.className = "leaf"; break;
6 }
7 }
Our driver function takes a single argument, which would be a reference to an Event object (which of course would be null in any version of IE). Line 1 determines the selected tree branch based on where the event happened, and does it in a cross-browser fashion. Line 2 checks the className of the clicked element, and goes up one more level in the DOM node tree if it has a class of branch. Remember from the Markup section that the branch object is at the same level as the node object we are trying to affect, and you'll realize why we need to manipulate the class of the object which contains them both.
Okay, now that's done. The money code starts with line 3. The container class can only have two values: leaf and active. I use a switch construct at this point to set the className accordingly (lines 5-6). The style cascade does the rest from this point.
The code outlined above also assumes some level of CSS level 1 compatibility from your browser in the construction of some of the rule selectors.