使用HTML5(datalist)自动完成“包含”而不仅仅是“以...开始”的方法

38

(我找不到它,但我也不知道如何搜索它。)

我想使用<input list=xxx><datalist id=xxx>来实现自动完成,但我希望浏览器使用“包含”而不是“以...开头”的方式进行匹配,因为后者似乎是标准的方式。有办法吗?

如果没有简单方法,是否可以强制显示我要显示的建议,而不是浏览器匹配的建议?假设我正在输入“foo”,我想显示选项“bar”和“baz”。我能强迫用户选择这些吗?如果我只是用JS填充数据列表,浏览器仍然会进行“以...开头”的检查,并过滤它们。

我想完全控制数据列表选项的显示方式,而不是控制其UI、灵活性、可访问性等等,所以我不想完全重新制作它。甚至不要建议使用jQuery插件。

如果我可以终极控制表单元素验证,为什么不能控制自动完成呢?

编辑:我现在看到Firefox确实使用了“包含”方式......这甚至不是一个标准吗?有什么办法可以强制这样吗?我能改变Firefox的方式吗?

编辑:我做了这个来说明我的要求:http://jsfiddle.net/rudiedirkx/r3jbfpxw/


2
没有人能帮忙吗?难以置信...当我需要使用数据列表时,这是我首先想到的事情。 - Todd Vance
问题是,数据列表在IE11中无法编辑。我遇到了这个错误:0x800a13b5 - JavaScript运行时错误:在严格模式下不允许对只读属性进行赋值 - Nina Scholz
@NinaScholz你什么时候遇到了那个错误?你把什么分配给什么了吗?你能创建一个fiddle吗? - Rudie
我采用了你的 innerHTML 方法,它有效。 - Nina Scholz
1
Chrome浏览器的当前版本(56)已经改变了行为,以适应当前规范和Firefox的行为。 (我没有测试过旧版本,但似乎这个更改是最近的) - tsh
4个回答

17

'包含'方式

也许这就是您正在寻找的内容(您问题的第一部分)。

它遵循“以...开头”的限制,并在进行选择时发生更改。

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

编辑

展示搜索内容的不同方法,以便清晰地了解发生了什么。这也适用于Chrome浏览器。受显示数据列表标签但提交实际值启发。

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


这很聪明! 而且它有效,但它看起来像这样:http://screencast.com/t/igeQtYJ2。这不太漂亮,特别是搜索诸如“ameri”之类的内容时。在我看来,这是自动完成功能中的一个巨大缺陷。我们正在花时间改进更好的3D动画,但却没有改善这样的功能。=( - Rudie
你使用哪个浏览器? - Nina Scholz
Chrome在Windows上。Canary也是一样。Firefox看起来更好,但Firefox已经使用了“包含”方法。 - Rudie
第一种方法:Chrome 56不会在以下情况下显示工具提示:r、左箭头、e。解决方法:input.value = anyFooResetsCursor; window.setTimeout(() => this.setSelectionRange(oldStart + FooLen - oldLen, oldEnd + FooLen - oldLen), 0);。我猜超时可能会更改为onkeyup(如果更喜欢)。 - ilyaigpetrov
1
@NinaScholz 注意,第一次尝试时,会在控制台上记录“Uncaught TypeError: Cannot read property 'value' of undefined”和“Uncaught TypeError: Cannot read property 'lastValue' of undefined”。 - guest271314
@guest271314,对的,那是 undefined。现在两个函数都进行了检查。 - Nina Scholz

5

尽管这个帖子已经发布了约2年,但如果您正在阅读这个帖子,可能需要检查您的浏览器是否有更新版本:

当前规范: https://html.spec.whatwg.org/multipage/forms.html#the-list-attribute

当建议列表中的选项数量较多时,鼓励用户代理过滤建议源元素所代表的建议,并仅包含最相关的建议(例如基于用户到目前为止输入的内容)。没有定义明确的阈值,但将列表限制在4至7个值是合理的。如果根据用户的输入进行过滤,则用户代理应使用子字符串匹配同时针对建议的标签和值

而当此帖子被编写时,Firefox (51) 和 Chrome (56) 的行为已经改变以符合规范。

这意味着操作者想要的功能现在应该可以正常工作。


是的,它确实有这个功能,但我仍然希望能够对其进行一些控制。我的下一个自动完成可能必须是“以...开头”,但没有办法覆盖自动完成功能。真遗憾。 - Rudie
这个答案太短了... 请注意自动填充部分:https://www.w3.org/TR/html/sec-forms.html#autofill(特别是如果省略`autocomplete`属性时该属性所具有的外观,以及“期望外观”和“锚定外观”之间的区别)- 还要注意这个链接:https://www.w3.org/TR/html/sec-forms.html#ref-for-autofill-expectation-mantle-4 - yckart
@yckart autocomplete 属性是另一个故事。在我看来与这个问题无关。如果你愿意,可以发表另一个答案或者提出另一个问题。 - tsh
截至今天,Firefox不符合规范建议的valuelabel两者匹配。您可以查看示例,或阅读相关错误 - thdoan

0

这个代码片段已经解决了你所要求的问题,但是我不确定如何在没有这个依赖项的情况下使其正常工作,因为当与Bootstrap一起使用时,UI看起来有点奇怪和不协调。

 elem.autocomplete({
    source: list.children().map(function() {
        return $(this).text();
    }).get()

2
这里使用了jQuery UI的autocomplete,完全没有使用原生的自动完成功能。我本来希望2016年的自动完成功能会更好一些 =( - Rudie
当我在Bootstrap 3页面中使用它时,它对我产生了有趣的影响。 - M.M

0
我发现这个问题是因为我想要“以...开始”的行为,但现在所有的浏览器似乎都实现了“包含”。所以我实现了这个函数,在Firefox(和可能其他浏览器)中,如果从输入事件处理程序(可选地,从focusin事件处理程序)调用它,就提供了“以...开始”的行为。
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;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接