Select dependant on other select
The need for this solution pops up just rarely enough so I have time to forget where I wrote some similar code last time, and there are always some minor details I have to lookup in order to get everything to work properly. This time I decided to put it in my blog. That way I can always look it up later.
First we need to setup the html. One category select. It represent the trigger that should update the second select, in this case our food select. The first time I did this, I put all the options in the food select, looped through them and added display: none; or something similar to the once I did not want to be visible. But a few weeks later the customer came back and asked why the filter had stopped working... I did not do anything and as I tested the code still worked... Turned out our customer hade just started using internet explorer where you, at least at that point, could not hide options in a select that way. That is why we add a second select I call food_resource. Sometimes I have the resources in a div containing a json_object that I just can read and work with. In some cases that does make more sence, but in this instance I wanted to just copy the relevant options from a hidden select over to the visible one.
Category:
<select name="category" id="category">
<option value="0">Select</option>
<option value="vegetable" selected="selected">Vegetable</option>
<option value="fruit" selected="selected">Fruit</option>
<option value="berrie">Berrie</option>
</select>
Favorite food:
<select name="food" id="food" class="mandatory">
<option value="">Select</option>
</select>
<select name="food_resource" id="food_resource" style="display: none;">
<option value="carret" data-category="vegetable">Carrot</option>
<option value="potato" data-category="vegetable">Potato</option>
<option value="tomato" data-category="vegetable">Tomato</option>
<option value="banana" data-category="fruit">Banana</option>
<option value="apple" data-category="fruit">Apple</option>
<option value="orange" data-category="fruit">Orange</option>
<option value="blueberries" data-category="berrie">Blueberries</option>
<option value="raspberries" data-category="berrie">Raspberries</option>
<option value="strawberries" data-category="berrie">Strawberries</option>
</select>
Over to the javascript. Since the food select is empty when the page is loaded the first thing that happends is that the function updateFood(); is called. It start with cleaning out any options from the food select, that is not the Select-option and moving them back into food_resource. The reason I only increment i if a resource is not copied is because the i:th item is always recalculated. So, as I remove one option the next option takes it place. If I were to increment i I would only access every other option. The same situation occure in the second while loop, where I pick the options I want in my food select.
When I move options back to the food select I check if the item has the selected attribute set and stor the index for that option in the variable selectedIndex. The reason for this is that javascript will by defalut select the last added element, regardles if any of them have the select attribute set or not. In the last line I manually change the selected item to the one that was actually seleted and if none of the options was selected it will select the first element.
To complete the functionallity I end by adding an event listender that calls the updateFood-function every time the category select is changed.
<script>
var updateFood = function() {
var food = document.getElementById('food');
var foodResource = document.getElementById('food_resource');
// Restore all options from food to foodResource
var i = 0;
while (food.options.length > 1) {
if (food.options[i].value !== '') {
foodResource.options[foodResource.options.length] = food.options[i];
} else {
i++;
}
}
// Move all valid options from foodResource to food
var category = document.getElementById('category');
var selectedType = category.options[category.selectedIndex].value;
var i = 0;
var selectedIndex = 0;
while (foodResource.options[i] !== undefined) {
if (foodResource.options[i].getAttribute('data-category') === selectedType) {
if (foodResource.options[i].getAttribute('selected') === 'selected') {
selectedIndex = food.options.length;
}
food.options[food.options.length] = foodResource.options[i];
} else {
i++;
}
}
// If some of the values was preselected then make sure that it is restored here
food.selectedIndex = selectedIndex;
};
updateFood();
var category = document.getElementById('category');
category.addEventListener('change', updateFood);
</script>
Hope this was usefull and if you want to try out the live code you can do that in my dependant select sample. If you have a cleaner solution than I'd love to see how you do it =)
- select, javascript, components, software