tablesorter.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview A table sorting decorator.
  16. *
  17. * @author robbyw@google.com (Robby Walker)
  18. * @see ../demos/tablesorter.html
  19. */
  20. goog.provide('goog.ui.TableSorter');
  21. goog.provide('goog.ui.TableSorter.EventType');
  22. goog.require('goog.array');
  23. goog.require('goog.dom');
  24. goog.require('goog.dom.TagName');
  25. goog.require('goog.dom.classlist');
  26. goog.require('goog.events.EventType');
  27. goog.require('goog.functions');
  28. goog.require('goog.ui.Component');
  29. /**
  30. * A table sorter allows for sorting of a table by column. This component can
  31. * be used to decorate an already existing TABLE element with sorting
  32. * features.
  33. *
  34. * The TABLE should use a THEAD containing TH elements for the table column
  35. * headers.
  36. *
  37. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
  38. * document interaction.
  39. * @constructor
  40. * @extends {goog.ui.Component}
  41. */
  42. goog.ui.TableSorter = function(opt_domHelper) {
  43. goog.ui.Component.call(this, opt_domHelper);
  44. /**
  45. * The current sort header of the table, or null if none.
  46. * @type {HTMLTableCellElement}
  47. * @private
  48. */
  49. this.header_ = null;
  50. /**
  51. * Whether the last sort was in reverse.
  52. * @type {boolean}
  53. * @private
  54. */
  55. this.reversed_ = false;
  56. /**
  57. * The default sorting function.
  58. * @type {function(*, *) : number}
  59. * @private
  60. */
  61. this.defaultSortFunction_ = goog.ui.TableSorter.numericSort;
  62. /**
  63. * Array of custom sorting functions per colun.
  64. * @type {Array<function(*, *) : number>}
  65. * @private
  66. */
  67. this.sortFunctions_ = [];
  68. };
  69. goog.inherits(goog.ui.TableSorter, goog.ui.Component);
  70. goog.tagUnsealableClass(goog.ui.TableSorter);
  71. /**
  72. * Row number (in <thead>) to use for sorting.
  73. * @type {number}
  74. * @private
  75. */
  76. goog.ui.TableSorter.prototype.sortableHeaderRowIndex_ = 0;
  77. /**
  78. * Sets the row index (in <thead>) to be used for sorting.
  79. * By default, the first row (index 0) is used.
  80. * Must be called before decorate() is called.
  81. * @param {number} index The row index.
  82. */
  83. goog.ui.TableSorter.prototype.setSortableHeaderRowIndex = function(index) {
  84. if (this.isInDocument()) {
  85. throw Error(goog.ui.Component.Error.ALREADY_RENDERED);
  86. }
  87. this.sortableHeaderRowIndex_ = index;
  88. };
  89. /**
  90. * Table sorter events.
  91. * @enum {string}
  92. */
  93. goog.ui.TableSorter.EventType = {
  94. BEFORESORT: 'beforesort',
  95. SORT: 'sort'
  96. };
  97. /** @override */
  98. goog.ui.TableSorter.prototype.canDecorate = function(element) {
  99. return element.tagName == goog.dom.TagName.TABLE;
  100. };
  101. /** @override */
  102. goog.ui.TableSorter.prototype.enterDocument = function() {
  103. goog.ui.TableSorter.superClass_.enterDocument.call(this);
  104. var table = this.getElement();
  105. var headerRow = table.tHead.rows[this.sortableHeaderRowIndex_];
  106. this.getHandler().listen(headerRow, goog.events.EventType.CLICK, this.sort_);
  107. };
  108. /**
  109. * @return {number} The current sort column of the table, or -1 if none.
  110. */
  111. goog.ui.TableSorter.prototype.getSortColumn = function() {
  112. return this.header_ ? this.header_.cellIndex : -1;
  113. };
  114. /**
  115. * @return {boolean} Whether the last sort was in reverse.
  116. */
  117. goog.ui.TableSorter.prototype.isSortReversed = function() {
  118. return this.reversed_;
  119. };
  120. /**
  121. * @return {function(*, *) : number} The default sort function to be used by
  122. * all columns.
  123. */
  124. goog.ui.TableSorter.prototype.getDefaultSortFunction = function() {
  125. return this.defaultSortFunction_;
  126. };
  127. /**
  128. * Sets the default sort function to be used by all columns. If not set
  129. * explicitly, this defaults to numeric sorting.
  130. * @param {function(*, *) : number} sortFunction The new default sort function.
  131. */
  132. goog.ui.TableSorter.prototype.setDefaultSortFunction = function(sortFunction) {
  133. this.defaultSortFunction_ = sortFunction;
  134. };
  135. /**
  136. * Gets the sort function to be used by the given column. Returns the default
  137. * sort function if no sort function is explicitly set for this column.
  138. * @param {number} column The column index.
  139. * @return {function(*, *) : number} The sort function used by the column.
  140. */
  141. goog.ui.TableSorter.prototype.getSortFunction = function(column) {
  142. return this.sortFunctions_[column] || this.defaultSortFunction_;
  143. };
  144. /**
  145. * Set the sort function for the given column, overriding the default sort
  146. * function.
  147. * @param {number} column The column index.
  148. * @param {function(*, *) : number} sortFunction The new sort function.
  149. */
  150. goog.ui.TableSorter.prototype.setSortFunction = function(column, sortFunction) {
  151. this.sortFunctions_[column] = sortFunction;
  152. };
  153. /**
  154. * Sort the table contents by the values in the given column.
  155. * @param {goog.events.BrowserEvent} e The click event.
  156. * @private
  157. */
  158. goog.ui.TableSorter.prototype.sort_ = function(e) {
  159. // Determine what column was clicked.
  160. // TODO(robbyw): If this table cell contains another table, this could break.
  161. var target = e.target;
  162. var th = goog.dom.getAncestorByTagNameAndClass(target, goog.dom.TagName.TH);
  163. // If the user clicks on the same column, sort it in reverse of what it is
  164. // now. Otherwise, sort forward.
  165. var reverse = th == this.header_ ? !this.reversed_ : false;
  166. // Perform the sort.
  167. if (this.dispatchEvent(goog.ui.TableSorter.EventType.BEFORESORT)) {
  168. if (this.sort(th.cellIndex, reverse)) {
  169. this.dispatchEvent(goog.ui.TableSorter.EventType.SORT);
  170. }
  171. }
  172. };
  173. /**
  174. * Sort the table contents by the values in the given column.
  175. * @param {number} column The column to sort by.
  176. * @param {boolean=} opt_reverse Whether to sort in reverse.
  177. * @return {boolean} Whether the sort was executed.
  178. */
  179. goog.ui.TableSorter.prototype.sort = function(column, opt_reverse) {
  180. var sortFunction = this.getSortFunction(column);
  181. if (sortFunction === goog.ui.TableSorter.noSort) {
  182. return false;
  183. }
  184. // Remove old header classes.
  185. if (this.header_) {
  186. goog.dom.classlist.remove(
  187. this.header_, this.reversed_ ?
  188. goog.getCssName('goog-tablesorter-sorted-reverse') :
  189. goog.getCssName('goog-tablesorter-sorted'));
  190. }
  191. // If the user clicks on the same column, sort it in reverse of what it is
  192. // now. Otherwise, sort forward.
  193. this.reversed_ = !!opt_reverse;
  194. var multiplier = this.reversed_ ? -1 : 1;
  195. var cmpFn = function(a, b) {
  196. return multiplier * sortFunction(a[0], b[0]) || a[1] - b[1];
  197. };
  198. // Sort all tBodies
  199. var table = this.getElement();
  200. goog.array.forEach(table.tBodies, function(tBody) {
  201. // Collect all of the rows into an array.
  202. var values = goog.array.map(tBody.rows, function(row, rowIndex) {
  203. return [goog.dom.getTextContent(row.cells[column]), rowIndex, row];
  204. });
  205. goog.array.sort(values, cmpFn);
  206. // Remove the tBody temporarily since this speeds up the sort on some
  207. // browsers.
  208. var nextSibling = tBody.nextSibling;
  209. table.removeChild(tBody);
  210. // Sort the rows, using the resulting array.
  211. goog.array.forEach(values, function(row) { tBody.appendChild(row[2]); });
  212. // Reinstate the tBody.
  213. table.insertBefore(tBody, nextSibling);
  214. });
  215. // Mark this as the last sorted column.
  216. this.header_ = /** @type {!HTMLTableCellElement} */
  217. (table.tHead.rows[this.sortableHeaderRowIndex_].cells[column]);
  218. // Update the header class.
  219. goog.dom.classlist.add(
  220. this.header_, this.reversed_ ?
  221. goog.getCssName('goog-tablesorter-sorted-reverse') :
  222. goog.getCssName('goog-tablesorter-sorted'));
  223. return true;
  224. };
  225. /**
  226. * Disables sorting on the specified column
  227. * @param {*} a First sort value.
  228. * @param {*} b Second sort value.
  229. * @return {number} Negative if a < b, 0 if a = b, and positive if a > b.
  230. */
  231. goog.ui.TableSorter.noSort = goog.functions.error('no sort');
  232. /**
  233. * A numeric sort function. NaN values (or values that do not parse as float
  234. * numbers) compare equal to each other and greater to any other number.
  235. * @param {*} a First sort value.
  236. * @param {*} b Second sort value.
  237. * @return {number} Negative if a < b, 0 if a = b, and positive if a > b.
  238. */
  239. goog.ui.TableSorter.numericSort = function(a, b) {
  240. a = parseFloat(a);
  241. b = parseFloat(b);
  242. // foo == foo is false if and only if foo is NaN.
  243. if (a == a) {
  244. return b == b ? a - b : -1;
  245. } else {
  246. return b == b ? 1 : 0;
  247. }
  248. };
  249. /**
  250. * Alphabetic sort function.
  251. * @param {*} a First sort value.
  252. * @param {*} b Second sort value.
  253. * @return {number} Negative if a < b, 0 if a = b, and positive if a > b.
  254. */
  255. goog.ui.TableSorter.alphaSort = goog.array.defaultCompare;
  256. /**
  257. * Returns a function that is the given sort function in reverse.
  258. * @param {function(*, *) : number} sortFunction The original sort function.
  259. * @return {function(*, *) : number} A new sort function that reverses the
  260. * given sort function.
  261. */
  262. goog.ui.TableSorter.createReverseSort = function(sortFunction) {
  263. return function(a, b) { return -1 * sortFunction(a, b); };
  264. };