LuLu UI pure版中文文档 » Datalist数据列表

Fork Me

Datalist数据列表

Datalist组件是一个与输入框列表相关联的万能数据列表组件,可以用于各种常见的输入框下拉列表。

作为插件单独使用

引入CSS:

<link rel="stylesheet" href="https://qidian.gtimg.com/lulu/pure/css/common/ui/Datalist.css">

引入JS:

<script src="https://qidian.gtimg.com/lulu/pure/js/common/ui/Follow.js"></script>
<script src="https://qidian.gtimg.com/lulu/pure/js/common/ui/Datalist.js"></script>

<datalist>列表功能

借助原生的<datalist>元素实现数据列表功能,适合静态数据,语义更好,功能更健壮,JS异常用户也能无障碍使用。

1. 姓名匹配-基于元素(推荐)

员工姓名:

HTML代码

<input id="inputPerson" class="ui-input" list="datalistPerson">
<!-- 员工数据 -->
<datalist id="datalistPerson">
    <!--[if IE 9]><select disabled style="display:none"><![endif]-->
    <option value="蔡伦">
    ...
    <option value="彭大帅">
    <!--[if IE 9]></select><![endif]-->
</datalist>

JS代码

直接绑定Datalist方法即可:

new Datalist('#inputPerson');

说明

  1. <input>输入框元素的list属性值和原生的<datalist>元素的id属性值保持一致即可。
  2. <datalist> IE9浏览器不支持,会忽略其中的<option>元素,可以使用<!--[if IE 9]>条件注释嵌套一个<select>元素。

    如果您的项目无需兼容IE9浏览器,可以去掉相关代码,另外,为了减少干扰,下面案例展示的源码就不显示IE9的条件注释代码了。

  3. 我们可以在<input>元素上设置results属性指定显示最大的列表个数,可参考后面案例。默认最大显示列表个数是8项。
  4. <datalist>列表输入框在点击时候,如果里面有值,会清空变成placeholder属性值,这样完整列表可以展示,增强体验。如果不希望是这样的交互,可以设置可选参数placeholderfalse

2. 姓名匹配-基于数据

员工姓名:

使用可选参数中的data传参,通过<input>输入框的results属性指定列表最多显示5项。

<input id="inputPerson2" class="ui-input" results="5">
new Datalist('#inputPerson2', {
    // 如果数据是动态变化的,请使用function类型
    data: [{
        value: '蔡伦',
        id: 1
    }, {
        value: '李莲英',
        id: 2
    }, {
        value: '高松正',
        id: 3
    }, {
        value: '郑工',
        id: 4
    }, {
        value: '陈晓芳',
        id: 5
    }, {
        value: '李琳琳',
        id: 6
    }, {
        value: '彭大帅',
        id: 7
    }]
});
事件传参

实际开发中,开发者更关心的是用户的ID,而不是姓名,此时,可以通过事件对象的detail属性获取,代码示意如下:

// 演示数据参数如何传递到事件中
document.querySelector('#inputPerson2').addEventListener('change', function (event) {
    console.log('用户变化,当前用户id是:' + event.detail.id);
});

在控制台就可以看到对应的输出。

3. 姓名匹配-支持拼音

Datalist组件默认前值匹配,如果想要模糊匹配,需要使用可选参数中的filter接口修改过滤规则,可以通过插入HTML设置加粗或高亮。

员工姓名:

可以把拼音数据写在label属性上,然后使用filter参数进行匹配过滤。

<input id="inputPerson3" class="ui-input" list="datalistPerson3">
<datalist id="datalistPerson3">
    <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>
// 支持拼音
new Datalist('#inputPerson3', {
    // data是完整数据,value是输入框的值
    filter: function (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;
        });
    }
});

说明

默认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" list="datalistHour" results="24" value="06">:<input id="inputMinute" 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 {
    border: 1px solid #d0d0d5;
    border-radius: 4px;
}
.input-time-x > input {
    width: 4em;
    border: 0;
    text-align: center;
}

JS非常简单:

new Datalist('#inputHour');
new Datalist('#inputMinute');

Autocomplete功能

1. 原生autocomplete行为

会记忆当前输入框元素表单提交过的值,并在下一次连续点击的时候出现历史提交的数据列表,可以使用Del键删除某一项数据,若想清空所有数据,可以使用实例对象暴露的removeStore()方法。

案例

输入内容并回车,触发表单的submit提交事件,此时,再次点击输入框,之前提交的数据会作为列表项呈现出来。

<form>
    <span class="ui-input ui-input-search" align="right">
        <input type="search" id="inputSimple" name="search" results="5" placeholder="输入内容并回车" required>
        <button type="submit" class="ui-icon-search">搜索</button>
    </span>
</form>

<button id="clearStore" class="ui-button" data-type="primary" disabled>清除当前数据</button>
<button id="clearAllStore" class="ui-button" data-type="primary">清除所有数据</button>
// 浏览器原生autocomplete模拟
var datalistSimple = new Datalist('#inputSimple');

// 其实就上面一行就可以了,下面代码是演示如何清除数据
// 两个清除按钮
var eleBtnClearStore = document.getElementById('clearStore');
var eleBtnClearAllStore = document.getElementById('clearAllStore');

eleBtnClearStore.addEventListener('click', function () {
    // 删除当前实例对象对应输入框里面的值
    datalistSimple.removeStore();

    new LightTip().success('“' + datalistSimple.value() + '”这条搜索记录已经从本地数据中清除');
});

eleBtnClearAllStore.addEventListener('click', function () {
    // 传入参数true表示删除所有数据
    datalistSimple.removeStore(true);

    new LightTip().success('所有搜索历史记录已经从本地数据中清除');
});
// 表单提交后存储的提示处理
// 实际开发无需该提示
form.addEventListener('submit', function (event) {
    event.preventDefault();

    new LightTip().success('当前搜索内容已经在本地存储,再次点击或输入,可以触发匹配');
});
其他说明

这里的autocomplete自动完成功能采用和浏览器自身一样的实现机制,包括:

  1. <form>元素submit事件执行的时候,才会记录该数据;
  2. 数据查询key通过name属性值进行匹配;
  3. autocomplete="off"可以关闭自动提示和记忆功能。
  4. 操作交互也和浏览器内置交互一致,例如,输入框2次点击,显示对应列表,上下键选择,回车赋值等。

换句话说,就是把浏览器内置的autocomplete功能美化了下,所谓UI组件,本来就应该只关注UI。

当选择列表内容之后,会触发<input>输入框的changeinput事件。

2. Ajax autocomplate功能

<form>
    <input class="ui-input" type="search" id="ajaxSearch" name="kw" placeholder="输入字母">
    <button type="submit" class="ui-button" data-type="primary">搜索</button>
</form>
var eleSearch = document.getElementById('ajaxSearch');
new Datalist(eleSearch, {
    data: {
        url: '/search',
        data: {
            userid: 1
        },
        success: function() {
            console.log('success callback success!');
        }
    }
});

这里的列表数据由后端提供,前端发送搜索内容给后端,后端决定要显示的数据。

参数

当可选参数data类型为纯对象,并且有url属性的时候,Datalist组件会认为是Ajax请求列表。

后端返回值必须是'json'类型数据。

data对象支持下面4个属性:

url
字符串。必需。请求后端数据的地址。注意,IE9浏览器不支持跨域的URL地址。
data
纯对象。可选。请求后端数据时发送的数据。其中输入框数据发送是组件内置的,默认使用<input>元素的name属性值作为字段发送数据,如果缺省,使用字符k代替。
success
函数。可选。成功回调,支持一个参数,为后端返回的JSON数据。
error
函数。可选。失败回调。
更多说明

然而,我们实际使用的时候不会像上面这么简单,因此不同项目不同开发返回的数据格式和后台接口都是不一样的。

本Datalist组件默认支持的JSON数据结构如下:

{
    data: []
}

但有些项目返回的JSON数据中的列表数组在更深的层级中,例如:

{
    data: {
        pageInfo: {},
        data: []
    }
}

也就是数据列表需要使用data.data获取,此时,我们可以使用filter参数处理,filter参数是个专门对数据进行过滤和处理的方法,例如,我们可以这样:

new Datalist(eleSearch, {
    data: {
        url: '/search'
    },
    filter: function(data) {
        return data.data;
    }
});

如果更进一步,data.data数组中的对象属性名不是value(必须),也不是label(可缺省),而是其他,例如:

{
    data: {
        pageInfo: {},
        data: [{
            content: 'aaaaa'
        }, {
            content: 'bbbbb'
        }]
    }
}

此时,同样在filter方法中进行处理,返回Datalist需要的数组格式就可以了:

new Datalist($('#inputAjaxSearch'), {
    data: {
        url: '/search'
    },
    filter: function(data) {
        return data.data.map(function (obj) {
            return {
                value: obj.content
            }
        });
    }
});

自动补全功能

例如下面的邮箱自动补全功能:

邮箱:

<input type="email" id="inputEmail" class="ui-input" size="30">
// 邮箱匹配
new Datalist('#inputEmail', {
    data: ['qq.com', 'gmail.com', '126.com', '163.com'],
    placeholder: false,
    filter: function (data, value) {
        value = value.split('@')[0];

        if (!value) {
            return [];
        }

        return data.map(function (domain) {
            return {
                value: value + '@' + domain
            };
        });
    }
});

is-datalist与初始化

对于 Vue 或 Preact 等框架,直接获取 DOM 元素然后进行组件初始化是不太方便的,此时,可以使用 is-datalist 属性和 connected 自定义事件进行处理。

例如:

<div id="app">
    <template v-if="1">
        <input class="ui-input" :results="results" @connected="setParams" is-datalist>
    </template>
</div>
new Vue({
  el: '#app',
  data: {
    results: 5
  },
  methods: {
    setParams: function (event) {
        new Datalist(event.target, {
            data: [{
                value: '蔡伦',
                id: 1
            }, ..., {
                value: '彭大帅',
                id: 7
            }]
        });
    }
  }
});

语法和参数

语法

var myDatalist = new Datalist(trigger, options);

参数

trigger
必需。Object DOM对象|String选择器。指输入框DOM元素或者输入框元素的选择器。
options
可选。Object纯对象。可选参数具体见下表:
参数名称 支持类型 默认值 释义
data String|Array|Function|Object 'auto'

列表的数据,默认auto表示根据trigger元素不同参数值进行数据类型识别。

可以指定数据,无论是静态数组,还是函数,最终返回数据结构都要如下:

{
  // 可选,禁用
  disabled: '',
  // 可选,右侧小文字
  label: '',
  // 必须
  value: ''
}

可以指定其他自定义数据,在输入框的'change''input'事件中使用event.detail或者myDatalist.data[myDatalist.params.index]获取选中的值。

max String|Number 8 最多显示的列表项条数,默认8条。使用关键字'auto'表示数据没有限制。可以使用HTML原生的类目个数属性results重置显示的条目数,例如results="5"最多显示5条列表数据。
width String|Number 'auto' 列表的宽度,默认'auto'表示和trigger触发元素的宽度一致。
container Element|String document.body DOM元素对象,指装载列表的容器,默认是<body>元素,该参数值也可以是元素的选择器。
placeholder Boolean 'auto' 是否是占位符交互模式(点击输入框值清空使用placeholder属性值代替),默认'auto'表示自动识别,规则如下:Autocomplete功能的参数值认为是false,其他功能识别为true。我们也可以通过实例对象暴露的placeholder进行设置,例如myDatalist.placeholder = false
filter Function function (data, value) {
  /* 首字符匹配过滤 */
}
对现有的数据进行重新过滤或者重新组装,只要返回需要的数组就好了,数组格式和细节和data参数一致。其中,this上下文指当前实例元素,支持2个参数,data为需要处理的数据(也是数组格式),value是输入框xss过滤后的值。
onSelect Function function () {} 列表选择时候的回调。this上下文指当前实例元素,支持3个参数,分别是选择的数据data,触发元素trigger和列表面板元素target。只要点击和回车才触发,上下键移动不会触发此回调。
onShow Function function () {} 列表显示时候触发的回调。this上下文指当前实例元素,支持2个参数,分别是触发元素trigger和列表面板元素target
onHide Function function () {} 列表隐藏时候触发的回调。this上下文指当前实例元素,支持2个参数,分别是触发元素trigger和列表面板元素target

关于实例对象

myDatalist就是实例对象,暴露了如下属性和方法:

{
    el: {
        // 输入框元素
        trigger: null,
        input: null,
        // append列表的容器
        container: null,
        // 列表面板元素
        target: null
    },
    callback: {
        // 根据params.data生成的获得实时列表数据的方法,真正的数据源
        data: function () {},
        filter: function () {},
        select: function () {},
        show: function () {},
        hide: function () {}
    },
    params: {
        width: 'auto',
        max: 8,
        // 可选参数和默认参数合并后的data
        data: [],
        // 当前选中的列表所在的索引值
        index: -1
    },
    // 交互模式是否是Placeholder模式
    placeholder: true,
    // 当前列表显示与否
    display: false,
    // 当前列表所使用的数据
    data: [],
    // 存储本地输入框的值
    store: function () {},
    // 清除本地记录,支持1个可选的value参数,有3种逻辑:
       1. 字符串内容,表示移除本地该值数据(如果有)
       2. true, 表示清空本地对应该存储
       3. undefined, 表示使用trigger输入框的value值作为移除对象
    removeStore: function () {},
    // 根据当前value值刷新列表,可以使用data参数指定数据进行列表呈现
    refresh: function (data) {},
    // 有value参数表示赋值,没有表示取值(xss过滤后的值)
    value: function (value) {},
    // 事件绑定
    events: function () {},
    // 列表定位
    position: function () {},
    // 显示列表
    show: function () {},
    // 隐藏列表
    hide: function () {}
}

我们也可以通过trigger.data.datalist获取实例对象。

new Datalist(trigger);
var myDatalist = trigger.data.datalist;

Datalist组件默认关闭了边缘检测,如果希望下拉列表在浏览器外面的时候自动朝上显示,可以这么设置:

myDatalist.params.edgeAdjust = true;

综合拓展案例

这里演示如何借助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组件UI,因为我懒:

<div class="ui-select" style="width:300px;">
    <a href="javascript:" id="listTrigger" class="ui-select-button"><span class="ui-select-text">企点服务迭代三(4月6日~5月5日)</span></a>
</div>
<!-- 下拉面板主体结构 -->
<div id="listTarget" class="ui-droplist-x" hidden>
    <div class="ui-input ui-input-search" align="right"><input type="search"><label class="ui-icon-search">搜索</label></div>
    <div id="listBox" class="datalist-container"></div>
</div>

然后是JS代码,使用了组件体系中的Drop.js,而这里的Datalist则静态处理:

// 下拉带搜索示意
var listTrigger = document.getElementById('listTrigger');
var listTarget = document.getElementById('listTarget');
// 尺寸
listTarget.style.width = listTrigger.clientWidth + 'px';
// 下拉处理
new Drop(listTrigger, listTarget, {
    eventType: 'click',
    onShow: function (trigger, target) {
        var input = target.querySelector('input');
        var drop = this;

        if (!this.datalist) {
            // 只构造一次
            var datalist = new Datalist(input, {
                data: data,
                width: 280,
                container: '#listBox',
                max: 'auto',
                filter: function (data, value) {
                    return data.filter(function (obj) {
                        return value == '' || obj.value.indexOf(value) != -1;
                    });
                },
                onSelect: function (data) {
                    var span = listTrigger.querySelector('span');
                    if (data && span) {
                        span.innerHTML = data.value;
                        drop.hide();
                    }
                }
            });

            datalist.refresh();
            this.datalist = datalist;
        } else {
            input.click();
        }
    }
});
本页贡献者:

zhangxinxu