前几天项目经理跟我说有这么个需求:一个很大的表格,垂直滑动时表头固定,水平滑动里第一、二列固定。我一想这也太难实现了,直接回复说可能实现不了,可是没过会经理发个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" οnscrοll="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());
}