问题
Wanted to make a generic cascading dropdown but am weak in recursion
The code is supposed to end up with
- One select for items - clothes or gadgets - when a choice is made
- One select with either Levis/Gucci or LG/Apple - when a choice is made
- One select with either Levis jeans or jackets or Gucci shoes or dresses - when a choice is made
- One select with Levis jeans sizes OR levis jacket sizes OR
- One select with Gucci shoe sizes OR Gucci dress sizes
OR
- One select with either LG TVs or phones or Apple Macbooks or iPhones - when a choice is made
- One select with LG TV sizes OR LG Phone sizes OR
- One select with Apple Macbook sizes OR Apple iPhone sizes
I lost my train of thoughts when I got to actually recurse - or perhaps filtering can be used?
I assume one could make a set of paths and then just show/hide depending on path
const selObject = {
"-- Select Item --": {
"Clothes": {
"-- Select brands --": {
"Levis": {
"-- Select product --": {
"Jeans": {
"-- Select size --": [
"38",
"39",
"40"
]
},
"Jackets": {
"-- Select size --": [
"41",
"42",
"43"
]
}
}
}, // end Levis
"Gucci": {
"-- Select product --": {
"Shoes": {
"-- Select size --": [
"45",
"50",
"55"
]
},
"Dresses": {
"-- Select size --": [
"8",
"9",
"10"
]
}
}
} // end Gucci
} // end brands
}, // End clothes
"Gadgets": {
"-- Select brands --": {
"LG": {
"-- Select product --": {
"TVs": {
"-- Select size --": [
"38",
"39",
"40"
]
},
"Phones": {
"-- Select size --": [
"8",
"9",
"10"
]
}
}
}, // end Levis
"Apple": {
"-- Select product --": {
"Macbooks": {
"-- Select size --": [
"15",
"17",
"21"
]
},
"iPhones": {
"-- Select size --": [
"8",
"9",
"10"
]
}
}
} // end Apple
} // end brands
} // end Gadgets
} // end items
} // end
function createSel(obj) {
Object.keys(obj).forEach(function(item) {
if (typeof obj[item] == "object") {
var list = obj[item];
//console.log(item,typeof list);
if (typeof list == "object") {
if (list.length) {
list.forEach(function(val) {
console.log('<br/>'+val)
})
}
else createSel(list)
}
} else {
console.log("no", obj[item])
}
});
}
window.onload = function() {
createSel(selObject)
}
<form name="myform" id="myForm">
<div id="selContainer">
</div>
</form>
回答1:
Doing this in React would be easier,. But for a plain JS solution the below might be what your after.
Basically all I'm doing is using recursion to create the components, and attach the events.
const selObject = {
"-- Select Item --": {
"Clothes": {
"-- Select brands --": {
"Levis": {
"-- Select product --": {
"Jeans": {
"-- Select size --": [
"38",
"39",
"40"
]
},
"Jackets": {
"-- Select size --": [
"41",
"42",
"43"
]
}
}
}, // end Levis
"Gucci": {
"-- Select product --": {
"Shoes": {
"-- Select size --": [
"45",
"50",
"55"
]
},
"Dresses": {
"-- Select size --": [
"8",
"9",
"10"
]
}
}
} // end Gucci
} // end brands
}, // End clothes
"Gadgets": {
"-- Select brands --": {
"LG": {
"-- Select product --": {
"TVs": {
"-- Select size --": [
"38",
"39",
"40"
]
},
"Phones": {
"-- Select size --": [
"8",
"9",
"10"
]
}
}
}, // end Levis
"Apple": {
"-- Select product --": {
"Macbooks": {
"-- Select size --": [
"15",
"17",
"21"
]
},
"iPhones": {
"-- Select size --": [
"8",
"9",
"10"
]
}
}
} // end Apple
} // end brands
} // end Gadgets
} // end items
} // end
function fillDropdown(target, obj) {
const sel = document.createElement("select");
const sub = document.createElement("div");
if (typeof obj !== "object") {
sub.innerHTML = "<p>Thank you for your selection</p>";
target.appendChild(sub);
return;
}
target.appendChild(sel);
target.appendChild(sub);
const [title, value] = Object.entries(obj)[0];
//add our title option
const option1 = document.createElement("option");
option1.innerText = title;
sel.appendChild(option1);
//now add the sub items
const items = Object.entries(value);
items.forEach(([k, v]) => {
const option = document.createElement('option');
option.innerText = k;
sel.appendChild(option);
});
sel.addEventListener("change", () => {
sub.innerHTML = "";
if (sel.selectedIndex > 0) {
const i = items[sel.selectedIndex - 1];
fillDropdown(sub, i[1]);
}
});
}
window.onload = function() {
//createSel(selObject);
fillDropdown(
document.querySelector('#selContainer'),
selObject
);
}
select {
display: block;
width: 100%;
padding: 10px;
}
<form name="myform" id="myForm">
<div id="selContainer">
</div>
</form>
回答2:
here's some other options you might want to consider :
using OptGroup :
const selObject = { "-- Select Item --": { Clothes: { "-- Select brands --": { Levis: { "-- Select product --": { Jeans: { "-- Select size --": ["38", "39", "40"] }, Jackets: { "-- Select size --": ["41", "42", "43"] } } }, Gucci: { "-- Select product --": { Shoes: { "-- Select size --": ["45", "50", "55"] }, Dresses: { "-- Select size --": ["8", "9", "10"] } } } } }, Gadgets: { "-- Select brands --": { LG: { "-- Select product --": { TVs: { "-- Select size --": ["38", "39", "40"] }, Phones: { "-- Select size --": ["8", "9", "10"] } } }, Apple: { "-- Select product --": { Macbooks: { "-- Select size --": ["15", "17", "21"] }, iPhones: { "-- Select size --": ["8", "9", "10"] } } } } } } };
const generateDropDown = (obj, indent) => {
const spaces = Array(indent).fill(' ').join('');
if (Array.isArray(Object.values(obj)[0])) {
return Object.values(obj)[0].map(e => "<option>" + spaces + e + "</option>").join('');
} else {
return Object.values(obj).map(brand => {
return Object.keys(brand).map(product => {
//?
return `<optgroup label="${spaces + product}"> ${generateDropDown(brand[product], indent + 4)} </optgroup>`;
}).join('');
});
}
};
const list = generateDropDown(selObject, 0).join(' ');
document.querySelector('#dropDown').innerHTML = list;
<select id="dropDown">
</select>
using ul : ( more flexible to styling )
const selObject = { "-- Select Item --": { Clothes: { "-- Select brands --": { Levis: { "-- Select product --": { Jeans: { "-- Select size --": ["38", "39", "40"] }, Jackets: { "-- Select size --": ["41", "42", "43"] } } }, Gucci: { "-- Select product --": { Shoes: { "-- Select size --": ["45", "50", "55"] }, Dresses: { "-- Select size --": ["8", "9", "10"] } } } } }, Gadgets: { "-- Select brands --": { LG: { "-- Select product --": { TVs: { "-- Select size --": ["38", "39", "40"] }, Phones: { "-- Select size --": ["8", "9", "10"] } } }, Apple: { "-- Select product --": { Macbooks: { "-- Select size --": ["15", "17", "21"] }, iPhones: { "-- Select size --": ["8", "9", "10"] } } } } } } };
const generateDropDown = (obj, indent) => {
const values = Object.values(obj);
if (Array.isArray(values[0])) {
return values[0].map(e => `<li class="child">${e} </li>`).join(' ');
} else {
return values.map(brand => {
return Object.keys(brand).map(product => {
return `<ul class="parent"> <li class="title">${product}</li> ${generateDropDown(brand[product], indent + 2)} </ul>`;
}).join(' ');
});
}
};
const list = generateDropDown(selObject, 0).join(' ');
document.querySelector('#dropDown').innerHTML = list;
[...document.querySelectorAll('ul,li')].forEach(e => {
e.addEventListener('click', ev => {
ev.cancelBubble = true;
ev.target.classList.toggle('open');
// console.log(ev.target.innerText)
// do some stuff when the element is clicked.
})
})
ul{
padding: 0;
margin: 0;
list-style-type: none;
}
.parent, .child{
padding-left: 15px;
display: none;
cursor: pointer;
}
#dropDown > .parent {
padding-left: 0;
display: block;
}
.open ~ .parent{
opacity: 1;
display: block;
}
.open ~ .child{
opacity: 1;
display: block;
}
.title{
font-weight: bold;
}
.title.open{
color: red;
}
<div id="dropDown">
</div>
来源:https://stackoverflow.com/questions/54634114/plain-js-cascading-selects-of-unknown-depth