The requirements for this control were pretty straight forward. First, it must function in IE6, IE7 and Firefox. It should work by moving an option from one select to the other, while keeping the option within its defined optgroup. For instance, if Kansas is in the optgroup Midwest, when Kansas is moved to the other select, it needs to go in the Midwest optgroup. If the Midwest optgroup doesn’t exist then the optgroup needs to be created first. I also toyed with the idea of removing the optgroups once they were empty. I got it working but there are some odd idiosyncrasies to the way the hasChildNodes function works so I’m most likely not going to include it in my project. The final, and perhaps most important requirement, is that the tool needs to keep track of what items are added and removed from the right-hand side select. So, how do we do this?
To start, we pass in the ids of the three controls. The to select, the from select and the hidden that will be storing our values. Assuming they load appropriately, we should be good to go.
var DELETE_OPTGROUPS = false;
function optgroupMove(toID, fromID, addID)
var to = document.getElementById(toID);
var from = document.getElementById(fromID);
var deletedOptions = new Array();
var hidden = document.getElementById(addID);
Next, we cycle over the from select. Any option that is currently selected will be moved to the “to” select. To do this, we retrieve the selected option, get its optgroup, create a new option, add it to the select and then assign the text and value to it. It may seem odd to do the text/value assign last but it was the only way to get the appendChild function to work in IE.
for(x=0; x<from.options.length; x++)
if(from.options[x].selected == true)
/* Create the option, add to select and then add text and value.
* We do this because IE6 and IE7 lose the text and value when
* added to the select after the text and values have been set.
var option = from.options[x];
var optgroup = option.parentNode;
var optgroupMove = document.getElementById(toID + optgroup.label);
var newOption = new Option();
newOption.text = option.text;
newOption.value = option.value;
So far, we’ve just stuck the option in the select. It’s not associated with an optgroup. But, the optgroup we want may not exist. So, we check to see if it does and if not, we create one.
// Check to see if the optgroup exists. If it doesn't create it
var optgroupNew = document.createElement('OPTGROUP');
optgroupNew.id = toID + optgroup.label;
optgroupNew.label = optgroup.label;
optgroupMove = document.getElementById(toID+optgroup.label);
Now that we’re sure that we have an optgroup we can append our option to it. We also then add the old option to the deletedOptions array, as well as the provided hidden variable, if it exists.
// Add our wayward option to the optgroup
deletedOptions[index++] = option;
hidden.value = hidden.value + option.value + ',';
The final piece of the puzzle is to remove the option from the originating select. To do this we simply loop over the deleted options array, removing each option from its corresponding optgroup. This block also has the code to remove the optgroup if it’s empty. This is a bit finicky in how it works though. I’ll explain more below…
for (y = 0; y < deletedOptions.length; y++)
var optgroup = deletedOptions[y].parentNode;
/* The following removes the optgroup from the select if it is
* empty. I made this optional because there is odd behavior
* in the return from hasChildNodes if there is any whitespace at
* all within the optgroup as it was orignally defined. White space
* constitutes a child node.
if(DELETE_OPTGROUPS && !optgroup.hasChildNodes())
var pNode = optgroup.parentNode;
Finally, the last piece is to deselect the selects so the have no options selected.
// Unselect all options in both "to" and "from" select
to.options.selectedIndex = -1;
from.options.selectedIndex = -1;
And that’s it. View/test the code. Enjoy!