这次开发了个小TRS系统,虽然是很小,但是作为初心者,第一次用到了很多看起来洋气使用起来有相对简单的各种前端(主要是和bootstrap配合使用)组件。包括bootstrap-select2,bootstrap-datetimepicker,bootstrap-fileinput等。本文就旨在记录一些这些组件相关的内容
【bootstrap-select2】
官方文档:【https://select2.org/options】
这个组件主要用于优化<select>等页面组件。比如我们想要在下拉菜单的顶部加上一个搜索框支持我们对选项进行搜索,抑或是在多选下拉菜单中我们要有那种类似于tag形式的表现,用这个组件就很好了。首先是这个组件需要在页面中进行引入的文件:
<link href="{% static 'select2/dist/css/select2.min.css' %}" rel="stylesheet" /> <script src="{% static 'select2/dist/js/select2.min.js' %}"></script>
<script src="{% static 'select2/dist/js/i18n/zh-CN.js' %}"></script>
zh-CN.js是语言翻译文件,需要注意引入必须在select2.min.js后面,否则会报错。这一点也适用于绝大多数支持国际化显示的组件。
当然因为是bootstrap的组件,自然是不能少bootstrap的js和css以及支持bootstrap的jquery了,这个就不写出来了。
■ 带静态搜索框的下拉菜单
所谓静态搜索框,就是指这个下拉菜单里所有的option都是在页面渲染时就已经固化好了的,用这个搜索框进行搜索时不会动态发请求到后台去取数据。这个搜索框的HTML可以这么写:
<select id="static_dropdown" class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
这就是一个普通的select,只不过需要有form-control这个class和一个用于后续表示用的id。
然后在js中这么写:
$('#static_dropdown').select2({
language: 'zh-CN',
width: '100%',
placeholder: '请选择',
minimumInputLength: 10
});
利用前端组件一个非常重要的内容就是把握初始化时各个初始化参数的含义。通过对不同参数发出调整来搞出一个符合自己需求的组件。
这里用到的四个参数,language显而易见是国际化显示用的。width指出了这个select的宽度,placeholder就是placeholder...,minimunInputLength指出了在搜索时至少要输入多少字符才会给出响应的搜索结果。如果待选项不是太多的话尽量不要设置过大的值,像示例的10,我觉得是很大的一个值了。。
下面也会零散性地给出一些参数说明:
selectOnClose 当设置成true时,当收起菜单时的高亮项会自动被选择,如果是false则不会自动选择。
dropdownParent 可以指定下拉菜单在html代码中的位置。默认情况下,下拉菜单都是被append到body中,并且被设置成position:absolute,且位置在select的下方。而设置这个参数比如dropdownParent: $('#myModal'),就可以将下拉菜单放入一个指定的容器中。
tags 把tags设置成true了之后,在搜索时即便没有搜索到相关内容,也会把你输入的内容当成一个可选项。之所以被称为tags,就是因为tags这个东西不一定要都是既存的,可以有非既存而我们自己添加的情况。
placeholder 如果值是一个字符串那么就是一个简单的placeholder,如果换成一个对象也可以,即为菜单添加一个默认的选项。比如placeholder: {id:-1,text:'--请选择--'}
tokenSeparators 可以设置成一个包含各种字符的列表如[',',' '],常和tags以及多选菜单配合使用。即自己输入时,输入逗号或空格(看你这个参数设置的情况)时认为是一个tag的终结,就把前面的内容作为一个新tag添加到多选框里面。
createTag 这个参数的值也是一个函数对象。这个函数接受一个参数param,其结构是这样的
我们主要关注term这一项的值,我们可以在createTag指定的函数中对term进行一些逻辑判断和处理,然后返回一些结果来让添加标签的过程更加智能化一些。比如:
$('#testselect').select2({
createTag: function(param){
var term = $.trim(param.term);
if (term.indexOf('@') == -1){
return null;
}
else {
var id = term.split('@')[0];
var text = term.split('@')[1];
return {id:id,text:text};
}
}
});
在用户输入自定义的tag的时候可以检查存不存在@,如果不就不允许其增加这个tag,如果存在则@前面的部分作为value,后面的部分作为<option>的text,加入一个新的tag。
minimunResultsForSearch 通常这个参数设置成一个整数,当option的待选数量大于这数字时才显示搜索框。当把这个参数设置为Infinity时将默认不显示搜索框即禁用搜索框。这样select就变成了一个simple的select了。
maximumSelectionLength 若是多选框,设置最多可以选择几项
另外,可以在select和option中引入一层optgroup标签并设置其label属性。optgroup可以为所有选项划分出合适的组分并且显示以label的标签。将option的disabled设置为disabled还可以禁止选择某一个选项。
● 带有图片的下拉菜单
上面的说明,得到的下拉菜单都是纯文字的。其实select2支持我们传递图片进去作为每个选项的一个修饰。这里就要用到templateResult这个参数。
首先要明确,我们虽然在html上写的是select和option,但是最终经过select2的加工呈现出来的是ul和li,每个li里面是当时option中的内容。在了解这一点的基础上,我们的templateResult参数是一个函数对象。这个函数对象接受一个object(这个object八成是select2内部定义的抽象了菜单选项的object)为参数并且返回字符串或jquery对象供select2渲染成菜单选项。简单来说,就是select2会对所有选项进行一个遍历,依次执行一下这个函数然后按照我们定义的方式渲染出我们想要的菜单选项的样式和内容。比如下面这样:
templateResult: function(row){
return $('<span><img class="img-flag" src="/static/img/'+row.id+'.png">'+row.text+'</span>');
}
上面这个函数有点绕,简单来说就是可以把/static/img/<每个option设置好的value值>.png作为标识图片放在option的旁边。如果在这个函数中断点看下row的具体情况的话,就可以看到其实这个row的结构是这样的:
Object { selected: false, disabled: false, text: "张三", id: "10001", title: "", _resultId: "select2-addTrainSpeaker-result-5o5i-10001", element: option }
也就是说我们的option的value在这里是id,option的字符串在这里是text,这样就可以根据value和字符串来定制了。顺便一提,上面的函数中return要加上$()是因为不这样返回的默认是字符串,也就是每一项的内容都会变成<span>xxxx的样子而不是html内容。所以返回jquery对象比较靠谱。
在编写templateResult函数的时候,还要注意对loading状态的处理。当打开下拉列表时,最先进入这个函数的应该是loading状态的对象,此时row没有id属性,当时有loading:true属性,如果此时不返回当前的row.text(正在加载中之类的提示文字)的话,渲染有可能就停止在这步。所以一般而言可以在templateResult函数里写上if(row.loading){return row.text;}之类的逻辑。
■ tag形式的多选框
所谓tag形式的多选框就是指这种:
这个看起来比较复杂,其实也通过select2实现也比较方便。html可以这么写:
<button class="close" type="button" id="multiple_all">+</button>
<select class="form-control" id="multiple_choice" multiple="multiple">
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
</select>
然后在js中这样写:
var multiple = $('#multiple_choice').select2({
placeholder: '请选择',
allowClear: true
}); $('#multiple_all').click(function(event){
event.preventDefault();
var res = [];
$(this).next('select').find('option').each(function(i,ele){
res.push($(ele).val())
});
$(multiple).val(res).trigger('change');
});
第一部分很好理解,和上面的下拉菜单一样,也是初始化。只是这个初始化返回了一个jquery对象,我们先用multiple这个变量保存下来。然后当select前面的那个按钮被点击时,就是执行了第二部分代码,是将select中所有选项的值都加入到一个数组中,然后把这个数组传递给刚才那个multiple对象的val方法,再trigger一下change事件。这样就可以自动地选择所有选项到多选框里面了。(这部分是我自己加上去,不是select2要求的)。
可以看出来$(multiple).val(xxx).trigger('change')就是拿来引发多选框被选中值变化的方法。如果xxx处填写null那么就是清空多选框了。
关于如何取值,类似的我们可以$('#multiple_choice').val()来取值,得到的应该是一个数组。这里多提一句,如何通过ajax传递数组给后台。直接写入ajax方法的data字段需要特殊处理过的数组。比如我们有数组对象array想要作为参数parti的值通过ajax传递出去的话,那么可以先JSON.stringify(array),得到的json化的字符串再拿来传递即可。在后台我们只要用相关后台的方法来解析json格式字符串即可。
■ 编程控制
上面提到了如何一键清空或全选多选框的选项、这个其实是一个select2组件的编程控制。更一般地来说我们可以这样给下拉菜单多增加一个选项:
var data = {
id: 1,
text: 'test'
}; var newOption = new Option(data.text,data.id,false,false);
$('#testselect').append(newOption).trigger('change');
如果是选取若干个可以传递一个数组给val方法。如果需要对待加入项是否已经存在做出判断的话可以这么做:
if ($('#testselect').find('option[value='+data.id+']').length){
$('#testselect').val(data.id).trigger('change'); //如果已经存在则不添加而是选择那一项
}
else{
var newOption = new Option(data.id,data.text,true,true);
$('#testselect').append(newOption).trigger('change');
}
■ 动态搜索下拉菜单
所谓动态,即每次搜索框中数据发生变化时,前端会向后台发送ajax请求来获取一些数据,把这些数据用于填充下拉菜单的内容。实现方法是在初始化select2组件的时候加入ajax参数,如:
$('#testselect').select2({
ajax : {
url: '/api',
dataType: 'json', //比较重要,没有的话返回json数据会无法识别,搜索失败
data: function(param){
return {
keyword: param.term,
searchType: 'public',
page: param.page || 1
}
}
}
})
ajax参数的值是一个object,包括url等key。url这个肯定是要有的,指出了ajax请求发往什么地方。data参数是可选的,在默认情况下select2发出的请求包含了以下几个参数:
term 此时搜索框中的内容
q 和term内容一样
_type 通常是query,如果涉及到结果分页时可能不一样
page 当有结果分页时有此参数,指出当前分页
*关于分页和page参数:在当前处于默认第一页的时候,发送的请求不带page参数,所以一般来说我们都会在data参数的函数对象中对page参数做这样一个处理: params.page = params.page || 1;来保证第一次打开输入框时也会给出page=1作为请求参数。
在设置data参数之后,data参数的值是一个获取当前搜索框中对象(具体搜索内容是其term参数)并返回一个object作为ajax参数的函数对象,我们可以借此来定义一些自定义的请求参数字段。
需要特别说明的是,这里的ajax请求默认是get方法,目前还没找到如何改成post的办法。
因为前端会自动对后端传递过来的数据进行适应select2组件的渲染,所以其对于数据的格式肯定是有要求的。具体的要求是这样的:
{
results: [
{id: '10001', text: 'Option_1'},
{id: '10002', text: 'Option_2'},
//等等,这部分就是主要的数据
],
pagination: {
more: true
}
}
主要数据部分每个列表项都是一个object且包含id和text两个字段分别用来填充option标签对应的那两个字段。pagination则是指出了前端会对上送的结果做一个分页处理,即一次性只在下拉列表中显示有限个项,但在最下方有一个“显示更多项”,当滚动条到最下方时再自动发送请求去调取下一个分页的数据。当然,顺利地实现分页需要前端发出的ajax请求中带有param.page这个信息(如上上面的代码所示),然后后端也要根据上送上来的page的值来进行相应数据段的返回。前后端协调了才可以给出比较好的分页效果。
上面的说明中可以看得出对数据格式要求比较严格(results,pagination,id,text等字段都不能自定义),为了适应更加多样化的形式的数据,我们可以在ajax参数中再加上一个processResults字段:
$('#testselect').select2({
ajax: {
url: '/api',
dataType: 'json',
data: function(param){
return {
kw: param.term,
page: param.page || 1
};
},
processResults: function(data){
return {
results: data.items
};
}
}
})
上面的示例虽然有了分页,不过还有个小瑕疵,就是即使加载完了所有数据,最下面的加载所有数据依然会存在而不消失。这个问题可以在前端解决,官方文档中给出的示例是这样的:
processResults: function(data,params){
params.page = params.page || 1;
return {
results: data.results,
pagination: {
more: (params.page * 10) < data.total_count
}
};
}
首先,返回的结果中多了一个total_count字段,它指出了未分页时结果集的个数。注意是未分页时,即搜索条件不变时不论page是多少这个字段的值是不变的。然后在后端返回中的pagination字段可以去掉了,因为这个字段通过前端的逻辑判断来置true或false,这个条件,就是上面写着的当前页面*10是否小于总条数(也就是一次显示10条)。若否,表明已经加载到了最后一页,此时pagination就可以是false,即不需要再显示“加载更多数据”了。
需要额外说一下,分页的个数最好要达到5个以上才会有正常的效果。我尝试了一次分页只给出两三个结果的时候,前端页面会一直显示在加载更多结果但是没有真的在加载数据
● 更多ajax参数
delay 可以在ajax的参数中加上delay,取值单位是毫秒。意思是说当用户停止输入了这么多毫秒之后再发出ajax请求。用户每改动一个字母就发出一次请求显然是比较浪费资源的,这么做更加合理一些。
■ 设置选择框的默认值
对于静态框,我们可以在生成之后通过$(element).val([...]).trigger('change')的办法来为select2渲染出来的东西添加一个或一些默认选中的值。如果一定要在初始化的时候就设置好默认的值那么也可以利用placeholder这个初始化参数。正如上面所说,如果placeholder是个字符串那么就是普通的placeholder,但如果是个object那么将会是一个提示默认选择值的东西。
对于有ajax参数的动态选择框有些复杂。我在实践中找到了一种方法。我们知道,构造一个动态的选择框是不需要在select标签中添加option的,但是即便我们あえて添加一些的话,渲染出来的动态框,我们打开选择框也不会出现那个预设的静态选项。不过它有一个用,就是可以被val([xxx]).trigger('change')这个操作捕捉到。
也就是说,如果我们想对一个动态框预设一个或几个选中值(通常这些值应该也是我们预期能够通过动态框获取到的值),我们可以首先假装它是一个静态选项,作为<option>写死到<select>中去,然后再通过val([...]).trigger('change')将这些所谓的静态选项置选中。事实上这些所谓的静态选项并不会出现在选择框中,而静态的<option>和val/trigger两者的交集会作为最终被选中的默认值。
示例:
<!-- HTML中 -->
<select multiple class="form-control" id="test">
<option value="0">Preselected Value 1</option>
<option value="1">Preselected Value 2</option>
</select>
//js中
$('#test').select2({
ajax: {...} // 构造了一个动态的选择框
}).val(['0','1']).trigger('change');
然后页面上的这个多选框就是默认选中了Preselected Value1和Preselected Value2两个选项(不管ajax请求发出之后能否真的能获取到这两个选项)