/* * SortableTable.js: sortable HTML table class * authors: Adam Cath (acath) and Dave Pacheco (dap) * (both email addresses @cs.brown.edu) * * Copyright 2006 by the Mocha team. All rights reserved. * * Email authors for permission to use. With enough (any?) demand, we might * release this under a BSD license. */ var UP_ARROW_IMAGE = "up.gif" var DOWN_ARROW_IMAGE = "down.gif" /* * Simple comparator converting the Javascript boolean '<' to something * returning {-1,0,1} */ function SortableTable_lessThanComparator(a,b) { if (a < b) return -1; if (b < a) return 1; return 0; } /* * Create a sortable table with the specified columns. * @param columns an array of objects with these members: * - headerNode: DOM node * The node displayed at the top of the column * - comparator: (cell datum, cell datum) -> {-1,0,1} * Compares the data of two cells in this column - follows * standard Java compareTo semantics. If null, then the table * cannot be sorted by this column. * - renderer: (cell datum) -> DOM node * For the data of a given cell in this column, return a * DOM object to be displayed as the cell. */ function SortableTable(columns) { this.rows = []; this.columns = columns; this.table = document.createElement('table'); this.tbody = document.createElement('tbody'); this.thead = document.createElement('thead'); this.table.appendChild(this.thead); this.table.appendChild(this.tbody); this.imgs = new Array(columns.length); var headerRow = document.createElement('tr'); this.thead.appendChild(headerRow); for (var ii = 0; ii < columns.length; ii++) { /* * We make a copy here because otherwise the client can't reuse this * column specification. */ var cell = columns[ii].headerNode.cloneNode(true); if (columns[ii].comparator != null) { cell.style.cursor = "pointer"; /* This is the only way I know how to create a proper closure in JS. */ var listenerGenerator = function (object, column) { return function (_) { if (column == object.currentSortColumn) { object.imgs[column].src = object.currentSortAscending ? "images/"+DOWN_ARROW_IMAGE : "images/"+UP_ARROW_IMAGE; object.sort(column, !object.currentSortAscending, false); } else { /* we're changing columns */ object.imgs[object.currentSortColumn].style.display = "none"; object.imgs[column].style.display = "inline"; object.imgs[column].src = "images/"+UP_ARROW_IMAGE; object.sort(column, true, false); } } } Util_addEvent(cell, 'click', listenerGenerator(this, ii)); /* add up/down arrows */ this.imgs[ii] = document.createElement('img'); this.imgs[ii].style.display = "none"; this.imgs[ii].src = "images/"+UP_ARROW_IMAGE; cell.appendChild(this.imgs[ii]); } headerRow.appendChild(cell); } this.currentSortColumn = 0; this.currentSortAscending = true; this.bodyRowCssClass = ''; this.imgs[this.currentSortColumn].style.display = "inline"; } /* * @return the number of data rows in this table */ SortableTable.prototype.getNumRows = function () { return this.rows.length; } SortableTable.prototype.getDomNode = function() { return this.table; } /* * @param name the CSS class that you want header cells to have */ SortableTable.prototype.setHeaderCellCssClass = function(name) { for (var ii = 0; ii < this.columns.length; ++ii) { this.thead.rows[0].cells[ii].className = name; } } /* * @param name the CSS class that you want header rows to have */ SortableTable.prototype.setHeaderRowCssClass = function(name) { this.thead.rows[0].className = name; } /* * @param name the CSS class that you want body rows to have */ SortableTable.prototype.setBodyRowCssClass = function(name) { this.bodyRowCssClass = name; this.rerender(); } /* * @param name the CSS class that you want the whole table to have */ SortableTable.prototype.setTableCssClass = function(name) { this.table.className = name; } /* * Adds the row with the given data. * @param rowid a unique id for this row, which you can use in "remove" to * remove this row. If this is not unique, the behavior is undefined. * @param rowdata an object, sortable by your comparator and renderable * by your renderer, which contains the data for this row. */ SortableTable.prototype.put = function (rowid, rowdata) { if (this.private_findRow(rowid) < 0) { this.rows.push({ rowid: rowid, rowdata: rowdata }); } this.sort(this.currentSortColumn, this.currentSortAscending); } /* * Removes the specified row. * @param rowid the unique id of the row (used to add it originally in * "put"). If this is not unique, then the behavior is undefined. */ SortableTable.prototype.remove = function (rowid) { var rowNum = this.private_findRow(rowid); if (rowNum >= 0) { this.rows.splice(rowNum, 1); this.tbody.deleteRow(rowNum); } } /* * Resorts the table by specified column using this object's comparator for * the column. * @param index the column index * @param bAscending true iff we should sort in ascending order */ SortableTable.prototype.sort = function (index, bAscending) { this.currentSortColumn = index; this.currentSortAscending = bAscending; if (this.columns[index].comparator != null) { var thisHack = this; this.rows.sort(function(a, b) { return thisHack.columns[index].comparator(a.rowdata, b.rowdata); }); if (!bAscending) { this.rows.reverse(); } } this.rerender(); } SortableTable.prototype.rerender = function () { while (this.tbody.rows.length > 0) { this.tbody.deleteRow(0); } for (var ii = 0; ii < this.rows.length; ++ii) { var tr = this.tbody.insertRow(-1); tr.className = this.bodyRowCssClass; for (var jj = 0; jj < this.columns.length; ++jj) { tr.appendChild(this.columns[jj].renderer(this.rows[ii].rowdata)); } } } /* * Removes all rows from this table. */ SortableTable.prototype.clear = function () { this.rows = []; while (this.tbody.rows.length > 0) { this.tbody.deleteRow(0); } } SortableTable.prototype.private_findRow = function(rowid) { for (var ii = 0; ii < this.rows.length; ii++) { if (this.rows[ii].rowid == rowid) return ii; } return -1; }