38

(I can't find it, but then again I don't really know how to search for it.)

I want to use <input list=xxx> and <datalist id=xxx> to get autocompletion, BUT I want the browser to match all options by 'contains' approach, instead of 'starts with', which seems to be standard. Is there a way?

If not simply, is there a way to force-show suggestions that I want to show, not those that the browser matched? Let's say I'm typing "foo" and I want to show options "bar" and "baz". Can I force those upon the user? If I just fill the datalist with those (with JS), the browser will still do its 'starts with' check, and filter them out.

I want ultimate control over HOW the datalist options show. NOT over its UI, flexibility, accessibility etc, so I don't want to completely remake it. Don't even suggest a jQuery plugin.

If I can ultimate-control form element validation, why not autocompletion, right?

edit: I see now that Firefox does use the 'contains' approach... That's not even a standard?? Any way to force this? Could I change Firefox's way?

edit: I made this to illustrate what I'd like: http://jsfiddle.net/rudiedirkx/r3jbfpxw/

Rudie
  • 52,220
  • 42
  • 131
  • 173
  • 2
    No help on this? Hard to believe...this was the first thing I thought of when I needed to use a datalist. – Todd Vance Jun 03 '15 at 22:15
  • the problem is, the datalist is not editable, at least in ie11. ther i get this error: `0x800a13b5 - JavaScript runtime error: Assignment to read-only properties is not allowed in strict mode`. – Nina Scholz Sep 03 '15 at 11:13
  • @NinaScholz When do you get that error? Assigning what to what? Can you make a fiddle? – Rudie Sep 03 '15 at 19:41
  • i took your approach with `innerHTML`. that works. – Nina Scholz Sep 04 '15 at 09:10
  • 1
    Current version of Chrome browser (56) had already changed the behavior to "substring match" to fit the current specification and Firefox's behavior. (I had not tested older version, but it seems this change is a recently one) – tsh Feb 17 '17 at 06:09

4 Answers4

17

'contains' approach

Maybe this is what you are looking for (part 1 of your question).

It goes with the limitation of "starts with" and changes when a selection is made.

'use strict';
function updateList(that) {
    if (!that) {
        return;
    }
    var lastValue = that.lastValue,
        value = that.value,
        array = [],
        pos = value.indexOf('|'),
        start = that.selectionStart,
        end = that.selectionEnd,
        options;

    if (that.options) {
        options = that.options;
    } else {
        options = Object.keys(that.list.options).map(function (option) {
            return that.list.options[option].value;
        });
        that.options = options;
    }

    if (lastValue !== value) {
        that.list.innerHTML = options.filter(function (a) {
            return ~a.toLowerCase().indexOf(value.toLowerCase());
        }).map(function (a) {
            return '<option value="' + value + '|' + a + '">' + a + '</option>';
        }).join();
        updateInput(that);
        that.lastValue = value;
    }
}

function updateInput(that) {
    if (!that) {
        return;
    }
    var value = that.value,
        pos = value.indexOf('|'),
        start = that.selectionStart,
        end = that.selectionEnd;

    if (~pos) {
        value = value.slice(pos + 1);
    }
    that.value = value;
    that.setSelectionRange(start, end);
}

document.getElementsByTagName('input').browser.addEventListener('keyup', function (e) {
    updateList(this);
});
document.getElementsByTagName('input').browser.addEventListener('input', function (e) {
    updateInput(this);
});
<input list="browsers" name="browser" id="browser" onkeyup="updateList();" oninput="updateInput();">
<datalist id="browsers">
    <option value="Internet Explorer">
    <option value="Firefox">
    <option value="Chrome">
    <option value="Opera">
    <option value="Safari">
</datalist>

Edit

A different approach of displaying the search content, to make clear, what happens. This works in Chrome as well. Inspired by Show datalist labels but submit the actual value

   'use strict';
var datalist = {
        r: ['ralph', 'ronny', 'rudie'],
        ru: ['rudie', 'rutte', 'rudiedirkx'],
        rud: ['rudie', 'rudiedirkx'],
        rudi: ['rudie'],
        rudo: ['rudolf'],
        foo: [
            { value: 42, text: 'The answer' },
            { value: 1337, text: 'Elite' },
            { value: 69, text: 'Dirty' },
            { value: 3.14, text: 'Pi' }
        ]
    },
    SEPARATOR = ' > ';

function updateList(that) {
    var lastValue = that.lastValue,
        value = that.value,
        array,
        key,
        pos = value.indexOf('|'),
        start = that.selectionStart,
        end = that.selectionEnd;

    if (lastValue !== value) {
        if (value !== '') {
            if (value in datalist) {
                key = value;
            } else {
                Object.keys(datalist).some(function (a) {
                    return ~a.toLowerCase().indexOf(value.toLowerCase()) && (key = a);
                });
            }
        }
        that.list.innerHTML = key ? datalist[key].map(function (a) {
            return '<option data-value="' + (a.value || a) + '">' + value + (value === key ? '' : SEPARATOR + key) + SEPARATOR + (a.text || a) + '</option>';
        }).join() : '';
        updateInput(that);
        that.lastValue = value;
    }
}

function updateInput(that) {
    var value = that.value,
        pos = value.lastIndexOf(SEPARATOR),
        start = that.selectionStart,
        end = that.selectionEnd;

    if (~pos) {
        value = value.slice(pos + SEPARATOR.length);
    }
    Object.keys(that.list.options).some(function (option) {
        var o = that.list.options[option],
            p = o.text.lastIndexOf(SEPARATOR);
        if (o.text.slice(p + SEPARATOR.length) === value) {
            value = o.getAttribute('data-value');
            return true;
        }
    });
    that.value = value;
    that.setSelectionRange(start, end);
}

document.getElementsByTagName('input').xx.addEventListener('keyup', function (e) {
    updateList(this);
});
document.getElementsByTagName('input').xx.addEventListener('input', function (e) {
    updateInput(this);
});
<input list="xxx" name="xx" id="xx">
<datalist id="xxx" type="text"></datalist>
Community
  • 1
  • 1
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • That's smart! And it works, but it looks like this: http://screencast.com/t/igeQtYJ2 That's not pretty, especially with a search like "ameri". This is a huge flaw in the autocomplete feature IMO. We're spending time on better 3d animations, but not improving functionality like this =( – Rudie Sep 04 '15 at 17:54
  • which browser do you use? – Nina Scholz Sep 04 '15 at 18:22
  • Chrome on Windows. Same in Canary. Firefox looks better, but Firefox already uses the 'contains' approach. – Rudie Sep 04 '15 at 19:13
  • First approach: tooltip is not shown in Chrome 56 for: `r`, left arrow, `e`. Workaround: `input.value = anyFooResetsCursor; window.setTimeout(() => this.setSelectionRange(oldStart + FooLen - oldLen, oldEnd + FooLen - oldLen), 0);`. I guess timeout may be changed to onkeyup if preferred. – ilyaigpetrov Jan 29 '17 at 18:54
  • 1
    @NinaScholz Note, at first approach `Uncaught TypeError: Cannot read property 'value' of undefined`, `Uncaught TypeError: Cannot read property 'lastValue' of undefined` are logged at `console` – guest271314 Mar 04 '17 at 07:57
  • @guest271314, right that was `undefined`. now both functions got a check for it. – Nina Scholz Mar 04 '17 at 08:12
5

yet this thread is posted about 2 years ago. but if you are reading this thread, you maybe need to check a newer version of your browser:

Current specification: https://html.spec.whatwg.org/multipage/forms.html#the-list-attribute

User agents are encouraged to filter the suggestions represented by the suggestions source element when the number of suggestions is large, including only the most relevant ones (e.g. based on the user's input so far). No precise threshold is defined, but capping the list at four to seven values is reasonable. If filtering based on the user's input, user agents should use substring matching against both the suggestions' label and value.

And when this post written, behavior of Firefox (51) and Chrome (56) had already been changed to match the specification.

which means what op want should just work now.

tsh
  • 4,263
  • 5
  • 28
  • 47
  • Yes, it does, but I'd still have liked it if I had some control over it. My next autocomplete might have to be 'starts with', but there's no way to override autocomplete functionality. What a shame. – Rudie Feb 18 '17 at 11:15
  • This answer is just too short... Please reflect also the section about autofilling: https://www.w3.org/TR/html/sec-forms.html#autofill (Especially what mantle the attribute is wearing *If the `autocomplete` attribute is omitted*, but also the difference between "**expectation** mantle" and "**anchor** mantle") — and don't miss this: https://www.w3.org/TR/html/sec-forms.html#ref-for-autofill-expectation-mantle-4 – yckart Jul 24 '17 at 03:11
  • @yckart `autocomplete` attribute is just another story. Not related to this question IMO. you may want post another answer or maybe another question if you like. – tsh Jul 24 '17 at 03:41
  • As of today Firefox does not match against both `value` and `label` as per the spec's suggestion. You can see an [example](https://codepen.io/thdoan/pen/wvmWJMZ), or read about a [related bug](https://bugzilla.mozilla.org/show_bug.cgi?id=869690). – thdoan Jul 10 '22 at 20:17
0

this fiddle here has cracked what you are asking for But I am not sure how to make it work without this dependency as the UI looks bit odd and out of place when used along with Bootstrap.

 elem.autocomplete({
    source: list.children().map(function() {
        return $(this).text();
    }).get()
Ananda
  • 888
  • 9
  • 19
  • 2
    This uses jQuery UI's `autocomplete`. Not using native autocomplete at all. I was hoping 2016 autocomplete would be better =( – Rudie Apr 20 '16 at 20:41
  • This does funny things for me when used in a Bootstrap 3 page – M.M Aug 25 '16 at 08:58
0

I found this question because I wanted "starts with" behavior, and now all the browsers seem to implement "contains". So I implemented this function, which on Firefox (and probably others), if called from input event handler (and optionally, from focusin event handler) provides "starts with" behavior.

let wrdlimit = prefix =>
{ let elm = mydatalist.firstElementChild;
  while( elm )
  { if( elm.value.startsWith( prefix ))
    { elm.removeAttribute('disabled');
    } else
    { elm.setAttribute('disabled', true );
    }
    elm = elm.nextElementSibling;
  }
}
Victoria
  • 497
  • 2
  • 10
  • 20