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.
8 <link rel="import" href="/tracing/analysis/toggle_container.html">
11 @fileoverview A container that constructs a table-like container.
14 <polymer-element name="tracing-analysis-nested-table"
15 constructor="TracingAnalysisNestedTable">
19 border-collapse: collapse;
22 font-family: monospace;
27 padding: 2px 4px 2px 4px;
28 vertical-align: text-top;
31 button.toggle-button {
34 vertical-align: middle;
40 vertical-align: middle;
52 background-color: #bbbbbb;
58 background-color: #bbbbbb;
63 -webkit-user-select: none;
64 display: inline-block;
69 transform: rotate(90deg);
84 var RIGHT_ARROW = String.fromCharCode(0x25b6);
85 var UNSORTED_ARROW = String.fromCharCode(0x25BF);
86 var ASCENDING_ARROW = String.fromCharCode(0x25BE);
87 var DESCENDING_ARROW = String.fromCharCode(0x25B4);
92 this.tableColumns_ = [];
94 this.tableRowsInfo_ = [];
95 this.tableFooterRows_ = [];
96 this.sortColumnIndex_ = undefined;
97 this.sortDescending_ = false;
98 this.columnsWithExpandButtons_ = [];
99 this.headerCells_ = [];
103 this.textContent = '';
104 this.tableColumns_ = [];
105 this.tableRows_ = [];
106 this.tableRowsInfo_ = [];
107 this.tableFooterRows_ = [];
108 this.sortColumnIndex_ = undefined;
109 this.sortDescending_ = false;
110 this.columnsWithExpandButtons_ = [];
111 this.headerCells_ = [];
115 * @param {Array} columns An array of objects with the following fields:
116 * mandatory: title, value
117 * optional: width {string}, cmp {function}, colSpan {number},
118 * showExpandButtons {boolean}
120 set tableColumns(columns) {
121 this.tableColumns_ = columns;
122 this.columnsWithExpandButtons_ = [];
123 for (var i = 0; i < columns.length; i++) {
124 if (columns[i].showExpandButtons)
125 this.columnsWithExpandButtons_.push(i);
127 // First column if none have specified.
128 if (this.columnsWithExpandButtons_.length === 0)
129 this.columnsWithExpandButtons_ = [0];
130 this.sortColumnIndex = undefined;
131 this.scheduleRebuildHeaders_();
135 return this.tableColumns_;
139 * @param {Array} rows An array of 'row' objects with the following
141 * optional: subRows An array of objects that have the same 'row'
144 set tableRows(rows) {
145 this.tableRows_ = rows;
146 this.tableRowsInfo_ = [];
147 this.createTableRowsInfo_(rows, this.tableRowsInfo_);
148 if (this.sortColumnIndex_ !== undefined)
150 this.scheduleRebuildBody_();
154 return this.tableRows_;
157 set footerRows(rows) {
158 this.tableFooterRows_ = rows;
159 this.tableFooterRowsInfo_ = [];
160 this.createTableRowsInfo_(rows, this.tableFooterRowsInfo_);
161 this.scheduleRebuildFooter_();
165 return this.tableFooterRows_;
168 set sortColumnIndex(number) {
169 if (number === undefined) {
170 this.sortColumnIndex_ = undefined;
171 this.updateHeaderArrows_();
175 if (this.tableColumns_.length <= number)
176 throw new Error('Column number ' + number + ' is out of bounds.');
177 if (!this.tableColumns_[number].cmp)
178 throw new Error('Column ' + number + ' does not have a comparator.');
180 this.sortColumnIndex_ = number;
181 this.updateHeaderArrows_();
185 get sortColumnIndex() {
186 return this.sortColumnIndex_;
189 set sortDescending(value) {
190 var newValue = !!value;
192 if (newValue !== this.sortDescending_) {
193 this.sortDescending_ = newValue;
194 this.updateHeaderArrows_();
195 if (this.sortColumnIndex_ !== undefined)
200 get sortDescending() {
201 return this.sortDescending_;
204 updateHeaderArrows_: function() {
205 for (var i = 0; i < this.headerCells_.length; i++) {
206 if (!this.tableColumns_[i].cmp) {
207 this.headerCells_[i].sideContent = '';
210 if (i !== this.sortColumnIndex_) {
211 this.headerCells_[i].sideContent = UNSORTED_ARROW;
214 this.headerCells_[i].sideContent = this.sortDescending_ ?
215 DESCENDING_ARROW : ASCENDING_ARROW;
219 sortTable_: function() {
220 this.sortRows_(this.tableRowsInfo_);
221 this.scheduleRebuildBody_();
224 sortRows_: function(rows) {
225 rows.sort(function(rowA, rowB) {
226 if (this.sortDescending_)
227 return this.tableColumns_[this.sortColumnIndex_].cmp(
228 rowB.userRow, rowA.userRow);
229 return this.tableColumns_[this.sortColumnIndex_].cmp(
230 rowA.userRow, rowB.userRow);
232 // Sort expanded sub rows recursively.
233 for (var i = 0; i < rows.length; i++) {
234 if (rows[i].isExpanded)
235 this.sortRows_(rows[i].subRows);
239 generateHeaderColumns_: function() {
240 this.headerCells_ = [];
241 var tr = this.appendNewElementAfter_(this.$.head, 'tr');
242 for (var i = 0; i < this.tableColumns_.length; i++) {
243 var td = this.appendNewElementAfter_(tr, 'td');
244 if (this.tableColumns_[i].width)
245 td.style.width = this.tableColumns_[i].width;
247 var headerCell = new TracingAnalysisHeaderCell();
248 headerCell.cellTitle = this.tableColumns_[i].title;
249 // If the table can be sorted by this column, attach a tap callback
251 if (this.tableColumns_[i].cmp) {
252 headerCell.tapCallback = this.createSortCallback_(i);
253 // Set arrow position, depending on the sortColumnIndex.
254 if (this.sortColumnIndex_ === i)
255 headerCell.sideContent = this.sortDescending_ ?
256 DESCENDING_ARROW : ASCENDING_ARROW;
258 headerCell.sideContent = UNSORTED_ARROW;
261 td.appendChild(headerCell);
262 this.headerCells_.push(headerCell);
266 createSortCallback_: function(columnNumber) {
268 var previousIndex = this.sortColumnIndex;
269 this.sortColumnIndex = columnNumber;
270 if (previousIndex !== columnNumber)
271 this.sortDescending = false;
273 this.sortDescending = !this.sortDescending;
277 generateTableRowNodes_: function(tableSection, sectionRows, indentation,
279 var sibling = opt_prevSibling;
280 for (var i = 0; i < sectionRows.length; i++) {
281 var row = sectionRows[i];
282 this.generateRowNode_(tableSection, row, indentation);
283 this.appendElementAfter_(tableSection, row.htmlNode, sibling);
284 if (row.isExpanded) {
285 sibling = this.generateTableRowNodes_(tableSection, row.subRows,
286 indentation + 1, row.htmlNode);
288 sibling = row.htmlNode;
294 generateRowNode_: function(tableSection, row, indentation) {
298 var INDENT_SPACE = indentation * 16;
299 var INDENT_SPACE_NO_BUTTON = indentation * 16 + 8;
300 var tr = this.ownerDocument.createElement('tr');
302 row.indentation = indentation;
304 for (var i = 0; i < this.tableColumns_.length;) {
305 var td = this.appendNewElementAfter_(tr, 'td');
306 var column = this.tableColumns_[i];
307 var value = column.value(row.userRow);
308 var colSpan = column.colSpan ? column.colSpan : 1;
309 td.style.colSpan = colSpan;
311 if (this.columnsWithExpandButtons_.indexOf(i) != -1) {
312 if (row.subRows.length > 0) {
313 td.style.paddingLeft = INDENT_SPACE + 'px';
314 var expandButton = this.appendNewElementAfter_(td, 'expand-button');
315 expandButton.textContent = RIGHT_ARROW;
316 this.addToggleListenerForRowToButton_(tableSection, row,
319 td.style.paddingLeft = INDENT_SPACE_NO_BUTTON + 'px';
323 if (value instanceof HTMLElement)
324 td.appendChild(value);
326 td.appendChild(this.ownerDocument.createTextNode(value));
332 addToggleListenerForRowToButton_: function(tableSection, row, button) {
333 button.addEventListener('click', function() {
334 row.isExpanded = !row.isExpanded;
336 if (row.isExpanded) {
337 button.classList.add('button-expanded');
338 // Before adding the expanded nodes, sort them if we can.
339 if (this.sortColumnIndex_ !== undefined)
340 this.sortRows_(row.subRows);
341 var sibling = row.htmlNode;
342 this.generateTableRowNodes_(tableSection,
343 row.subRows, row.indentation + 1, sibling);
345 button.classList.remove('button-expanded');
346 this.removeSubNodes_(tableSection, row);
351 removeSubNodes_: function(tableSection, row) {
352 for (var i = 0; i < row.subRows.length; i++) {
353 var subNode = row.subRows[i].htmlNode;
354 if (subNode && subNode.parentNode === tableSection) {
355 tableSection.removeChild(row.subRows[i].htmlNode);
356 this.removeSubNodes_(tableSection, row.subRows[i]);
361 scheduleRebuildHeaders_: function() {
362 if (this.headerDirty_)
364 this.headerDirty_ = true;
365 setTimeout(function() {
366 this.generateHeaderColumns_();
367 this.headerDirty_ = false;
371 scheduleRebuildBody_: function() {
374 this.bodyDirty_ = true;
375 setTimeout(function() {
376 this.generateTableRowNodes_(this.$.body, this.tableRowsInfo_, 0);
377 this.bodyDirty_ = false;
381 scheduleRebuildFooter_: function() {
382 if (this.footerDirty_)
384 this.footerDirty_ = true;
385 setTimeout(function() {
386 this.generateTableRowNodes_(this.$.foot, this.tableFooterRowsInfo_,
388 this.footerDirty_ = false;
392 createTableRowsInfo_: function(rows, containerForResults) {
393 for (var i = 0; i < rows.length; i++) {
394 var subRowsArray = [];
396 this.createTableRowsInfo_(rows[i].subRows, subRowsArray);
398 containerForResults.push({
401 subRows: subRowsArray,
407 appendElementAfter_: function(parent, element, opt_prevSibling) {
408 var nodeAfter = undefined;
410 nodeAfter = opt_prevSibling.nextSibling;
411 parent.insertBefore(element, nodeAfter);
414 appendNewElementAfter_: function(parent, tagName, opt_prevSibling) {
415 var element = parent.ownerDocument.createElement(tagName);
416 this.appendElementAfter_(parent, element, opt_prevSibling);
423 <polymer-element name="tracing-analysis-header-cell"
424 constructor="TracingAnalysisHeaderCell"
429 -webkit-user-select: none;
438 -webkit-user-select: none;
443 font-family: sans-serif;
449 <span>{{ cellTitle_ }}</span><side-element id="side"></side-element>
456 created: function() {
457 this.tapCallback_ = undefined;
458 this.cellTitle_ = 'No Title';
461 set cellTitle(value) {
462 this.cellTitle_ = value;
466 return this.cellTitle_;
469 clearSideContent: function() {
470 this.$.side.textContent = '';
473 set sideContent(content) {
474 this.$.side.textContent = content;
478 return this.$.side.textContent;
481 set tapCallback(callback) {
482 this.style.cursor = 'pointer';
483 this.tapCallback_ = callback;
487 return this.tapCallback_;
491 if (this.tapCallback_)