因为项目需要对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",
width: "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-width: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>