1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * Inherit the prototype methods from one constructor into another. 7 */ 8function inherits(childCtor, parentCtor) { 9 function tempCtor() {}; 10 tempCtor.prototype = parentCtor.prototype; 11 childCtor.superClass_ = parentCtor.prototype; 12 childCtor.prototype = new tempCtor(); 13 childCtor.prototype.constructor = childCtor; 14}; 15 16/** 17 * Sets the width (in pixels) on a DOM node. 18 */ 19function setNodeWidth(node, widthPx) { 20 node.style.width = widthPx.toFixed(0) + 'px'; 21} 22 23/** 24 * Sets the height (in pixels) on a DOM node. 25 */ 26function setNodeHeight(node, heightPx) { 27 node.style.height = heightPx.toFixed(0) + 'px'; 28} 29 30/** 31 * Sets the position and size of a DOM node (in pixels). 32 */ 33function setNodePosition(node, leftPx, topPx, widthPx, heightPx) { 34 node.style.left = leftPx.toFixed(0) + 'px'; 35 node.style.top = topPx.toFixed(0) + 'px'; 36 setNodeWidth(node, widthPx); 37 setNodeHeight(node, heightPx); 38} 39 40/** 41 * Sets the visibility for a DOM node. 42 */ 43function setNodeDisplay(node, isVisible) { 44 node.style.display = isVisible ? '' : 'none'; 45} 46 47/** 48 * Adds a node to |parentNode|, of type |tagName|. 49 */ 50function addNode(parentNode, tagName) { 51 var elem = parentNode.ownerDocument.createElement(tagName); 52 parentNode.appendChild(elem); 53 return elem; 54} 55 56/** 57 * Adds |text| to node |parentNode|. 58 */ 59function addTextNode(parentNode, text) { 60 var textNode = parentNode.ownerDocument.createTextNode(text); 61 parentNode.appendChild(textNode); 62 return textNode; 63} 64 65/** 66 * Adds a node to |parentNode|, of type |tagName|. Then adds 67 * |text| to the new node. 68 */ 69function addNodeWithText(parentNode, tagName, text) { 70 var elem = parentNode.ownerDocument.createElement(tagName); 71 parentNode.appendChild(elem); 72 addTextNode(elem, text); 73 return elem; 74} 75 76/** 77 * Adds or removes a CSS class to |node|. 78 */ 79function changeClassName(node, classNameToAddOrRemove, isAdd) { 80 // Multiple classes can be separated by spaces. 81 var currentNames = node.className.split(' '); 82 83 if (isAdd) { 84 if (!(classNameToAddOrRemove in currentNames)) { 85 currentNames.push(classNameToAddOrRemove); 86 } 87 } else { 88 for (var i = 0; i < currentNames.length; ++i) { 89 if (currentNames[i] == classNameToAddOrRemove) { 90 currentNames.splice(i, 1); 91 break; 92 } 93 } 94 } 95 96 node.className = currentNames.join(' '); 97} 98 99function getKeyWithValue(map, value) { 100 for (key in map) { 101 if (map[key] == value) 102 return key; 103 } 104 return '?'; 105} 106 107/** 108 * Looks up |key| in |map|, and returns the resulting entry, if there is one. 109 * Otherwise, returns |key|. Intended primarily for use with incomplete 110 * tables, and for reasonable behavior with system enumerations that may be 111 * extended in the future. 112 */ 113function tryGetValueWithKey(map, key) { 114 if (key in map) 115 return map[key]; 116 return key; 117} 118 119/** 120 * Builds a string by repeating |str| |count| times. 121 */ 122function makeRepeatedString(str, count) { 123 var out = []; 124 for (var i = 0; i < count; ++i) 125 out.push(str); 126 return out.join(''); 127} 128 129/** 130 * TablePrinter is a helper to format a table as ascii art or an HTML table. 131 * 132 * Usage: call addRow() and addCell() repeatedly to specify the data. 133 * 134 * addHeaderCell() can optionally be called to specify header cells for a 135 * single header row. The header row appears at the top of an HTML formatted 136 * table, and uses thead and th tags. In ascii tables, the header is separated 137 * from the table body by a partial row of dashes. 138 * 139 * setTitle() can optionally be used to set a title that is displayed before 140 * the header row. In HTML tables, it uses the title class and in ascii tables 141 * it's between two rows of dashes. 142 * 143 * Once all the fields have been input, call toText() to format it as text or 144 * toHTML() to format it as HTML. 145 */ 146function TablePrinter() { 147 this.rows_ = []; 148 this.hasHeaderRow_ = false; 149 this.title_ = null; 150} 151 152/** 153 * Links are only used in HTML tables. 154 */ 155function TablePrinterCell(value) { 156 this.text = '' + value; 157 this.link = null; 158 this.alignRight = false; 159 this.allowOverflow = false; 160} 161 162/** 163 * Starts a new row. 164 */ 165TablePrinter.prototype.addRow = function() { 166 this.rows_.push([]); 167}; 168 169/** 170 * Adds a column to the current row, setting its value to cellText. 171 * 172 * @returns {!TablePrinterCell} the cell that was added. 173 */ 174TablePrinter.prototype.addCell = function(cellText) { 175 var r = this.rows_[this.rows_.length - 1]; 176 var cell = new TablePrinterCell(cellText); 177 r.push(cell); 178 return cell; 179}; 180 181TablePrinter.prototype.setTitle = function(title) { 182 this.title_ = title; 183}; 184 185/** 186 * Adds a header row, if not already present, and adds a new column to it, 187 * setting its contents to |headerText|. 188 * 189 * @returns {!TablePrinterCell} the cell that was added. 190 */ 191TablePrinter.prototype.addHeaderCell = function(headerText) { 192 // Insert empty new row at start of |rows_| if currently no header row. 193 if (!this.hasHeaderRow_) { 194 this.rows_.splice(0, 0, []); 195 this.hasHeaderRow_ = true; 196 } 197 var cell = new TablePrinterCell(headerText); 198 this.rows_[0].push(cell); 199 return cell; 200}; 201 202/** 203 * Returns the maximum number of columns this table contains. 204 */ 205TablePrinter.prototype.getNumColumns = function() { 206 var numColumns = 0; 207 for (var i = 0; i < this.rows_.length; ++i) { 208 numColumns = Math.max(numColumns, this.rows_[i].length); 209 } 210 return numColumns; 211} 212 213/** 214 * Returns the cell at position (rowIndex, columnIndex), or null if there is 215 * no such cell. 216 */ 217TablePrinter.prototype.getCell_ = function(rowIndex, columnIndex) { 218 if (rowIndex >= this.rows_.length) 219 return null; 220 var row = this.rows_[rowIndex]; 221 if (columnIndex >= row.length) 222 return null; 223 return row[columnIndex]; 224}; 225 226/** 227 * Returns a formatted text representation of the table data. 228 * |spacing| indicates number of extra spaces, if any, to add between 229 * columns. 230 */ 231TablePrinter.prototype.toText = function(spacing) { 232 var numColumns = this.getNumColumns(); 233 234 // Figure out the maximum width of each column. 235 var columnWidths = []; 236 columnWidths.length = numColumns; 237 for (var i = 0; i < numColumns; ++i) 238 columnWidths[i] = 0; 239 240 // If header row is present, temporarily add a spacer row to |rows_|. 241 if (this.hasHeaderRow_) { 242 var headerSpacerRow = []; 243 for (var c = 0; c < numColumns; ++c) { 244 var cell = this.getCell_(0, c); 245 if (!cell) 246 continue; 247 var spacerStr = makeRepeatedString('-', cell.text.length); 248 headerSpacerRow.push(new TablePrinterCell(spacerStr)); 249 } 250 this.rows_.splice(1, 0, headerSpacerRow); 251 } 252 253 var numRows = this.rows_.length; 254 for (var c = 0; c < numColumns; ++c) { 255 for (var r = 0; r < numRows; ++r) { 256 var cell = this.getCell_(r, c); 257 if (cell && !cell.allowOverflow) { 258 columnWidths[c] = Math.max(columnWidths[c], cell.text.length); 259 } 260 } 261 } 262 263 var out = []; 264 265 // Print title, if present. 266 if (this.title_) { 267 var titleSpacerStr = makeRepeatedString('-', this.title_.length); 268 out.push(titleSpacerStr); 269 out.push('\n'); 270 out.push(this.title_); 271 out.push('\n'); 272 out.push(titleSpacerStr); 273 out.push('\n'); 274 } 275 276 // Print each row. 277 var spacingStr = makeRepeatedString(' ', spacing); 278 for (var r = 0; r < numRows; ++r) { 279 for (var c = 0; c < numColumns; ++c) { 280 var cell = this.getCell_(r, c); 281 if (cell) { 282 // Pad the cell with spaces to make it fit the maximum column width. 283 var padding = columnWidths[c] - cell.text.length; 284 var paddingStr = makeRepeatedString(' ', padding); 285 286 if (cell.alignRight) { 287 out.push(paddingStr); 288 out.push(cell.text); 289 } else { 290 out.push(cell.text); 291 out.push(paddingStr); 292 } 293 out.push(spacingStr); 294 } 295 } 296 out.push('\n'); 297 } 298 299 // Remove spacer row under the header row, if one was added. 300 if (this.hasHeaderRow_) 301 this.rows_.splice(1, 1); 302 303 return out.join(''); 304}; 305 306/** 307 * Adds a new HTML table to the node |parent| using the specified style. 308 */ 309TablePrinter.prototype.toHTML = function(parent, style) { 310 var numRows = this.rows_.length; 311 var numColumns = this.getNumColumns(); 312 313 var table = addNode(parent, 'table'); 314 table.setAttribute('class', style); 315 316 var thead = addNode(table, 'thead'); 317 var tbody = addNode(table, 'tbody'); 318 319 // Add title, if needed. 320 if (this.title_) { 321 var tableTitleRow = addNode(thead, 'tr'); 322 var tableTitle = addNodeWithText(tableTitleRow, 'th', this.title_); 323 tableTitle.colSpan = numColumns; 324 changeClassName(tableTitle, 'title', true); 325 } 326 327 // Fill table body, adding header row first, if needed. 328 for (var r = 0; r < numRows; ++r) { 329 var cellType; 330 var row; 331 if (r == 0 && this.hasHeaderRow_) { 332 row = addNode(thead, 'tr'); 333 cellType = 'th'; 334 } else { 335 row = addNode(tbody, 'tr'); 336 cellType = 'td'; 337 } 338 for (var c = 0; c < numColumns; ++c) { 339 var cell = this.getCell_(r, c); 340 if (cell) { 341 var tableCell = addNode(row, cellType, cell.text); 342 if (cell.alignRight) 343 tableCell.alignRight = true; 344 // If allowing overflow on the rightmost cell of a row, 345 // make the cell span the rest of the columns. Otherwise, 346 // ignore the flag. 347 if (cell.allowOverflow && !this.getCell_(r, c + 1)) 348 tableCell.colSpan = numColumns - c; 349 if (cell.link) { 350 var linkNode = addNodeWithText(tableCell, 'a', cell.text); 351 linkNode.href = cell.link; 352 } else { 353 addTextNode(tableCell, cell.text); 354 } 355 } 356 } 357 } 358 return table; 359}; 360 361