1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 * @fileoverview A sortable table with history states.
10 base.requireStylesheet('ui.sortable_table');
14 base.exportTo('ui', function() {
18 var SortableTable = ui.define('sortable-table');
20 var UNSORTED_ARROW = '▿';
21 var SORT_ASCENDING_ARROW = '▾';
22 var SORT_DESCENDING_ARROW = '▴';
23 var SORT_DIR_ASCENDING = 'downward';
24 var SORT_DIR_DESCENDING = 'upward';
26 SortableTable.prototype = {
27 __proto__: HTMLTableElement.prototype,
29 decorate: function() {
30 this.classList.add('sortable-table');
33 var headerRow = this.tHead.rows[0];
34 var currentState = window.history.state;
35 for (var i = 0; i < headerRow.cells.length; i++) {
36 headerRow.cells[i].addEventListener('click',
37 this.onItemClicked_, true);
38 headerRow.cells[i].innerHTML += ' ' + UNSORTED_ARROW;
41 if (currentState && currentState.tableSorting) {
42 var hashCode = this.sortingHashCode_();
43 if (currentState.tableSorting[hashCode]) {
44 this.sort(currentState.tableSorting[hashCode].col,
45 currentState.tableSorting[hashCode].sortDirection);
50 onItemClicked_: function(e) {
51 // 'this' refers to the table cell that has been clicked.
52 var headerRow = this.parentNode;
53 var table = headerRow.parentNode.parentNode;
54 var colIndex = Array.prototype.slice.call(headerRow.cells).indexOf(this);
55 var sortDirection = table.sort(colIndex);
56 var currentState = history.state;
57 if (!currentState.tableSorting)
58 currentState.tableSorting = {};
59 currentState.tableSorting[table.sortingHashCode_()] = {
61 sortDirection: sortDirection
63 window.history.pushState(currentState);
66 sort: function(colIndex, opt_sortDirection) {
67 var headerRow = this.tHead.rows[0];
68 var headerCell = headerRow.cells[colIndex];
70 if (!headerCell.hasAttribute('sort')) {
71 // we are either sorting a new column (not previously sorted),
72 // or sorting based on a given sort direction (opt_sortDirection).
73 return sortByColumn_(headerRow, headerCell, colIndex,
76 // resort the current sort column in the other direction
77 return reverseSortDirection_(headerRow, headerCell, opt_sortDirection);
82 // A very simple hash function, based only on the header row and
83 // the table location. It is used to check that table loaded
84 // can be sorted according to the given history information.
85 sortingHashCode_: function() {
86 if (this.sortingHashValue_)
87 return this.sortingHashValue_;
88 var headerText = this.tHead.rows[0].innerText;
90 for (var i = 0; i < headerText.length; i++) {
91 if (headerText.charCodeAt(i) < 127)
92 hash += headerText.charCodeAt(i);
95 // use the table index as well in case the same table
96 // is displayed more than once on a single page.
97 var tableIndex = Array.prototype.slice.call(
98 document.getElementsByClassName('sortable-table')).indexOf(this);
99 this.sortingHashValue_ = tableIndex + '' + hash;
100 return this.sortingHashValue_;
104 function compareAscending_(a, b) {
105 return compare_(a, b);
108 function compareDescending_(a, b) {
109 return compare_(b, a);
112 function compare_(a, b) {
113 var a1 = parseFloat(a);
114 var b1 = parseFloat(b);
115 if (isNaN(a1) && isNaN(b1))
116 return a.toString().localeCompare(b.toString());
124 function sortByColumn_(headerRow, headerCell, colIndex, opt_sortDirection) {
125 var sortDirection = opt_sortDirection || SORT_DIR_ASCENDING;
126 // remove sort attribute from other header elements.
127 for (var i = 0; i < headerRow.cells.length; i++) {
128 if (headerRow.cells[i].getAttribute('sort')) {
129 headerRow.cells[i].removeAttribute('sort');
130 var headerStr = headerRow.cells[i].innerHTML;
131 headerRow.cells[i].innerHTML =
132 headerStr.substr(0, headerStr.length - 2) + UNSORTED_ARROW;
136 var headerStr = headerRow.cells[colIndex].innerHTML;
137 headerCell.innerHTML = headerStr.substr(0, headerStr.length - 2) +
138 (sortDirection == SORT_DIR_ASCENDING ?
139 SORT_ASCENDING_ARROW : SORT_DESCENDING_ARROW);
141 headerCell.setAttribute('sort', sortDirection);
142 var rows = headerRow.parentNode.parentNode.tBodies[0].rows;
144 for (var i = 0; i < rows.length; i++) {
145 tempRows.push([rows[i].cells[colIndex].innerText, rows[i]]);
148 tempRows.sort(sortDirection == SORT_DIR_ASCENDING ?
149 compareAscending_ : compareDescending_);
151 for (var j = 0; j < tempRows.length; j++) {
152 headerRow.parentNode.parentNode.tBodies[0].
153 appendChild(tempRows[j][1]);
155 return sortDirection;
158 function reverseSortDirection_(headerRow, headerCell, opt_sortDirection) {
159 var sortDirection = headerCell.getAttribute('sort');
160 // if it is already sorted in the correct direction, do nothing.
161 if (opt_sortDirection == sortDirection)
162 return sortDirection;
163 sortDirection = sortDirection == SORT_DIR_DESCENDING ?
164 SORT_DIR_ASCENDING : SORT_DIR_DESCENDING;
165 headerCell.setAttribute('sort', sortDirection);
166 var headerStr = headerCell.innerHTML;
167 headerCell.innerHTML = headerStr.substr(0, headerStr.length - 2) +
168 (sortDirection == SORT_DIR_ASCENDING ?
169 SORT_ASCENDING_ARROW : SORT_DESCENDING_ARROW);
170 // instead of re-sorting, we reverse the sorted rows.
171 var headerRow = headerCell.parentNode;
172 var tbody = headerRow.parentNode.parentNode.tBodies[0];
174 for (var i = 0; i < tbody.rows.length; i++)
175 tempRows[tempRows.length] = tbody.rows[i];
176 for (var i = tempRows.length - 1; i >= 0; i--)
177 tbody.appendChild(tempRows[i]);
178 return sortDirection;
182 SortableTable: SortableTable