我有一棵树,作为嵌套字典实现,我需要想出一种方法让用户选择两个节点,其中一个必须是另一个的后代。
一个简单的下拉菜单可能会起作用,但我想缩进选项以清楚地传达哪个“子”选项来自哪个“父”选项。我认为通常这可以用
<optgroup>
来完成,但是 IINM 这些不能相互嵌套,我需要一个适用于任何任意深度的树的解决方案。
所以我做了一些挖掘,显然一种进行缩进的方法是在应该缩进的选项的标签上添加一堆
。 (另请参阅this和this)这就是我所做的:
let g_pLanguageTree = {
"Name": "Indo-European",
"Children": [
{
"Name": "Germanic",
"Children": [
{
"Name": "North Germanic",
"Children": [
{
"Name": "Norwegian",
"Children": []
},
{
"Name": "Swedish",
"Children": []
},
{
"Name": "Danish",
"Children": []
}
]
},
{
"Name": "West Germanic",
"Children": [
{
"Name": "German",
"Children": []
},
{
"Name": "Dutch",
"Children": []
},
{
"Name": "English",
"Children": []
}
]
},
{
"Name": "East Germanic",
"Children": [
{
"Name": "Gothic",
"Children": []
}
]
}
]
},
{
"Name": "Italic",
"Children": [
{
"Name": "Umbrian",
"Children": []
},
{
"Name": "Oscan",
"Children": []
},
{
"Name": "Latin",
"Children": [
{
"Name": "Spanish",
"Children": []
},
{
"Name": "French",
"Children": []
},
{
"Name": "Italian",
"Children": []
},
{
"Name": "Portuguese",
"Children": []
},
{
"Name": "Romanian",
"Children": []
}
]
},
]
},
{
"Name": "Anatolian",
"Children": [
{
"Name": "Hittite",
"Children": []
},
{
"Name": "Luwian",
"Children": []
}
]
},
{
"Name": "Armenian",
"Children": []
}
]
}
function CreateStageDropdown(pRootStage = g_pLanguageTree) {
let eDropDown = document.createElement("select");
document.getElementById("MainDiv").appendChild(eDropDown);
let tOptions = CreateStageDropdown_Options(pRootStage, 0);
for (let i = 0; i < tOptions.length; i++) {
let eOption = document.createElement("option");
eOption.label = tOptions[i];
eOption.value = i;
eDropDown.appendChild(eOption);
}
eDropDown.addEventListener("change", function(Event) {
let sIn = eDropDown.value;
let eSelected = eDropDown.querySelector("option[value = '"+sIn+"']");
let sOut = tOptions[sIn].replaceAll(/\s*/g,"");
eDropDown.label = sOut;
});
return [eDropDown, tOptions];
}
function CreateStageDropdown_Options(pStage, iNestingLayers) {
let tOptions = [];
tOptions.push("\u00A0\u00A0\u00A0\u00A0".repeat(iNestingLayers)+pStage.Name);
let tChildren = pStage.Children;
if (tChildren.length > 0) {
for (let i = 0; i < tChildren.length; i++) {
let pChild = tChildren[i];
tOptions.push(CreateStageDropdown_Options(pChild, iNestingLayers + 1));
}
}
tOptions = tOptions.flat();
return tOptions;
}
<div id="MainDiv">
<input type="button" value="Click to create dropdown" onclick="CreateStageDropdown()"/>
</div>
<div id="OtherDiv">
<input type="button" value="Click to create dropdown (Just Germanic)" onclick="CreateStageDropdown(g_pLanguageTree.Children[0])"/>
</div>
它递归地遍历字典,每次必须在树中向下一层时,都会在选项名称的开头添加更多空格。
这种方法可行,但看起来确实很奇怪,当选择一个选项并关闭下拉列表时,框中显示的最终标签如此不一致地对齐。有时它向左对齐,有时稍微偏离中心,有时完全向右对齐。
所以我一直在尝试找到一种方法,在关闭下拉列表时从
select
元素本身的标签中删除所有不间断空格,并让它们 only 显示在 option
的标签中
当下拉菜单打开时显示。您可以看到我试图在事件侦听器中执行此操作,通过使用正则表达式将所有空白替换为空,但是呃...显然 select
没有 label
属性?
是否无法设置
select
元素的文本独立于已选择的 option
的标签?
下拉菜单的实现依赖于浏览器和操作系统。不幸的是,没有足够的事件来描述您想要的操作,例如何时显示或隐藏选项。我得到的最接近的是
focus
和blur
。我尝试用 mousedown
和 change
和 click
破解它,但最可靠的是等待用户 blur
后再更改值。
var oldValue = null
function CreateStageDropdown(pRootStage = g_pLanguageTree) {
let eDropDown = document.createElement("select");
document.getElementById("MainDiv").appendChild(eDropDown);
let tOptions = CreateStageDropdown_Options(pRootStage, 0);
for (let i = 0; i < tOptions.length; i++) {
let eOption = document.createElement("option");
eOption.label = tOptions[i];
eOption.value = i;
eDropDown.appendChild(eOption);
}
eDropDown.addEventListener("blur", function(Event) {
let sIn = eDropDown.options[eDropDown.selectedIndex].label
oldValue = sIn
let sOut = sIn.replaceAll(/\s*/g, "");
eDropDown.options[eDropDown.selectedIndex].label = sOut
});
eDropDown.addEventListener("change", function(Event) {
// eDropDown.blur()
});
eDropDown.addEventListener("focus", function(Event) {
if (oldValue !== null) {
eDropDown.options[eDropDown.selectedIndex].label = oldValue
oldValue = null
}
//eDropDown.value = sOut;
});
return [eDropDown, tOptions];
}
function CreateStageDropdown_Options(pStage, iNestingLayers) {
let tOptions = [];
tOptions.push("\u00A0\u00A0\u00A0\u00A0".repeat(iNestingLayers) + pStage.Name);
let tChildren = pStage.Children;
if (tChildren.length > 0) {
for (let i = 0; i < tChildren.length; i++) {
let pChild = tChildren[i];
tOptions.push(CreateStageDropdown_Options(pChild, iNestingLayers + 1));
}
}
tOptions = tOptions.flat();
return tOptions;
}
let g_pLanguageTree = {
"Name": "Indo-European",
"Children": [{
"Name": "Germanic",
"Children": [{
"Name": "North Germanic",
"Children": [{
"Name": "Norwegian",
"Children": []
},
{
"Name": "Swedish",
"Children": []
},
{
"Name": "Danish",
"Children": []
}
]
},
{
"Name": "West Germanic",
"Children": [{
"Name": "German",
"Children": []
},
{
"Name": "Dutch",
"Children": []
},
{
"Name": "English",
"Children": []
}
]
},
{
"Name": "East Germanic",
"Children": [{
"Name": "Gothic",
"Children": []
}]
}
]
},
{
"Name": "Italic",
"Children": [{
"Name": "Umbrian",
"Children": []
},
{
"Name": "Oscan",
"Children": []
},
{
"Name": "Latin",
"Children": [{
"Name": "Spanish",
"Children": []
},
{
"Name": "French",
"Children": []
},
{
"Name": "Italian",
"Children": []
},
{
"Name": "Portuguese",
"Children": []
},
{
"Name": "Romanian",
"Children": []
}
]
},
]
},
{
"Name": "Anatolian",
"Children": [{
"Name": "Hittite",
"Children": []
},
{
"Name": "Luwian",
"Children": []
}
]
},
{
"Name": "Armenian",
"Children": []
}
]
}
CreateStageDropdown()
<p>Select a sub item
</p>
<div id="MainDiv">
</div>
<p>and then</p>
<button>lose focus</button>