Datalist组件是一个与输入框列表相关联的万能数据列表组件,可以用于各种常见的输入框下拉列表。
安装与调用
引入CSS:
<link rel="stylesheet" href="https://unpkg.com/lu2/theme/edge/css/common/ui/Datalist.css">
引入JS:
<script type="module">
import 'https://unpkg.com/lu2/theme/edge/js/common/ui/Datalist.js';
</script>
也可以直接src引用。
<script type="module" src="https://unpkg.com/lu2/theme/edge/js/common/ui/Datalist.js"></script>
本组件为内置自定义元素组件,如果需要兼容Safari浏览器,还需要引入下面的JS。
<script src="https://unpkg.com/lu2/theme/edge/js/common/safari-polyfill.js"></script>
由于此Polyfill执行了querySelectorAll匹配,因此Safari浏览器下,组件connetedCallback生命周期函数的执行时机比Chrome/Firefox浏览器靠后,因此,如果要兼容Safari浏览器,在执行参数设置的时候,可以在当前输入框元素的DOMContentLoaded或connected回调中设置。详见本文档源代码。
如果是npm安装调用,则:
import 'lu2/theme/edge/css/common/ui/Datalist.css'; import 'lu2/theme/edge/js/common/safari-polyfill.js'; import 'lu2/theme/edge/js/common/ui/Datalist.js';
如果 JS import语法报错,试试在业务代码中动态引入。
import('lu2/theme/edge/js/common/safari-polyfill.js');
import('lu2/theme/edge/js/common/ui/Datalist.js');
<datalist>列表功能
借助原生的<datalist>元素实现数据列表功能,适合静态数据,语义更好,功能更健壮,JS异常用户也能无障碍使用。
给元素添加is="ui-datalist"属性,即可拥有自定义的Datalist交互行为。
1. 姓名匹配-基于元素(推荐)
员工姓名:
HTML代码
<input id="i1" class="ui-input" list="d0" is="ui-datalist"> <!-- 员工数据 --> <datalist id="d0"> <option value="蔡伦"> ... <option value="彭大帅"> </datalist>
说明
is="ui-datalist"的初始化方式只适用于基于<datalist>元素数据的交互形式。<input>输入框元素的list属性值和原生的<datalist>元素的id属性值保持一致即可。- 我们可以在
<input>元素上设置results属性指定显示最大的列表个数,可参考后面案例。默认最大显示列表个数是8项。 <datalist>列表输入框在点击时候,如果里面有值,会清空变成placeholder属性值,这样完整列表可以展示,增强体验。如果不希望是这样的交互,可以设置可选参数placeholder为false。
2. 姓名匹配-基于数据
员工姓名:
使用可选参数中的data传参,通过<input>输入框的results属性指定列表最多显示5项。
<input id="i2" class="ui-input" results="5" is="ui-datalist">
i2.params.data = [{
value: '新数据蔡伦',
userid: 1
}, {
value: '李莲英',
userid: 2
}, {
value: '高松正',
userid: 3
}, {
value: '郑工',
userid: 4
}, {
value: '陈晓芳',
userid: 5
}, {
value: '李琳琳',
userid: 6
}, {
value: '彭大帅',
userid: 7
}];
实际开发中,开发者更关心的往往是是userid值,而不是用户选择的姓名名称,此时可以通过event.detail获取,例如:
input.addEventListener('change', function (event) {
console.log('选择的数据是:', event.detail);
});
3. 姓名匹配-支持拼音
Datalist组件默认前值匹配,如果想要模糊匹配,需要使用可选参数中的filter接口修改过滤规则,可以通过插入HTML设置加粗或高亮。
员工姓名:
可以把拼音数据写在label属性上,然后使用filter参数进行匹配过滤。
<input id="i3" class="ui-input" list="d3" is="ui-datalist">
<datalist id="d3">
<option value="蔡伦" label="cailun">
<option value="郑成功" label="zhengchenggong">
<option value="李莲英" label="lilianying">
<option value="高松正" label="gaosongzheng">
<option value="郑工" label="zhenggong">
<option value="陈晓芳" label="chenxiaofang">
<option value="李琳琳" label="lilinlin">
<option value="彭大帅" label="pengdashuai">
</datalist>
i3.params.filter = (data, value) => {
if (!value) {
return data;
}
return data.filter(function (obj) {
var valueReplace = obj.value.replace(value, '<mark>' + value + '</mark>');
var valueLabel = obj.label.replace(value, '<mark>' + value + '</mark>');
if (valueReplace != obj.value || valueLabel != obj.label) {
obj.value = valueReplace;
obj.label = valueLabel;
return true;
}
return false;
});
}
i3.addEventListener('select', function (event) {
console.log('选择的数据是:', event.detail);
});;
说明
默认label值为灰色小字,如果有特殊需要想要改变列表的样式,可以通过特殊的类名进行重置。
规则是这样的,列表显示的时候,会基于<input>输入框元素的id值生成一个关联类名。例如<input>元素的id值是whatever,则列表容器会包含一个类名ui-datalist-whatever;又如这里的id是inputPerson3,则列表容器会有类名ui-datalist-input-person3。
4. 时间选择-部分选项禁用测试
凌晨00:00~05:59不能选择::
HTML代码如下:
<!-- 输入框部分 --> <span class="ui-input input-time-x"><input id="inputHour" is="ui-datalist" list="datalistHour" results="24" value="06">:<input id="inputMinute" is="ui-datalist" list="datalistMinute" results="60" value="00"></span> <!-- 数据部分,使用原生<datalist>元素 --> <!-- 小时数据 --> <datalist id="datalistHour"> <option value="00" disabled> <option value="01" disabled> ... <option value="23"> </datalist> <!-- 分钟数据 --> <datalist id="datalistMinute"> <option value="00"> <option value="01"> ... <option value="59"> </datalist>
然后CSS部分,需要微调下LuLu UI默认的输入框样式:
.input-time-x {
box-shadow: inset 0 0 0 1px #d0d0d5;
border-radius: 4px;
background-color: #fff;
}
.input-time-x > input {
width: 4em;
border: 0;
text-align: center;
background-color: transparent;
}
Autocomplete功能
1. 原生autocomplete行为
会记忆当前输入框元素表单提交过的值,并在下一次连续点击的时候出现历史提交的数据列表,可以使用Del键删除某一项数据,若想清空所有数据,可以使用实例对象暴露的removeStore()方法。
案例
输入内容并回车,触发表单的submit提交事件,此时,再次点击输入框,之前提交的数据会作为列表项呈现出来。
<form>
<span class="ui-input ui-input-search" align="end">
<input type="search" id="s1" name="search" results="5" is="ui-datalist" placeholder="输入内容并回车" required>
<button type="submit" class="ui-icon-search">搜索</button>
</span>
</form>
<button id="b1" class="ui-button" data-type="primary" disabled>清除当前数据</button>
<button id="b2" class="ui-button" data-type="primary">清除所有数据</button>
// 下面代码是演示如何清除数据 b1.addEventListener('click', function () { // 删除当前输入框里面的值 s1.removeStore(); }); b2.addEventListener('click', function () { // 传入参数true表示删除所有数据 s1.removeStore(true); });
其他说明
这里的autocomplete自动完成功能采用和浏览器自身一样的实现机制,包括:
<form>元素submit事件执行的时候,才会记录该数据;- 数据查询key通过
name属性值进行匹配; autocomplete="off"可以关闭自动提示和记忆功能。- 操作交互也和浏览器内置交互一致,例如,输入框2次点击,显示对应列表,上下键选择,回车赋值等。
换句话说,就是把浏览器内置的autocomplete功能美化了下,所谓UI组件,本来就应该只关注UI。
当选择列表内容之后,会触发<input>输入框的change和input事件。
2. Ajax autocomplate功能
<form>
<input class="ui-input" type="search" id="s2" is="ui-datalist" placeholder="输入字母">
<button type="submit" class="ui-button" data-type="primary">搜索</button>
</form>
// 列表内容不转义,支持HTML显示 s2.params.encode = false; // 请求的参数和回调 s2.params.data = { url: '/search', data: { userid: 1, }, name: 'kw', success() { console.log('success callback successed!'); } }
这里的列表数据由后端提供,前端发送搜索内容给后端,后端决定要显示的数据。
参数
当可选参数data类型为纯对象,并且有url属性的时候,Datalist组件会认为是Ajax请求列表。
后端返回值必须是'json'类型数据。
input.params.data对象支持下面4个属性:
- url
- 字符串。必需。请求后端数据的地址。注意,IE9浏览器不支持跨域的URL地址。
- data
- 纯对象。可选。请求后端数据时发送的数据。其中输入框数据发送是组件内置的,无需额外设置。
- name
- 字符串。可选。搜索内容对应的字段名称,如果不设置,使用
<input>元素的name属性值,如果name属性值也没设置,使用字符k代替。 - success
- 函数。可选。成功回调,支持一个参数,为后端返回的JSON数据。
- error
- 函数。可选。失败回调。
更多说明
实际使用的时候不会像上面这么简单,因为不同项目后台返回的数据格式是不一样的。
本Datalist组件默认支持的JSON数据结构如下:
{
data: []
}
但有些项目返回的JSON数据中的列表数组在更深的层级中,例如:
{
data: {
pageInfo: {},
data: []
}
}
也就是数据列表需要使用data.data获取,此时,我们可以使用filter参数处理,filter参数是个专门对数据进行过滤和处理的方法,例如,我们可以这样:
input.params.filter = function (data) {
return data.data;
};
如果更进一步,data.data数组中的对象属性名不是value(必须),也不是label(可缺省),而是其他,例如:
{
data: {
pageInfo: {},
data: [{
content: 'aaaaa'
}, {
content: 'bbbbb'
}]
}
}
此时,同样在filter方法中进行处理,返回Datalist需要的数组格式就可以了:
input.params.filter = function (data) {
return data.data.map(function (obj) {
return {
value: obj.content
}
});
}
3. 基于<datalist>的autocomplate
此实现适用于Vue等数据驱动的开发场景,纯HTML驱动,<datalist>内容是什么,列表显示就是什么,且实时变化更新。
使用方法,输入框元素设置filter="none",<datalist>列表基于数据实时更新即可。
演示案例:
相关代码:
<input id="s4" class="ui-input" type="search" list="l3" filter="none" placeholder="任意输入" is="ui-datalist"> <input type="hidden" id="userid" name="userid" required> <datalist id="l3"></datalist>
// 基于datalist的autocomplete
s4.addEventListener('input', function (event) {
if (!event.isTrusted) {
return;
}
const value = this.value;
fetch('/search?key=' + encodeURIComponent(value)).then(function (response) {
return response.json();
}).then(function (json) {
l3.innerHTML = '';
json.data.forEach(function (item) {
var eleOption = document.createElement('option');
eleOption.setAttribute('value', item.value);
eleOption.dataset.id = item.id;
l3.appendChild(eleOption);
});
});
});
s4.addEventListener('change', function (event) {
if (event.detail?.id) {
userid.value = event.detail.id;
}
});
自动补全功能
例如下面的邮箱自动补全功能:
邮箱:
<input type="email" id="e1" class="ui-input" size="30" is="ui-datalist">
// 邮箱匹配
e1.setParams({
data: ['qq.com', 'gmail.com', '126.com', '163.com'],
filter: (data, value) => {
value = value.split('@')[0];
if (!value) {
return [];
}
return data.map(function (domain) {
return {
value: value + '@' + domain
};
});
}
});
语法和参数
已知:
let myDatalist = document.querySelector('input[is="ui-datalist"]');
则myDatalist元素自身就是一个支持Datalist能力的Web组件。
直接通过对myDatalist元素进行参数设置实现不同类型的数据列表功能。
语法
参数设置有2种方法,一种是单独设置,语法如下:
myDatalist.params.data = ...;
myDatalist.params.filter = ...;
...
还有一种是一次性设置,语法如下:
myDatalist.setParams(options);
其中options是可选参数。Object纯对象。支持的参数值见下表:
| 参数名称 | 支持类型 | 默认值 | 释义 |
|---|---|---|---|
| data | String| |
'auto' | 列表的数据,默认值 可以指定数据,无论是静态数组,还是函数,最终返回数据结构都要如下: {
可以指定其他自定义数据,例如id信息,在输入框的 |
| width | String| |
'auto' | 列表的宽度,默认'auto' |
| place |
Boolean | 'auto' | 是否是占位符交互模式(点击输入框值清空使用'auto'false,其他功能识别为true。 |
| filter | Function | function (data, value) { /* 原封不动返回或首字符匹配过滤 */ } |
对现有的数据进行重新过滤或者重新组装,只要返回需要的数组就好了,数组格式和细节和data参数一致。其中,thisdatavalue |
事件
支持的事件类型包括下面这些:
| 事件类型 | 释义 |
|---|---|
| show | 列表显示的时候触发,使用示意:input.addEventListener('show', () => {
console.log('显示了');
}); |
| hide | 列表隐藏的时候触发,使用示意:input.addEventListener('hide', () => {
console.log('隐藏了');
}); |
| select | 列表选择的时候触发,非组件触发的'select'事件event.detail的值是undefined。使用示意:input.addEventListener('select', (event) => {
// 上下键选择时候event.detail没有数据,走的是原生select事件
console.log(event.detail);
}); |
| change | 输入框内容因为列表选择变化的时候触发,非组件触发的'change'事件event.detail的值是undefined。使用示意:input.addEventListener('change', event => {
console.log('值选择且变化了');
}); |
| input | 同change。 |
| DOM |
当下拉列表面板元素初始化完毕的时候执行。 |
| connected | 当组件和页面建立连接的时候执行。 |
暴露的属性和方法
myDatalist暴露了如下属性和方法:
{
element: {
// 列表面板元素
target: null
},
callback: {
// 根据params.data生成的获得实时列表数据的方法,真正的数据源
data: function () {},
filter: function () {},
select: function () {},
show: function () {},
hide: function () {}
},
params: {
width: 'auto',
// 原始数据过滤和筛选方法
filter: function () {},
// 原始数据,任意类型,不同类型不同列表交互
data: object,
// 占位符交互细节处理
placeholder: 'auto',
// 当前列表选择的索引值
index: -1
},
// 当前列表显示与否
display: false,
// 当前列表所使用的数据
datalist: [],
// 存储本地输入框的值
store: function () {},
// 清除本地记录,支持1个可选的value参数,有3种逻辑:
1. 字符串内容,表示移除本地该值数据(如果有)
2. true, 表示清空本地对应该存储
3. undefined, 表示使用trigger输入框的value值作为移除对象
removeStore: function () {},
// 根据当前value值刷新列表,可以使用data参数指定数据进行列表呈现
refresh: function (data) {},
// 有value参数表示赋值,没有表示取值(HTML转义后的值)
val: function (value) {},
// 列表浮层容器元素创建
create: function () {},
// 列表定位
position: function () {},
// 显示列表
show: function () {},
// 隐藏列表
hide: function () {}
}
浮层的控制
Datalist组件浮层的控制参见“Color颜色选择”文档中“浮层的控制”。
综合拓展案例
这里演示如何借助Datalist组件的筛选过滤逻辑实现其他更复杂的交互效果。
带搜索的下拉列表
使用CSS重置了一些样式,例如,绝对定位就不要了,阴影效果也不要了:
.datalist-container .ui-datalist {
display: block!important;
position: static!important;
}
.datalist-container .ui-datalist-datalist {
margin-top: 10px;
border: 1px solid #d0d0d5;
box-shadow: none;
}
这里的按钮HTML直接取自Select组件:
<div class="ui-select" style="width:300px;">
<a href="javascript:" id="b0" class="ui-select-button" data-target="t0" is-drop>
<span class="ui-select-text">企点服务迭代三(4月6日~5月5日)</span>
</a>
</div>
<!-- 下拉主体结构 -->
<div id="t0" class="ui-droplist-x" hidden>
<div class="ui-input ui-input-search" align="end">
<input type="search" id="s0" is="ui-datalist"><label class="ui-icon-search">搜索</label>
</div>
<div id="c0" class="datalist-container"></div>
</div>
然后是JS代码,需要引用组件体系中的Drop.js:
// 下拉处理 s0.setParams({ width: 280, data: data, filter: function (data, value) { return data.filter(function (obj) { return value == '' || obj.value.indexOf(value) != -1; }); } }); // 选择列表修改按钮中的值 s0.addEventListener('select', function (event) { if (event.detail) { b0.querySelector('span').textContent = event.detail.value; // 下拉关闭 b0.click(); } }); // 下拉列表在特定容器显示 s0.refresh(); c0.appendChild(s0.element.target);
本页贡献者:
zhangxinxu,sunseekers