'How do I build a series of dropdown menus dynamically?
I'm trying to build a series of dropdown menus using select elements dynamically with javascript - preferably not jQuery. Each select option is filled from an array using a for loop. What I've got right now is code that builds only one select element dynamically, but where I'm trying to get to is code that builds a new select when the user chooses an option from the dropdown menu, and keeps doing so until there's no data left to choose from. My current idea is to get the index of the selected element (taken from the array it's based on) and use that to determine which select element shows up next, but I'm unsure where to go with this idea. The goal is for it to be a car chooser that asks for the brand, then based on which brand you choose asks for the model, and then a trim level, condition (used/new), and transmission type. Any help is appreciated!
const DATA = {
brand: ['What brand?','Aston Martin','Bugatti','Ferrari','Ford','Koenigsegg','Lamborghini','Lotus','McLaren','Pagani','Porsche','SSC'],
aston: ['What model?','Valkyrie','Vantage','Vulcan'],
bugatti: ['What model?','Boldie','Centodieci','Chiron','Divo','Veyron'],
ferrari: ['What model?','250 GTO','488','Dino 246 GT','F355','F40','F8','Enzo','LaFerrari'],
ford: ['What model?', 'GT'],
koenigsegg:['What model?','Agera','CCX','Gemera','Jesko','One:1','Regera'],
lamborghini: ['What model?', 'Aventador','Centenario','Countach','Diablo','Gallardo','Huracan','Murcielago','Sian','Urus','Veneno'],
lotus: ['What model?','Evija'],
mclaren: ['What model?','540c','570s','675LT','720s','F1','M6GT','P1'],
pagani: ['What model?','Huayra','Zonda'],
porsche: ['What model?','911','918','Carrera GT','Cayman','GT3RS'],
ssc: ['What model?','Tuatara'],
trim: ['Additional trim options?', 'CarPlay', 'Heated steering wheel', 'Tinted windows', 'Turbo', 'Upgraded Wheels'],
condition: ['New or used?', 'New', 'Used'],
trans: ['Manual or automatic?', 'Manual', 'Automatic']
}
var brand = document.getElementById('brand');
var brandSelect = document.createElement('select');
brandSelect.setAttribute('name', 'brandSelect');
brandSelect.addEventListener(
'change',
function() {alert(document.querySelector("select[name='brandSelect'] option:selected").index());},
false
);
brand.appendChild(brandSelect);
for (var i = 0; i < DATA.brand.length; i++) {
var option = document.createElement('option');
option.setAttribute('value', i);
option.text = DATA.brand[i];
brandSelect.appendChild(option);
}
<body>
<div id='brand'></div>
<div id="model"></div>
<div id="trim"></div>
<div id="condition"></div>
<div id="trans"></div>
</body>
Solution 1:[1]
Based on your code, we will notice several things:
- The only truly dynamic field is
model, because its options is populated based on the selected brand. All the other fields (trim, condition, trans) are generic and brand/model-independent. - We need some kind of mapping from
brandto the key inDATA. Most of the time we just need to convert the value to lowercase and we will get the corresponding model data, with the exception of "Aston Martin": if you can rename theastonkey toastonmartin, then our mapping function simply needs to transform the value to lowercase and remove empty spaces. - The rest of the fields are simply dependent on whether the previous field is selected or not, regardless of the actual selected value.
My solution is a little verbose, but it gets the job done. The trick is to have a series of function that call the next one that will render the correct select. The function that is responsible to render the <select> element will add the event handler that will invoke the rendering of the next select element.
The insertSelect function will accept 3 parameters:
idis the ID of the DOM node where we want to append<select>optionsis the array of options we want to pass in to be rendered as<option>elementonChangeis an optional argument where we pass in a function that will be invoked, when the<select>element is changed
The logic mostly follows what you've already written:
function insertSelect(id, options, onChange) {
const selectElement = document.createElement('select');
options.forEach((option, i) => {
const optionElement = document.createElement('option');
optionElement.value = option;
optionElement.textContent = option;
if (i === 0) optionElement.disabled = true;
selectElement.appendChild(optionElement);
});
if (typeof onChange === 'function') {
// NOTE: If element has a single choice, trigger immediately
if (options.length <=2) onChange();
selectElement.addEventListener('change', onChange);
}
document.getElementById(id).innerHTML = '';
document.getElementById(id).appendChild(selectElement);
}
Then, it is a matter of calling insertSelect() for each of the options you want. See proof-of-concept below:
const DATA = {
brand: ['What brand?', 'Aston Martin', 'Bugatti', 'Ferrari', 'Ford', 'Koenigsegg', 'Lamborghini', 'Lotus', 'McLaren', 'Pagani', 'Porsche', 'SSC'],
astonmartin: ['What model?', 'Valkyrie', 'Vantage', 'Vulcan'],
bugatti: ['What model?', 'Boldie', 'Centodieci', 'Chiron', 'Divo', 'Veyron'],
ferrari: ['What model?', '250 GTO', '488', 'Dino 246 GT', 'F355', 'F40', 'F8', 'Enzo', 'LaFerrari'],
ford: ['What model?', 'GT'],
koenigsegg: ['What model?', 'Agera', 'CCX', 'Gemera', 'Jesko', 'One:1', 'Regera'],
lamborghini: ['What model?', 'Aventador', 'Centenario', 'Countach', 'Diablo', 'Gallardo', 'Huracan', 'Murcielago', 'Sian', 'Urus', 'Veneno'],
lotus: ['What model?', 'Evija'],
mclaren: ['What model?', '540c', '570s', '675LT', '720s', 'F1', 'M6GT', 'P1'],
pagani: ['What model?', 'Huayra', 'Zonda'],
porsche: ['What model?', '911', '918', 'Carrera GT', 'Cayman', 'GT3RS'],
ssc: ['What model?', 'Tuatara'],
trim: ['Additional trim options?', 'CarPlay', 'Heated steering wheel', 'Tinted windows', 'Turbo', 'Upgraded Wheels'],
condition: ['New or used?', 'New', 'Used'],
trans: ['Manual or automatic?', 'Manual', 'Automatic']
};
function insertSelect(id, options, onChange) {
const selectElement = document.createElement('select');
options.forEach((option, i) => {
const optionElement = document.createElement('option');
optionElement.value = option;
optionElement.textContent = option;
if (i === 0) optionElement.disabled = true;
selectElement.appendChild(optionElement);
});
if (typeof onChange === 'function') {
// NOTE: If element has a single choice, trigger immediately
if (options.length <=2) onChange();
selectElement.addEventListener('change', onChange);
}
document.getElementById(id).innerHTML = '';
document.getElementById(id).appendChild(selectElement);
}
function renderBrand() {
insertSelect('brand', DATA.brand, (e) => renderModel(e.target.value));
}
function renderModel(brand) {
insertSelect('model', DATA[brand.toLowerCase().replace(' ', '')], () => renderTrim());
}
function renderTrim() {
insertSelect('trim', DATA.trim, () => renderCondition());
}
function renderCondition() {
insertSelect('condition', DATA.condition, () => renderTrans());
}
function renderTrans() {
insertSelect('trans', DATA.trans);
}
renderBrand();
<div id='brand'></div>
<div id="model"></div>
<div id="trim"></div>
<div id="condition"></div>
<div id="trans"></div>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Terry |
