I recently stumbled across a newsgroup post where someone needed dependent list boxes that let users select a car make from one list box and a second list box would be populated with car models. He wanted to use AJAX to get the list of models from the server, but also wanted a solution that would work for users that didn’t have JavaScript (particularly, mobile device users). My suggestion was to use progressive enhancement. The end result can be viewed at http://www.fotiweb.com/samples/dependent-list-box/, though some of this solution is PHP based (see below for that code).
My suggestion was to create a web service that would take a car make and return a list of models. He could interact with this service via an AJAX call, parse the results, and populate the list box with the results. Alternatively, his server side processing could determine if a make had been selected, and if so call the service to get the list and populate the list box that way.
I started by creating a basic HTML form:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
<style type="text/css">
select {
width: 15em;
}
<title>Untitled</title>
<body>
</body>
</html>
I used placeholders where the list options will be.
Next, I created the web service at . Here’s what that service looks like:
<?php
// This example returns just a simple comma separated list.
// A real world example would probably look up values from a
// database and might return a JSON or XML string with the
// results. This is just a proof of concept.
if (!isset($_GET['make'])) {
// Exit nicely
exit(0);
}
$make = $_GET['make'];
switch ($make) {
case 'Ford':
echo 'Focus,Explorer';
break;
case 'Nissan':
echo 'Sentra';
break;
case 'Toyota':
echo 'Prius,Tundra';
break;
default:
echo '';
break;
}
exit(0);
?>
Next I needed to create the PHP behind my form page. This PHP populates the vehicle makes and if the user has selected a make and clicked the “Get Models” button, will also populate the models by making a curl request to the web service.
<?php
// The 'makes' list might be populated from a database
$makes = array('Ford', 'Nissan', 'Toyota');
// 'models'
$models = array();
// 'selectedMake' The make selected by the user (if any)
$selectedMake = '';
// Check whether the make has been submitted
if (array_key_exists('getmodels', $_GET)) {
if ( isset($_GET['make']) ) {
$selectedMake = $_GET['make'];
// Request model values for the selected
$ch = curl_init("http://www.fotiweb.com/samples/dependent-list-box/getModels/?make=$selectedMake");
// Return the transfer as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// $output contains the output string
$models = curl_exec($ch);
// Close the curl resource
curl_close($ch);
// Convert results to array
$models = explode(',', $models);
}
}
// Format variables for HTML output
$makeOptionList = '';
$modelsOptionList = '';
// Generate the options list of makes
foreach ($makes as $make) {
$makeOptionList .= "<option value='$make'";
if ($selectedMake == $make) {
$makeOptionList .= " selected='selected'";
}
$makeOptionList .= ">$make</option>";
}
// Generate the options list of models
if (count($models) > 0) {
foreach ($models as $model) {
$modelsOptionList .= "<option value='$model'>$model</option>";
}
}
else {
$modelsOptionList = '<option value="">Select a Make</option>';
}
?>
So now the page is functioning for all users that don’t have JavaScript. When the page loads, the makes will be populated. If the user clicks on the “Get Models” button, the selected make will be submitted back to the PHP above, which in turn will populate the models and show the form again, but this time with the populated results.
All that’s left is to use Progressive Enhancement to provide an AJAX alternative to the post back to the server. An AJAX solution will be more responsive, as the entire page doesn’t need to be reloaded. Here’s the JavaScript code. Note, I’m using the Yahoo UI Library for it’s DOM manipulation, Event handling utility, Element manipulation, and Connection Manager for it’s simplified AJAX interface.
And then my own script:
var make = new YAHOO.util.Element('make'),
submitbtn = new YAHOO.util.Element('getmodels'),
models = new YAHOO.util.Element('models');
// Remove the submit button
submitbtn.setStyle('display','none');
// Attach event listeners to 'make' that will call getModels
// service, parse the results, and populate 'models' options
make.on('click', function (e) {
YAHOO.util.Connect.setForm('getMakeModel');
var transaction = YAHOO.util.Connect.asyncRequest('GET',
'http://www.fotiweb.com/samples/dependent-list-box/getModels/', {
success: function(o) {
var i, op, old,
modelList = o.responseText;
// Remove the existing option elements
old = models.getElementsByTagName('option');
while (old.length > 0) {
old[old.length - 1].parentNode.removeChild(old[old.length - 1]);
}
// Convert results to array
modelList = modelList.split(",");
// Generate the options list of models
for (i = 0; i < modelList.length; i++) {
op = document.createElement('option');
op.text = modelList[i];
op.value = modelList[i];
models.appendChild(op);
}
}
}, null);
});
This will hide the submit button and perform the request to the getModels service when the user selects a make. This is just one way you could use Progressive Enhancement to enhance your dependent lists.