A dependent select is when a form select box's options depend on the values of a previous form select box. Two examples of this would be a USA State selector, which then filled the next box with cities from that state. Another example would be a car make selector (ie. Ford) which then displayed models (ie. Taurus, Bronco, F-150).
Why would you want to do this? Primarily for useability. In the case of car make/model, there are about 130 makes, but there are over 2000 models. If you didn't use javascript, you'd have a model select that was 2279 items long! That's pretty unwieldy.
Remember the source code for this project is attached at the end! (Both a sample HTML document, and the javascript)
01: Pick A Data Source
In making a plug-in like this, your first decision has to be about how it's going to work. Where is the data for the 2nd select going to come from? There are two options:
* Load Data via AJAX request
* Store all the data on page in javascript arrays
In many cases you want your web pages to work without javascript, relying on script to only enhance the user experience. If we use AJAX to load the data, then users without javascript won't see any data in the second or subsequent selects. All 2000+ options is better then no options.
02: Design the API
API means "application programming interface" -- or in more simple terms, we mean "how will the plug-in be used?". The first thing I'm going to do is create a call to the mythical plug-in.
$('#master_select').dependent({ chain : ['#slave_select', '#slave_to_slave_select'] });
#('#country').dependent({ chain : ['#state', '#city'] });
03: Other Assumptions
We're going to assume that slaved selects are already pre-populated with data because we want the page to work without javascript.
We're also going to assume the code for the selects has been set up properly, so our script can know what information is related to what options. To make that happen, we're going to use optgroups. Optgroups are part of HTML that allows you to put a non-selectable label above a group of options in a select. The label of each optgroup will identify which options belong with the choice from the previous select. Here's an example to help make it more clear:
<select id="country"> <option value="USA">USA</option> <option value="Australia">Australia</option> </select> <select id="state"> <optgroup label="USA"> <option value="IL">IL</option> <option value="TX">TX</option> </optgroup> <optgroup label="Australia"> <option value="New South Wales">New South Wales</option> <option value="Western Australia">Western Australia</option> </optgroup> </select> <select id="city"> <optgroup label="IL"> <option value="Chicago">Chicago</option> <option value="Springfield">Springfield</option> </optgroup> <optgroup label="TX"> <option value="Houston">Houston</option> <option value="Dallas">Dallas</option> </optgroup> <optgroup label="New South Wales"> <option value="Sydney">Sydney</option> <option value="Taree">Taree</option> </optgroup> <optgroup label="Western Australia"> <option value="Perth">Perth</option> <option value="Albany">Albany</option> </optgroup> </select>
04: Processing the Selects
When the plug-in is called on a select there's not going to be much to do outside of the main this.each loop (see the previous article for the general plug-in architecture). However once we get into the loop, we have to parse the select options and stuff them into an array for later use. The array we're going to put everything in will be stored as opts.data.
The first thing we're going to process is the selects listed in the chain option. Here's the beginning of our main loop:
this.each(function() { $.each(opts.chain, function(index, selectID) {
array['TX'] = "<option>Dallas</option><option>Houston</option>";
// substr is used to strip off the # at the start of the string var ident = selectID.substr(1); var selectHTML = $(selectID).html(); opts.data[indent] = {};
$('<select>'+selectHTML+'</select>').find('optgroup').each(function() {
var optGroup = $('<div>').append( $(this).eq(0).clone() ).html(); var group = $(this).attr('label');
Then, store optgroup HTML in our data array:
// an example is: opts.data[city][IL] = optGroup's HTML opts.data[ident][group] = optGroup;
05: Binding the events
Next we need to create some event bindings. If we are linking more then two selects, then the chain will be at least two values. In fact, we need to keep binding selects until the select we're working on doesn't have one following it. Here’s how we'll check for that:
if(opts.chain[index+1]) {
$(opts.chain[index]).bind('change', function() { // returns the bare ID like "state" var next = opts.chain[index+1].substr(1); $('#'+next).html(opts.data[next][$(this).val()]); $('#'+next).trigger('change'); });
The next line changes the contents of the 'next' select to the optgroup we want. Here's a more readable version of that array value, for clarity:
opts.data[the-next-selects-name][the-current-select-value]
The final line triggers the change event in the select because programmatically changing the values of the select does not fire this event. This means when you make a change on any select, it filters all the selects following it in the chain. You can see this pretty clearly in the attached example files, because that code includes 4 dependent selects.
06: The Last Step
There's one last step in the main $.each loop -- and outside the loop created for the values of opts.chain. Can you guess what that is? We need to bind the original select passed into the plug-in!
$(this).bind('change', function() { var next = opts.chain[0].substr(1); $('#'+next).html(opts.data[next][$(this).val()]); $('#'+next).trigger('change'); });
Conclusion
Now that you've read the tutorial, have a look at the source code file attached to see the complete code in one place. Because it's packaged as a plug-in you can put the javascript in its own file and include it in any page, and then use it just like any other jQuery plug-in.
dependent-selects.zip
In our next jQuery article, we're going to explore turning your standard forms into AJAX submitted forms. We'll continue with the theme of putting our custom code into a reusable plug-in so we can write once and reuse.