Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / tracing / analysis / table_builder.html
1 <!DOCTYPE html>
2 <!--
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.
6 -->
7
8 <link rel="import" href="/tracing/analysis/toggle_container.html">
9
10 <!--
11 @fileoverview A container that constructs a table-like container.
12 -->
13
14 <polymer-element name="tracing-analysis-nested-table"
15     constructor="TracingAnalysisNestedTable">
16   <template>
17     <style>
18       table {
19         border-collapse: collapse;
20         border-spacing: 0;
21         border-width: 0;
22         font-family: monospace;
23         width: 100%;
24       }
25
26       table > * > tr > td {
27         padding: 2px 4px 2px 4px;
28         vertical-align: text-top;
29       }
30
31       button.toggle-button {
32         height: 15px;
33         line-height: 60%;
34         vertical-align: middle;
35         width: 100%;
36       }
37
38       button > * {
39         height: 15px;
40         vertical-align: middle;
41       }
42
43       td.button-column {
44         width: 30px;
45       }
46
47       table > * > .hidden {
48         display: none;
49       }
50
51       table > thead {
52         background-color: #bbbbbb;
53         font-weight: bold;
54         text-align: left;
55       }
56
57       table > tfoot {
58         background-color: #bbbbbb;
59         font-weight: bold;
60       }
61
62       expand-button {
63         -webkit-user-select: none;
64         display: inline-block;
65         cursor: pointer;
66       }
67
68       .button-expanded {
69         transform: rotate(90deg);
70       }
71     </style>
72     <table>
73       <thead id="head">
74       </thead>
75       <tbody id="body">
76       </tbody>
77       <tfoot id="foot">
78       </tfoot>
79     </table>
80   </template>
81   <script>
82   'use strict';
83   (function () {
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);
88
89
90     Polymer({
91       created: function (){
92         this.tableColumns_ = [];
93         this.tableRows_ = [];
94         this.tableRowsInfo_ = [];
95         this.tableFooterRows_ = [];
96         this.sortColumnIndex_ = undefined;
97         this.sortDescending_ = false;
98         this.columnsWithExpandButtons_ = [];
99         this.headerCells_ = [];
100       },
101
102       clear: function() {
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_ = [];
112       },
113
114       /**
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}
119        */
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);
126         }
127         // First column if none have specified.
128         if (this.columnsWithExpandButtons_.length === 0)
129           this.columnsWithExpandButtons_ = [0];
130         this.sortColumnIndex = undefined;
131         this.scheduleRebuildHeaders_();
132       },
133
134       get tableColumns() {
135         return this.tableColumns_;
136       },
137
138       /**
139        * @param {Array} rows An array of 'row' objects with the following
140        * fields:
141        *   optional: subRows An array of objects that have the same 'row'
142        *                     structure.
143        */
144       set tableRows(rows) {
145         this.tableRows_ = rows;
146         this.tableRowsInfo_ = [];
147         this.createTableRowsInfo_(rows, this.tableRowsInfo_);
148         if (this.sortColumnIndex_ !== undefined)
149           this.sortTable_();
150         this.scheduleRebuildBody_();
151       },
152
153       get tableRows() {
154         return this.tableRows_;
155       },
156
157       set footerRows(rows) {
158         this.tableFooterRows_ = rows;
159         this.tableFooterRowsInfo_ = [];
160         this.createTableRowsInfo_(rows, this.tableFooterRowsInfo_);
161         this.scheduleRebuildFooter_();
162       },
163
164       get footerRows() {
165         return this.tableFooterRows_;
166       },
167
168       set sortColumnIndex(number) {
169         if (number === undefined) {
170           this.sortColumnIndex_ = undefined;
171           this.updateHeaderArrows_();
172           return;
173         }
174
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.');
179
180         this.sortColumnIndex_ = number;
181         this.updateHeaderArrows_();
182         this.sortTable_();
183       },
184
185       get sortColumnIndex() {
186         return this.sortColumnIndex_;
187       },
188
189       set sortDescending(value) {
190         var newValue = !!value;
191
192         if (newValue !== this.sortDescending_) {
193           this.sortDescending_ = newValue;
194           this.updateHeaderArrows_();
195           if (this.sortColumnIndex_ !== undefined)
196             this.sortTable_();
197         }
198       },
199
200       get sortDescending() {
201         return this.sortDescending_;
202       },
203
204       updateHeaderArrows_: function() {
205         for (var i = 0; i < this.headerCells_.length; i++) {
206           if (!this.tableColumns_[i].cmp) {
207             this.headerCells_[i].sideContent = '';
208             continue;
209           }
210           if (i !== this.sortColumnIndex_) {
211             this.headerCells_[i].sideContent = UNSORTED_ARROW;
212             continue;
213           }
214           this.headerCells_[i].sideContent = this.sortDescending_ ?
215             DESCENDING_ARROW : ASCENDING_ARROW;
216         }
217       },
218
219       sortTable_: function() {
220         this.sortRows_(this.tableRowsInfo_);
221         this.scheduleRebuildBody_();
222       },
223
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);
231         }.bind(this));
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);
236         }
237       },
238
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;
246
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
250           // to the column.
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;
257             else
258               headerCell.sideContent = UNSORTED_ARROW;
259           }
260
261           td.appendChild(headerCell);
262           this.headerCells_.push(headerCell);
263         }
264       },
265
266       createSortCallback_: function(columnNumber) {
267         return function() {
268           var previousIndex = this.sortColumnIndex;
269           this.sortColumnIndex = columnNumber;
270           if (previousIndex !== columnNumber)
271             this.sortDescending = false;
272           else
273             this.sortDescending = !this.sortDescending;
274         }.bind(this);
275       },
276
277       generateTableRowNodes_: function(tableSection, sectionRows, indentation,
278                                        opt_prevSibling) {
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);
287           } else {
288             sibling = row.htmlNode;
289           }
290         }
291         return sibling;
292       },
293
294       generateRowNode_: function(tableSection, row, indentation) {
295         if (row.htmlNode)
296           return row.htmlNode;
297
298         var INDENT_SPACE = indentation * 16;
299         var INDENT_SPACE_NO_BUTTON = indentation * 16 + 8;
300         var tr = this.ownerDocument.createElement('tr');
301         row.htmlNode = tr;
302         row.indentation = indentation;
303
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;
310
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,
317                   expandButton);
318             } else {
319               td.style.paddingLeft = INDENT_SPACE_NO_BUTTON + 'px';
320             }
321           }
322
323           if (value instanceof HTMLElement)
324             td.appendChild(value);
325           else
326             td.appendChild(this.ownerDocument.createTextNode(value));
327
328           i += colSpan;
329         }
330       },
331
332       addToggleListenerForRowToButton_: function(tableSection, row, button) {
333         button.addEventListener('click', function() {
334           row.isExpanded = !row.isExpanded;
335
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);
344           } else {
345             button.classList.remove('button-expanded');
346             this.removeSubNodes_(tableSection, row);
347           }
348         }.bind(this));
349       },
350
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]);
357           }
358         }
359       },
360
361       scheduleRebuildHeaders_: function() {
362         if (this.headerDirty_)
363           return;
364         this.headerDirty_ = true;
365         setTimeout(function() {
366           this.generateHeaderColumns_();
367           this.headerDirty_ = false;
368         }.bind(this), 0);
369       },
370
371       scheduleRebuildBody_: function() {
372         if (this.bodyDirty_)
373           return;
374         this.bodyDirty_ = true;
375         setTimeout(function() {
376           this.generateTableRowNodes_(this.$.body, this.tableRowsInfo_, 0);
377           this.bodyDirty_ = false;
378         }.bind(this), 0);
379       },
380
381       scheduleRebuildFooter_: function() {
382         if (this.footerDirty_)
383           return;
384         this.footerDirty_ = true;
385         setTimeout(function() {
386           this.generateTableRowNodes_(this.$.foot, this.tableFooterRowsInfo_,
387                                       0);
388           this.footerDirty_ = false;
389         }.bind(this), 0);
390       },
391
392       createTableRowsInfo_: function(rows, containerForResults) {
393         for (var i = 0; i < rows.length; i++) {
394           var subRowsArray = [];
395           if (rows[i].subRows)
396             this.createTableRowsInfo_(rows[i].subRows, subRowsArray);
397
398           containerForResults.push({
399             userRow: rows[i],
400             htmlNode: undefined,
401             subRows: subRowsArray,
402             isExpanded: false
403           });
404         }
405       },
406
407       appendElementAfter_: function(parent, element, opt_prevSibling) {
408         var nodeAfter = undefined;
409         if (opt_prevSibling)
410           nodeAfter = opt_prevSibling.nextSibling;
411         parent.insertBefore(element, nodeAfter);
412       },
413
414       appendNewElementAfter_: function(parent, tagName, opt_prevSibling) {
415         var element = parent.ownerDocument.createElement(tagName);
416         this.appendElementAfter_(parent, element, opt_prevSibling);
417         return element;
418       }
419     });
420   })();
421   </script>
422 </polymer-element>
423 <polymer-element name="tracing-analysis-header-cell"
424     constructor="TracingAnalysisHeaderCell"
425     on-tap="onTap_">
426   <template>
427   <style>
428     :host {
429       -webkit-user-select: none;
430       display: flex;
431     }
432
433     span {
434       flex: 0 1 auto;
435     }
436
437     side-element {
438       -webkit-user-select: none;
439       flex: 1 0 auto;
440       padding-left: 4px;
441       vertical-align: top;
442       font-size: 15px;
443       font-family: sans-serif;
444       display: inline;
445       line-height: 85%;
446     }
447   </style>
448
449     <span>{{ cellTitle_ }}</span><side-element id="side"></side-element>
450   </template>
451
452   <script>
453   'use strict';
454
455   Polymer({
456     created: function() {
457       this.tapCallback_ = undefined;
458       this.cellTitle_ = 'No Title';
459     },
460
461     set cellTitle(value) {
462       this.cellTitle_ = value;
463     },
464
465     get cellTitle() {
466       return this.cellTitle_;
467     },
468
469     clearSideContent: function() {
470       this.$.side.textContent = '';
471     },
472
473     set sideContent(content) {
474       this.$.side.textContent = content;
475     },
476
477     get sideContent() {
478       return this.$.side.textContent;
479     },
480
481     set tapCallback(callback) {
482       this.style.cursor = 'pointer';
483       this.tapCallback_ = callback;
484     },
485
486     get tapCallback() {
487       return this.tapCallback_;
488     },
489
490     onTap_: function() {
491       if (this.tapCallback_)
492         this.tapCallback_();
493     }
494   });
495 </script>
496 </polymer-element>