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