3 Copyright (c) 2014 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
7 <link rel="import" href="/tvcm/ui.html">
8 <link rel="stylesheet" href="/tvcm/ui/sortable_table.css">
13 * @fileoverview A sortable table with history states.
15 tvcm.exportTo('tvcm.ui', function() {
19 var SortableTable = tvcm.ui.define('sortable-table');
21 var UNSORTED_ARROW = '▿';
22 var SORT_ASCENDING_ARROW = '▾';
23 var SORT_DESCENDING_ARROW = '▴';
24 var SORT_DIR_ASCENDING = 'downward';
25 var SORT_DIR_DESCENDING = 'upward';
27 SortableTable.prototype = {
28 __proto__: HTMLTableElement.prototype,
30 decorate: function() {
31 this.classList.add('sortable-table');
34 var headerRow = this.tHead.rows[0];
35 var currentState = window.history.state;
36 for (var i = 0; i < headerRow.cells.length; i++) {
37 headerRow.cells[i].addEventListener('click',
38 this.onItemClicked_, true);
39 headerRow.cells[i].innerHTML += ' ' + UNSORTED_ARROW;
42 if (currentState && currentState.tableSorting) {
43 var hashCode = this.sortingHashCode_();
44 if (currentState.tableSorting[hashCode]) {
45 this.sort(currentState.tableSorting[hashCode].col,
46 currentState.tableSorting[hashCode].sortDirection);
51 onItemClicked_: function(e) {
52 // 'this' refers to the table cell that has been clicked.
53 var headerRow = this.parentNode;
54 var table = headerRow.parentNode.parentNode;
55 var colIndex = Array.prototype.slice.call(headerRow.cells).indexOf(this);
56 var sortDirection = table.sort(colIndex);
57 var currentState = history.state;
58 if (!currentState.tableSorting)
59 currentState.tableSorting = {};
60 currentState.tableSorting[table.sortingHashCode_()] = {
62 sortDirection: sortDirection
64 window.history.pushState(currentState);
67 sort: function(colIndex, opt_sortDirection) {
68 var headerRow = this.tHead.rows[0];
69 var headerCell = headerRow.cells[colIndex];
71 if (!headerCell.hasAttribute('sort')) {
72 // we are either sorting a new column (not previously sorted),
73 // or sorting based on a given sort direction (opt_sortDirection).
74 return sortByColumn_(headerRow, headerCell, colIndex,
77 // resort the current sort column in the other direction
78 return reverseSortDirection_(headerRow, headerCell, opt_sortDirection);
83 // A very simple hash function, based only on the header row and
84 // the table location. It is used to check that table loaded
85 // can be sorted according to the given history information.
86 sortingHashCode_: function() {
87 if (this.sortingHashValue_)
88 return this.sortingHashValue_;
89 var headerText = this.tHead.rows[0].innerText;
91 for (var i = 0; i < headerText.length; i++) {
92 if (headerText.charCodeAt(i) < 127)
93 hash += headerText.charCodeAt(i);
96 // use the table index as well in case the same table
97 // is displayed more than once on a single page.
98 var tableIndex = Array.prototype.slice.call(
99 document.getElementsByClassName('sortable-table')).indexOf(this);
100 this.sortingHashValue_ = tableIndex + '' + hash;
101 return this.sortingHashValue_;
105 function compareAscending_(a, b) {
106 return compare_(a, b);
109 function compareDescending_(a, b) {
110 return compare_(b, a);
113 function compare_(a, b) {
114 var a1 = parseFloat(a);
115 var b1 = parseFloat(b);
116 if (isNaN(a1) && isNaN(b1))
117 return a.toString().localeCompare(b.toString());
125 function sortByColumn_(headerRow, headerCell, colIndex, opt_sortDirection) {
126 var sortDirection = opt_sortDirection || SORT_DIR_ASCENDING;
127 // remove sort attribute from other header elements.
128 for (var i = 0; i < headerRow.cells.length; i++) {
129 if (headerRow.cells[i].getAttribute('sort')) {
130 headerRow.cells[i].removeAttribute('sort');
131 var headerStr = headerRow.cells[i].innerHTML;
132 headerRow.cells[i].innerHTML =
133 headerStr.substr(0, headerStr.length - 2) + UNSORTED_ARROW;
137 var headerStr = headerRow.cells[colIndex].innerHTML;
138 headerCell.innerHTML = headerStr.substr(0, headerStr.length - 2) +
139 (sortDirection == SORT_DIR_ASCENDING ?
140 SORT_ASCENDING_ARROW : SORT_DESCENDING_ARROW);
142 headerCell.setAttribute('sort', sortDirection);
143 var rows = headerRow.parentNode.parentNode.tBodies[0].rows;
145 for (var i = 0; i < rows.length; i++) {
146 tempRows.push([rows[i].cells[colIndex].innerText, rows[i]]);
149 tempRows.sort(sortDirection == SORT_DIR_ASCENDING ?
150 compareAscending_ : compareDescending_);
152 for (var j = 0; j < tempRows.length; j++) {
153 headerRow.parentNode.parentNode.tBodies[0].
154 appendChild(tempRows[j][1]);
156 return sortDirection;
159 function reverseSortDirection_(headerRow, headerCell, opt_sortDirection) {
160 var sortDirection = headerCell.getAttribute('sort');
161 // if it is already sorted in the correct direction, do nothing.
162 if (opt_sortDirection == sortDirection)
163 return sortDirection;
164 sortDirection = sortDirection == SORT_DIR_DESCENDING ?
165 SORT_DIR_ASCENDING : SORT_DIR_DESCENDING;
166 headerCell.setAttribute('sort', sortDirection);
167 var headerStr = headerCell.innerHTML;
168 headerCell.innerHTML = headerStr.substr(0, headerStr.length - 2) +
169 (sortDirection == SORT_DIR_ASCENDING ?
170 SORT_ASCENDING_ARROW : SORT_DESCENDING_ARROW);
171 // instead of re-sorting, we reverse the sorted rows.
172 var headerRow = headerCell.parentNode;
173 var tbody = headerRow.parentNode.parentNode.tBodies[0];
175 for (var i = 0; i < tbody.rows.length; i++)
176 tempRows[tempRows.length] = tbody.rows[i];
177 for (var i = tempRows.length - 1; i >= 0; i--)
178 tbody.appendChild(tempRows[i]);
179 return sortDirection;
183 SortableTable: SortableTable