HTML multiple selections with datalist

 
 
  • Gérald Barré
 

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:

Source code of the demo: demo.zip
HTML
<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:

HTML
<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:

TypeScript
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!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub