前几个月,客户要求显示列表做到列锁定+表头锁定+列组合,但从Extjs到Jquery EasyUi,从Jquery Grid到Telerik等等组件,发现无一符合条件,要么只有列锁定,要么只有列组合,当两者结合就不行了。于是只好开始自己琢磨了,然后就有了jqGridView。
设计思路
在开始之前,总得理下思路。我CSS不行,JS一般,但是我有思路,先看看下面两个图:
从上图中可以看出,毫无疑问的,我们需要将一个列表切成4块——锁定列表头、锁定列数据行、非锁定列表头、非锁定列数据行。如图:
其中,锁定列表头、锁定列数据列、非锁定列表头均无滚动条,滚动条全在非锁定列数据列,但拖动右侧滚动条,需要联动锁定列数据行,但拖动底部滚动条,需联动非锁定列表头。
在我认为,磨刀不误砍柴工。好的想法、好的设计才能更大可能性的走向成功。
接下来,是细节的实现了:
选择什么编程语言呢?好像还没写过Jquery插件,那么就用这个来练练手吧。我对开发新东西或者实现自己的想法或者有兴趣却不熟悉的编码特别来劲。
选择什么方式呢?开始,毫无意外的想到使用Table来组合,于是坑次坑次的开始了。当编码完成后,发现一个棘手的问题——模块之间无法对齐。即使设置了每个单元格宽度以及表格宽度也不行,请了美工辅助也不行,于是放弃了。有意向的朋友可以试试。table不行,那就试试Div吧,人总不能在一条路上走死吧。于是又坑次坑次的开始了,终于修改N次后完成了。
样例
首先举几个例子来说明如何使用:
简单单行表头
<script type="text/javascript"> $(function() { $.jqGridView('<%=gvData.ClientID %>', { lockColumns: 3, isRemoveGridView: true, rowStyle: 'gv-tr-rowStyle', alternatingRowStyle: 'gv-tr-alternatingRowStyle', hoverRowStyle: 'gv-tr-hoverRowStyle' }); }); </script>
合并列
<script type="text/javascript"> $(function () { $.jqGridView('<%=gvData.ClientID %>', { lockColumns: 3, isRemoveGridView: true, rowStyle: 'gv-tr-rowStyle', alternatingRowStyle: 'gv-tr-alternatingRowStyle', hoverRowStyle: 'gv-tr-hoverRowStyle', leftGroupCols: '<tr><th rowspan="2" colspan="3">单位</th></tr>', rightGroupCols: '<tr><th scope="col" colspan="3" style="text-align:center">待定</th><th scope="col" colspan="3" style="text-align:center">1级</th><th scope="col" colspan="3" style="text-align:center">2级</th><th scope="col" colspan="3" style="text-align:center">3级</th><th scope="col" colspan="3" style="text-align:center">4级</th><th scope="col" colspan="3" style="text-align:center">5级</th><th scope="col" colspan="3" style="text-align:center">6级</th><th scope="col" colspan="3" style="text-align:center">7级</th><th scope="col" colspan="3" style="text-align:center">8级</th><th scope="col" colspan="3" style="text-align:center">9级</th><th scope="col" colspan="3" style="text-align:center">10级</th><th scope="col" colspan="3" style="text-align:center">11级</th><th scope="col" colspan="3" style="text-align:center">12级</th><th scope="col" colspan="3" style="text-align:center">13级</th><th scope="col" colspan="3" style="text-align:center">14级</th><th scope="col" colspan="3" style="text-align:center">15级</th><th scope="col" colspan="2" style="text-align:center">16级</th></tr>' }); }); </script>
行点击事件
<script type="text/javascript"> $(function () { $.jqGridView('<%=gvData.ClientID %>', { lockColumns: 3, isRemoveGridView: true, rowStyle: 'gv-tr-rowStyle', alternatingRowStyle: 'gv-tr-alternatingRowStyle', hoverRowStyle: 'gv-tr-hoverRowStyle', leftGroupCols: '<tr><th rowspan="2" colspan="3">单位</th></tr>', rightGroupCols: '<tr><th scope="col" colspan="3" style="text-align:center">待定</th><th scope="col" colspan="3" style="text-align:center">1级</th><th scope="col" colspan="3" style="text-align:center">2级</th><th scope="col" colspan="3" style="text-align:center">3级</th><th scope="col" colspan="3" style="text-align:center">4级</th><th scope="col" colspan="3" style="text-align:center">5级</th><th scope="col" colspan="3" style="text-align:center">6级</th><th scope="col" colspan="3" style="text-align:center">7级</th><th scope="col" colspan="3" style="text-align:center">8级</th><th scope="col" colspan="3" style="text-align:center">9级</th><th scope="col" colspan="3" style="text-align:center">10级</th><th scope="col" colspan="3" style="text-align:center">11级</th><th scope="col" colspan="3" style="text-align:center">12级</th><th scope="col" colspan="3" style="text-align:center">13级</th><th scope="col" colspan="3" style="text-align:center">14级</th><th scope="col" colspan="3" style="text-align:center">15级</th><th scope="col" colspan="2" style="text-align:center">16级</th></tr>', rowClick: function (e) { var tds = e.data.tds; var rowIndex = e.data.rIndex; var isLeft = e.data.isLeft; alert("列1行" + rowIndex + "的值为:" + tds.eq(0).text() + ",您点击的是" + (isLeft ? "锁定列" : "非锁定列")); } }); }); </script>
样式
既然决定使用div,那么样式少不了。我倾向于外观方面的控制全放在样式里面,于是定义了以下样式:
/*jqGridView Style */.gv-dataContent{ width: 750px; height: 320px; margin: 5px auto; text-align:left;}.gv-dataContent th, tr{ white-space: nowrap;}.gv-dataContent td, th{ padding: 0 0 0 0; margin: 0 0 0 0; white-space: nowrap;}.gv-header-left{ width: auto; overflow: hidden; float: left; border-left: 1px solid #539ebb; border-collapse: collapse;}.gv-header-right{ overflow: hidden; border-collapse: collapse;}.gv-data-left{ width: auto; overflow: hidden; float: left; border-left: 1px solid #539ebb; border-bottom: 1px solid #539ebb; border-collapse: collapse;}.gv-data-right{ overflow: scroll; border-collapse: collapse;}.gv-div-table{ border-collapse: collapse; width: auto;}.gv-div-tr{ clear: both; height: 30px; overflow: hidden;}.gv-div-th, .gv-div-td{ height: 30px; float: left; border: 1px solid white; margin: 0px 0 0 -1px; text-align: center; vertical-align: middle; line-height: 30px; border-collapse: collapse; float: left; color: #3274a4;}.gv-div-th{ background-color: #86A9D2; font-weight: bold; border: 1px solid white;}.gv-tr-rowStyle{ background-color: #8ecbe3;}.gv-tr-alternatingRowStyle{ background-color: #d3ebf4;}.gv-tr-hoverRowStyle{ background-color: #6786d6; cursor:pointer;}/*GridView 样式*/.HeaderStyle{ background-color:#86A9D2; font-weight:bold; height:26px; white-space: nowrap; text-align:center; border: 1px solid white; color: #3274a4;}.RowStyle{ background-color: #8ecbe3; height:26px; text-align:center; white-space: nowrap; border: 1px solid white; color: #3274a4;}.AlternatingRowStyle{ background-color: #d3ebf4; height:26px; white-space: nowrap; border: 1px solid white; color: #3274a4; text-align: center;}.datacontent{ font-size:12px; font-family:微软雅黑; width:740px; border:1px solid #7C9BBF; margin:0 auto; clear: both; text-align: center; *text-align: center; margin-left: 5px; padding:5px; width:760px; overflow:auto; }
从样式中可以看出大体的规则,也是仿照table元素来的。table、tr、th、td等等。其中有几个重要样式要注意了:
- .gv-dataContent 整个jqGridView的整体样式
- .gv-header-left 左侧表头区域
- .gv-header-right 右侧表头区域
- .gv-data-left 左侧数据区域
- .gv-data-right 右侧数据区域
- .gv-div-table 表示一个表格,每个区域均存在
- .gv-div-tr 表示行样式
- .gv-div-th 表示表头单元格样式
- .gv-div-td 表示单元格样式
- .gv-tr-rowStyle 表示奇数行样式
- .gv-tr-alternatingRowStyle 表示偶数行样式
- .gv-tr-hoverRowStyle 表示悬浮行样式
编码
先贴上代码:
1: /**
2: * 本插件用于实现GridView的列锁定和表头锁定,以及表头组合
3: * @example $.jqGridView('<%=gvData.ClientID %>', { lockColumns: 3 });
4: * @param String gridViewClientID GridView的客户端ID
5: * @option Number lockColumns 锁定的列数。如果包含合并表头,请与合并表头的列数一致。
6: * @option String leftGroupCols 左侧合并列的HTML,不设置则默认为单行表头。
7: * @option String rightGroupCols 右侧合并列的HTML,不设置则默认为单行表头。
8: * @option String removeLeftHeaderStrBySplit 根据分隔符移除左侧头部字符。
9: * @option String removeRightHeaderStrBySplit 根据分隔符移除右侧头部字符。
10: * @option String rowStyle 偶数行样式。
11: * @option String alternatingRowStyle 奇数行样式。
12: * @option String hoverRowStyle 悬浮行样式。
13: * @option Bool isRemoveEmptyAndZeroCols 是否移除空列或者0列。
14: * @option Bool isHideGridView 是否在处理后隐藏GridView。
15: * @option Bool isRemoveGridView 是否在处理后移除GridView。
16: * @option String emptyMessage 没有数据时显示的内容,默认为“没有数据。”。
17: * @option event rowClick 行单击事件。
18: * @author 雪雁
19: * @email codelove1314@foxmail.com
20: * @webSite http://www.cnblogs.com/codelove/
21: */
22: jQuery.jqGridView = function (gridViewClientID, options) {
23: if (gridViewClientID !== undefined && options !== undefined) {
24: function formatHeaderHtml(html) {
25: return html.replace(/\<tr/g, '<div class="gv-div-tr" ').replace(/<\/tr>/g, '</div>')
26: .replace(/\<th/g, '<div class="gv-div-th" ').replace(/<\/th>/g, '</div>')
27: .replace(/\<td/g, '<div class="gv-div-th" ').replace(/<\/td>/g, '</div>');
28: }
29: //锁定的列数
30: var lockColumns = options.lockColumns === undefined ? 1 : options.lockColumns;
31: //左侧组合列HTML
32: var leftGroupCols = $(options.leftGroupCols === undefined ? '' : formatHeaderHtml(options.leftGroupCols));
33: //右侧组合列HTML
34: var rightGroupCols = $(options.rightGroupCols === undefined ? '' : formatHeaderHtml(options.rightGroupCols));
35: //根据分隔符移除左侧头部字符
36: var removeLeftHeaderStrBySplit = options.removeLeftHeaderStrBySplit === undefined ? '' : options.removeLeftHeaderStrBySplit;
37: var removeRightHeaderStrBySplit = options.removeRightHeaderStrBySplit === undefined ? '' : options.removeRightHeaderStrBySplit;
38: //偶数行样式
39: var rowStyle = options.rowStyle === undefined ? '' : options.rowStyle;
40: //奇数行样式
41: var alternatingRowStyle = options.alternatingRowStyle === undefined ? '' : options.alternatingRowStyle;
42: //鼠标悬浮行样式
43: var hoverRowStyle = options.hoverRowStyle === undefined ? '' : options.hoverRowStyle;
44: var isSafari = $.browser.safari;
45: //数据空显示内容
46: var emptyMessage = options.emptyMessage === undefined ? '没有数据。' : options.emptyMessage;
47: var gvData = http://www.cnblogs.com/codelove/archive/2012/07/29/$('#' + gridViewClientID);
48: if (!gvData || gvData.length == 0) {
49: console.error("GridView不存在,请检查!!!", gridViewClientID, options);
50: return;
51: }
52: //是否移除空列或者0列
53: if (options.isRemoveEmptyAndZeroCols !== undefined && options.isRemoveEmptyAndZeroCols) {
54: var arr_remove = new Array(gvData.find('tr:eq(0) th').length);
55: var rowsCount = gvData.find('tr:gt(0)').each(function (rIndex) {
56: var tr = $(this);
57: tr.find('td').each(function (cIndex) {
58: if (arr_remove[cIndex] === undefined || arr_remove[cIndex] == null)
59: arr_remove[cIndex] = 0;
60: var val = $(this).text().replace(/(^\s*)|(\s*$)/g, "");
61: if (val == '' || val == 0) {
62: arr_remove[cIndex]++;
63: }
64: });
65: }).length;
66: gvData.find('tr').each(function (rIndex) {
67: var tr = $(this);
68: tr.find('td,th').each(function (cIndex) {
69: if (arr_remove[cIndex] == rowsCount)
70: $(this).remove();
71: });
72: });
73: }
74: var leftCols = lockColumns - 1;
75: var rightCols = lockColumns;
76: var isRemoveGridView = options.isRemoveGridView === undefined ? true : options.isRemoveGridView;
77: //所有列宽
78: var colsLengsArr = new Array();
79: var colsCount = gvData.find('tr:eq(0) th').each(function (i) {
80: colsLengsArr[i] = ($(this).outerWidth() + 1);
81: }).length;
82: if (lockColumns >= colsCount) lockColumns = colsCount;
83: //左侧table宽度
84: var leftTableWidth = 1;
85: //右侧table宽度
86: var rightTableWidth = 1;
87: for (var i = 0; i < lockColumns; i++) {
88: leftTableWidth += (colsLengsArr[i] + 1);
89: if (isSafari) leftTableWidth += 0.3;
90: }
91: for (var i = lockColumns; i < colsLengsArr.length; i++) {
92: rightTableWidth += (colsLengsArr[i] + 1);
93: if (isSafari) rightTableWidth += 0.3;
94: }
95:
96: gvData.parent().prepend('<div class="gv-dataContent"></div>');
97: var gv_dataContent = $('.gv-dataContent');
98: if (gvData.find('tr').length <= 1) {
99: gv_dataContent.prepend('<div class="gv-empty">' + emptyMessage + '</div>');
100: return;
101: }
102: //右侧区域宽度
103: var rightAreaWidth = gv_dataContent.width() - (leftTableWidth + 25);
104: //数据区域高度
105: var dataAreaHeight = gv_dataContent.height();
106:
107: gv_dataContent.prepend('<div class="gv-header-left"></div><div class="gv-header-right"></div><div class="gv-data-left"></div><div class="gv-data-right"></div>');
108: var gv_header_left = gv_dataContent.find('div.gv-header-left');
109: var gv_header_right = gv_dataContent.find('div.gv-header-right');
110: var gv_data_left = gv_dataContent.find('div.gv-data-left');
111: var gv_data_right = gv_dataContent.find('div.gv-data-right');
112: if (lockColumns == colsCount) {
113: gv_header_right.hide(); gv_data_right.hide();
114: } else {
115: if (rightAreaWidth > 0) {
116: gv_header_right.width(rightAreaWidth);
117: gv_data_right.width(rightAreaWidth + 18);
118: }
119: }
120: var gvData_header_left = gvData.clone();
121: gvData_header_left.find('tr:gt(0)').remove();
122:
123: var gvData_header_right = gvData_header_left.clone();
124: gv_header_right.find('tr th').remove();
125:
126: gv_data_right.find('tr:eq(0)').prepend(gvData_header_left.find('th:gt(' + lockColumns + ')').clone());
127: gvData_header_right.find('th:lt(' + rightCols + ')').remove();
128: gvData_header_left.find('th:gt(' + leftCols + ')').remove();
129: var colIndex = 0;
130:
131: function setThs(jqTr, jqHeader, isLeft) {
132: // var trHtml = '<div class="gv-div-table" style="width:' + (isLeft ? leftTableWidth : rightTableWidth) + 'px;"><div class="gv-div-tr">';
133: var trHtml = '<div class="gv-div-table" style="width:' + (isLeft ? 'auto' : (rightTableWidth + 'px;')) + '"><div class="gv-div-tr">';
134: jqTr.find('th').each(function (j) {
135: trHtml += '<div class="gv-div-th" style="width:' + colsLengsArr[colIndex] + 'px;">';
136: if (removeLeftHeaderStrBySplit != '') {
137: var splitStrs = $(this).text().split(removeLeftHeaderStrBySplit);
138: trHtml += splitStrs.length > 1 ? splitStrs[1] : splitStrs[0];
139: } else if (removeRightHeaderStrBySplit != '') {
140: var splitStrs = $(this).text().split(removeRightHeaderStrBySplit);
141: trHtml += splitStrs[0];
142: }
143: else
144: trHtml += $(this).html();
145: trHtml += '</div>';
146: colIndex++;
147: });
148: trHtml += '</div></div>';
149: jqHeader.prepend(trHtml);
150: }
151:
152: //设置左侧头部HTML
153: setThs(gvData_header_left, gv_header_left, true);
154: //设置右侧头部HTML
155: setThs(gvData_header_right, gv_header_right, false);
156: // var gvData_Data_left = $('<div class="gv-div-table" style="width:' + leftTableWidth + 'px;"></div>');
157: var gvData_Data_left = $('<div class="gv-div-table" style="width:auto;"></div>');
158: var gvData_Data_right = $('<div class="gv-div-table" style="width:' + rightTableWidth + 'px;"></div>');
159: gvData.find("tr:gt(0)").each(function (i) {
160: var tr = $(this);
161: var trLeft = tr.clone();
162: var trRight = tr.clone();
163: trLeft.find('td:gt(' + leftCols + ')').remove();
164: trRight.find('td:lt(' + rightCols + ')').remove();
165: colIndex = 0;
166: function setTrTds(tr_left, gvData_Data_left, tr_right, gvData_Data_right, trInfo) {
167: var trLeftHtml = '<div class="gv-div-tr';
168: if (rowStyle != '' && i % 2 == 0)
169: trLeftHtml += ' ' + rowStyle;
170: else if (alternatingRowStyle != '' && i % 2 == 1)
171: trLeftHtml += ' ' + alternatingRowStyle;
172: trLeftHtml += '">';
173: var trRightHtml = trLeftHtml;
174: tr_left.find('td').each(function (j) {
175: trLeftHtml += '<div class="gv-div-td" style="width:' + colsLengsArr[colIndex] + 'px;">' + $(this).html() + '</div>';
176: colIndex++;
177: });
178: tr_right.find('td').each(function (j) {
179: trRightHtml += '<div class="gv-div-td" style="width:' + colsLengsArr[colIndex] + 'px;">' + $(this).html() + '</div>';
180: colIndex++;
181: });
182: trLeftHtml += '</div>'; trRightHtml += '</div>';
183: var jqLeftTrHrml = $(trLeftHtml); var jqRightTrHrml = $(trRightHtml);
184: if (options.rowClick !== undefined) {
185: jqLeftTrHrml.bind('click', { tds: trInfo.find('td'), rIndex: i, isLeft: true }, options.rowClick);
186: jqRightTrHrml.bind('click', { tds: trInfo.find('td'), rIndex: i, isLeft: false }, options.rowClick);
187: }
188: if (hoverRowStyle != '') {
189: jqLeftTrHrml.hover(function () { jqLeftTrHrml.addClass(hoverRowStyle); jqRightTrHrml.addClass(hoverRowStyle); }, function () { jqLeftTrHrml.removeClass(hoverRowStyle); jqRightTrHrml.removeClass(hoverRowStyle); });
190: jqRightTrHrml.hover(function () { jqLeftTrHrml.addClass(hoverRowStyle); jqRightTrHrml.addClass(hoverRowStyle); }, function () { jqLeftTrHrml.removeClass(hoverRowStyle); jqRightTrHrml.removeClass(hoverRowStyle); });
191: }
192: gvData_Data_left.append(jqLeftTrHrml);
193: gvData_Data_right.append(jqRightTrHrml);
194: }
195: setTrTds(trLeft, gvData_Data_left, trRight, gvData_Data_right, tr);
196: });
197: gv_data_left.prepend(gvData_Data_left);
198: gv_data_right.prepend(gvData_Data_right);
199: if (options.isHideGridView !== undefined && options.isHideGridView)
200: gvData.hide();
201: if (isRemoveGridView)
202: gvData.remove();
203: if (leftGroupCols != '' && rightGroupCols != '') {
204: dataAreaHeight -= 62;
205: colIndex = 0;
206: function calcGroupCol(groupCols) {
207: var groupThs = groupCols.find('.gv-div-th');
208: groupThs.each(function (i) {
209: var col_width = 0;
210: if ($(this).attr('colspan') !== undefined) {
211: var colSpan = parseInt($(this).attr('colspan'));
212: for (var i = 0; i < colSpan; i++) {
213: col_width += colsLengsArr[colIndex];
214: colIndex++;
215: }
216: col_width += (colSpan - 1);
217: }
218: else if ($(this).attr('rowspan') !== undefined) {
219: var rowspan = parseInt($(this).attr('rowspan'));
220: col_height = rowspan * 30 + (rowspan - 1);
221: $(this).height(col_height).css('border-bottom-style', 'none');
222: col_width = colsLengsArr[colIndex];
223: if (colIndex <= leftCols)
224: gv_header_left.find('.gv-div-th').eq(colIndex).html('').css('border-top-style', 'none');
225: else if (colIndex >= rightCols)
226: gv_header_right.find('.gv-div-th').eq(colIndex - lockColumns).html('').css('