1<!DOCTYPE html>
2<!--
3Copyright (c) 2015 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7<link rel="import" href="/tracing/base/iteration_helpers.html">
8<link rel="import" href="/tracing/base/statistics.html">
9<link rel="import" href="/tracing/ui/analysis/generic_object_view.html">
10<link rel="import" href="/tracing/ui/base/table.html">
11<link rel="import" href="/tracing/value/generic_table.html">
12<link rel="import" href="/tracing/value/ui/array_of_numbers_span.html">
13
14<polymer-element name="tr-v-ui-generic-table-view">
15  <template>
16    <style>
17    :host {
18    display: flex;
19    }
20    #table {
21      flex: 1 1 auto;
22      align-self: stretch;
23    }
24    </style>
25    <tr-ui-b-table id="table"></tr-ui-b-table>
26  </template>
27</polymer-element>
28
29<script>
30'use strict';
31
32tr.exportTo('tr.v.ui', function() {
33  var TEXT_COLUMN_MODE = 1;
34  var NUMERIC_COLUMN_MODE = 2;
35  var ELEMENT_COLUMN_MODE = 3;
36
37  function isNumeric(value) {
38    // TODO(nduca): Also consider other units that are numeric.
39    if ((typeof value) === 'number')
40      return true;
41    else if (value instanceof Number)
42      return true;
43    return false;
44  }
45
46  function GenericTableViewTotalsItem(opt_values) {
47    if (opt_values !== undefined)
48      this.values = opt_values;
49    else
50      this.values = [];
51  }
52
53  function GenericTableViewColumnDescriptor(fieldName, firstFieldValue) {
54    this.title = fieldName;
55    this.fieldName = fieldName;
56
57    this.updateModeGivenValue(firstFieldValue);
58  }
59
60  GenericTableViewColumnDescriptor.prototype = {
61    get columnMode() {
62      return this.columnMode_;
63    },
64
65    get isInNumericMode() {
66      return this.columnMode_ === NUMERIC_COLUMN_MODE;
67    },
68
69    cmp: function(a, b) {
70      if (this.columnMode_ === ELEMENT_COLUMN_MODE)
71        return 0;
72
73      return tr.b.comparePossiblyUndefinedValues(a, b, function(a, b) {
74        var vA = a[this.fieldName];
75        var vB = b[this.fieldName];
76        return tr.b.comparePossiblyUndefinedValues(vA, vB, function(vA, vB) {
77          if (vA.localeCompare)
78            return vA.localeCompare(vB);
79          return vA - vB;
80        }, this);
81      }, this);
82    },
83
84    updateModeGivenValue: function(fieldValue) {
85      if (this.columnMode_ === undefined) {
86        if (fieldValue === undefined || fieldValue === null)
87          return;
88
89        if (isNumeric(fieldValue)) {
90          this.columnMode_ = NUMERIC_COLUMN_MODE;
91          return;
92        }
93
94        if (fieldValue instanceof HTMLElement) {
95          this.columnMode_ = ELEMENT_COLUMN_MODE;
96          return;
97        }
98
99        this.columnMode_ = TEXT_COLUMN_MODE;
100        return;
101      }
102
103      // Undefineds & nulls shouldn't change the mode.
104      if (fieldValue === undefined || fieldValue === null)
105        return;
106
107      // If we were already in numeric mode, then we don't
108      // need to put it into numeric mode again. And, if we were
109      // previously in text mode, then we can't go into numeric mode now.
110      if (isNumeric(fieldValue))
111        return;
112
113      if (fieldValue instanceof HTMLElement) {
114        this.columnMode_ = ELEMENT_COLUMN_MODE;
115        return;
116      }
117
118      if (this.columnMode_ === NUMERIC_COLUMN_MODE)
119        this.columnMode_ = TEXT_COLUMN_MODE;
120    },
121
122    value: function(item) {
123      var fieldValue = item[this.fieldName];
124      if (fieldValue instanceof GenericTableViewTotalsItem) {
125        var span = document.createElement('tr-v-ui-array-of-numbers-span');
126        span.summaryMode = tr.v.ui.ArrayOfNumbersSummaryModes.TOTAL_MODE;
127        span.numbers = fieldValue.values;
128        return span;
129      }
130
131      if (fieldValue === undefined)
132        return '-';
133
134      if (fieldValue instanceof HTMLElement)
135        return fieldValue;
136
137      if (fieldValue instanceof Object) {
138        var gov = document.createElement('tr-ui-a-generic-object-view');
139        gov.object = fieldValue;
140        return gov;
141      }
142
143      // TODO(nduca): Use units objects if applicable.
144      return fieldValue;
145    }
146  };
147
148  Polymer('tr-v-ui-generic-table-view', {
149    created: function() {
150      this.items_ = undefined;
151      this.importantColumNames_ = [];
152    },
153
154    get items() {
155      return this.items_;
156    },
157
158    set items(itemsOrGenericTable) {
159      if (itemsOrGenericTable === undefined) {
160        this.items_ = undefined;
161      } else if (itemsOrGenericTable instanceof Array) {
162        this.items_ = itemsOrGenericTable;
163      } else if (itemsOrGenericTable instanceof tr.v.GenericTable) {
164        this.items_ = itemsOrGenericTable.items;
165      }
166      this.updateContents_();
167    },
168
169    get importantColumNames() {
170      return this.importantColumNames_;
171    },
172
173    set importantColumNames(importantColumNames) {
174      this.importantColumNames_ = importantColumNames;
175      this.updateContents_();
176    },
177
178    createColumns_: function() {
179      var columnsByName = {};
180      this.items_.forEach(function(item) {
181        tr.b.iterItems(item, function(itemFieldName, itemFieldValue) {
182          var colDesc = columnsByName[itemFieldName];
183          if (colDesc !== undefined) {
184            colDesc.updateModeGivenValue(itemFieldValue);
185            return;
186          }
187
188          colDesc = new GenericTableViewColumnDescriptor(
189              itemFieldName, itemFieldValue);
190          columnsByName[itemFieldName] = colDesc;
191        }, this);
192      }, this);
193
194      var columns = tr.b.dictionaryValues(columnsByName);
195      if (columns.length === 0)
196        return undefined;
197
198      // Sort by name.
199      var isColumnNameImportant = {};
200      var importantColumNames = this.importantColumNames || [];
201      importantColumNames.forEach(function(icn) {
202        isColumnNameImportant[icn] = true;
203      });
204      columns.sort(function(a, b) {
205        var iA = isColumnNameImportant[a.title] ? 1 : 0;
206        var iB = isColumnNameImportant[b.title] ? 1 : 0;
207        if ((iB - iA) !== 0)
208          return iB - iA;
209        return a.title.localeCompare(b.title);
210      });
211
212      // Set sizes. This is convoluted by the fact that the first
213      // table column must have fixed size.
214      var colWidthPercentage;
215      if (columns.length == 1)
216        colWidthPercentage = '100%';
217      else
218        colWidthPercentage = (100 / (columns.length - 1)).toFixed(3) + '%';
219      columns[0].width = '250px';
220      for (var i = 1; i < columns.length; i++)
221        columns[i].width = colWidthPercentage;
222
223      return columns;
224    },
225
226    createFooterRowsIfNeeded_: function(columns) {
227      // Make totals row if needed.
228      var hasColumnThatIsNumeric = columns.some(function(column) {
229        return column.isInNumericMode;
230      });
231      if (!hasColumnThatIsNumeric)
232        return [];
233
234      var totalsItems = {};
235      columns.forEach(function(column) {
236        if (!column.isInNumericMode)
237          return;
238        var totalsItem = new GenericTableViewTotalsItem();
239        this.items_.forEach(function(item) {
240          var fieldValue = item[column.fieldName];
241          if (fieldValue === undefined || fieldValue === null)
242            return;
243          totalsItem.values.push(fieldValue);
244        });
245        totalsItems[column.fieldName] = totalsItem;
246      }, this);
247
248      return [totalsItems];
249    },
250
251    updateContents_: function() {
252      var columns;
253      if (this.items_ !== undefined)
254        columns = this.createColumns_();
255
256      if (!columns) {
257        this.$.table.tableColumns = [];
258        this.$.table.tableRows = [];
259        this.$.table.footerRows = [];
260        return;
261      }
262
263      this.$.table.tableColumns = columns;
264      this.$.table.tableRows = this.items_;
265      this.$.table.footerRows = this.createFooterRowsIfNeeded_(columns);
266      this.$.table.rebuild();
267    },
268
269    get selectionMode() {
270      return this.$.table.selectionMode;
271    },
272
273    set selectionMode(selectionMode) {
274      this.$.table.selectionMode = selectionMode;
275    },
276
277    get rowHighlightStyle() {
278      return this.$.table.rowHighlightStyle;
279    },
280
281    set rowHighlightStyle(rowHighlightStyle) {
282      this.$.table.rowHighlightStyle = rowHighlightStyle;
283    },
284
285    get cellHighlightStyle() {
286      return this.$.table.cellHighlightStyle;
287    },
288
289    set cellHighlightStyle(cellHighlightStyle) {
290      this.$.table.cellHighlightStyle = cellHighlightStyle;
291    }
292  });
293
294  return {
295    GenericTableViewTotalsItem: GenericTableViewTotalsItem,
296    GenericTableViewColumnDescriptor: GenericTableViewColumnDescriptor
297  };
298});
299</script>
300