Paired multi-selects with Optgroups
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="-->" onClick="move_right();">
<input type="button" value="<--" 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.
Leave a comment