因为项目需要对Table进行汇总以及定义列的显示隐藏,所以需要对jQueryUI的TableSorter进行扩展,下面2个插件是我写的,第一个是用显示/隐藏列(右键点击列头会出现右键菜单)。第二个是对列宽度的修正(可以忽略)。第三个是对数据进行汇总,再table footer处会显示汇总列)
Widget的代码:
; (function ($) { "use strict"; function getColumnsByIndex(table, iColIndex) { var expression = ':eq(' + iColIndex + ')'; var $table = $(table); var $items = $table.find("tr").find('td' + expression); $.merge($items, $table.find('th' + expression)); return $items; } function getColumnIndexById(table, colId) { return $(table).find("th").filter('[data-column-id="' + colId + '"]').index(); } function getColumnsById(table, colId) { var index = getColumnIndexById(table, colId); return getColumnsByIndex(table, index); } function ColumnVisibilitySettings() { this.serialize = function (objSettings) { return { version: "1", visibilities: objSettings }; }; this.deserialize = function (jsonSettings) { if (jsonSettings) { var version = jsonSettings.version; var objSettings = {}; switch (version) { case "1": return jsonSettings.visibilities; } } return undefined; }; } $.tablesorter.addWidget({ id: 'column-selector', priority: 51, // Should be called later than filter options: { selector_contextmenu: '.tablesorter-context-menu' }, m_config: null, m_widgetOptions: null, m_settingsLoaded: false, init: function (table, thisWidget, config, widgetOptions) { // widget initialization code - this is only *RUN ONCE* this.m_config = config; this.m_widgetOptions = widgetOptions; var self = this; var $table = config.$table; $(widgetOptions.selector_contextmenu).hide(); $table.children('thead').bind("contextmenu", $.proxy(this.onShowContextMenu, this)); // setup our event handler $table.on("save_column_visibility", $.proxy(this.saveColumnVisibility, this)); $table.on("show_context_menu", $.proxy(this.onShowContextMenu, this)); }, format: function (table, config, widgetOptions) { // widget code to apply to the table *AFTER EACH SORT* if (!this.m_settingsLoaded) { this.loadColumnVisibility(); this.m_settingsLoaded = true; } }, remove: function (table, config, widgetOptions) { // do what ever needs to be done to remove stuff added by your widget // unbind events, restore hidden content, etc. $table.children('thead').unbind("contextmenu", $.proxy(this.onShowContextMenu, this)); $table.off("save_column_visibility", $.proxy(this.saveColumnVisibility, this)); $table.off("show_context_menu", $.proxy(this.onShowContextMenu, this)); }, onShowContextMenu: function (event, extraEvent) { // We allow to fack the event if (extraEvent) event = extraEvent; event.preventDefault(); var self = this; var $table = this.m_config.$table; var $columnsUl = $('<ul></ul>'); var $columns = $table.find("thead>tr>th"); for (var i = 0; i < $columns.length; i++) { var $column = $($columns[i]); var columnDisplayName = $column.text(); var columnId = $column.data('column-id'); var columnIsFixed = $column.data('column-fixed'); if (!columnId) { console.log("No column id avaliable. Cannot config columns!"); return; } // Create li element var $columnLi = $('<li></li>').appendTo($columnsUl); // Create label for the checkbox var $label = $('<label for="' + columnDisplayName + '"></label>') .append($('<span>' + columnDisplayName + '</span>')) .appendTo($columnLi); // Create checkbox var $checkbox = $('<input type="checkbox" />') .attr('name', columnDisplayName) .data('column-id', columnId) .prependTo($label) .click(function () { var $clickedCheckbox = $(this); var columnId = $clickedCheckbox.data('column-id'); var $columns = getColumnsById($table, columnId); $columns.fadeToggle("fast"); self.saveColumnVisibility(); }); if (columnIsFixed) $checkbox.attr('disabled', "disabled"); // Update checkbox state if ($(":visible", $($columns[i])).length > 0) { $checkbox.attr('checked', 'checked'); } } // Show context menu this.showContextMenu($columnsUl, event); }, showContextMenu: function (contextMenuContent, event) { var contextMenu = $(this.m_widgetOptions.selector_contextmenu).hide().empty(); contextMenu.append(contextMenuContent); // Marker $('<div />') .css({ position: "fixed", left: "0px", top: "0px", "100%", height: "100%", "z-index": "100" }) .click(function () { $(this).remove(); contextMenu.hide(); }) .bind('contextmenu', function () { return false; }) .appendTo(document.body); contextMenu.css({ left: event.pageX, top: event.pageY, "z-index": 101 }).show(); }, loadColumnVisibility: function () { var $table = this.m_config.$table; var ts = $.tablesorter.storage; var jsonSettings = ts && ts(this.m_config.$table[0], "tablesorter-column-selector"); var settings = new ColumnVisibilitySettings().deserialize(jsonSettings); if (settings) { for (var col in settings) { if (settings[col] === true) getColumnsById($table, col).show(); else getColumnsById($table, col).hide(); } } }, saveColumnVisibility: function () { var $table = this.m_config.$table; var ts = $.tablesorter.storage; var settings = ts && ts(this.m_config.$table[0], "tablesorter-column-selector"); if (ts) { setTimeout(function () { var visibilities = {}; $table.find("thead>tr>th").each(function (index) { var $col = $(this); visibilities[$col.data("column-id")] = ($col.css('display') !== "none"); }); ts($table[0], "tablesorter-column-selector", new ColumnVisibilitySettings().serialize(visibilities)); }, 1000); } }, }); $.tablesorter.addWidget({ id: 'column-width-setter', priority: 81, // Should be called later than filter options: { }, m_config: null, init: function (table, thisWidget, config, widgetOptions) { this.m_config = config; this.resetColumnWidth(); }, resetColumnWidth: function () { var $columns = this.m_config.$headers; for (var i = 0; i < $columns.length; i++) { var width = parseInt($($columns[i]).attr("data-width")); if (width) { getColumnsByIndex(this.m_config.$table, i).width(width); } } } }); $.tablesorter.addWidget({ id: 'summery-row', options: { selector_summery_row: '.tablesorter-summery-row', selector_column_enable_summery: '.tablesorter-enable-summery', show_NaN: true }, init: function (table, thisWidget, config, widgetOptions) { // widget initialization code - this is only *RUN ONCE* this.m_config = config; this.m_widgetOptions = widgetOptions; var $table = config.$table; $table.bind("filterEnd", $.proxy(this.updateSummeryRow, this)); this.updateSummeryRow(); }, updateSummeryRow: function () { var self = this; var $table = this.m_config.$table; var summery = $table.find(this.m_widgetOptions.selector_summery_row) var $headers = $table.find("thead>tr>th"); $headers.each(function (index) { var $column = $(this); if ($column.filter(self.m_widgetOptions.selector_column_enable_summery).length <= 0) return; var sum = 0; var isPercentage = false; var td = summery.find('td:eq(' + index + ')'); $table.find("tbody:not(" + self.m_widgetOptions.selector_summery_row + ")>tr:visible").find("td:eq(" + index + ")").each(function (index) { var sVal = $(this).text(); if (sVal.indexOf("%") !== -1) { isPercentage = true; } var iVal = parseFloat(sVal); sum += iVal; }); if (!isNaN(sum) || self.m_widgetOptions.show_NaN) { if (isPercentage) { td.text(sum.toFixed(2) + "%"); } else { td.text(sum); } } }); } }); })(jQuery);
下面是在nodeJs的EJS模板里面的应用,支持右键点击列头以及左键点击Custom View按钮对显示的列进行定制:
js_make_table_sortable代码:
<!-- Require jQuery, jQueryUI --> <!-- Table should has id "data-table" --> <% include header_table %> <script class="code" type="text/javascript"> $(document).ready(function () { // Make table sort-able { // Extend the themes to change any of the default class names ** NEW ** $.extend($.tablesorter.themes.jui, { // change default jQuery uitheme icons - find the full list of icons here: http://jqueryui.com/themeroller/ (hover over them for their name) table: 'ui-widget ui-widget-content ui-corner-all', // table classes header: 'ui-widget-header ui-corner-all ui-state-default', // header classes footerRow: '', footerCells: '', icons: 'ui-icon', // icon class added to the <i> in the header sortNone: 'ui-icon-carat-2-n-s', sortAsc: 'ui-icon-carat-1-n', sortDesc: 'ui-icon-carat-1-s', active: 'ui-state-active', // applied when column is sorted hover: 'ui-state-hover', // hover class filterRow: '', even: 'ui-widget-content', // odd row zebra striping odd: 'ui-state-default' // even row zebra striping }); var dataTable = $("#data-table"); // call the tablesorter plugin and apply the ui theme widget dataTable.tablesorter({ theme: 'jui', headerTemplate: '{content} {icon}', widgets: ['uitheme', 'zebra', 'filter', 'column-selector', 'summery-row'], widgetOptions: { zebra: ["even", "odd"], } }); } }); </script>
EJS代码:
<% include js_make_table_sortable %> <script class="code" type="text/javascript"> $(document).ready(function () { var $table = $("#data-table"); // Different View switch buttons $(".view-button[data-filter]").button().click(function () { var filter = $(this).data('filter'); $table.find("th").each(function (index, header) { var $header = $(header); var index = $header.index(); if ($header.filter(filter).length > 0) { $table.find("tr").find("td:eq(" + index + ")").show(); $header.show(); } else { $table.find("tr").find("td:eq(" + index + ")").hide(); $header.hide(); } }); // Persist our settings $table.trigger("save_column_visibility"); }); $(".custom-view-button").button().click(function (event) { $table.trigger("show_context_menu", event); }); }); </script> <div id="table-container"> <div class="tablesorter-context-menu" style="position: absolute;"></div> <div class="switch-view-area"> <button class="view-button" data-filter=":not(false)">Full View</button> <button class="view-button" data-filter=".swd_view">SWD View</button> <button class="view-button" data-filter=".td_view">TD View</button> <button class="view-button" data-filter=".report_view">Report View</button> <button class="custom-view-button">Custom View</button> </div> <table id="data-table" class="tablesorter" style="max-1750px;"> <caption><%= title %></caption> <thead> <tr> <th class="report_view swd_view td_view" data-width="10px" data-column-id="Ranking">Ranking</th> <th class="report_view swd_view td_view" data-width="10px" data-column-id="Bucket" data-column-fixed="true">Bucket</th> <th class="report_view swd_view td_view" data-width="10px" data-column-id="Resolved">Resolved</th> <th class="td_view" data-width="50px" data-column-id="PurportedlyFixedin">Purportedly Fixed in</th> <th class="swd_view" data-width="100px" data-column-id="Notes">Notes</th> <th class="tablesorter-enable-summery report_view swd_view" data-width="10px" data-column-id="CurrentReleaseCount">Current Release Count</th> <th class="tablesorter-enable-summery report_view swd_view" data-width="10px" data-column-id="ofTotal">% of Total</th> <th class="tablesorter-enable-summery swd_view" data-width="10px" data-column-id="UniqueUsers">Unique Users</th> <th class="tablesorter-enable-summery swd_view" data-width="10px" data-column-id="NewReportsinThisWeek">New Reports in This Week</th> <th class="tablesorter-enable-summery swd_view" data-width="10px" data-column-id="TotalCount">Total Count</th> <th class="report_view swd_view td_view" data-width="10px" data-column-id="DefectId">Defect Id</th> <th class="report_view td_view" data-width="400px" data-column-id="DefectStatus">Defect Status</th> <th class="swd_view" data-width="20px" data-column-id="Module">Module</th> <th class="swd_view" data-width="20px" data-column-id="Object">Object</th> <th class="swd_view" data-width="20px" data-column-id="Function">Function</th> <th class="swd_view" data-width="20px" data-column-id="Offset">Offset</th> </tr> </thead> <tbody> <% for (var i = 0; i < items.length; ++ i) { %> <tr> <td><%= i+1 %></td> <td><%- link('http://xxxx.xxxx.com/cer/ViewBucket.aspx?bucket=' + items[i].bucketId, items[i].bucketId, "class='bucket-link' target='_blank'") %></td> <td><%= items[i].isResolved ? "Yes" : "No" %></td> <td><%= items[i].purportedlyFixedIn %></td> <td><%= items[i].note %></td> <td><%= items[i].currentReleaseCount %></td> <td><%= (items[i].totalPercent*100).toFixed(2)+"%" %></td> <td><%= items[i].uniqueUsers %></td> <td><%= items[i].newReportsInThisWeek %></td> <td><%= items[i].totalCount %></td> <td><%- splitDefectIds(items[i].defectId) %></td> <td><%- formatDefectStatus(items[i].defectStatus) %></td> <td><%= items[i].module %></td> <td><%= items[i].object %></td> <td><%= items[i].function %></td> <td><%= items[i].offset %></td> </tr> <% } %> </tbody> <tfoot> <tr class="tablesorter-summery-row"> <td>Sum</td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> </tr> </tfoot> </table> </div>