Complex Dynamic Lists: Your Order Please

In our struggle to reduce the number of steps site visitors must take to accomplish their goals, we face a number of challenges. One of them is to provide a good way for users to choose from a list of hierarchical elements. For example, a list that serves as a diner menu, offering a selection of drinks, main dishes, salads, and desserts.

Article Continues Below

Two techniques might be used to solve this kind of problem:

  • The straightforward, step-by-step approach with reloads in between each page. This is the safest and most commonly used approach; but it increases server traffic and requires patience on the user’s part.
  • Dynamic select boxes.

The complex solution#section2

Dynamic select boxes, in which choosing an item in the first box changes the content of the second box, saves the user a reload — if she has JavaScript at her disposal. If not, we need to reload the page and populate the second select box on the server. Both options have several problems though:

  • Unless we generate the second list via the DOM, or after the page was
    submitted for the first time, visitors without JavaScript will get an
    interactive page element that doesn’t work: an empty select box.
  • Unless we generate the arrays of the script on the server (by setting a text/javascript header in a PHP script for example) or write them out inline, we have to maintain the data in two locations.
  • The more levels we need, the more complex our JavaScript will get (nested or complex arrays might not be easy to maintain for other developers who will inherit the code after us).
  • If we cannot have the same values as the displayed text (for example on a multilingual site), the arrays get even bigger and more complex.

The easier option#section3

Let’s analyze the problem at hand. We want to:

  • Display a hierarchical list of options in detail and with many elements.
  • Offer the visitor one option level at a time and hide the others.

We don’t want:

  • Tricky or redundant maintenance.
  • “Dead” markup.
  • Dependence on scripting that must be mitigated by duplicate effort on the server side.

Our HTML arsenal gives us a perfect tool for the job: lists. With an unordered list, we can easily display a hierarchical structure of a complexity that would be very hard to achieve with dynamic select boxes. We already do that for the site navigation, so why not here? The only difference is that not all the list items are links; only the final options point to a backend script.

There are several ways to turn this list into an easier-to-use navigation system. We can turn it into a nested drop-down navigation, or something resembling the Windows File Explorer tree. For our example, we’ll work with the following assumptions:

  • We want the result to be completely accessible.
  • As most screens are wider than they are high, we want our navigation system to be horizontally oriented.

The first assumption rules out CSS-only solutions like the Suckerfish Dropdowns, as we cannot expand and collapse elements via keyboard using those techniques. (Few browsers support the :focus property.)

We’ll use the Macintosh OS X Finder as an example interface. In Column Mode (shown below), this application displays the content of your hard drive in, not surprisingly,  columns; clicking a folder icon opens a new one.

Mac OS X Finder in Column View mode (reduced size)

Creating the menu#section4

Our HTML is pretty simple. We use a nested list, and give it the IDfinder,” We then nest it in a DIV to restrain its dimensions and position the list.

    […]

Our script should follow the principles of unobtrusive JavaScript and apply itself only when the browser can deal with it and the correct HTML is available. We add and remove classes dynamically to enable us to maintain the whole look and feel in the CSS.

The script does the following:

  • Checks to see if the browser can use the DOM properly.
  • Tries to find our list with the ID “finder”.
  • Applies the class “domenabled” to the body of the document, which allows us to define different looks depending on whether JavaScript is available or not.
  • Applies the class “hidden” to all lists nested inside the finder list.
  • Loops through all the list items inside the finder list and adds links with the class “parent” to each that contains a nested list.
  • Applies the class “open” to the link when it is clicked on and the class “shown” to the nested list.

To show our finder menu, we need to define the following CSS:

.domenabled #finderparent
{
  position:relative;
  height:150px;
}
.domenabled #finder
{
  position:absolute;
  top:1em;
  left:1em;
}
.domenabled ul#finder,
.domenabled ul#finder li,
.domenabled ul#finder ul
{
  width:200px;
  list-style-type:none;
  margin:0;
  padding:0;
}

We give the parent element a height to contain the finder and position it relatively to make it the relative positioning element of the finder. Then we position the finder list in it and get rid of all margins and paddings. We define each nested list as 200 pixels wide.

Next we must define the two classes to show and hide the nested lists. For that, we use an “off left” technique, based on the work of Mike Rundle:

.domenabled ul#finder ul.hidden
{
  top:0px;
  left:-2000px;
  position:absolute;
}
.domenabled ul#finder ul.shown
{
  top:0px;
  left:200px;
  position:absolute;
}

The last thing we need to define is the look of the different states of the links, the “parent” link indicating that it contains another sub menu, the “open” link showing when the contained sub menu is shown and the final option links:

.domenabled ul#finder li a
{
  color:#000;
  background:url(normal.gif) no-repeat #fff 0 50% ;
  padding-left:16px;
  text-decoration:none;
}
domenabled #finder a.open
{
  background:url(arrowon.gif) no-repeat 90% 50% #eee;
  padding-right:16px;
  padding-left:0px;
  display:block;
}
.domenabled #finder a.parent
{
  background:url(arrow.gif) no-repeat #fff 100% 50%;
  padding-right:16px;
  padding-left:0px;
}

That’s all there is to it. Our finder-style menu is finished. If we click the links of the final options pointing to a backend script we can add our items to an order.

We could even enhance the script to allow the visitor to assemble the whole order without reloading the page. The problem with this is that we cannot offer the same experience to the visitors a real reload can give them. For example, we render the back button of the browser useless. (This is known as the AJAX Problem — or soon will be.)

Please don’t take the order-enabled examples here too literally; this article was meant to show how to use DOM to replace dynamic select boxes with a more accessible list. A real product-ordering system, of course, should have removal options and a price list. Feel free to download the examples and amend them to suit your fancy.

57 Reader Comments

  1. I really like the idea of using more JavaScript in order to make life easier while still keeping accessibility, even if either JS or CSS are disabled.
    When javascript came up, people used to use it for everything, including important things such as navigations which were unreachable for those having JS disabled. However, soon most of them noticed that they excluded them and therefore they decided to completely set it aside. But now the point is reached where we start to combine the power of JS with the DOM. This makes it possible to view and use pages having JavaScript disabled without causing any problems. But if you have it switched on, it’s more comfortable. And that’s what I really like!

  2. I downloaded the example.zip from the link at the end of the article, but it failed at decompression ( Stuffit 9, OS X 10.3.9 ), with an indication that zipping of the files was not correctly executed.

  3. Progressive enhancement is the way to go. The AJaX problem is really a problem though. It makes me wonder if I should reset values in cookie with each action taken by the user. That way the back/forward could possibly bring them to where they left off, but that could get messy.

  4. Jakob Nielson would have a great deal of negative things to say about such a system. Having so many levels is not usable, especially for partially sighted people.

  5. I sometimes wonder if which audience is more important, the people that navigate without js, groups of visually imparired people or the not so small group of 60%+ that navigate with ie…

    your example has images of arrows are all over the show… this is definately due to bugs in ie but i think that all solutions should cater for this audience.

  6. A lot of articles, such as this one, discussing Ajax and the use of Javascript for “progressive enhancement” seem to acknowledge “the Ajax problem”, but few seem to offer any ideas as how to overcome the problem.

    Anybody got any bright ideas for implementing the benefits of Ajax without breaking the older, less-cool, but hugely important web controls such as the back button and bookmarking?

    I can’t think of the number of times I’ve hit the Back button whilst using Gmail, only to be spat back to the entry page.

  7. I really dug the article. I may implement something very similar on a site of mine now.

    As for the back button, I picked up an idea from Eric Costello’s Ajax Summit presentation: Use cookies to store the state. When the person goes back to the page, it looks at the cookie and gets the necessary data to revert to their current state. If you were just concerned with knowing which list item was selected, you could possibly pass something in the URL hash. Something like “#deserts-pancakes-short_stack”.

    Just a thought.

  8. I’m with Alex on this one. This solution (purportedly) increases accessibility while decreasing usability. Actually, the point he made about partially sighted people might infringe on the accessibility front as well. Don’t get me wrong, interesting concept and definitely worth exploring derivatives and other applications, but I just don’t see this being a viable alternative.

  9. This is no triumph for accessibility, either. Using the keyboard, you must tab through all of the links, regardless if they are currently shown or not. A solution to that would need to be found before I would consider implementing this anywhere.

  10. I haven’t been able to toy with AJAX much, but I have had a similar problem with the back button in a WYSIWYG editor we used. When you hit back (before we modded it), everything in the editor was gone.

    The trick was to store the state information in a hidden form element when the onbeforeunload(IE)/onunload(Mozilla) events were raised. When the onload event gets raised, you just pull the state information you stored out and load it. Since onload fires when you hit back/forward and the state information is stored in a form, you can maintain it without using Sessions/Cookies and running into the trouble storing navigational elements in Sessions/Cookies tends to cause (e.g. what if I hit back more than once).

    Again, I’m not absolutely sure the will work in AJAX (or even if it’s a great idea), but it’s probably worth a shot.

  11. There is an assumption here that you can make a menu act like a drop down. The assumption is the reverse of the thinking that a dropdown can be used as a menu. Both are REALLY bad thinking.

    These should not be done as nested lists of links. How would you submit the choice to the server? Use javascript to set a hidden field. Suddenly you don’t have javascript independance.

    The solution is frighteningly simple. It should resolve the accessbility issue and it should resolve the AJAX problem.

    A hierarchical list of Radio Buttons. Now you should be able to use back since the state of the radio button should be preserved. I’ve had issues with onload firing in this situation but that should be resolvable. And it allows you to have defaulted values easily. And it allows you to have more than just the leaf elements selectable.

    Further, using this technique for multiple selections is, IMHO, a usability nightmare. A better solution is a nested list of check boxes that you render as a tree view.

    Finally, if you don’t like seeing the checkboxes and radio buttons, then I think you can get away with using javascript to change the types to hidden. Everything should work honky dory at that point.

  12. You are right if you use the list as a part of a bigger form. However, the final example shows how you send the final option to the server, as a parameter.

  13. Seems like way more clicking than I want to do to eat some Waffles with ice cream. One concept from Edward Tufte comes to my mind when seeing this example:

    Comparison between groups is not possible. Sure, I can see where I’ve been (Desserts [misspelled in the examples], Waffles), but I wouldn’t mind seeing what kind of burgers are available at the same time as what fries to pick. Maybe I’d like to pick some fruit to go well with a drink, but without seeing those items next to each other, I have to flip around a lot between categories (more clicking!!).

    “High density is good: the human eye/brain can select, filter, edit, group, structure, highlight, focus, blend, outline, cluster, itemize, winnow, sort, abstract, smooth, isolate, idealize, summarize, etc. Give people the data so they can exercise their full powers — don’t limit them.”

    I feel like I’m using some poorly designed social networking website with 10 clicks and a few page reloads to accomplish anything. I get irritated and annoyed quickly and want to leave. Surely there are better ways.

  14. The article did what I wanted it to do: It made people think about those two 🙂

    * Keyboard Access:
    It is true that keyboard access is bad, and I don’t know a way around that. The only option would be to check for onkeyup and set the focus on the next link on the same level when enter was not pressed. However, this might interfere with the keyboard settings of assistive technology.
    Another option would be to check if a mouse is in use. I toyed with the idea of checking onmousemove on the body as an indicator. Anyone interested in doing some tests?

    This is a common problem with nested lists, how come it doesn’t crop up when the talk is about fading in CSS only multi level dropdown lists? 🙂

    * Horizontal alignment bad for sight impaired users:
    Again true, but is there a better solution?
    We could collapse the parent level to a smaller area, or just order the whole screen differently. The other option is to offer both ways of choosing your meal: A step by step reload for a smaller screen, or this high level choice. The data could be generated by the same server side script.
    The only way to accommodate every user is to offer her a chance to customise to her needs.

    * 60% of the Users on IE
    I presume you mean the dead IE/Mac? There is a style sheet, it can be changed. As I don’t own a Mac I cannot predict every error of it.

    And finally, Usability is not Nielsen, Usability is dependent on the audience and the users of the product. There is no silver bullet.

  15. How is a link offering “show me all options” any better than just the whole original list with no styles?

    Don’t get me wrong, I think it’s a cool technology concept and degrades well, but I am just questioning how a user would feel about all that clicking. I know I hate those three drop down boxes at car websites: New/Used, Year, Brand, Model. Refresh. Refresh. Refresh.

  16. It makes sense, but honestly if your code is lean a page refresh is not going to break the www. I like the ideas but the end result is disparagingly ugly.

  17. I think you ideas are great. There needs to be some more time spent on such a system if it were to be used. Also, this would not be good for a majority of cases, only some of them.

    Thanx for the article.

  18. I don’t understand why the “off-left” technique is used here. How about display:none instead?

    .domenabled ul#finder ul.hidden
    {
    display:none;
    }
    .domenabled ul#finder ul.shown
    {
    display:block;
    top:0px;
    left:200px;
    position:absolute;
    }

    That way the invisible links aren’t part of the tab order anymore.

    As I understand it, the point of the Rundle’s technique is that screen readers will read text when it is invisible, so that image replaced text will still be read by screen readers. In this case, I don’t think you want a screen reader to read text that has been rendered invisible, right?

  19. During the last Usability testing I conducted a lot of Users kept pressing the back button on a Flash only site. For me that meant the Flash application was not obvious and maybe a bad solution in this context.

    Should I claim now that in every situation and for every project a pure flash site is bad or is usability also dependent on who you expect to reach?
    I never claimed this is the solution to end all solutions, it is something to consider. The safest way is still a step by step order with reloads in between.

    I dont know how ALA would ensure “tested solutions”, can you elaborate?

  20. I like it very much. Just one comment though: under Fries, you forgot Poutine, a French-Canadian delicacy of hot fries covered with white cheese curds and then a heaping layer of gravy, mmmmm, good for the heart.

  21. I’ve been thinking about the state/back button and bookmark problem for a while. Could you not do something as simple as:

    1) Check for session variable. If the session contains a “state” reference ID, load the page with the with a hash reference of the state variable: i.e. mypage.php#9999-9999-9999-9999

    2) If there is no state reference ID, create a new one. This involves creating a new entry in a database table called “state” which will use the state reference ID as the primary key.

    3) For every message that is SENT to the server side, store the message content.

    4) If the user visits the page via a bookmark or URL pasted into the address bar, check the database for the state reference ID, pull out the messages and run them again to rebuild the state of the page.

    All you need to do then is store the last message sent for each updated section of the page (ie. if you have a table containing a list of records, only store the message that was sent to retrieve that list), and then “action replay” the message to get the state back. This would work for the back button/forward button too… (just replay the messages).

    Doing it this way would mean that all the state control stuff would be server side: you would just need to run a function onLoad that checks the database for a state reference ID: if it exists, re-run the message used to generate that state…

  22. oops, forgot to add: the reason for the session stuff is to allow only the session owner to make changes to the page state: “viewers” of the page state would see the page in its last updated form. The viewers ofthe page would be able to change the state, but doing so would give it a new state reference ID….

    Hmmmmm… thinking about it… the state reference table could get very big very quickly if you had a popular “state” page…..

  23. Thanks for the link Christian, I’m glad that people can adapt negative indents or placement to fit their own needs.

    The article was great, thanks for the write-up!

  24. Ben, you’ve proposed a nice solution but I can see that this may have problems scaling for larger sites.

    If you’re going to use a database to record every change in state and then query this every time a page is loaded you’re going to see your database getting out of control very quickly, not to mention that additional overhead that this will add to the loading of your pages.

  25. Yeah… I figured that the DB would get very big very quickly.

    I like the idea of storing the messages sent to create the state of the page, and using the reference to track that state, but it also leads to another problem: how long do you store a “state”? Ideally, it would be for ever.

    Binding the state to the browser (using cookies for example) would let you send the URL to other people either, so thats out of the question.

    One to ponder..

  26. I found a error in your article (not related to your dynamic list).
    you say:
    [snip]Unless we generate the arrays of the script on the server (by setting a text/javascript header in a PHP script for example) or write them out inline, we have to maintain the data in two locations.[/spin]

    I once did make dynamic js with header set to text/javascript and it made IE6 Win98 crash in combination with window.open fore some strange reason.

    One SHOULD set the header to application/x-javascript which is the correct MIME-type. I hope this will help some people.

  27. I like the idea behind this and it might spark some no methods, but the final example takes to much vertical space to use. Which is one of the reason to use a drop down box (select tag).

    A better method is to use the optgroup tag. This way each group of options can be divided nicely in one drop down box.

    If you have sub groups you can use CSS or   entity to add an ident levels. I used   since IE will not let you use CSS to modify the contents of select tag.

  28. Optgroups are a wonderful way to sort a select into different categories, but it doesn’t really indicate a relationship between them and you cannot nest them, or am I wrong there?

    W3c:
    The OPTGROUP element allows authors to group choices logically. This is particularly helpful when the user must choose from a long list of options; groups of related choices are easier to grasp and remember than a single long list of options. In HTML 4, all OPTGROUP elements must be specified directly within a SELECT element (i.e., groups may not be nested).

    As for nbsp, that is a bit oldschool and really is mixing content with presentation- painting with text.

  29. If you define an off-left (or off-top) shift in pixels, then resize text to be extremely large, the text can intrude on the visible page unless carefully constrained. I’ve found it more foolproof to set the left margin of hidden text in ems so that its off-screen offset increases directly with the font size.

    Nice article, Chris. Although, in the face of this bramble of accessibility & usability issues, my approach has been to keep a page quite lean to get immediate response time reloading it with new values. Most of the CSS you’re using here could still apply just fine if each menu level were supplied by the server.

    Since you’re using JavaScript, why don’t you store unused portions of the menu system in hidden elements and then copy them to create new DOM nodes to produce submenus on demand? Or does that strategy have as many thorns for screen-readers as trying to unhide hidden elements?

  30. I like the hierarchical presentaion of information: I am not a web designer, and so do not know the gui issues well. But I am interested in the use of wizards to program code or at least do some kind of code generation.
    BTW, whats wrong with a page submit to send the results to the server? And what about this XMLHttpRequest object I keep hearing about? There is a technique to use a submit from an invisible (1 pixel) frame to transfer data without a page change.
    These techniques get a bit confusing to a server programmer drilled in the ideology of MVC.
    JB

  31. I really like the idea of dynamic web content and I’m trying to utilise as much of it as I can in the slow process of redesigning my web site in my free time. One thing I thought would be nice was dynamic menus and after finding several possibilities I thought I’d look to some of my favorite CSS-oriented sites. Lo and behold I found this. It’s a cool idea, but I was wondering if it’d be possible to implement it differently.

    I’d like to have links that are displayed across a top box (ah, the multi-box stereotypical layout…) change a menu in a left box when clicked or rolled over (either way is fine). So if one link is Home then it’d change the left box to have all the links for the home section. If one top link is Comics then the links in the left box might display stuff like Top Sellers, Recommended Reading, New This Week, etc.

    I don’t know javaScript and I’m still learning PHP. The example, though, seems to load everything to the right.

    Also, what’s this about tabbing through the links? Does that mean all the links are already there and they just have to be displayed, so a user could tab through the ones they can’t even see yet?!

  32. Hi Christian,

    I love your article and solution. It works well in the majority of browsers my visitors use and degrades well if javascript is turned off.

    I’m trying to use it in a situation where I have two lists and I want my visitors to easily tab back and forth between the two lists. I almost have it working except I’d like to have the first list open by default – without the user having to click on the first tab – is that possible to do with your code and what would I need to change?

    Many thanks for a great solution!

  33. shouldn’t be hard to do, I will look into it after the bank holiday. There is sun outside here in London, and that is an occassion far too seldom to let go away unused.

  34. This is a great example. But there is one piece of functionality I would like to have the parents (Main Courses, Drinks, Salads, Deserts in the example) be links (targeted to another frame). When I make them links they become null. It would be great to have another frame display all of the deserts when you click it and also have desert act as a parent and expand to show the desert categories. Is this possible without radically altering the code?

  35. It is possible, but how would you expand the children? This would make you dependent on a mouse again.

  36. I would like to put this in a box (e.g. border around the content), but I cannot get the box to contain the nested lists. In the posted examples, the size of the content area is fixed,so it is a non-issue. I thought that once the absolutely positioned inner boxes were nested inside a relatively positioned box, then the external box would grow to hold vertically to accomodate the the content, but for whatever reason, I can either show the entire list of nested lists and it fits in the box, or I can use the javascript to hide/show elements, but everything after the top level is outside the box.

    Any examples of how I could get this working would be greatly appreciated.

  37. Hello Mr. Niggel

    This is my first time posting… Please don’t be offended.

    I don’t know much about JavaScript, but is it possible to solve the tabbing problem by using tabindex attribute? And could this tab attribute be changed dynamically using JavaScript?

  38. I just posted a new technique on my website to create accessible dynamic select boxes without arrays, using unobtrusive JavaScript and progressive enhancement: http://www.bobbyvandersluis.com/articles/unobtrusivedynamicselect.php

    This technique will probably change the perspective on the complex solution as described in this article. However, the real question will probably be how usable (or suitable) dynamic select boxes are as navigation tools.

    From that point of view I like the solution Chris offers, because it is based on existing popular navigation interfaces and it visualizes how you navigate through a hierarchy.

  39. The Widget Browser on the Apple site should be marked up as a table instead of a collection of unordered lists. There are clearly three columns – not just visually, but logically! – namely Category, Widget and Info.
    Set up as a table, it would also degrade much better for people without CSS or JavaScript. Let’s see if I can cook up an example.

  40. Very incredible script but a big “default” : if you have 100 or 150 possible lists with a graphic (normal.gif), it loads the 100 / 150 normal.gif each time you click. The only solution I have found is to not put pics.

  41. It becomes easy to criticize a script and throw problems at it like “what about the ajax problem?” – “what about when js and css is disabled?” – “what about screen readers?”

    Sheesh. I can see why ALA initiated the ‘experimentation – use with caution’ warnings from a while back.

    However, I will agree on the asthetics of it. It does look a bit awkardly ugly. But what’s really at stake here is the functionality. ALA has never claimed to be awesome designers, but this is a killer script (killer being a good thing).

    Most likely, these are the kinds of widgets that I would implement on my own site. It would take weeks of meetings to get something like this implemented on a real project.

  42. I was and am still very much interested in your method of display and choosing options. However, like some others thoughts – while it appears to be an excellent script technically, the aesthetics does baffle me slightly. I wonder how easy it is to make it into a vertical menu, (rather like the navigation menu for http://www.saila.com/usage/layouts/ ) while still retaining its accessibility? 🙂 thanks!

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA