A List Apart

Menu
Issue № 200

Complex Dynamic Lists: Your Order Please

by Published in CSS, JavaScript, Usability · 57 Comments

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.

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

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

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

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

Load Comments