前言
最近对滑动开关有需求,由于当前的项目采用springboot+bootsrtap3架构,所以目标优先定位到基于bootstrap的滑动开关解决方案。先后尝试了bootstrap-swtich和bootstrap-toggle,期间踩坑无数,最后选择了bootstrap-toggle。在此详细记录一下整个过程。
需求:在bootstrap-tables的某一列中显示滑动开关,操作本列对象是否启用。初始化时需要显示本列对象的初始状态(有的已经off,有的已经on)。
bootsrap-toggle的效果图:
简单说下bootstrap-switch
网上关于bootstrap-switch的资料是最多的,可见这个插件是相对流行的基于bootstrap的开关组件了。但是在使用过程中发现很多坑,首先这个组件的网页上的例子所有滑块竟然都没有渲染,全是原始的checkbox样式-_-;其次,渲染了之后发现也略丑,最小尺寸也很大;最后,解决一堆问题终于让开关渲染出来,状态切换事件也run起来之后,发现一个一直没有办法解决的问题:初始状态永远在off。由于本人前端水平是N手业余,所以stackoverflow查了半天没解决的前提下,选择了放弃。附bootstrap-switch的“官网”:https://www.bootcss.com/p/bootstrap-switch/
言归正传:bootstrap-toggle
这个插件在网上的资料极少极少,原因很简单——停更了...看js文件注释应该是2014年最后一次更新。然而这并不影响我们使用它(尽管那个年代还是面向bootstrap2,但在bootstrap3中亲测可用),因为滑动开关这种组件功能简单,就算实时更新也不会有太多花样。另外就是bootstrap-toggle的使用方法很简单,个人认为堪称优雅了。当然这里面也有个巨坑,好在已经排掉,后文详述。
bootstrap-toggle官网:http://www.bootstraptoggle.com/
bootstrap-toggle的用法在官网说的已经非常清楚了,在这里结合bootstrap-table一起总结一下。
常规用法
引入bootstrap-toggle,从官网下载代码解压后,css文件和js文件分别在根目录下的css和js文件夹中。
<link href="/bootstrap-toggle/css/bootstrap-toggle.css" rel="stylesheet">
<script src="/bootstrap-toggle/js/bootstrap-toggle.js"></script>
在页面里定义一个checkbox元素:
<input name='mycheck' type='checkbox' checked/>
然后在js中初渲染:
<input name="mycheck" checked type="checkbox">
<script>
$(function() {
$("[name='mycheck']").bootstrapToggle();
})
</script>
这样就可以得到一个开关了:
当然,这个开关只是展示了出来,还不具备数据传递功能。可以利用jquery的on方法绑定事件:
<input name="mycheck" checked type="checkbox">
<script>
$(function() {
$("[name='mycheck']").bootstrapToggle().on('change.status',function(){
//do something
});
})
</script>
结合bootstrap-table使用
首先在bootstrap-table对应列里动态生成开关:
$('#yourTable').bootstrapTable({
columns: [
/**
略去其他代码
**/
{
field: 'available',
title: "是否启用",
align: 'left',
valign: 'middle',
formatter: function (value, row, index) {
var $switch;
if (value == true) {
$switch = "<input data-toggle='toggle' value=" + row.id + " name='avaCheck' type='checkbox' checked/>";
} else {
$switch = "<input data-toggle='toggle' value=" + row.id + " name='avaCheck' type='checkbox'/>";
}
return $switch;
}
}
],
onLoadSuccess: function () {
var changeHandler = function () {
var id = $(this).val();
var state = !$(this).prop('checked');
updateStatus(id, state);
};
$("[name='avaCheck']").bootstrapToggle('destroy');
return $("[name='avaCheck']").bootstrapToggle({
on: '启用',//选中时显示文本
off: '停用',//未选中时显示文本
onstyle: 'success',//on样式:default,primary,success,info,warning,danger
offstyle: 'default',//off样式:default,primary,success,info,warning,danger
size: 'small',//控件大小:large,normal,small,mini
}).off('change.status').on('change.status', changeHandler);
}
})
要点说明:
- formatter中把本列的对象id绑定到checkbox的value中,可以透传到后续的状态变更事件。
- 表格的onLoadSuccess事件保障表格载入完成后,对checkbox进行渲染,这步是无法在formatter中完成的。
- changeHandler方法即是在开关状态变更的事件触发时执行,具体逻辑在updateStatus(id, state)中实现,其中id是前面透传过来的本行对象在系统中的id,state是开关当前的状态(on对应true,off对应fasle)。
- 这里调用bootstrapToggle方法时也对开关样式进行了部分定制。
还差最后一步
这样开关就搞定了。用法是不是很简洁?然后此时还没最终完工,此组件还有个大坑需要填上。按照这个方式启动程序后,首次载入表格,开关可以正常工作,点击开关会切换状态并且更新到后台。但是如果点击别的页面,再回到表格页面(表格会reload一次),再点击开关切换状态就会发生异常,具体表现:点击开关一次,后台会发送n次请求,n=表格load的总次数。
那么问题就可以总结为:表格每load一次,点击开关,开关就会自动多切一次。这个问题很难查,看似是change事件出问题了,最开始以为是每次表格reload后,都会叠加绑定一次change事件,所以在on方法前加了off方法,即绑事件前先卸载,保障事件只绑一次,然而问题并没有解决,甚至把绑事件的代码注释掉依然还是这样!这就说明,这个问题与事件绑定这个操作无关,是开关在初始化时出了一些问题。没办法,硬着头皮看bootstrap-toggle.js文件里的代码,幸好代码行数很少,很快发现问题所在,原来在初始化时,bootstrap-toggle会给自己绑定一个鼠标点击事件,源码176行:
$(document).on('click.bs.toggle', 'div[data-toggle^=toggle]', function (e) {
var $checkbox = $(this).find('input[type=checkbox]')
$checkbox.bootstrapToggle('toggle')
e.preventDefault()
})
问题很明显了:绑定click事件只用了on,没有用off,所以每次都会叠加一个click事件,最后表现就是:初始化N次就绑定N次click,那么你点击一次它内部就会触发N次点击的效果。这是click事件问题,却很容易误导到往change事件的方向排查。简单修改一下代码,绑定前先增加off方法:
$(document).off('click.bs.toggle').on('click.bs.toggle', 'div[data-toggle^=toggle]', function (e) {
var $checkbox = $(this).find('input[type=checkbox]')
$checkbox.bootstrapToggle('toggle')
e.preventDefault()
})
重新编译,运行,测试,问题解决!这样我们就可以真正优雅的使用这款滑动开关了。
默默的感谢一下作者,感谢互联网共享精神,让我在2019年用到了2014年就停更的来自NewYork的这款开关组件:
Copyright 2014 Min Hur, The New York Times Company