Use HTML5 (datalist) autocomplete with 'contains' approach, not just 'starts with'

前端 未结 4 715
清歌不尽
清歌不尽 2020-11-29 05:03

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

I want to use and

相关标签:
4条回答
  • 2020-11-29 05:46

    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;
      }
    }
    
    0 讨论(0)
  • 2020-11-29 05:58

    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.

    0 讨论(0)
  • 2020-11-29 05:59

    '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>

    0 讨论(0)
  • 2020-11-29 05:59

    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()
    
    0 讨论(0)
提交回复
热议问题