Cascading Selects: tree data
Cascading selects, where only the leaf node will be sent as the actual form value.
by schinckel
HTML
<form>
<!--
This element shows that we do indeed include all items in the form
with the same name.
-->
<fieldset>
<label for="text-category">Extra value:</label>
<input type="text" name="category" value="xxx" id="text-category">
</fieldset>
<fieldset>
<label for="category">
Category:
</label>
<select name="category" id="category">
<option value=""></option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</fieldset>
</form>
<pre id="category-value"></pre>
CSS
form {
min-height: 200px;
}
fieldset {
border: none;
}
label {
min-width: 120px;
display: inline-block;
}
select {
display: block;
margin-left: 124px;
min-width: 80px;
}
select:first-of-type {
display: inline-block;
margin-left: 0;
}
pre {
position: absolute;
top: 0;
right: 10px;
border: 1px solid silver;
background: rgba(0, 0, 0, 0.1);
min-width: 200px;
min-height: 42px;
padding: 8px;
}
JavaScript
function fakeResponse(value) {
switch (Number(value)) {
case 1:
return [
{'value': 4, 'label': 'Four'},
{'value': 5, 'label': 'Five'},
];
case 2:
return [];
case 3:
return [
{'value': 6, 'label': 'Six'},
{'value': 7, 'label': 'Seven'},
];
case 5:
return [
{'value': 8, 'label': 'Eight'},
{'value': 9, 'label': 'Nine'},
{'value': 10, 'label': 'Ten'},
{'value': 11, 'label': 'Eleven'},
];
case 10:
return [
{'value': 12, 'label': 'Twelve'},
{'value': 13, 'label': 'Thirteen'},
]
}
}
function Cascade(base_element, url) {
const name = base_element.name;
function removeChildren(element) {
while (element.nextElementSibling) {
element.nextElementSibling.remove();
}
}
function setChildChoices(element, choices) {
if (!choices || !choices.length) {
return removeChildren(element);
}
if (element.nextElementSibling) {
// First, remove all subesquent nodes.
removeChildren(element.nextElementSibling);
} else {
// create the new node.
element.parentElement.appendChild(element.cloneNode(true));
element.nextElementSibling.addEventListener('change', handleChange);
}
// Clear the name, since we don't want to send it unless it has a value.
let child = element.nextElementSibling;
child.removeAttribute('name');
let empty = Array.prototype.filter.call(child.options, x => x.value == '');
child.innerHTML = '<option></option>';
child.append(...choices.map(x => new Option(x.label, x.value)));
}
function handleChange(change) {
if (change.target.value) {
if (change.target.previousElementSibling) {
change.target.previousElementSibling.removeAttribute('name');
}
change.target.setAttribute('name', name);
// Perform an ajax request here, and see if we need to add extra values.
// fetch(url).then(r =>...