JS 组件系列之Bootstrap Table的冻结列功能彻底解决
正文
前言一年前,博主分享过一篇关于bootstrapTable组件冻结列的解决方案 ,通过该篇,确实可以实现bootstrapTable的冻结列效果,并且可以兼容ie浏览器。这一年的时间,不断有园友以及群里面的朋友问过我关于固定高度之后,冻结列页面效果不能对齐的问题,奈何博主太忙,一直没有抽空将这个问题优化。最近项目里面也不断有人提过这个bug,这下子不能再推了,必须要直面“惨淡的bug”,于是昨天利用一天的时间将原来的扩展做了一下修改,能够完美解决固定高度之后冻结列的问题,并且,博主还加了一些特性,比如右侧列的冻结、冻结列的选中等等,有需要的朋友可以捧个场。相信通过此篇,老板再也不用担心我的冻结列不能固定高度了~~
一、问题追踪
记得在之前的那篇里面介绍过,bootstrapTable组件自带的冻结列扩展,不能兼容ie浏览器,即使最新版本的ie也会无法使用,这是一般的系统不能忍受的,所以在那篇里面给出过解决方案,但并未分析ie浏览器不能兼容的原因,昨天博主花了点时间特意调试了下源码,原来在ie里面,使用jquery的clone()方法和谷歌等浏览器有所区别。为了展示这个区别,这里先抛个砖。比如有如下代码
<table id="tbtest"> <tr><td>aaa</td><td>bbb</td><td>c</td></tr> <tr><td>ddd</td><td>eee</td><td>fff</td></tr> <tr><td>ggg</td><td>hhh</td><td>iii</td></tr> </table> <script type="text/javascript"> var $tr = $('#tbtest tr:eq(0)').clone(); var $tds = $tr.find('td'); $tr.html(''); alert($tds.eq(0).html()); </script>
代码本身很简单,只是为了测试用。看到这里你可以试着猜一下alert的结果。
算了,不考大家了,直接贴出来吧,有图有真相!
相信不用我过多的解释哪个是ie,哪个是谷歌了吧。
两者的区别很明显,谷歌里面得到“aaa”,而ie里面得到空字符串。这是为什么呢?
其实如果你用值类型和引用类型的区别来解释这个差别你就不难理解了,在谷歌浏览器里面,$tr变量是一个引用类型,当你清空了它里面的内容,只是清除了$tr这个变量的“指针”,或者叫指向,$tds变量仍然指向了$tr的原始内容,所以调用$tds.eq(0).html()的时候仍然能得到结果aaa;同样的代码在ie浏览器里面,$tr变量就是一个值类型,你清空了它里面的内容之后,$tds的内容也被清空了。如果你有更好的解释,欢迎赐教哈。
之所以组件原生的js不能兼容ie浏览器,就是因为它使用了clone()这个方法,导致在不同的浏览器看到不同的结果。相信bootstrapTable组件的作者应该是知道这个区别的,只不过没有太在意这些,从作者做的很多功能的兼容性能够看出,他做的功能很多没有太多的考虑ie浏览器的效果。
二、效果预览
还是老规矩,说了这个多,没图怎么行,小二,上图!
没有固定高度的情况单列冻结。
多列冻结。
固定任意高度效果
ie浏览器也没有问题,这里就不再重复上图了。
三、源码解析
源码没啥说的,有兴趣可以自己看看,主要的原理还是重写bootstrapTable构造器的事件,来达到想要的效果。
(function ($) { 'use strict'; $.extend($.fn.bootstrapTable.defaults, { fixedColumns: false, fixedNumber: 1 }); var BootstrapTable = $.fn.bootstrapTable.Constructor, _initHeader = BootstrapTable.prototype.initHeader, _initBody = BootstrapTable.prototype.initBody, _resetView = BootstrapTable.prototype.resetView; BootstrapTable.prototype.initFixedColumns = function () { this.$fixedHeader = $([ '<div class="fixed-table-header-columns">', '<table>', '<thead></thead>', '</table>', '</div>'].join('')); this.timeoutHeaderColumns_ = 0; this.$fixedHeader.find('table').attr('class', this.$el.attr('class')); this.$fixedHeaderColumns = this.$fixedHeader.find('thead'); this.$tableHeader.before(this.$fixedHeader); this.$fixedBody = $([ '<div class="fixed-table-body-columns">', '<table>', '<tbody></tbody>', '</table>', '</div>'].join('')); this.timeoutBodyColumns_ = 0; this.$fixedBody.find('table').attr('class', this.$el.attr('class')); this.$fixedBodyColumns = this.$fixedBody.find('tbody'); this.$tableBody.before(this.$fixedBody); }; BootstrapTable.prototype.initHeader = function () { _initHeader.apply(this, Array.prototype.slice.apply(arguments)); if (!this.options.fixedColumns) { return; } this.initFixedColumns(); var that = this, $trs = this.$header.find('tr').clone(); $trs.each(function () { $(this).find('th:gt(' + (that.options.fixedNumber - 1) + ')').remove(); }); this.$fixedHeaderColumns.html('').append($trs); }; BootstrapTable.prototype.initBody = function () { _initBody.apply(this, Array.prototype.slice.apply(arguments)); if (!this.options.fixedColumns) { return; } var that = this, rowspan = 0; this.$fixedBodyColumns.html(''); this.$body.find('> tr[data-index]').each(function () { var $tr = $(this).clone(), $tds = $tr.find('td'); //$tr.html('');这样存在一个兼容性问题,在IE浏览器里面,清空tr,$tds的值也会被清空。 //$tr.html(''); var $newtr = $('<tr></tr>'); $newtr.attr('data-index', $tr.attr('data-index')); $newtr.attr('data-uniqueid', $tr.attr('data-uniqueid')); var end = that.options.fixedNumber; if (rowspan > 0) { --end; --rowspan; } for (var i = 0; i < end; i++) { $newtr.append($tds.eq(i).clone()); } that.$fixedBodyColumns.append($newtr); if ($tds.eq(0).attr('rowspan')) { rowspan = $tds.eq(0).attr('rowspan') - 1; } }); }; BootstrapTable.prototype.resetView = function () { _resetView.apply(this, Array.prototype.slice.apply(arguments)); if (!this.options.fixedColumns) { return; } clearTimeout(this.timeoutHeaderColumns_); this.timeoutHeaderColumns_ = setTimeout($.proxy(this.fitHeaderColumns, this), this.$el.is(':hidden') ? 100 : 0); clearTimeout(this.timeoutBodyColumns_); this.timeoutBodyColumns_ = setTimeout($.proxy(this.fitBodyColumns, this), this.$el.is(':hidden') ? 100 : 0); }; BootstrapTable.prototype.fitHeaderColumns = function () { var that = this, visibleFields = this.getVisibleFields(), headerWidth = 0; this.$body.find('tr:first-child:not(.no-records-found) > ').each(function (i) { var $this = $(this), index = i; if (i >= that.options.fixedNumber) { return false; } if (that.options.detailView && !that.options.cardView) { index = i - 1; } that.$fixedHeader.find('th[data-field="' + visibleFields[index] + '"]') .find('.fht-cell').width($this.innerWidth()); headerWidth += $this.outerWidth(); }); this.$fixedHeader.width(headerWidth).show(); }; BootstrapTable.prototype.fitBodyColumns = function () { var that = this, = -(parseInt(this.$el.css('margin-'))), // the fixed height should reduce the scorll-x height height = this.$tableBody.height() - 18; debugger; if (!this.$body.find('> tr[data-index]').length) { this.$fixedBody.hide(); return; } if (!this.options.height) { = this.$fixedHeader.height()- 1; height = height - ; } this.$fixedBody.css({ width: this.$fixedHeader.width(), height: height, : + 1 }).show(); this.$body.find('> tr').each(function (i) { that.$fixedBody.find('tr:eq(' + i + ')').height($(this).height() - 0.5); var thattds = this; debugger; that.$fixedBody.find('tr:eq(' + i + ')').find('td').each(function (j) { $(this).width($($(thattds).find('td')[j]).width() + 1); }); }); // events this.$tableBody.on('scroll', function () { that.$fixedBody.find('table').css('', -$(this).scrollTop()); }); this.$body.find('> tr[data-index]').off('hover').hover(function () { var index = $(this).data('index'); that.$fixedBody.find('tr[data-index="' + index + '"]').addClass('hover'); }, function () { var index = $(this).data('index'); that.$fixedBody.find('tr[data-index="' + index + '"]').removeClass('hover'); }); this.$fixedBody.find('tr[data-index]').off('hover').hover(function () { var index = $(this).data('index'); that.$body.find('tr[data-index="' + index + '"]').addClass('hover'); }, function () { var index = $(this).data('index'); that.$body.find('> tr[data-index="' + index + '"]').removeClass('hover'); }); }; })(jQuery);
.fixed-table-header-columns, .fixed-table-body-columns { position: absolute; background-color: #fff; display: none; box-sizing: border-box; overflow: hidden; } .fixed-table-header-columns .table, .fixed-table-body-columns .table { border-right: 1px solid #ddd; } .fixed-table-header-columns .table.table-no-bordered, .fixed-table-body-columns .table.table-no-bordered { border-right: 1px solid transparent; } .fixed-table-body-columns table { position: absolute; animation: none; } .bootstrap-table .table-hover > tbody > tr.hover > td { background-color: #f5f5f5; }
如何使用呢?这里博主单独搞了一个静态的html测试页,还是贴出来供大家参考。
<!DOCTYPE html> <html lang="en" xmlns="http://.w3./1999/xhtml"> <head> <meta charset="utf-8" /> <title></title> <!--必须的css引用--> <link href="Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" /> <link href="Content/bootstrap-table/bootstrap-table.min.css" rel="stylesheet" /> <link href="Content/bootstrap-table/extensions/fixed-column/bootstrap-table-fixed-columns.css" rel="stylesheet" /> </head> <body> <div class="panel-body" style="padding-bottom:0px;"> <!--<div class="panel panel-default"> <div class="panel-heading">查询条件</div> <div class="panel-body"> <form id="formSearch" class="form-horizontal"> <div class="form-group" style="margin-:15px"> <label class="control-label col-sm-1" for="name">员工姓名</label> <div class="col-sm-3"> <input type="text" class="form-control" id="name"> </div> <label class="control-label col-sm-1" for="address">家庭住址</label> <div class="col-sm-3"> <input type="text" class="form-control" id="address"> </div> <div class="col-sm-4" style="text-align:left;"> <button type="button" style="margin-left:50px" id="btn_query" class="btn btn-primary">查询</button> </div> </div> </form> </div> </div>--> <div id="toolbar" class="btn-group"> <button id="btn_add" type="button" class="btn btn-suess"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增 </button> </div> <table id="tb_user"></table> </div> <!--新增或者编辑的弹出框--> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="myModalLabel">操作</h4> </div> <div class="modal-body"> <div class="row" style="padding:10px;"> <label class="control-label col-xs-2">姓名</label> <div class="col-xs-10"> <input type="text" name="Name" class="form-control" placeholder="姓名"> </div> </div> <div class="row" style="padding:10px;"> <label class="control-label col-xs-2">年龄</label> <div class="col-xs-10"> <input type="text" name="Age" class="form-control" placeholder="年龄"> </div> </div> <div class="row" style="padding:10px;"> <label class="control-label col-xs-2">学校</label> <div class="col-xs-10"> <input type="text" name="School" class="form-control" placeholder="学校"> </div> </div> <div class="row" style="padding:10px;"> <label class="control-label col-xs-2">家庭住址</label> <div class="col-xs-10"> <input type="text" name="Address" class="form-control" placeholder="学校"> </div> </div> <div class="row" style="padding:10px;"> <label class="control-label col-xs-2">备注</label> <div class="col-xs-10"> <textarea class="form-control" placeholder="备注" name="Remark"></textarea> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span>关闭</button> <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>保存</button> </div> </div> </div> </div> <!--必须的js文件--> <script src="Content/jquery-1.9.1.min.js"></script> <script src="Content/bootstrap/js/bootstrap.min.js"></script> <script src="Content/bootstrap-table/bootstrap-table.min.js"></script> <script src="Content/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script> <script src="Content/bootstrap-table/extensions/fixed-column/bootstrap-table-fixed-columns.js"></script> <script type="text/javascript"> //页面加载完成之后 var data = [ { Id: 1, Name: 'Jim', Age: 30, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 2, Name: 'Kate', Age: 30, School: '光明小学', Address: '深圳市', Remark: 'My Name is Jim Green' }, { Id: 3, Name: 'Lucy', Age: 30, School: '光明小学', Address: '广州天河机场', Remark: 'My Name is Jim Green' }, { Id: 4, Name: 'Lilei', Age: 30, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 5, Name: 'Lintao', Age: 30, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 6, Name: 'Lily', Age: 30, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 7, Name: 'Hanmeimei', Age: 30, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 8, Name: '张三', Age: 46, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 9, Name: '李四', Age: 23, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 10, Name: '王五', Age: 33, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 11, Name: '赵六', Age: 22, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 12, Name: 'Polly', Age: 300, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, { Id: 13, Name: 'Uncle', Age: 50, School: '光明小学', Address: '北京市光明小学旁', Remark: 'My Name is Jim Green' }, ]; var childData = [ { SourceField: 'A', BackField: 'BB' }, { SourceField: 'CC', BackField: 'UU' }, { SourceField: 'DD', BackField: 'J' }, ]; $(function () { //表格的初始化 $('#tb_user').bootstrapTable({ data: data, //直接从本地数据初始化表格 method: 'get', //请求方式() toolbar: '#toolbar', //工具按钮用哪个容器 striped: true, //是否显示行间隔色 cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性() pagination: true, //是否显示分页() sortable: false, //是否启用排序 sortOrder: "asc", //排序方式 queryParams: function (params) { return params; }, //传递参数() sidePagination: "client", //分页方式client客户端分页,server服务端分页() pageNumber: 1, //初始化加载第一页,默认第一页 pageSize: 5, //每页的记录行数() pageList: [10, 25, 50, 100], //可供选择的每页的行数() search: true, //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,所以,个人感觉意义不大 strictSearch: true, showColumns: true, //是否显示所有的列 showRefresh: true, //是否显示刷新按钮 minimumCountColumns: 2, //最少允许的列数 height:400, selectItemName: 'parentItem', fixedColumns: true, fixedNumber: 6, //注册加载子表的事件。注意下这里的三个参数! onExpandRow: function (index, row, $detail) { InitSubTable(index, row, $detail); }, columns: [{ checkbox: true }, { field: 'Name', title: '姓名', width:200 }, { field: 'Age', title: '年龄', width:200 }, { field: 'School', title: '毕业院校', width:200 }, { field: 'Address', title: '家庭住址', width:100 }, { field: 'Remark', title: '备注', width:100 }, { field: 'Remark', title: '备注', width:100 }, { field: 'Remark', title: '备注', width:100 }, { field: 'Remark', title: '备注', width:100 }, { field: 'Remark', title: '备注', width:100 }, { field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ field: 'Remark', title: '备注', width:100 },{ title: '操作', width:200, formatter: function (value, row, index) {//这里的三个参数value表示当前行当前列的值;row表示当前行的数据;index表示当前行的索引(从0开始)。 var html = '<button type="button" onclick="editModel('+row.Id+')" class="btn btn-primary"><span class="glyphicon glyphicon-pencil" aria- hidden="true" ></span >编辑</button > ' + '<button type="button" onclick="deleteModel(' + row.Id + ')" class="btn btn-danger"><span class="glyphicon glyphicon-remove" aria- hidden="true" ></span >删除</button >'; return html; } }], onEditableSave: function (field, row, oldValue, $el) { alert("更新保存事件,原始值为" + oldValue); //$.ajax({ // type: "post", // url: "/Editable/Edit", // data: row, // dataType: 'JSON', // suess: function (data, status) { // if (status == "suess") { // alert('提交数据成功'); // } // }, // error: function () { // alert('编辑失败'); // }, // plete: function () { // } /
编程语言
- 如何快速学会编程 如何快速学会ug编程
- 免费学编程的app 推荐12个免费学编程的好网站
- 电脑怎么编程:电脑怎么编程网咯游戏菜单图标
- 如何写代码新手教学 如何写代码新手教学手机
- 基础编程入门教程视频 基础编程入门教程视频华
- 编程演示:编程演示浦丰投针过程
- 乐高编程加盟 乐高积木编程加盟
- 跟我学plc编程 plc编程自学入门视频教程
- ug编程成航林总 ug编程实战视频
- 孩子学编程的好处和坏处
- 初学者学编程该从哪里开始 新手学编程从哪里入
- 慢走丝编程 慢走丝编程难学吗
- 国内十强少儿编程机构 中国少儿编程机构十强有
- 成人计算机速成培训班 成人计算机速成培训班办
- 孩子学编程网上课程哪家好 儿童学编程比较好的
- 代码编程教学入门软件 代码编程教程