HTML multiple selections with datalist
The multiple
attribute (specification) is used to notate that multiple values should be able to be selected. The specification for the multiple attribute shows an example of usage with datalists. However, browsers have implemented the multiple attribute only for input elements where type=email
, the type of input element shown in the specifications example. It would seem like the multiple attribute should also be followed for when type is not email, especially when type=text
, however the specification does not make this clear, creating a large discrepancy between what people would expect, and what happens. This discrepancy also has the potential to lead to bad practices where type=email
is used for non-email inputs.
Let's improve input to support datalists and multiple values! Here's the final result:
<input type="text" list="Suggestions" multiple />
<datalist id="Suggestions">
<option>option 1</option>
<option>option 2</option>
<option>option 3</option>
</datalist>
The idea is to dynamically edit the option
element of the datalist
while you are typing to remove selected values and prefix all others with the already selected values. For instance, if the input value is option 2,
, the HTML will be:
<input type="text" list="Suggestions" multiple value="option 2," />
<datalist id="Suggestions">
<option>option 2, option 1</option>
<option>option 2, option 3</option>
</datalist>
Here's the TypeScript code to dynamically update the datalist
's children:
document.addEventListener("DOMContentLoaded", function () {
const separator = ',';
for (const input of document.getElementsByTagName("input")) {
if (!input.multiple) {
continue;
}
if (input.list instanceof HTMLDataListElement) {
const optionsValues = Array.from(input.list.options).map(opt => opt.value);
let valueCount = input.value.split(separator).length;
input.addEventListener("input", () => {
const currentValueCount = input.value.split(separator).length;
// Do not update list if the user doesn't add/remove a separator
// Current value: "a, b, c"; New value: "a, b, cd" => Do not change the list
// Current value: "a, b, c"; New value: "a, b, c," => Update the list
// Current value: "a, b, c"; New value: "a, b" => Update the list
if (valueCount !== currentValueCount) {
const lsIndex = input.value.lastIndexOf(separator);
const str = lsIndex !== -1 ? input.value.substr(0, lsIndex) + separator : "";
filldatalist(input, optionsValues, str);
valueCount = currentValueCount;
}
});
}
}
function filldatalist(input: HTMLInputElement, optionValues: string[], optionPrefix: string) {
const list = input.list;
if (list && optionValues.length > 0) {
list.innerHTML = "";
const usedOptions = optionPrefix.split(separator).map(value => value.trim());
for (const optionsValue of optionValues) {
if (usedOptions.indexOf(optionsValue) < 0) { // Skip used values
const option = document.createElement("option");
option.value = optionPrefix + optionsValue;
list.append(option);
}
}
}
}
});
When you add this script in your page it should detect input with the multiple
attribute and update its behavior with the datalist
.
Do you have a question or a suggestion about this post? Contact me!