Journal tags: datalist

5

sparkline

The datalist element on iOS

The datalist element is good. It was a bit bumpy there for a while, but browser implementations have improved over time. Now it’s by far the simplest and most robust way to create an autocompleting combobox widget.

Hook up an input element with a datalist element using the list and id attributes and you’re done. You can even use a bit of Ajax to dynamically update the option elements inside the datalist in response to the user’s input. The browser takes care of all the interaction. If you try to roll your own combobox implementation, it’s almost certainly going to involve a lot of JavaScript and still probably won’t account for all use cases.

Safari on iOS—and therefore all browsers on iOS—didn’t support datalist for quite a while. But once it finally shipped, it worked really nicely. The options showed up just like automplete suggestions above the keyboard.

But that broke a while back.

The suggestions still appeared, but if you tapped on one of them, nothing happened. The input element didn’t get updated. You had to tap on a little downward arrow inside the input in order to see the list of options.

That was really frustrating for anybody on iOS using The Session. By far the most common task on the site is searching for a tune, something that’s greatly (progressively) enhanced with a dynamically-updating datalist.

I just updated to iOS 18 specifically to see if this bug has been fixed, and it has:

Fixed updating the input value when selecting an option from a datalist element.

Hallelujah!

But now there’s some additional behaviour that’s a little weird.

As well as showing the options in the autocomplete list above the keyboard, Safari on iOS—and therefore all browsers on iOS—also pops up the options as a list (as if you had tapped on that downward arrow). If the list is more than a few options long, it completely obscures the input element you’re typing into!

I’m not sure if this is a bug or if it’s the intended behaviour. It feels like a bug, but I don’t know if I should file something.

For now, I’ve updated the datalist elements on The Session to only ever hold three option elements in order to minimise the problem. Seeing as the autosuggest list above the keyboard only ever shows a maximum of three suggestions anyway, this feels like a reasonable compromise.

Submitting a form with datalist

I’m a big fan of HTML5’s datalist element and its elegant design. It’s a way to progressively enhance any input element into a combobox.

You use the list attribute on the input element to point to the ID of the associated datalist element.

<label for="homeworld">Your home planet</label>
<input type="text" name="homeworld" id="homeworld" list="planets">
<datalist id="planets">
 <option value="Mercury">
 <option value="Venus">
 <option value="Earth">
 <option value="Mars">
 <option value="Jupiter">
 <option value="Saturn">
 <option value="Uranus">
 <option value="Neptune">
</datalist>

It even works on input type="color", which is pretty cool!

The most common use case is as an autocomplete widget. That’s how I’m using it over on The Session, where the datalist is updated via Ajax every time the input is updated.

But let’s stick with a simple example, like the list of planets above. Suppose the user types “jup” …the datalist will show “Jupiter” as an option. The user can click on that option to automatically complete their input.

It would be handy if you could automatically submit the form when the user chooses a datalist option like this.

Well, tough luck.

The datalist element emits no events. There’s no way of telling if it has been clicked. This is something I’ve been trying to find a workaround for.

I got my hopes up when I read Amber’s excellent article about document.activeElement. But no, the focus stays on the input when the user clicks on an option in a datalist.

So if I can’t detect whether a datalist has been used, this best I can do is try to infer it. I know it’s not exactly the same thing, and it won’t be as reliable as true detection, but here’s my logic:

  • Keep track of the character count in the input element.
  • Every time the input is updated in any way, check the current character count against the last character count.
  • If the difference is greater than one, something interesting happened! Maybe the user pasted a value in …or maybe they used the datalist.
  • Loop through each of the options in the datalist.
  • If there’s an exact match with the current value of the input element, chances are the user chose that option from the datalist.
  • So submit the form!

Here’s how that translates into DOM scripting code:

document.querySelectorAll('input[list]').forEach( function (formfield) {
  var datalist = document.getElementById(formfield.getAttribute('list'));
  var lastlength = formfield.value.length;
  var checkInputValue = function (inputValue) {
    if (inputValue.length - lastlength > 1) {
      datalist.querySelectorAll('option').forEach( function (item) {
        if (item.value === inputValue) {
          formfield.form.submit();
        }
      });
    }
    lastlength = inputValue.length;
  };
  formfield.addEventListener('input', function () {
    checkInputValue(this.value);
  }, false);
});

I’ve made a gist with some added feature detection and mustard-cutting at the start. You should be able to drop it into just about any page that’s using datalist. It works even if the options in the datalist are dynamically updated, like the example on The Session.

It’s not foolproof. The inference relies on the difference between what was previously typed and what’s autocompleted to be more than one character. So in the planets example, if someone has type “Jupite” and then they choose “Jupiter” from the datalist, the form won’t automatically submit.

But still, I reckon it covers most common use cases. And like the datalist element itself, you can consider this functionality a progressive enhancement.

Ch-ch-ch-changes

It’s browser updatin’ time! Firefox 65 just dropped. So did Chrome 72. Safari 12.1 is shipping with iOS 12.2.

It’s interesting to compare the release notes for each browser and see the different priorities reflected in them (this is another reason why browser diversity is A Good Thing).

A lot of the Firefox changes are updates to dev tools; they just keep getting better and better. In fact, I’m not sure “dev tools” is the right word for them. With their focus on layout, typography, and accessibility, “design tools” might be a better term.

Oh, and Firefox is shipping support for some CSS properties that really help with print style sheets, so I’m disproportionately pleased about that.

In Safari’s changes, I’m pleased to see that the datalist element is finally getting implemented. I’ve been a fan of that element for many years now. (Am I a dork for having favourite HTML elements? Or am I a dork for even having to ask that question?)

And, of course, it wouldn’t be a Safari release without a new made up meta tag. From the people who brought you such hits as viewport and apple-mobile-web-app-capable, comes …supported-color-schemes (Apple likes to make up meta tags almost as much as Google likes to make up rel values).

There’ll be a whole bunch of improvements in how progressive web apps will behave once they’ve been added to the home screen. We’ll finally get some state persistence if you navigate away from the window!

Updated the behavior of websites saved to the home screen on iOS to pause in the background instead of relaunching each time.

Maximiliano Firtman has a detailed list of the good, the bad, and the “not sure yet if good” for progressive web apps on iOS 12.2 beta. Thomas Steiner has also written up the progress of progressive web apps in iOS 12.2 beta. Both are published on Ev’s blog.

At first glance, the release notes for Chrome 72 are somewhat paltry. The big news doesn’t even seem to be listed there. Maximiliano Firtman again:

Chrome 72 for Android shipped the long-awaited Trusted Web Activity feature, which means we can now distribute PWAs in the Google Play Store!

Very interesting indeed! I’m not sure if I’m ready to face the Kafkaesque process of trying to add something to the Google Play Store just yet, but it’s great to know that I can. Combined with the improvements coming in iOS 12.2, these are exciting times for progressive web apps!

Generating placeholders from datalists

Here’s a cute little markup pattern for ya.

Suppose you’ve got an input element that has—by means of a list attribute—an associated datalist. Here’s the example I used in HTML5 For Web Designers:

<label for="homeworld">Your home planet</label>
<input type="text" name="homeworld" id="homeworld" list="planets">
<datalist id="planets">
 <option value="Mercury">
 <option value="Venus">
 <option value="Earth">
 <option value="Mars">
 <option value="Jupiter">
 <option value="Saturn">
 <option value="Uranus">
 <option value="Neptune">
</datalist>

That results in a combo-box control in supporting browsers: as you type in the text field, you are presented with a subset of the options in the datalist that match what you are typing. It’s more powerful than a regular select, because you aren’t limited by the list of options: you’re free to type something that isn’t in the list (like, say, “Pluto”).

I’ve already written about the design of datalist and how you can use a combination of select and input using the same markup to be backward-compatible. I like datalist.

I also like the placeholder attribute. Another recent addition to HTML, this allows you to show an example of the kind of content you’d like the user to enter (note: this is not the same as a label).

It struck me recently that all the options in a datalist are perfectly good candidates for placeholder text. In the example above, I could update the input element to include:

<input type="text" name="homeworld" id="homeworld" list="planets" placeholder="Mars">

or:

<input type="text" name="homeworld" id="homeworld" list="planets" placeholder="Saturn">

I wrote a little piece of JavaScript to do this:

  1. Loop through all the input elements that have a list attribute.
  2. Find the corresponding datalist element (its ID will match the list attribute).
  3. Pick a random option element from that datalist.
  4. Set the placeholder value of the input to that option value.

Put that JavaScript at the end of your document (or link to it from the end of your document) and you’re all set. You might want to tweak it a little: I find it helps to preface placeholder values with “e.g.” to make it clear that this is an example value. You can do that by changing the last line of the script:

input.setAttribute('placeholder','e.g. '+value);

You also might want to show more than one possible value. You might want the placeholder value to read “e.g. Mercury, Venus, Earth, etc.” …I’ll leave that as an exercise for the reader.

The design of datalist

One of the many form enhancements provided by HTML5 is the datalist element. It allows you to turn a regular input field into a .

Using the list attribute on an input, you can connect it to a datalist with the corresponding ID. The datalist itself contains a series of option elements.

<input list="suggestions">
<datalist id="suggestions">
    <option value="foo"></option>
    <option value="bar"></option>
    <option value="baz"></option>
</datalist>

I can imagine a number of use cases for this:

  • “Share this” forms, like the one on Last.fm, that allow you to either select from your contacts on the site, or enter email addresses, separated by commas. Using input type="email" with a multiple attribute, in combination with a datalist would work nicely.
  • Entering the details for an event, where you can either select from a list of venues or, if the venue is not listed, create a new one.
  • Just about any form that has a selection of choices, of which the last choice is “other”, followed by “If other, please specify…”

You can take something like this:

<label for="source">How did you hear about us?</label>
<select name="source">
    <option>please choose...</option>
    <option value="television">Television</option>
    <option value="radio">Radio</option>
    <option value="newspaper">Newspaper</option>
    <option>Other</option>
</select>
If other, please specify:
<input id="source" name="source">

And replace it with this:

<label for="source">How did you hear about us?</label>
<datalist id="sources">
    <option value="television"></option>
    <option value="radio"></option>
    <option value="newspaper"></option>
</datalist>
<input id="source" name="source" list="sources">

The datalist element has been designed according to one of the design principles driving HTML5—Degrade Gracefully:

On the World Wide Web, authors are often reluctant to use new language features that cause problems in older user agents, or that do not provide some sort of graceful fallback. HTML 5 document conformance requirements should be designed so that Web content can degrade gracefully in older or less capable user agents, even when making use of new elements, attributes, APIs and content models.

Because the datalist element contains a series of option elements with value attributes, it is effectively invisible to user-agents that don’t support the datalist element. That means you can use the datalist element without worrying it “breaking” in older browsers.

If you wanted, you could include a message for non-supporting browsers:

<datalist id="sources">
    Your browser doesn't support datalist!
    <option value="television"></option>
    <option value="radio"></option>
    <option value="newspaper"></option>
</datalist>

That message—“Your browser doesn’t support datalist!”—will be visible in older browsers, but browsers that support datalist know not to show anything that’s not an option. But displaying a message like this for older browsers is fairly pointless; I certainly wouldn’t consider it graceful degradation.

In my opinion, one of the best aspects of the design of the datalist element is that you can continue to do things the old-fashioned way—using a select and an input—and at the same time start using datalist. There’s no violation of either; you can use the same option elements for the select and the datalist:

<label for="source">How did you hear about us?</label>
<datalist id="sources">
    <select name="source">
        <option>please choose...</option>
        <option value="television">Television</option>
        <option value="radio">Radio</option>
        <option value="newspaper">Newspaper</option>
        <option>Other</option>
    </select>
    If other, please specify:
</datalist>
<input id="source" name="source" list="sources">

Browsers that support datalist will display the label “How did you hear about us?” followed by a combo-box text field that allows the user to select an option, or enter some free text.

Browsers that don’t support datalist will display the the label “How did you hear about us?” followed by a drop-down list of of options (the last of which is “other”), followed by the text “If other, please specifiy”, followed by a text field.

Take a look at this example in Opera to see datalist in operation. Take a look at it in any other browser to see the fallback. The source is on Github if you’d like to play around with it.

WebKit’s mistake

If you try that example in any browser, you’ll get something that works; either through datalist support or through the select fallback …unless the browser is using WebKit.

It took me a while to figure out why this would be the case. I didn’t think that Safari or Chrome supported datalist and a little digging around with object detection in JavaScript confirmed this. So why don’t those browsers follow the standard behaviour and simply ignore the element they don’t understand and render what’s inside it instead.

Here’s the problem: line 539 of WebKit’s default CSS:

datalist {
    display: none;
}

This is pretty much the worst possible behaviour for a browser to implement. An element should either be recognised—like p, h1 or img—and parsed accordingly, or an element is unrecognised—like foo or bar—and ignored. WebKit does not support the datalist element (even in the current nightly build), so the element should be ignored.

Fortunately the problem is easily rectified by adding something like this to your own stylesheet:

datalist {
    display: inline-block;
}

I’ve submitted a bug report on the WebKit Bugzilla.

Update: That Webkit bug has now been fixed so the extra CSS is no longer necessary.