Selectbox enhancement for Javascript

Summary

Links two select boxes together, so that making a selection in one will populate new options in another.

Example

The choices you make on the left may expand or limit your options of the right.

Options are TRIGGER_PREFIX and TARGET_PREFIX : a pattern to replace in the IDs of the two boxes to find which matches with which. The selector object should have an id of the form {trigger}_name Which tells us it relates to the element with id {target}_name found nearby on the page.

The source data lists are expected to be globally available, A big array of "ranges" which contains the data for the target and a small array of "selector_ranges" arrays which map the selected value to one or more of the named ranges.

Code


/////////////////////////////////////////////////////////////////
// DEMO DATA

var selector_ranges = new Array();
// selector_ranges define how the list on the left maps to GROUPS of
// values that may go into the right.
selector_ranges['canines'] = new Array('doglist');
selector_ranges['felines'] = new Array('catlist');
selector_ranges['zoo']     = new Array('doglist','catlist');
selector_ranges['other']   = new Array('Literal');

var ranges = new Array();
ranges['doglist'] = new Array();
ranges['doglist']['Canus Canus']      = "Cross breed";
ranges['doglist']['Canus Familiarus'] = "Sheep Dog";
ranges['doglist']['Canus Domesticus'] = "Lap Dog";

ranges['catlist'] = new Array();
ranges['catlist']['Felix']            = "Moggy";
ranges['catlist']['Felis Domesticus'] = "Pussy";
ranges['catlist']['Felis Catus']      = "Cooking Fat";

function demo_list(){
  var target       = document.getElementById("target_xyz");
  var list         = new Array("monkeys","lions","tigers");
  list["swallows"] = new Array("African","European");
  target = change_element_to_select(target,list);
}

//
// The ranges and selector_ranges arrays
// should be over-ridden in your own page
// for yout own data.
// If neccessary, override the _PREFIXes as well.
/////////////////////////////////////////////////////////////////

var TRIGGER_PREFIX = 'trigger_';
var TARGET_PREFIX  = 'target_';

/**
 * Find partner with matching name and tell it what I've changed to
 */
function change_select_range(selector,target_name) {
  var selected = selector.options[selector.selectedIndex].value;
  if (!selected) return;
  if(!target_name){
    target_name = selector.id.replace(TRIGGER_PREFIX,TARGET_PREFIX);
  }
  change_select_range_to(target_name,selected);
}

/**
 * Get options appropriate to the current selection
 * from the global arrays and send them to the target.
 */
function change_select_range_to(target_name, range_name) {
  var target = document.getElementById(target_name);
  if (!target){
    alert("Failed to locate target '"+target_name+"' when trying to repopulate the options. "+target); return;
  }

  var new_ranges = selector_ranges[range_name];

  if((! new_ranges) || ! new_ranges[0] ){
    new_ranges = selector_ranges["default"];
  }

  if(new_ranges[0]  == "Literal" ){
    // The only valid value is freetext. Change input to a text box
    change_element_to_freetext(target);
    return;

  } else {

    // retrieve the contents of the named ranges to insert into the option list.
    var indexed_options = new Array();

    for(var r in new_ranges){
      // may be nested references to other sets. Expand them
      var subset = selector_ranges[new_ranges[r]];
      if(subset){
        for(var ss in subset){
          new_ranges[ss] = subset[ss];
        }
      }
    }


    for(var r in new_ranges){
      var r_name = new_ranges[r];
      if(! ranges[r_name]){
        alert("Unknown range. The selector "+r_name+" is supposed to indicate targets of type \'"+new_ranges[r]+"\' but that data was unavailable.");
        continue;
      }
      indexed_options[r_name] = ranges[r_name];
    }

    change_element_to_select(target,indexed_options);
    return;
  }

}

/**
 * Destroy and re-create an input field as a select (retaining old value)
 * Insert a structured array of values into it
 */
function change_element_to_select(element,indexed_options) {
  var newField = document.createElement("select");
  newField.setAttribute("name", element.getAttribute("name"));
  newField.setAttribute("class", element.getAttribute("class"));
  newField.setAttribute("className", element.getAttribute("className"));
  newField.setAttribute("className", element.getAttribute("className"));

  var currentval=element.getAttribute('value');
  if(!currentval && (element.selectedIndex>-1) ){currentval = element.options[element.selectedIndex].value;}

  for(o in indexed_options){
    add_option_to_select(newField, o, indexed_options[o], currentval );
  }

  var id = element.id;
  element.parentNode.replaceChild(newField,element);
  newField.id = id;

  if(typeof(init_combo_box)=='function')init_combo_box(newField);
  return newField;
}

function add_option_to_select(select,label,list,selected) {
  // allow recursive option groups
  // IE doesn't display nested optgroups :(

  if(typeof(list) == 'object'){
    // the options are grouped
    var og = document.createElement("optgroup");
    og.setAttribute("label",label);
    select.appendChild(og);

    for(var varname in list)
    {
      add_option_to_select(og, varname, list[varname],selected);
    }

  } else {
    var op = document.createElement("option");
    op.setAttribute("value",label);
    op.appendChild(document.createTextNode(list));
    select.appendChild(op);
    if(label == selected){op.setAttribute('selected','selected');op.selected=true;}
  }
}



/////////////////////////////////////////////////////////////////
DOM by
Credits to quirksmode for a selectbox manipulation demo.

Self-documenting code idea from Tantek Çelik