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