This works with a static datalist. I’m wondering if there is a way to recompute such a datalist dynamically, according to the data that the user has just typed in the same textbox (e.g., a kind of typeahead triggering API calls).
PS: HTML <datalist> is based on ids. They must be unique. I designed the autocomplete form passing in a user-defined namespace to avoid id conflicts when you want more forms in the same notebook. Probably there is a better approach out there. Let me know!
You can get a unique ID via DOM.uid().id - this is the recommended approach within Observable
Don’t use <form> - there’s no point to it, and nesting forms creates an undefined state
Don’t use querySelector to retrieve your elements. Instead define them upfront and then interpolate:
const input = htl.html`<input>`;
const form = htl.html`<div>${input}`;
You may want to memoize options so that they don’t have to be recreated:
const options = new Map();
const getOption = name => options.get(name) || (options.set(name, htl.html`<option value=${name}>`), options.get(name));
// ...
list.appendChild(getOption(name));
Minor nitpick: I’d avoid <br> as it makes custom styling more difficult. Consider using inline styles or a dedicated style element with scoped rules. I’ve found that the following pattern works well to produce scoped styles:
// Browsers don't support :scope, so :scope is equal to :root.
// However we're going to replace it.
const css = `:scope input {display:block}`;
const scope = DOM.uid('my-form').id;
const style = htl.html`<style>${css.replace(/:scope\b/g, `.${scope}`)}`;
const form = html`<div class="${scope}">${style}`;
Hi @mootari. Sorry for my late reply. I refactored the code sticking to your suggestions. Thank you for sharing them!
I interpolated hypertext literals and I added memoization for the options. I used a slightly different approach. I use a Map to store each query (key) and its resultset (value), thus avoiding to fetch data from the source (e.g., from Web APIs) every time input changes. <option> elements are not cached but I managed to handle them with D3’s data.join. Final version in the same place.