I have a tree, implemented as a nested dictionary, and I need to come up with a way for the user to choose two nodes, one of which must be a descendant of the other.
A simple drop-down would probably work, but I wanted to indent the options to clearly communicate which "child" option descends from which "parent" option. I think normally this would be done with <optgroup>, but IINM these can't nest inside each other, and I need a solution that works for any arbitrarily deep tree.
So I did some digging around and apparently a sort of hacky way to do the indenting thing is to add a bunch of to the label of the options that are supposed to be indented. (see also this and this) So that's what I did:
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>
It recursively traverses the dictionary, adding more spaces to the start of the options' names every time it has to go down a level in the tree.
This sort of works, but it looks really wonky how the final label that shows up in the box when an option is selected and the dropdown is closed, is so inconsistently aligned. Sometimes it's aligned left, sometimes it's slightly off center, sometimes it's all the way to the right.
So I've been trying to find a way to remove all the non-breaking spaces from the label of the select element itself when the dropdown is closed, and have them only show up in the labels of the options that show up when the dropdown is open. You can see I was trying to do that in the event listener, by using a regex to replace all the whitespace with nothing, but eh... apparently select doesn't have a label attribute?
Is there no way to set the text of the select element independent of the label of the option that's been selected?
The implementation of the dropdown is dependent on the browser and the os. Unfortunately there are not enough events to describe the actions you wish for, like when the options are shown or hidden. The closest I got was with
focusandblur. I tried hacking it withmousedownandchangeandclickbut the most reliable is to wait for the user toblurbefore changing values.