Paired multi-selects with Optgroups

| | Comments (1)

One of the most underused HTML elements is the optgroup. When you have a select with many options, you can separate them into groups, indented with labels. Like this:

One of my least favorite web idioms is the paired multi-select where you have two select boxes and buttons to move items between them. For work, I needed to add optgroups to an existing paired-select. Unfortunately, simply adding the optgroups revealed limitations in the Javascript that moves the items left and right. I looked around on the web and couldn’t find any examples that implemented this with optgroups in mind. Most of the implementations I’ve seen re-sort and completely recreate both lists on each move. It seemed like trying to add optgroups into this method would be problematic so I decided to try and come up with something from scratch…

Here is what the test looks like:

Rather than recreating the selects elements each time, I simply use the DOM methods to move the individual objects. Also, I store the optgroup id name as an attribute in the object in case we need to move it back. Here is the code:

<script>
function add_sorted (parent_node, new_node) {
    var children = parent_node.getElementsByTagName('OPTION');

    for (var i = 0; i < children.length; i++) {
        if (new_node.text < children[i].text) {
            parent_node.insertBefore(new_node, children[i]);
            return;
        }
    }
    parent_node.appendChild(new_node);
}
function move_right () {
    var left = document.getElementById('left');
    var right = document.getElementById('right');

    var Options = left.getElementsByTagName('OPTION');
    for (var i = 0; i < Options.length; i++) {
        if (Options[i].selected) {
            Options[i].setAttribute("group_id", Options[i].parentNode.id);
            add_sorted(right, Options[i]);
            i = -1;
        }
    }
}

function move_left () {
    var left = document.getElementById('left').getElementsByTagName('OPTGROUP')[0];
    var right = document.getElementById('right');

    var Options = right.getElementsByTagName('OPTION');
    for (var i = 0; i < Options.length; i++) {
        if (Options[i].selected) {
            var group_id = Options[i].getAttribute("group_id");
            var move_to = left;
            if (group_id) {
                move_to = document.getElementById(group_id);
            }
            add_sorted(move_to, Options[i]);
            i = -1;
        }
    }
}
</script>
<form>
<select multiple id="left">
<optgroup label="Group 1" id="group1">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</optgroup>
<optgroup label="Group 2" id="group2">
<option>Option A</option>

<option>Option B</option>
<option>Option C</option>
</optgroup>
</select>
<input type="button" value="--&gt;" onClick="move_right();">
<input type="button" value="&lt;--" onClick="move_left();">

<select multiple id="right">
<option>Option Y</option>
</select>
</form>

This is just test code and could be generalized more. It makes some assumptions based on our particular problem domain. Only the left box is grouped and items that start on the right (Option Y) default to the first optgroup when moving right to left. There is a bug for IE6 where the selects don’t resize based on their new content. I have not tested in IE7.

1 Comments

Sean said:

Here’s a FIX for FF 3.6.17: I’m not sure why your example on this page works (transitional doctype?), but in my copy locally this happens:

After the first move you have to click either button twice to get it’s click event to register. I fixed this by adding style.height to both tags. Apparently they need “layout”. No problems in IE.

Leave a comment

Are you a spammer? (Yes/No)

About this Entry

This page contains a single entry by Stuart published on November 5, 2007 4:05 PM.

Romney and Me was the previous entry in this blog.

Battlestar Galactica producers are pinko-commies is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.