If you can turn the "value" associated with the option into a number, you can use unicode invisible characters, U+E0001 through U+E007F to store the "value" part of the option alongside the title.
Append an arbitrary number of invisible characters to the end of your title. Since there are ~120 such invisible characters, each double-byte invisible character on the end of a title represents an integer from 0 to 120.
My solution below stores breaks up numbers into equal chunks, so that number parts stored in invisible characters need only be summed as is to get the original. If numbers could be broken up such that each invisible character represents increasingly larger portions of the original number, perhaps using powers of two or some other exponential math/magic, then the solution would be significantly faster.
const BASE_CODEPOINT = 'E0001';
const NUM_EMPTY_CHARS = 120; // actually 127, but let's be safe.
const numToEmptyChar = num => String.fromCodePoint(`0x${(parseInt(BASE_CODEPOINT, 16) + num).toString(16)}`);
const withNumberAsTrailingEmptySpace = (string, number, maxNumber = NUM_EMPTY_CHARS) => {
const numChars = Math.ceil(maxNumber / NUM_EMPTY_CHARS);
const numCharsMinusOne = Math.max(0, numChars - 1);
return string
+ numToEmptyChar(NUM_EMPTY_CHARS).repeat(numCharsMinusOne)
+ numToEmptyChar(number - (NUM_EMPTY_CHARS * numCharsMinusOne));
};
const numberFromTrailingEmptySpace = (string, maxNumber = NUM_EMPTY_CHARS) => {
const numChars = Math.ceil(maxNumber / NUM_EMPTY_CHARS);
let total = 0;
for (let i = 0; i < numChars; i++) {
const codePoint = string.codePointAt(string.length - 2 * (i + 1));
total += codePoint - parseInt(BASE_CODEPOINT, 16);
}
return total;
};
let mystring = "Anything at all can go here.....";
let maxNum = 2000;
let number = 1337;
let withNumber = withNumberAsTrailingEmptySpace(mystring, number, maxNum);
console.log(numberFromTrailingEmptySpace(withNumber, maxNum));
So, you'd end up creating your datalist via JS (untested):
const options = [
{label: "Foo", value: 1},
{label: "Bar", value: 2},
{label: "Baz", value: 3}
];
const input = document.createElement('input');
input.setAttribute('list', 'options');
input.addEventListener('change', e => {
// https://w3c.github.io/input-events/#interface-InputEvent-Attributes
const isDatalist = (typeof inputType === "undefined") || (inputType === "insertReplacementText");
if (isDatalist) {
const idx = numberFromTrailingEmptySpace(e.target.value, options.length);
const selectedOption = options[idx];
e.target.value = selectedOption.label;
console.log("The user selected option: ", selectedOption);
}
});
const datalist = document.createElement('datalist');
datalist.setAttribute('id', 'options');
options.forEach(({label}, idx) => {
const o = document.createElement('option');
o.label = withNumberAsTrailingEmptySpace(label, idx, options.length);
datalist.appendChild(o);
});
1, should the text becomeFoo? Should.valuereturn1or3if the user typesFoomanually?Foo,Bar, etc should be accessible as suggestions - just like a traditional<select>. If the user typesFoothere will be two suggestions and the value should correspond with the selected value (when clicked or highlighted with keyboard). If no suggestions are selected and the text value isFoowe can only assume the user selected the first option.