We all know that a tree, like the one seen in Windows' File Explorer, is nothing more than a nested list. But is it possible to code a tree up in HTML/CSS as a nested list?
Let's start off with the list. This is a snippet of a standard file tree, organised in "folders".
<ul> <li> Graphics <ul> <li><a href='gpu.h'>gpu.h: Function prototypes</a></li> <li><a href='gpu.c'>gpu.c: Graphic output implementation</a></li> <li> Debug Output <ul> <li><a href='dbgout.h'>dbgout.h: Output prototypes</a></li> <li><a href='dbgout.c'>dbgout.c: Output fixed-width font drawing</a></li> <li><a href='font5x7.h'>font5x7.h: Fixed-width font definitions</a></li> </ul> </li> </ul> </li> </ul>
And this is what we get from that code.
- Graphics
Having a tree means that each branching node can expand or collapse, to
show or hide the elements of the tree within it. The showing and the hiding
isn't so difficult; the display
property in CSS allows us to
do this pretty quickly, if we define two classes:
ul.hide { display: none; } ul.show { display: block; }
Of course, it's not quite that simple. You can't change the state of a
UL
very easily (clicking on it won't do); but you can
change the state of an LI
. So we move the two classes to the
enclosing LI:
li.hide ul { display: none; } li.show ul { display: block; }
So, we have the CSS for hiding the tree. But how do we switch states?
How can we show and hide the nodes at will? That's where the DOM comes in.
If we put the description of the tree item ("Debug Output" for example")
in an active element (DIV
or A
maybe), we can
attach DOM events to it.
I've decided not to use A
, because an anchor requires a
href
, and using a link of #
will clutter up
your browser's History facility. So, let's use a DIV
.
What do we want to happen when we click the DIV
? Basically,
just flip the state of the parent LI
, such that the
UL
s underneath are visible.
<ul> <li class='hide'> <div onclick='toggle(this.parentNode)'>Graphics</div> <ul> <li><a href='gpu.h'>gpu.h: Function prototypes</a></li> <li><a href='gpu.c'>gpu.c: Graphic output implementation</a></li> <li class='hide'> <div onclick='toggle(this.parentNode)'>Debug Output</div> <ul> <li><a href='dbgout.h'>dbgout.h: Output prototypes</a></li> <li><a href='dbgout.c'>dbgout.c: Output fixed-width font drawing</a></li> <li><a href='font5x7.h'>font5x7.h: Fixed-width font definitions</a></li> </ul> </li> </ul> </li> </ul>
So when you click the "Graphics" DIV
, toggle()
runs and flips the top LI
from hide
to show
.
And of course, if you click it again, it flips back to hide
.
We'll need some JavaScript to do this; fortunately, JS gives us the ternary
operator, where we can select two options based on a condition.
function toggle(x) { x.className = (x.className=='show') ? 'hide' : 'show'; }
What this means is: "If the className is show
, set it to
hide
, otherwise [ie. if it's not show
] set it to
show
". Since there're only two possibilities for the class
name, you can see that this toggles between the two.
Just before we get to an actual working example, you should remember that
you'll have to define CSS for each level of menu that we go down, since
the properties won't inherit between UL
s if there's an
LI
in the way (which there always is).
So now, we can put it all together, and come up with a simple tree that is collapsible/expandable with a bit of DOM fiddling.
-
Graphics
Here, I've just added some styling to the text DIV
, which can change
along with the parent LI
state just as the UL
does. Again, the inheritance of properties will be lost between levels,
so just put in an extra line for each level down.
Now we have just one problem. As you can see, the page loads with
the Graphics item collapsed. What if you don't have JS running? Click on
the DIV
and nothing happens; you can't get to the list
underneath! Obviously a problem. The way to alleviate this is to have
everything expanded by default instead of collapsed; if you need to,
use an onload
tree collapse so that the tree will collapse
if you run JS, and stay expanded if you don't.
function treeCollapse(){ var list = document.getElementById('yourtree').getElementsByTagName('li'); for(var i=0;i<list.length;i++) list[i].className = 'hide'; }
This'll just get a list of all the LI
s in the tree, and
set them to class hide
.
So, that's how to make a nested list into an expandable tree.