Cascading Selects
by schinckel
HTML
<p>
This will limit the available choices in the second SELECT based upon the
selection in the first SELECT.
</p>
<p>
Bonus: if you have a selection in the second SELECT, and then change the first
SELECT, it will "remember" that selection if you return the first SELECT to the
previous value.
</p>
<select name="x">
<option value="">Please select...</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<br>
<select name="y" id="">
<option value=""></option>
<option value="A" data-x="1">A</option>
<option value="B" data-x="1">B</option>
<option value="C" data-x="2">C</option>
<option value="D" data-x="1">D</option>
</select>
JavaScript
function IDENTITY(x) { return x; }
function Related(sourceElement, targetElement) {
// Given a sourceElement and targetElement, filter the available options in the targetElement
// according to the selection in the sourceElement.
if (!(this instanceof Related)) return new Related(sourceElement, targetElement);
var allTargetOptions = Array.apply(null, targetElement.options).map(IDENTITY);
var previousSelections = {};
if (sourceElement.value) {
previousSelections[sourceElement.value] = targetElement.value;
}
function filterTargetOptions() {
var lastValue = targetElement.value;
targetElement.innerHTML = '';
targetElement.disabled = true;
allTargetOptions.forEach(function(option) {
if (!option.value || option.dataset[sourceElement.name] == sourceElement.value) {
targetElement.options.add(option);
if (option.value) {
targetElement.removeAttribute('disabled');
}
}
});
targetElement.value = "";
if (previousSelections[sourceElement.value]) {
targetElement.value = previousSelections[sourceElement.value];
if (lastValue != targetElement.value) {
setTimeout(function() {
targetElement.dispatchEvent(new Event('change', {bubbles: true}));
}, 0);
}
}
targetElement.dispatchEvent(new Event('options-change', {bubbles: true}));
}
filterTargetOptions();
sourceElement.addEventListener('change', filterTargetOptions);
targetElement.addEventListener('change', function() {
previousSelections[sourceElement.value] = targetElement.value;
});
}
new Related(
document.querySelector('[name="x"]'),
document.querySelector('[name="y"]')
);