Unobtrusive DHTML, and the power of unordered lists

In recent months, we've seen people begin to take advantage of the fact that nested unordered lists (that's <UL> and <LI> tags) express a hierarchial structure by making code that displays this simple hierarchy in a more complex form: a set of dropdown menus, or an expandable/collapsable tree. Notable efforts in this direction include:

My own aqtree2
This creates an expandable/collapsable tree from a set of nested ULs. Badly, though, as I'll discuss below.
Dave Lindquist's dropdown and expandable tree implementations
These have a much more elegant approach to working with the HTML, using CSS for much of the work, but the initialisation is stressful (again, see below).
Eric Meyer's pure CSS menus
The most impressive, and indeed most ideologically pure, of these implementations, Eric (as you might expect) uses no Javascript at all, just pure CSS. However, it only works in browsers with tip-top up-to-date CSS support.

So, what's good and bad about these? The best thing about Dave's take on the problem is how elegant and neat it is; most of the positioning of menus and submenus is accomplished through CSS, which is what CSS is for. The JavaScript just binds it all together. By contrast, my previous implementation uses the DOM to convert the neat nested lists structure into a hideous mess of nested DIVs. Now, this isn't a problem for accessibility, because they both appear the same way to users: Dave never changes the markup in the document, and aqtree never changes it away from accessible ULs/LIs for people whose browsers aren't capable of running the script. But I still don't like aqtree's approach to this; as I say, it lacks elegance.

However, a big victory with the aqtree approach, I think, is that you can just include the Javascript file in your page and set a class name on lists you want converted, and that's it. With Dave's approach, on the other hand, you need to add script statements to your page saying "make this list a menu, make that list a menu", and add classes to lots of different bits of your menu. Bah. In these days of full DOM control from Javascript, a page can be extremely introspective; you shouldn't have to label up bits of your page if the code can work them out for itself. So that should also be part of our bigger and better solution.

I've also taken into account some of the work done by Aaron over at youngpup; he lists how he thinks that DHTML should be done while discussing his Labels.js demo. The key word here, I think, is unobtrusive. DHTML of this kind should just drop into place, providing a better user experience for people whose browsers can support it, and not affecting those whose browsers cannot. As Aaron says, "DHTML is designed to be just another layer of enhancement on top of core HTML." I like it to be that unobtrusive for the developer, too; if implementing these scripts is awkward then people won't do it, or will do it wrong, or have to work hard to do it right, and that's bad all round.

So, without further ado, here's a couple of examples.

This is a nested unordered list. Not all that exciting, I think you'll agree.

This, however, is a dropdown menu. The point here is that in the HTML it is exactly the same source, exactly the same list as the above one. The only change is that we've added a class to this one, so that the Javascript library and the CSS parser that make this work knows which lists to change and which to leave alone.

This, once again, is exactly the same list, but with a different class again, and now it's an explorer-style tree.

And this is the same list again, as an explorer-style tree with clickable bullets.

How you can do this

Dropdown lists with aqdd

Making these effects happen is pretty easy, in accordance with our principles of ease and unobtrusiveness from above. To convert a list into a dropdown menu, add the following lines to your code:

In the header of your document (between the <head> and </head> tags), add a line to include the Javascript file that does the work:

<script src="aqdd.js" type="text/javascript"></script>

Also in the header, add a line to bring in the CSS stylesheet that does the rest of the work:

<link rel="stylesheet" href="aqdd.css">

Take copies of the CSS stylesheet, aqdd.css, the Javascript library, aqdd.js, and the arrow image used to indicate a submenu, and put them in the same directory as your HTML file.

Finally, find each set of nested lists that you want to make into a dropdown menu, and make each one of class aqdd:

<ul class="aqdd">
... etc etc ...

Note that you do not need to change the class of any of the internal ULs, only the top-level one.

Note also that you need valid markup for this. To be specific, you must ensure that a UL contains only LIs and nothing else. For aqdd to work properly, you should then ensure that an LI contains either:

That's all you need to do to have that nested list structure converted to a dropdown list.

Explorer trees with aqtree3

You set up aqtree3 in almost exactly the same way as aqdd, above; just a couple of filename changes are needed. First, add lines for Javascript and stylesheet:

<script src="aqtree3.js" type="text/javascript"></script>

<link rel="stylesheet" href="aqtree3.css">

Grab the files:

And add the class name aqtree3 to the ULs that you want to be displayed as explorer trees.

Better explorer trees with aqtree3clickable

You set up aqtree3clickable identically to aqtree3. First, add lines for Javascript and stylesheet:

<script src="aqtree3clickable.js" type="text/javascript"></script>

<link rel="stylesheet" href="aqtree3clickable.css">

Grab the files:

And add the class name aqtree3clickable to the ULs that you want to be displayed as explorer trees.

There you have it. Unobtrusive but effective DHTML.

Update (09-04-2003): minor change by Albert de Klein to fix a bug.

Update (18-09-2003): Added aqtree3clickable.

Update (24-12-2003): Allow setting of aq3open class on LIs in aqtree3clickable to have some branches expanded by default.

Update (01-08-2005): Fix bug in aqtree3clickable.css regarding indents, thanks to bug report from Adam J. Sontag

Updated (2006-01-13): use the corrected addEvent function.

Stuart Langridge, November 2002

kryogenix.org | other browser experiments