Is it possible to create nested option fields in a form drop down, much like you would create nested ul lists?
Since the change is just aesthetical, is it possible to do this with css?
Is it possible to create nested option fields in a form drop down, much like you would create nested ul lists?
Since the change is just aesthetical, is it possible to do this with css?
You can use <optgroup>
to create a single level of nesting...
<select>
<optgroup label="Options 1">
<option>Option 1.1</option>
<option>Option 1.2</option>
</optgroup>
<optgroup label="Options 2">
<option>Option 2.1</option>
<option>Option 2.2</option>
</optgroup>
</select>
Note that the group labels are not selectable options. In that case, I would recommend using the text-indent solution that is mentioned in the top answer to the question that home linked to in his comment.
I made this approach since I couldn´t find what I was searching. A nested accordeon select. Its CSS is very simple and can be improved. The only thing you need is an object with keys and values you want to add into the select. Keys
would be subgroups, and key values
(arrays and single elements) would be selectable items.
Once you have your array, only thing you need to do is to call
initAccordeon(obj);
with your data object as an argument, and the nested accordeon will appear:
const obj = {
Cars: {
SwedishCars: [
"Volvo",
"Saab"
],
GermanCars: [
"Mercedes",
{
Audi: [
"Audi A3",
"Audi A4",
"Audi A5"
]
}
]
},
Food: {
Fruits: [
"Orange",
"Apple",
"Banana"
],
SaltyFoods: [
"Pretzels",
"Burger",
"Noodles"
],
Drinks: "Water"
}
};
initAccordeon(obj); // <--------------------------- Call initialization
function accordeonAddEvents() {
Array.from(document.getElementsByClassName("accordeon-header")).forEach(function(header) {
if (header.getAttribute("listener") !== "true") {
header.addEventListener("click", function() {
this.parentNode.getElementsByClassName("accordeon-body")[0].classList.toggle("hide");
});
header.setAttribute("listener", "true");
}
});
Array.from(document.getElementsByClassName("button-group")).forEach(function(but) {
if (but.getAttribute("listener") !== "true") {
but.addEventListener("click", function() {
if (this.getAttribute("depth") === "-1") {
let header = this;
while ((header = header.parentElement) && header.className !== "accordeon");
header.getElementsByClassName("accordeon-header")[0].innerHTML = this.innerHTML;
return;
}
const groups = Array.from(this.parentNode.getElementsByClassName("accordeon-group"));
groups.forEach(g => {
if (g.getAttribute("uuid") === this.getAttribute("uuid") &&
g.getAttribute("depth") === this.getAttribute("depth")) {
g.classList.toggle("hide");
}
});
});
but.setAttribute("listener", "true");
}
});
}
function initAccordeon(data) {
accordeons = Array.from(document.getElementsByClassName("accordeon-body"));
accordeons.forEach(acc => {
acc.innerHTML = "";
const route = (subObj, keyIndex = 0, parent = acc, depth = 0) => {
const keys = Object.keys(subObj);
if (typeof subObj === 'object' && !Array.isArray(subObj) && keys.length > 0) {
while (keyIndex < keys.length) {
var but = document.createElement("button");
but.className = "button-group";
but.setAttribute("uuid", keyIndex);
but.setAttribute("depth", depth);
but.innerHTML = keys[keyIndex];
var group = document.createElement("div");
group.className = "accordeon-group hide";
group.setAttribute("uuid", keyIndex);
group.setAttribute("depth", depth);
route(subObj[keys[keyIndex]], 0, group, depth + 1);
keyIndex++;
parent.append(but);
parent.append(group);
}
} else {
if (!Array.isArray(subObj)) subObj = [subObj];
subObj.forEach((e, i) => {
if (typeof e === 'object') {
route(e, 0, parent, depth);
} else {
var but = document.createElement("button");
but.className = "button-group";
but.setAttribute("uuid", i);
but.setAttribute("depth", "-1");
but.innerHTML = e;
parent.append(but);
}
});
}
};
route(data);
});
accordeonAddEvents();
}
.accordeon {
width: 460px;
height: auto;
min-height: 340px;
font-size: 20px;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
display: block;
position: relative;
z-index: 10;
}
.accordeon-header {
display: inline-block;
width: 450px;
border: solid 0.1vw black;
border-radius: 0.2vw;
background-color: white;
padding-left: 10px;
color: black;
}
.accordeon-header:hover {
opacity: 0.7;
}
.accordeon-body {
display: block;
position: absolute;
}
.button-group {
display: block;
cursor: pointer;
width: 460px;
text-align: left;
font-size: 20px;
font-weight: bold;
}
.accordeon-group {
padding-left: 20px;
}
.accordeon-group .button-group {
width: 100%;
}
.button-group[depth="-1"] {
color: green;
}
.hide {
display: none;
}
<div class="accordeon">
<span class="accordeon-header">Select something</span>
<div class="accordeon-body hide">
</div>
</div>
You cannot nest multiple <option>
s. If you want to group <option>
elements, use <optgroup>
.
It's possible to nest options and even make them selectables. But you'll need to use JavaScript. In this example, the code is written in TypeScript (Angular v6), but you could do the same with any other modern Javascript framework, pure Javascript or jQuery.
Imagine A, B and C are your options:
let options = [
'A',
'B',
'C'
];
You want to display them like: A->B->C (A is B's parent & B is C's parent).
And you want the user to be able to select A and C, but no B. Let's create a simple interface that will make that easier:
interface CoolOption {
content: string,
selectable: boolean,
depth: number
};
Now, your options will look like:
let selectedOption: string = null;
let options: CoolOption[] = new Array<CoolOption>();
let A: CoolOption = {
content: 'A',
selectable: true,
depth: 0
};
let B: CoolOption = {
content: 'B',
selectable: false,
depth: 1
};
let C: CoolOption = {
content: 'A',
selectable: true,
depth: 2
};
And your select template will look like:
<mat-select>
<mat-option *ngFor="let option of options" (change)="setSelectedOption($event)">
<span [style.padding-left.px]="option.depth * 5">
{{
option.content
}}
</span>
</mat-option>
</mat-select>
Simple explanation: when the user selects an option, setSelectedOption function (we'll define it next) will be called. Also, the CSS padding-left property will be affected by the 'depth' property we set before.
This way we'll 'emulate' the nest effect.
At last, our setSelectedOption function:
setSelectedOption(option: CoolOption) {
if (option.selectable) {
this.selectedOption = option.content;
}
}
Basically if it's selectable, the value of selectedOption will change. Otherwise it will remain intact.
Hope this helps to somebody, and sorry for not having enough time to replicate the examples in pure JavaScript.
No, not really. There is an optgroup
tag which are unselectable headers you can add between sections, but nesting is not possible for <select>
elements.
Look into using the optgroup
tag. As for styling support, there is some, but you are at the mercy of the browser as to how far you can take it as it is a form element.
http://www.456bereastreet.com/lab/styling-form-controls-revisited/select-single-optgroup/
If you need extensive restyling, consider building your own UI widget using perhaps a nested UL
structure and giving it the interaction via JavaScript.