前幾個月,客戶要求顯示列表做到列鎖定+表頭鎖定+列組合,但從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