You're right. There have been plenty of similar questions, but there always seem to be little differences between them.
I've written widgets like this before. Here's an approach that's fairly different from gibberish's answer.
It involves a widget that you would use like this:
var widget = new DropdownWidget(options);
widget.onComplete(function(vals) {
console.log(vals);
});
widget.addTo(document.body);
The handlers supplied to onComplete
would receive objects such as
{
"main_list": "mobile list",
"brands": "Samsung",
"samsung_select": "4.2"
}
You would not use any markup at all. Instead it would be configured with an object like this:
var options = [
{
id: 1,
name: "main_list",
defaultVal: "Select Your List",
choices: [
{value: "mobile", text: "mobile list", nextId: 2},
{value: "laptop", text: "laptop list", nextId: 3}
]
},
{
id: 2,
name: "brands",
defaultVal: "Select Your Mobile Brand",
choices: [
{value: "mobile_1", text: "Samsung", nextId: 4},
{value: "mobile_2", text: "Nokia", nextId: 5}
]
},
{
id: 3,
name: "brands",
defaultVal: "Select Your Laptop Brand",
choices: [
{value: "laptop_1", text: "HP"},
{value: "laptop_2", text: "Dell"}
]
},
{
id: 4,
name: "samsung_select",
defaultVal: "Select Your Andriod Version",
choices: [
{value: "android_1", text: "4.1"},
{value: "android_2", text: "4.2"}
]
},
{
id: 5,
name: "nokia_select",
defaultVal: "Select Your Windows Version",
choices: [
{value: "windows_1", text: "windows 8"},
{value: "windows_2", text: "windows 8.1"}
]
}
];
Note that this could be a little different if you weren't using repeated names. The id
s could be removed, and nextId
could become nextName
.
This is a naive implementation of such a widget:
var DropdownWidget = (function() {
var DropdownWidget = function(options) {
// TODO: type-checking on options: should be array of acceptable configurations...
this.options = options;
this.selectedVals = [];
this.showing = false;
this.handlers = [];
};
DropdownWidget.prototype.onComplete = function(handler) {
this.handlers.push(handler);
};
DropdownWidget.prototype.addTo = function(element) {
if (this.showing) {
alert("Oops!"); // TODO: real error handling, or should this be moveable?
return;
}
var dropdown = createDropdown(this.options[0]);
this.elements = [dropdown];
addHandlers(this, dropdown, options, 0);
element.appendChild(dropdown);
};
var createDropdown = function(config) {
var select = document.createElement("SELECT");
select.name = config.name;
select.options[select.options.length] = new Option(config.defaultVal, "default");
config.choices.forEach(function(choice) {
select.options[select.options.length] = new Option(choice.text, choice.value);
});
return select;
};
var addHandlers = function(widget, select, options, index) {
select.onchange = function() {
removeSubsequentSelects(widget, options, index);
if (this.selectedIndex > 0) {
var choice = options[index].choices[this.selectedIndex - 1];
if (widget.selectedVals[widget.selectedVals.length - 1] !== options[index]) {
widget.selectedVals.push(options[index]);
}
if (choice.nextId) {
var nextIndex = findIndex(function(item) {
return item.id === choice.nextId;
}, options);
if (nextIndex > -1) {
var dropdown = createDropdown(options[nextIndex]);
widget.elements.push(dropdown);
addHandlers(widget, dropdown, options, nextIndex);
this.parentNode.appendChild(dropdown);
}
} else {
complete(widget);
}
}
}
};
var removeSubsequentSelects = function(widget, options, index) {
var start = findIndex(function(selected) {
return selected == options[index];
}, widget.selectedVals);
var idx = start;
if (idx > -1) {
while (++idx < widget.elements.length) {
widget.elements[idx].parentNode.removeChild(widget.elements[idx]);
}
widget.elements.length = widget.selectedVals.length = start + 1;
}
}
var findIndex = function(predicate, list) {
var idx = -1;
while (++idx < list.length) {if (predicate(list[idx])) {return idx;}}
return -1;
};
var complete = function(widget) {
var vals = widget.selectedVals.map(function(val, idx) {
return {name: val.name, val: val.choices[widget.elements[idx].selectedIndex - 1].text}
}).reduce(function(memo, obj) {memo[obj.name] = obj.val; return memo;}, {});
widget.handlers.forEach(function(handler) {
handler(vals);
});
};
return DropdownWidget;
}());
You can see it in action on JSFiddle.
There are plenty of things that could be made better. The biggest issue I see is that the DOM location for the constructed SELECTS is extremely simplistic.
In any case, it's a different approach.