// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Inherit the prototype methods from one constructor into another.
*/
function inherits(childCtor, parentCtor) {
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor;
};
/**
* Sets the width (in pixels) on a DOM node.
*/
function setNodeWidth(node, widthPx) {
node.style.width = widthPx.toFixed(0) + 'px';
}
/**
* Sets the height (in pixels) on a DOM node.
*/
function setNodeHeight(node, heightPx) {
node.style.height = heightPx.toFixed(0) + 'px';
}
/**
* Sets the position and size of a DOM node (in pixels).
*/
function setNodePosition(node, leftPx, topPx, widthPx, heightPx) {
node.style.left = leftPx.toFixed(0) + 'px';
node.style.top = topPx.toFixed(0) + 'px';
setNodeWidth(node, widthPx);
setNodeHeight(node, heightPx);
}
/**
* Sets the visibility for a DOM node.
*/
function setNodeDisplay(node, isVisible) {
node.style.display = isVisible ? '' : 'none';
}
/**
* Adds a node to |parentNode|, of type |tagName|.
*/
function addNode(parentNode, tagName) {
var elem = parentNode.ownerDocument.createElement(tagName);
parentNode.appendChild(elem);
return elem;
}
/**
* Adds |text| to node |parentNode|.
*/
function addTextNode(parentNode, text) {
var textNode = parentNode.ownerDocument.createTextNode(text);
parentNode.appendChild(textNode);
return textNode;
}
/**
* Adds a node to |parentNode|, of type |tagName|. Then adds
* |text| to the new node.
*/
function addNodeWithText(parentNode, tagName, text) {
var elem = parentNode.ownerDocument.createElement(tagName);
parentNode.appendChild(elem);
addTextNode(elem, text);
return elem;
}
/**
* Adds or removes a CSS class to |node|.
*/
function changeClassName(node, classNameToAddOrRemove, isAdd) {
// Multiple classes can be separated by spaces.
var currentNames = node.className.split(' ');
if (isAdd) {
if (!(classNameToAddOrRemove in currentNames)) {
currentNames.push(classNameToAddOrRemove);
}
} else {
for (var i = 0; i < currentNames.length; ++i) {
if (currentNames[i] == classNameToAddOrRemove) {
currentNames.splice(i, 1);
break;
}
}
}
node.className = currentNames.join(' ');
}
function getKeyWithValue(map, value) {
for (key in map) {
if (map[key] == value)
return key;
}
return '?';
}
/**
* Looks up |key| in |map|, and returns the resulting entry, if there is one.
* Otherwise, returns |key|. Intended primarily for use with incomplete
* tables, and for reasonable behavior with system enumerations that may be
* extended in the future.
*/
function tryGetValueWithKey(map, key) {
if (key in map)
return map[key];
return key;
}
/**
* Builds a string by repeating |str| |count| times.
*/
function makeRepeatedString(str, count) {
var out = [];
for (var i = 0; i < count; ++i)
out.push(str);
return out.join('');
}
/**
* TablePrinter is a helper to format a table as ascii art or an HTML table.
*
* Usage: call addRow() and addCell() repeatedly to specify the data.
*
* addHeaderCell() can optionally be called to specify header cells for a
* single header row. The header row appears at the top of an HTML formatted
* table, and uses thead and th tags. In ascii tables, the header is separated
* from the table body by a partial row of dashes.
*
* setTitle() can optionally be used to set a title that is displayed before
* the header row. In HTML tables, it uses the title class and in ascii tables
* it's between two rows of dashes.
*
* Once all the fields have been input, call toText() to format it as text or
* toHTML() to format it as HTML.
*/
function TablePrinter() {
this.rows_ = [];
this.hasHeaderRow_ = false;
this.title_ = null;
}
/**
* Links are only used in HTML tables.
*/
function TablePrinterCell(value) {
this.text = '' + value;
this.link = null;
this.alignRight = false;
this.allowOverflow = false;
}
/**
* Starts a new row.
*/
TablePrinter.prototype.addRow = function() {
this.rows_.push([]);
};
/**
* Adds a column to the current row, setting its value to cellText.
*
* @returns {!TablePrinterCell} the cell that was added.
*/
TablePrinter.prototype.addCell = function(cellText) {
var r = this.rows_[this.rows_.length - 1];
var cell = new TablePrinterCell(cellText);
r.push(cell);
return cell;
};
TablePrinter.prototype.setTitle = function(title) {
this.title_ = title;
};
/**
* Adds a header row, if not already present, and adds a new column to it,
* setting its contents to |headerText|.
*
* @returns {!TablePrinterCell} the cell that was added.
*/
TablePrinter.prototype.addHeaderCell = function(headerText) {
// Insert empty new row at start of |rows_| if currently no header row.
if (!this.hasHeaderRow_) {
this.rows_.splice(0, 0, []);
this.hasHeaderRow_ = true;
}
var cell = new TablePrinterCell(headerText);
this.rows_[0].push(cell);
return cell;
};
/**
* Returns the maximum number of columns this table contains.
*/
TablePrinter.prototype.getNumColumns = function() {
var numColumns = 0;
for (var i = 0; i < this.rows_.length; ++i) {
numColumns = Math.max(numColumns, this.rows_[i].length);
}
return numColumns;
}
/**
* Returns the cell at position (rowIndex, columnIndex), or null if there is
* no such cell.
*/
TablePrinter.prototype.getCell_ = function(rowIndex, columnIndex) {
if (rowIndex >= this.rows_.length)
return null;
var row = this.rows_[rowIndex];
if (columnIndex >= row.length)
return null;
return row[columnIndex];
};
/**
* Returns a formatted text representation of the table data.
* |spacing| indicates number of extra spaces, if any, to add between
* columns.
*/
TablePrinter.prototype.toText = function(spacing) {
var numColumns = this.getNumColumns();
// Figure out the maximum width of each column.
var columnWidths = [];
columnWidths.length = numColumns;
for (var i = 0; i < numColumns; ++i)
columnWidths[i] = 0;
// If header row is present, temporarily add a spacer row to |rows_|.
if (this.hasHeaderRow_) {
var headerSpacerRow = [];
for (var c = 0; c < numColumns; ++c) {
var cell = this.getCell_(0, c);
if (!cell)
continue;
var spacerStr = makeRepeatedString('-', cell.text.length);
headerSpacerRow.push(new TablePrinterCell(spacerStr));
}
this.rows_.splice(1, 0, headerSpacerRow);
}
var numRows = this.rows_.length;
for (var c = 0; c < numColumns; ++c) {
for (var r = 0; r < numRows; ++r) {
var cell = this.getCell_(r, c);
if (cell && !cell.allowOverflow) {
columnWidths[c] = Math.max(columnWidths[c], cell.text.length);
}
}
}
var out = [];
// Print title, if present.
if (this.title_) {
var titleSpacerStr = makeRepeatedString('-', this.title_.length);
out.push(titleSpacerStr);
out.push('\n');
out.push(this.title_);
out.push('\n');
out.push(titleSpacerStr);
out.push('\n');
}
// Print each row.
var spacingStr = makeRepeatedString(' ', spacing);
for (var r = 0; r < numRows; ++r) {
for (var c = 0; c < numColumns; ++c) {
var cell = this.getCell_(r, c);
if (cell) {
// Pad the cell with spaces to make it fit the maximum column width.
var padding = columnWidths[c] - cell.text.length;
var paddingStr = makeRepeatedString(' ', padding);
if (cell.alignRight) {
out.push(paddingStr);
out.push(cell.text);
} else {
out.push(cell.text);
out.push(paddingStr);
}
out.push(spacingStr);
}
}
out.push('\n');
}
// Remove spacer row under the header row, if one was added.
if (this.hasHeaderRow_)
this.rows_.splice(1, 1);
return out.join('');
};
/**
* Adds a new HTML table to the node |parent| using the specified style.
*/
TablePrinter.prototype.toHTML = function(parent, style) {
var numRows = this.rows_.length;
var numColumns = this.getNumColumns();
var table = addNode(parent, 'table');
table.setAttribute('class', style);
var thead = addNode(table, 'thead');
var tbody = addNode(table, 'tbody');
// Add title, if needed.
if (this.title_) {
var tableTitleRow = addNode(thead, 'tr');
var tableTitle = addNodeWithText(tableTitleRow, 'th', this.title_);
tableTitle.colSpan = numColumns;
changeClassName(tableTitle, 'title', true);
}
// Fill table body, adding header row first, if needed.
for (var r = 0; r < numRows; ++r) {
var cellType;
var row;
if (r == 0 && this.hasHeaderRow_) {
row = addNode(thead, 'tr');
cellType = 'th';
} else {
row = addNode(tbody, 'tr');
cellType = 'td';
}
for (var c = 0; c < numColumns; ++c) {
var cell = this.getCell_(r, c);
if (cell) {
var tableCell = addNode(row, cellType, cell.text);
if (cell.alignRight)
tableCell.alignRight = true;
// If allowing overflow on the rightmost cell of a row,
// make the cell span the rest of the columns. Otherwise,
// ignore the flag.
if (cell.allowOverflow && !this.getCell_(r, c + 1))
tableCell.colSpan = numColumns - c;
if (cell.link) {
var linkNode = addNodeWithText(tableCell, 'a', cell.text);
linkNode.href = cell.link;
} else {
addTextNode(tableCell, cell.text);
}
}
}
}
return table;
};