构建最小的 AutoSuggest
Web 花了好几年才引入<datalist>
-tag,而它对于创建最广泛使用的 UI 组件之一“AutoSuggest”至关重要。在本教程中,我们将构建一个极简的“AutoSuggest”,使用JavaScript和不使用JavaScript 两种方式。
在我读过的最早的 UI 设计书籍之一,1995 年出版的《Windows 软件界面设计指南》中,它被称为组合框——因为它是由下拉列表和文本输入框组合而成。我个人认为这个术语比“自动建议”或“预先输入”更有意义,但似乎大家已经选择了“自动建议”——所以就沿用这个名称吧!
jQueryUI 有一个“AutoComplete”插件,名字不太对,因为“自动完成”是一个稍微不同的东西,如下图所示的UX Stackexchange帖子所示:
基本结构
在网上看到的大多数例子中, a<datalist>
和 一起使用<input type="text">
。但我更喜欢用<input type="search">
。为什么?因为这种类型开箱即用,增加了一些很棒的、额外的、方便访问的功能:
-
按下该
Escape
键可清除列表选择,再次按下该键可完全清除输入。 -
在 Chrome 和 Safari 中,当您按下或,或者单击小“重置十字”时,会触发一个事件 - onsearch 。
Escape
Enter
标记
这些建议本身<option>
如下<datalist>
:
<datalist id="browsers">
<option value="Edge">
<option value="Firefox">
<option value="Chrome">
<option value="Opera">
<option value="Safari">
</datalist>
在 Chrome 中,也支持这种格式:
<option value="MSE">Microsoft Edge</option>
和value
都会innerText
显示在列表中,但只有value
当您选择一个项目时才会插入。
<datalist>
要将与输入链接起来,只需获取id
并用作list
-attribute:
<label>
<strong>Pick a browser</strong>
<input
autocomplete="off"
autocorrect="off"
list="browsers"
spellcheck="false"
type="search">
</label>
我们不想autocomplete
或spellcheck
干扰,所以我们将它们设置为off
和false
。autocorrect
是仅限 Safari 的属性,在这种情况下也应该禁用。
CSS
这里没什么内容。我们可以用它来-webkit-appearance: none
清除默认的浏览器样式,并添加我们自己的样式。以下是一个例子:
[type="search"] {
border: 1px solid #AAA;
font-size: 1rem;
margin-block: 0.5rem;
min-inline-size: 20rem;
padding: 0.5rem 0.75rem;
-webkit-appearance: none
}
您可能确实想要更改的是那个重置输入的小“十字图标”:
我在 中使用了一个 SVG 图标url()
,并将其存储在CSS 自定义属性中,因此它可以同时用作mask-image
和-webkit-mask-image
以实现浏览器兼容性:
[type="search"]::-webkit-search-cancel-button {
--reset: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.016 15.609l-3.609-3.609 3.609-3.609-1.406-1.406-3.609 3.609-3.609-3.609-1.406 1.406 3.609 3.609-3.609 3.609 1.406 1.406 3.609-3.609 3.609 3.609zM12 2.016q4.125 0 7.055 2.93t2.93 7.055-2.93 7.055-7.055 2.93-7.055-2.93-2.93-7.055 2.93-7.055 7.055-2.93z"/></svg>');
background-color: currentColor;
display: block;
height: 1rem;
mask-image: var(--reset);
width: 1rem;
-webkit-appearance: none;
-webkit-mask-image: var(--reset);
}
<input>
Chrome 添加了带有的下拉箭头<datalist>
,我们可以将其隐藏:
}
[list]::-webkit-calendar-picker-indicator {
display: none !important;
}
那里好多了:
在移动设备上,<input type="search">
会触发带有“搜索”按钮的虚拟键盘。如果您不想这样,请查看inputmode。
在 iPhone 上,<datalist>
显示如下:
远非完美,但仍然比许多自定义解决方案要好得多,其中虚拟键盘使“AutoSuggest”上下跳动!
这就是简约的、无 JavaScript 的AutoSuggest!
对于国家选择器等功能来说非常棒— — 并且比 jQueryUI 的“自动完成”插件占用的最小224kb(包括它的 CSS 和 jQuery 本身)要好得多。
但是如果您想使用 API 并<option>
动态创建 s 该怎么办?
添加 API
在查看 JavaScript 之前,让我们先向标记添加一些额外的属性<input type="search">
:
data-api="//domain.com?q="
data-api-cache="0"
data-api-key="key"
min-length="3"
这data-api
是url
我们想要的fetch()
。
搜索文本将附加到此处。
可以data-api-cache
是0
(禁用)或1
(启用)。如果启用,<datalist>
则初始 之后的 -options 将不会被覆盖fetch()
,并且当您输入更多文本时,<datalist>
将使用 的原生浏览器过滤。
data-api-key
是您想要搜索并显示为 s 的结果对象中的“键/属性” <option>
。
min-length
fetch()
是一个标准属性。在本例中,它表示在触发之前需要输入多少个字符。
JavaScript
对于 JavaScript,我将解释我正在使用的所有方法,以便您可以使用所需的功能构建自己的定制AutoSuggest 。
首先,我们添加一个函数,autoSuggest(input)
它只有一个参数:input
。
接下来,一个布尔值指示是否应该使用缓存:
const cache = input.dataset.apiCache - 0 || 0;
返回的数据将存储在:
let data = [];
为了不导致服务崩溃,我们需要一个debounce方法来过滤掉事件:
export default function debounced(delay, fn) {
let timerId;
return function(...args) {
if (timerId) clearTimeout(timerId);
timerId = setTimeout(() => { fn(...args); timerId = null }, delay)
}
}
我们存储对以下内容的引用<datalist>
:
const list = document.getElementById(input.getAttribute('list'));
eventListener
…并在上添加input
:
input.addEventListener('input', debounced(200, event => onentry(event)));
这200
是方法中使用的延迟debounce
。您可以修改它,或者将其添加到设置对象或类似的对象中。
最后,有一个onentry
从内部调用的方法debounce
:
const onentry = async function(event) {
const value = input.value.length >= input.minLength && input.value.toLowerCase();
if (!value) return;
if (!data.length || cache === false) {
data = await (await fetch(input.dataset.api + encodeURIComponent(value))).json();
list.innerHTML = data.map(obj => `<option value="${obj[input.dataset.apiKey]}">`).join('')
}
}
它是一个异步函数,首先检查输入是否包含最少数量的字符。如果没有,则直接返回。
如果没有数据存在,或者缓存设置为,则会触发0: false
a ,并更新s。fetch()
<option>
太棒了,我们现在有了动态选项和一个最小化的脚本,只有497 字节,压缩后大约为349 字节!
但我觉得它缺少一些功能。我想在从列表中选择一个选项时触发一个自定义事件,并希望在该事件中显示匹配搜索结果中的对象。
让我们onentry
稍微修改一下方法。我们可以使用 来event.inputType
检测用户何时点击列表项,或者使用 来选择它Enter
:
if (event.inputType == "insertReplacementText" || event.inputType == null) {
const option = selected();
if (option) input.dispatchEvent(new CustomEvent('autoSuggestSelect', { detail: JSON.parse(option.dataset.obj) }));
return;
}
-methodselected
查找并返回对象数组中的当前输入文本:
const selected = () => {
const option = [...list.options].filter(entry => entry.value === input.value);
return option.length === 1 ? option[0] : 0;
}
现在 — — 在另一个脚本中!— — 我们可以监听该事件:
input.addEventListener('autoSuggestSelect', event => { console.info(event.detail) });
如果我们想重置列表怎么办?在 Safari 和 Chrome 中,有一个onsearch
-event 事件,它会在 reset 和 时触发Enter
。
让我们添加一个reset()
-method :
const reset = () => { data = []; list.innerHTML = `<option value="">` }
当用户点击“重置十字”或按下时触发它Escape
:
input.addEventListener('search', () => input.value.length === 0 ? reset() : '// Do something on Enter');
<option>
-method中的空格reset()
是针对 Firefox 和 Safari 的 hack,否则在使用 dynamic 时会出现一些问题<datalist>
。因此,在标记中默认添加一个空选项是个好主意:
<datalist id="suggest"><option value=""></option></datalist>
该脚本压缩后大小为544 字节。我们还能做些什么吗?
在 Firefox 中,我们可以添加一个小的“polyfill” onsearch
:
if (!('onsearch' in input)) {
input.addEventListener('keydown', (event) => {
if (event.key === 'Escape') { input.value = ''; reset(); }
if (event.key === 'Enter') { ... }
})
}
还有什么?
您可以继续自行添加内容。但在此之前,让我们先添加一个settings
-object 来保存我们已有的配置参数——以及您想要添加的任何内容!首先,我们将修改 main 函数:
autoSuggest(input, args)
然后,我们将其合并args
到设置对象中:
const settings = Object.assign({
api: '',
apiCache: false,
apiKey: ''
}, datasetToType(args));
这datasetToType
是一个小的辅助函数,它将数据集条目转换为正确的类型(以 为前缀的非字符串值:
):
export default function datasetToType(obj) {
const object = Object.assign({}, obj);
Object.keys(object).forEach(key => {
if (typeof object[key] === 'string' && object[key].charAt(0) === ':') {
object[key] = JSON.parse(object[key].slice(1));
}
});
return object;
}
这样,我们可以autoSuggest
使用标准 JavaScript 对象调用方法:
autoSuggest(input, { apiCache: false });
— 或者它的dataset
:
autoSuggest(input, input.dataset);
在标记中,我们将 替换0
为:false
,1
将 替换为:true
:
data-api-cache=":false"
我们还需要用 替换input.dataset.api
,settings.api
删除cache
常量,然后用 替换它settings.cache
(以及其他各个地方,请查看最后的示例!),但我们现在有一个settings
-object,我们可以扩展新功能。
限制选择
你想限制value
只允许列表中的值吗?让我们扩展一下settings
-object:
invalid: 'Not a valid selection',
limit: false
我们将添加一个新方法:
const limit = () => {
const option = selected();
input.setCustomValidity(option ? '' : settings.invalid);
if (!input.checkValidity()) {
input.reportValidity();
console.log('invalid');
}
else {
console.log('valid');
}
}
最后,我们将更新onsearch
事件:
input.addEventListener('search', () => input.value.length === 0 ? reset() : settings.limit ? limit() : '');
此方法使用 HTML5 的默认验证 API — 目前不执行任何操作(除了记录到console
!)。您可以/应该对其进行调整,以使用您自己的方式处理无效状态。
示例
第一个例子是 DAWA,一个丹麦的地址查找服务(尝试输入“park”):
<label>
<strong>DAWA - Danish Address Lookup</strong>
<input
autocomplete="off"
autocorrect="off"
data-api="//dawa.aws.dk/adresser/autocomplete?side=1&per_side=10&q="
data-api-cache=":false"
data-api-key="tekst"
data-limit=":true"
list="dawa"
minlength="3"
spellcheck="false"
type="search">
</label>
<datalist id="dawa"><option value=""></option></datalist>
下面是 JSON 占位符(尝试输入“lorem”):
<label>
<strong>JSON placeholder</strong>
<input
autocomplete="off"
autocorrect="off"
data-api="//jsonplaceholder.typicode.com/albums/?_limit=10&q="
data-api-key="title"
list="jsonplaceholder"
minlength="3"
spellcheck="false"
type="search">
</label>
<datalist id="jsonplaceholder"><option value=""></option></datalist>
autoSuggest
对具有关联的所有元素运行 -method 的快速方法<datalist>
是:
import autoSuggest from './autosuggest.mjs';
const inputs = document.querySelectorAll('[list]');
inputs.forEach(input => {
if (input.dataset.api) {
input.addEventListener('autoSuggestSelect', event => { console.info(event.detail) });
autoSuggest(input, input.dataset);
}
})
结论
这并非一个久经考验的“AutoSuggest”,您可以直接在项目中使用。它更像是一套原则和理念,您可以根据自己的需求进行定制,打造自己的专属功能:无论是极简还是功能丰富!
更重要的是,它旨在展示如何使用内置标签及其内置功能的“本机优先”方法通常可以减少 JavaScript 的使用和开销。
我创建了一个代码库,你可以从中获取演示文件。在VS Code中打开文件夹,然后使用Live Server或类似工具启动它。现场演示在这里
文章来源:https://dev.to/madsstoumann/building-a-minimal-autosuggest-4big