前几个月,客户要求显示列表做到列锁定+表头锁定+列组合,但从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样式
从样式中可以看出大体的规则,也是仿照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的客户端ID5: * @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.com20: * @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: //左侧组合列HTML32: var leftGroupCols = $(options.leftGroupCols === undefined ? '' : formatHeaderHtml(options.leftGroupCols));33: //右侧组合列HTML34: 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 = $('#' + 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: //设置左侧头部HTML153: setThs(gvData_header_left, gv_header_left, true);
154: //设置右侧头部HTML155: 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('border-top-style', 'none');227: colIndex++;
228: }
229: else {
230: col_width = colsLengsArr[colIndex];
231: colIndex++;
232: }
233: $(this).width(col_width);234: });
235: }
236: calcGroupCol(leftGroupCols);
237: calcGroupCol(rightGroupCols);
238:
239: gv_header_left.find('.gv-div-table').prepend(leftGroupCols);240: gv_header_right.find('.gv-div-table').prepend(rightGroupCols);241: }
242: else
243: dataAreaHeight -= 31;244: if (dataAreaHeight > 0) {245: gv_data_left.height(dataAreaHeight - 18);246: gv_data_right.height(dataAreaHeight);247: }
248: //设置滚动事件249: $('.gv-data-right').scroll(function () {250: $('.gv-data-left').scrollTop($(this).scrollTop());251: $('.gv-header-right').scrollLeft($(this).scrollLeft());252: });
253: }
254: };
最后
虽然实现了上面的一些功能,但还是有些缺陷让人遗憾:
- 本人美工不行,样式上有些瑕疵。
- 测试过IE7、IE8、IE9与Chrome浏览器,目前是正常的,但Chrome浏览器可能有不兼容的情况出现。这点需要有时间慢慢解决。
- 不能支持超过2行以上的组合表头,不支持行合并。因为整个jqGridView均使用div组合,所以在行合并上是软肋,不知道各位朋友有什么好的方案没。
如果您对jqGridView感兴趣,或者有好的解决方案,或者对源码做了一些修正,请通知我。
最后,本人刚开了个拍拍商店,愿意捧场的朋友请点击下CodeLove1314的小店,想获取技术支持的或者想捧场的请点击下赞助,这年头,想真心编码不容易啊。
.net技术交流一群:85318032