数据表格表头与首列固定的完全实现
前几天项目经理跟我说有这么个需求:一个很大的表格,垂直滑动时表头固定,水平滑动里第一、二列固定。我一想这也太难实现了,直接回复说可能实现不了,可是没过会经理发个12306网站的一个现存的实现样子,汗!我只能说那我先调研下实现的成本吧。心里想想反正客户给钱,没有什么理由不做啊。
1. 前期调研
先打开12306网站上面的那个表格,打开浏览器调试工具,发现他们好像用的是插件(dhtmlxgrid),再看了下布局,发现它们把固定的表头和列放在一个DIV里面,心里想这样的需求肯定很常见,说不定网上早有相应插件了,于是上google搜“table header fixed ”(个人比较喜欢用英文关键字搜索信息),第一条信息就是一个基于jquery的插件叫Fixed Header Table,而且还不要钱的,于是下载下来,研究它的源码,发现它的布局与12306上面的类似也是把固定的表头和列放在一个DIV,然后在此DIV里面放table,整体布局如下图所示:
通过控制单元格的宽度和高度,使整体的单元格对齐,最后监听maindiv的滑动事件,当它滑动时通过js去滑动headerdiv与columndiv的滑动。其实里面最麻烦的应该是单元格对齐问题,因为单元格宽和高会随里面的内容变化(如果你想自适应的话)。
知道实现原理后,然后对照下我实际的项目,我项目中的表格和12306中的表格有的类似,不是普通的数据表格,里面的单元格的合并,而且我项目中的表格还有分组(grounp),截个图:
里面有编辑功能,还得在行和列分别加个total,表格够复杂吧。Fixed Header Table这个插件不支持我的表格(单元格的合并),dhtmlxgrid插件很强大,但要钱,项目中本来就有jquery了,不想再引用其它框架了,自己动手,丰衣足食!
2. 具体实现
2.1 简化需求
以前的布局就是一个table,要做这种行列锁定的表格,布局肯定大改,为了简单点,我把我表格中的单元格宽度和高度固定,超出的文字用…表示,呵呵,我还是想怎么简单怎么来,这么做客户也接受。把单元格定死后,代码量会少很多。布局尽量用CSS去控制,JS只控制滑动和初始化区域大小(因为我页面是自适应的)。
2.2 html+CSS
先画个两行两列的表格,把整体布局定好,我做布局都先做整体,再做局部。先看下整体布局的html:
<table> <thead> <tr > <th></th> <th> <div id="Headerdiv" style=" overflow: hidden"></div> </th> </tr> </thead> <tbody> <tr> <td > <div id="Columndiv" style=" overflow: hidden"></div> </td> <td> <div id="maindiv" style=" overflow: scroll" onscroll="fnScroll()"></div> </td> </tr> </tbody> </table>
里面div的主要属性和事件都已经声明,接下来做thead里的两行固定列,第一个th的源代码:
<th id="firstTd " class="tdborder"> <table cellspacing="0" cellpadding="0"> <tr> <td class="tableFirstCol txtcenter td_right td_bottom">Project</td> <td class="tableSecondCol td_bottom"> <table cellspacing="0" cellpadding="0" class="Tamount"> <tr> <td colspan="3" class="txtcenter td_bottom">Total</td> </tr> <tr> <td class="current td_right">current</td> <td class="scenario td_right">scenario</td> <td class="different">different</td> </tr> </table> </td> </tr> <tr> <td class="tableFirstCol td_right">Total</td> <td class="tableSecondCol "> <table cellpadding="0" cellspacing="0"> <tr> <td class="current td_right"><input type="text" /></td> <td class="scenario td_right"><input type="text" /</td> <td class="different"><input type="text" /</td> </tr> </table> </td> </tr> </table> </th>
然后是第二个th里面的Headerdiv的源代码:
<div id="Headerdiv" > <table cellspacing="0" cellpadding="0" width="1560" id="headertable" class="td_right" > <tr > <td class="td_left"> <table cellspacing="0" cellpadding="0" class="Tamount"> <tr> <td colspan="3" class="txtcenter td_bottom">1999</td> </tr> <tr> <td class="current td_right">current</td> <td class="scenario td_right">scenario</td> <td class="different">different</td> </tr> </table> </td> 重复TD... </tr> <tr > <td class="td_left td_top"> <table cellpadding="0" cellspacing="0"> <tr> <td class="current td_right"><input type="text" /></td> <td class="scenario td_right"><input type="text" /</td> <td class="different"><input type="text" /</td> </tr> </table> </td> 重复TD... </tr> </table> </div>
大家可能注意到里面对套了这么多table有异议,本人是为了站单元格对齐才出此下策,在整个页面的制作过程中单元格的边框对齐是最烦人的。
接下来展示固定列的html源码:
<div id="Columndiv" class="fixedcol"> <table cellspacing="0" cellpadding="0" > <tr> <td colspan="2" class="tdgroup"> group name group name group name group nam name group name group name </td> </tr> <tr> <td class="tableFirstCol">Project Name 1 </td> <td class="tableSecondCol"> <table cellpadding="0" cellspacing="0"> <tr> <td class="current td_right"><input type="text" /></td> <td class="scenario td_right"><input type="text" /</td> <td class="different"><input type="text" /</td> </tr> </table> </td> </tr>
里面class为tdgrounp的为表格的分组名,这分组名如果太长就会换行,我会在JS里面控制maindiv里对应单元格的高度,也是JS代码里唯一控制单元格的代码。
maindiv里的HTML就不粘出来了,里就就放了一个table。
3.3 javascript
接下来讲讲JS,JS 还是很简单的(基于jquery):
$(document).ready(function () { fnAdjustTable(); //先求页面给定的高度 var _h = $("#tablediv").height(); var _w = $("#tablediv").width(); //然后设定相关div的高度 var _head_h = $("#thead").height(); $("#maindiv").height(_h - _head_h); $("#Columndiv").height(_h - _head_h - 18);//18是空出了相应滚动条的距离 var _clo_w = $("#Columndiv").width(); $("#Headerdiv").width(_w - _clo_w-18 ); $("#maindiv").width(_w - _clo_w); }); function fnAdjustTable() { //调整组名的单元格高度 $('#Columndiv .tdgroup').each(function (i) { //不同浏览器这高度可能不一样,相关一两个像素 if ($.browser.msie) { $("#maindiv .tdgroup:eq(" + i + ")").height($(this).height()); } else { $("#maindiv .tdgroup:eq(" + i + ")").height($(this).height() + 1); } }); } //滑动事件 function fnScroll () { $('#Headerdiv').scrollLeft($('#maindiv').scrollLeft()); $('#Columndiv').scrollTop($('#maindiv').scrollTop()); }