Improved unobtrusive linked select boxes

By: on February 21, 2007

Here’s a problem that crops up regularly in Web interfaces: having a dropdown whose available options depend on another dropdown. The canonical example, if you like, is the date selector:

Select date

This has the obvious (though not serious) problem that one can select, say, February 31st. We’d like to catch those errors by having the values in the “day” select depend on what is chosen in “month”. It’s easy to do this on the server with a round-trip, of course, but that has the problems that the user interface can be in an inconsistent state before the form is submitted; it would be nice to be able to finesse the interface, using JavaScript, to remove that possibility.

We can do this ad-hoc, of course, but that needs specialised code for each instance (well, for each type of data and dependency); a general solution would be better. We’d also like it to be unobtrusive, by which I mean two closely related things:

  1. The markup is meaningful
  2. It works without JavaScript

Bobby van der Sluis gives a pretty good general solution. Briefly, the technique involves keeping a full set of the dependent options and picking the appropriate options from it when necessary.

It relies on encoding the dependency as an HTML class: to me it’s a muddy use of class, but the real problem is that it’s not a very obvious way of making the dependency explicit in the markup. I think we can improve on it.

Using OPTGROUP we can produce straight-forward markup that makes the dependency obvious:

        <legend>Select date</legend>
        <label for="month">Month</label>
        <select id="month">
          <option value="jan">January</option>
          <option value="feb">February</option>
          <option value="mar">March</option>
        <label for="day">Day</label>
        <select id="day">
          <optgroup label="January">
            <option value="1">01</option>
            <option value="1">02</option>
            <option value="1">03</option>
          <optgroup label="February">

Without JavaScript it looks like this:

Select date

Here’s my (allegedly) improved unobtrusive linked select box code (it uses MochiKit to avoid some verbose DOM manipulation — just read $(x) as “select the element with ID ‘x'”, and the rest does what it says):

function linkSelects(parent, child) {
  var parent = $(parent);
  var child  = $(child);
  var cloned = child.cloneNode(true);
  refreshDynamicSelectOptions(parent, child, cloned);
  connect(parent, 'onchange', function(event) {
    refreshDynamicSelectOptions(parent, child, cloned);

function refreshDynamicSelectOptions(parent, child, optionholder) {
  var alreadySelectedValue = (child.selectedIndex >= 0) && child.options[child.selectedIndex].value;
  var selectedLabel = strip(scrapeText(parent.options[parent.selectedIndex]));
  for (var i=0; i < optionholder.childNodes.length; i++) {
    var opt = optionholder.childNodes[i];
    if (opt.tagName.toLowerCase() == "option") {
      var newopt = opt.cloneNode(true);
      if (newopt.value == alreadySelectedValue) newopt.selected = true;
      appendChildNodes(child, newopt);
    else if (opt.tagName.toLowerCase() == "optgroup" && opt.label==selectedLabel) {
      for (var j=0; j < opt.childNodes.length; j++) {
    var newopt = opt.childNodes[j].cloneNode(true);
 if (newopt.value == alreadySelectedValue) newopt.selected = true;
   appendChildNodes(child, newopt);

and lastly, here's the working version:

Select date

There remain a couple of weaknesses. It relies on the convention of OPTGROUP labels being the same as OPTION text; it's reasonable, since there is a semantic link between those two things. Also, it doesn't always get selection right -- easily seen in this example, where you'd expect a choice of '01' to persist when changing months. I think those are tweaks away.


Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>