infimum.dk > DHTML F.A.Q. > Linked Select Elements

Linked Select Elements

A frequently asked question on comp.lang.javascript is how to make linked select elements.

Linked select elements are two or more selects, where choosing a value in one changes the available values in some of the rest.

Example: Selecting a country and town. Selecting the country in one select updates the other select to only contain towns from that country.

This page provides generic code that implements linked select elements.

Problems with Linked Select Elements

Linked select elements rely on Javascript. If a browser does not have Javascript available, for technical reasons or by choice, the entire page can be unusable.

As always, when designing a page with Javscript enhancement, one must ensure that it works without Javascript too.

There are two fundmentally different approaches to solving this:

Both solutions have usability issues.

Making all choices available can give a select with 400 towns to choose from. A select element is not the best way to choose from that many different values. Option groups can be used to group the possible values for easier access, but it is still not optimal.

A Wizard-like interface obscures the choices that needs to be made, and requires more round-trips to the server. Also, users are reported not to like them.

A general problem with a select element is that you can only see a small amount of the possible choices at a time. The functionality of a select element can also be achieved with a radio button group. A radio button group makes all choices visible to the user, but uses more space.

Example: Countries and Towns

As our example, we will select between 25 towns in 5 countries:

CountryTowns
Denmark Aalborg Aarhus Copenhagen Esbjerg Odense Randers
England Bradford Bristol Greenwich Liverpool London
Germany Berlin Dortmund Hamburg München Wuppertal
France Avignon Cannes Le Havre Lille Paris
Greece Athens Delphi Olympia Santorini

The result we want to send to the server, is a two-letter country code and a three-letter town code, separated by a dash. E.g., the code for Copenhagen in Denmark would be "DK-CPH".

The data sample is deliberatly small to make the examples managable. However, you should try to envision the problems, if there were 400 towns from 20 countries. These are the problems linked select elements are supposed to solve.

No linked select elements

If we have no linked select elements, e.g., due to no Javascript support, and we choose to make one large town list, we have one option for each town.

<select name="town"> <option value="DK-AAL">Aalborg</option> <option value="DK-ARH">Aarhus</option> <option value="DK-CPH">Copenhagen</option> ... <option value="GR-SAN">Santorini</option> </select>

The result of this simple code is one long select:

We will use these as the basis of our linked select elements.

Linked Select Elements without Data Tables

Our form needs a an input element with the name "town" that sends the town code. We want to add an extra select element, where one selects the country, and then modify the town select when the country changes.

To support browsers without Javascript, we make the extra select start out hidden, and the town select starts with all possible towns in it. We use the CSS property visibility for this, and it starts out hidden. If targeting only modern browsers, one can use the property display:none instead.

<select id="ctrySel" style="visibility:hidden"> <option value="DK" selected="selected">Denmark</option> <option value="UK">England</option> <option value="DE">Germany</option> <option value="FR">France</option> <option value="GR">Greece</option> </select> <select id="townSel" name="town"> <option value="DK-AAL">Aalborg</option> ... <option value="GR-SAN">Santorini</option> </select>

The Javascript code start by removing the options that are from the wrong country. We store references to all the options in an array, for later access. It then makes the country select visible.

function initLinkedSelect(from,to) {
/* Array for storing option text/value pairs */
var options = new Array();
/* Make country select visible */
(from.style || from).visibility = "visible";
for (var i=0; i < to.options.length; i++) {
/* Save text and value of original options */
options[i] = new Array(to.options[i].text,to.options[i].value);
}
/* When the country selection changes... */
from.onchange = function() {
/* The code for the selected country */
var fromCode = from.options[from.selectedIndex].value;
/* Remove current options */
to.options.length = 0;
/* Run throught all options... */
for (i = 0; i < options.length; i++) {
/* If the option starts with the selected country code */
if (options[i][1].indexOf(fromCode) == 0) {
/* Add the option to the town select */
to.options[to.options.length] = new Option(options[i][0],options[i][1]);
}
}
/* Select the first of the new options */
to.options[0].selected = true;
}
/* Update the town select now */
from.onchange();
}

All you then need to do, is to call initLinkedSelect with the country and town selects as arguments. You can find these elements either through a form's elements collection, or using document.getElementById. The initialization function can be called either in the page's onload handler, or in a script tag after the

The code above is called in this page's onload handler. The linked selects are:

You can try turning off Javascript and reload to see the difference.

This solution has a problem in Netscape 4. If the page is reloaded, Netscape 4 keeps the contents of the select elements, but reruns the initialization code. That results in all other countries' towns being gone.

Linked Select Elements with Data Tables

The previous solution was based on the data in the select elements at the beginning, and it build an array of data from that. Instead, one can start out with a data array and ignore the options already in the select.

A data array can be a simple array of arrays. For each option, we need the text and value, and we need a way to match it to the selected country. We can have more data than what ends up in the options, so we don't need to have the country keys as the options' values. To demonstrate that, we use a different type of country codes as keys.

The HTML is almost the same as before, except for the different country codes used as keys.

<select style="visibility:hidden"> <option value="DEN" selected="selected">Denmark</option> <option value="ENG">England</option> <option value="GER">Germany</option> <option value="FRA">France</option> <option value="GRE">Greece</option> </select> <select name="town"> <option value="DK-AAL">Aalborg</option> ... <option value="GR-SAN">Santorini</option> </select>

The difference is in the Javascript code. Instead of collecting the options from the select element, we have already made an array of option values:

var optionData = new Array(
new Array("DEN","Aalborg","DK-AAL"),
new Array("DEN","Aarhus","DK-AAR"),
...
new Array("GRE","Santorini","GR-SAN")
);
function initLinkedSelect(from,to,options) {
(from.style || from).visibility = "visible";
from.onchange = function() {
var fromCode = from.options[from.selectedIndex].value;
to.options.length = 0;
for (i = 0; i < options.length; i++) {
if (options[i][0] == fromCode) {
to.options[to.options.length] = new Option(options[i][1],options[i][2]);
}
}
to.options[0].selected = true;
}
from.onchange();
}

The resulting linked selects are:

This method works without problems in Netscape 4, and has the advantage of disconnecting the keys used to select the correct options, from the values of those options. The disadvantage is that you transfer the same data twice: once in the table and once in the initial options of the select element.

Other Options

The Wizard-like interface starts out with only the country select element and a "Go" (submit) button. If Javascript is available, the button is hidden, and the town select element is made visible. Apart from that, the code is the same.

Instead of having an invisible element on the page from the start, it can be added using Javascript document.write. Then it truly isn't there if Javascript is disabled. For a Wizard-like interface with a data table, the document.write can be based on the data table and avoid transfering the same data twice.


lrn@infimum.dk