optimize Table element. first pass at ListTable.

This commit is contained in:
Christopher Jeffrey 2015-03-31 00:25:38 -07:00
parent cbc94254b4
commit 8793be5562
4 changed files with 516 additions and 36 deletions

View File

@ -1076,6 +1076,41 @@ A stylized table of text elements.
]);
```
#### ListTable (from Box)
A stylized table of text elements with a list.
##### Options:
- inherits all from Box.
- __rows/data__ - array of array of strings representing rows.
- __pad__ - spaces to attempt to pad on the sides of each cell. `2` by default:
one space on each side.
- __style.header__ - header style.
- __style.cell__ - cell style.
##### Properties:
- inherits all from Box.
##### Events:
- inherits all from Box.
##### Methods:
- inherits all from Box.
- __setRows/setData(rows)__ - set rows in table. array of arrays of strings.
```
table.setData([
[ 'Animals', 'Foods' ],
[ 'Elephant', 'Apple' ],
[ 'Bird', 'Orange' ]
]);
```
#### Terminal (from Box)
A box which spins up a pseudo terminal and renders the output. Useful for

View File

@ -4658,6 +4658,12 @@ List.prototype.appendItem = function(item) {
options.right = this.iright + (this.scrollbar ? 1 : 0);
}
// if (this.shrink) {
if (this.shrink && this.options.normalShrink) {
delete options.right;
options.width = 'shrink';
}
['bg', 'fg', 'bold', 'underline',
'blink', 'inverse', 'invisible'].forEach(function(name) {
options[name] = function() {
@ -6783,7 +6789,6 @@ Log.prototype.add = function(text) {
/**
* Table
* TODO: Draw custom border with proper angles.
*/
function Table(options) {
@ -6795,6 +6800,11 @@ function Table(options) {
options = options || {};
options.shrink = true;
options.style = options.style || {};
options.style.border = options.style.border || {};
options.style.header = options.style.header || {};
options.style.cell = options.style.cell || {};
options.align = options.align || 'center';
Box.call(this, options);
@ -6804,10 +6814,16 @@ function Table(options) {
? options.pad
: 2;
this.__align = options.align || 'center';
delete this.align;
this.setData(options.rows || options.data);
this.screen.on('resize', function() {
var rows = self.rows;
// XXX Need to clear previous box.
self.setData([]);
self.screen.render();
self.setData(rows);
self.screen.render();
});
}
Table.prototype.__proto__ = Box.prototype;
@ -6820,16 +6836,20 @@ Table.prototype.setData = function(rows) {
, text = ''
, maxes = []
, total = 0
, line = '';
, line = ''
, align = this.align;
// TODO Pregenerate `generateTags` calls here!
var sborder = generateTags(this.style.border)
, sheader = generateTags(this.style.header)
, scell = generateTags(this.style.cell);
this.rows = rows || [];
this.rows.forEach(function(row) {
row.forEach(function(cell, i) {
if (!maxes[i] || maxes[i] < self._cellLength(cell) + self.pad) {
maxes[i] = self._cellLength(cell) + self.pad;
var clen = self._cellLength(cell);
if (!maxes[i] || maxes[i] < clen + self.pad) {
maxes[i] = clen + self.pad;
}
});
});
@ -6838,48 +6858,54 @@ Table.prototype.setData = function(rows) {
total += cell + 1 + self.pad;
});
// TODO Use these values to render the border
// elsewhere, in the usual border rendering.
this._maxes = maxes;
maxes.forEach(function(width, i) {
if (i !== 0) {
line += '\u253c'; // '┼'
}
for (var i = 0; i < width; i++) {
line += '\u2500'; // '─'
}
});
line = generateTags(self.style.border, line);
// maxes.forEach(function(width, i) {
// if (i !== 0) {
// line += '\u253c'; // '┼'
// }
// for (var i = 0; i < width; i++) {
// line += '\u2500'; // '─'
// }
// });
// line = sborder.open + line + sborder.close;
this.rows.forEach(function(row, i) {
var isHeader = i === 0;
var isFooter = i === self.rows.length - 1;
row.forEach(function(cell, i) {
var width = maxes[i];
var clen = self._cellLength(cell);
if (i !== 0) {
text += generateTags(self.style.border, '\u2502'); // '│'
// text += sborder.open + '\u2502' + sborder.close; // '│'
text += ' ';
}
while (self._cellLength(cell) < width) {
if (self.__align === 'center') {
while (clen < width) {
if (align === 'center') {
cell = ' ' + cell + ' ';
} else if (self.__align === 'left') {
clen += 2;
} else if (align === 'left') {
cell = cell + ' ';
} else if (self.__align === 'right') {
clen += 1;
} else if (align === 'right') {
cell = ' ' + cell;
clen += 1;
}
}
if (self._cellLength(cell) > width) {
if (self.__align === 'center') {
if (clen > width) {
if (align === 'center') {
// cell = cell.slice(0, -1);
cell = cell.substring(1);
} else if (self.__align === 'left') {
clen--;
} else if (align === 'left') {
cell = cell.slice(0, -1);
} else if (self.__align === 'right') {
clen--;
} else if (align === 'right') {
cell = cell.substring(1);
clen--;
}
}
@ -6889,16 +6915,17 @@ Table.prototype.setData = function(rows) {
}
if (isHeader) {
cell = generateTags(self.style.header, cell);
cell = sheader.open + cell + sheader.close;
} else {
cell = generateTags(self.style.cell, cell);
cell = scell.open + cell + scell.close;
}
text += cell;
});
text += '\n';
if (!isFooter) {
text += line + '\n';
// text += line + '\n';
text += '\n';
}
});
@ -6906,7 +6933,9 @@ Table.prototype.setData = function(rows) {
text = text.slice(0, -1);
}
return this.setContent(text);
delete this.align;
this.setContent(text);
this.align = align;
};
Table.prototype._cellLength = function(text) {
@ -6916,6 +6945,362 @@ Table.prototype._cellLength = function(text) {
return text.replace(/{(\/?)([\w\-,;!#]*)}/g, '').length;
};
Table.prototype.render = function() {
var self = this;
var coords = this._render();
if (!coords) return;
if (!this._maxes) return coords;
var lines = this.screen.lines
, xi = coords.xi
, xl = coords.xl
, yi = coords.yi
, yl = coords.yl
, rx
, ry
, i;
var header = this.sattr(
this.style.header,
this.style.header.fg,
this.style.header.bg);
var cell = this.sattr(
this.style.cell,
this.style.cell.fg,
this.style.cell.bg);
var battr = this.sattr(
this.style.border,
this.style.border.fg,
this.style.border.bg);
var width = coords.xl - coords.xi - this.iwidth / 2
, height = coords.yl - coords.yi - this.iheight / 2;
// Apply attributes to header cells and cells.
// Problem: Does not allow for tags in cells.
// for (var y = this.iheight / 2; y < height; y++) {
// for (var x = this.iwidth / 2; x < width; x++) {
// if (y === this.iheight / 2) {
// lines[yi + y][xi + x][0] = header;
// } else {
// lines[yi + y][xi + x][0] = cell;
// }
// }
// }
if (!this.border) return coords;
// Draw border with correct angles.
ry = 0;
for (i = 0; i < self.rows.length + 1; i++) {
rx = 0;
self._maxes.forEach(function(max, i) {
rx += max;
if (i === 0) {
// left side
if (ry === 0) {
// top
lines[yi + ry][xi + 0][0] = battr;
lines[yi + ry][xi + 0][1] = '\u250c'; // '┌'
} else if (ry / 2 === self.rows.length) {
// bottom
lines[yi + ry][xi + 0][0] = battr;
lines[yi + ry][xi + 0][1] = '\u2514'; // '└'
} else {
// middle
lines[yi + ry][xi + 0][0] = battr;
lines[yi + ry][xi + 0][1] = '\u251c'; // '├'
}
} else if (i === self.rows.length - 1) {
// right side
if (ry === 0) {
// top
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u2510'; // '┐'
} else if (ry / 2 === self.rows.length) {
// bottom
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u2518'; // '┘'
} else {
// middle
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u2524'; // '┤'
}
return;
}
// center
if (ry === 0) {
// top
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u252c'; // '┬'
} else if (ry / 2 === self.rows.length) {
// bottom
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u2534'; // '┴'
} else {
// middle
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u253c'; // '┼'
// ++rx;
}
});
ry += 2;
}
// Draw internal borders.
for (ry = 1; ry < self.rows.length * 2; ry++) {
rx = 0;
self._maxes.slice(0, -1).forEach(function(max, i) {
rx += max;
if (ry % 2 !== 0) {
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
} else {
rx++;
}
});
rx = 1;
self._maxes.forEach(function(max, i) {
while (max--) {
if (ry % 2 === 0) {
lines[yi + ry][xi + rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u2500'; // '─'
}
rx++;
}
// if (ry % 2 === 0 && i !== self._maxes.length - 1) {
// lines[yi + ry][xi + rx][0] = battr;
// lines[yi + ry][xi + rx][1] = '\u253c'; // '┼'
// }
rx++;
});
}
return coords;
};
/**
* ListTable
*/
function ListTable(options) {
var self = this;
if (!(this instanceof Node)) {
return new ListTable(options);
}
options = options || {};
options.width = 'shrink';
options.normalShrink = true;
options.style = options.style || {};
options.style.border = options.style.border || {};
options.style.header = options.style.header || {};
options.style.cell = options.style.cell || {};
this.__align = options.align || 'center';
delete options.align;
options.style.selected = options.style.cell.selected;
options.style.item = options.style.cell;
List.call(this, options);
this._header = new Box({
parent: this,
left: this.screen.autoPadding ? 0 : this.ileft,
top: 0,
width: 'shrink',
height: 1,
style: options.style.header,
tags: options.parseTags || options.tags
});
this.on('scroll', function() {
self._header.setFront();
var visible = self.height - self.iheight;
self._header.rtop = 1 + self.childBase - (self.border ? 1 : 0);
if (!self.screen.autoPadding) {
self._header.rtop = 1 + self.childBase;
}
// self.screen.render();
});
this.pad = options.pad != null
? options.pad
: 2;
this.setData(options.rows || options.data);
}
ListTable.prototype.__proto__ = List.prototype;
ListTable.prototype.type = 'list-table';
ListTable.prototype.setRows =
ListTable.prototype.setData = function(rows) {
var self = this
, maxes = []
, total = 0
, align = this.__align;
this.clearItems();
this.rows = rows || [];
this.rows.forEach(function(row) {
row.forEach(function(cell, i) {
var clen = self._cellLength(cell);
if (!maxes[i] || maxes[i] < clen + self.pad) {
maxes[i] = clen + self.pad;
}
});
});
maxes.forEach(function(cell) {
total += cell + 1 + self.pad;
});
this._maxes = maxes;
this.addItem('');
this.rows.forEach(function(row, i) {
var isHeader = i === 0;
var isFooter = i === self.rows.length - 1;
var text = '';
row.forEach(function(cell, i) {
var width = maxes[i];
var clen = self._cellLength(cell);
if (i !== 0) {
text += ' ';
}
while (clen < width) {
if (align === 'center') {
cell = ' ' + cell + ' ';
clen += 2;
} else if (align === 'left') {
cell = cell + ' ';
clen += 1;
} else if (align === 'right') {
cell = ' ' + cell;
clen += 1;
}
}
if (clen > width) {
if (align === 'center') {
cell = cell.substring(1);
clen--;
} else if (align === 'left') {
cell = cell.slice(0, -1);
clen--;
} else if (align === 'right') {
cell = cell.substring(1);
clen--;
}
}
text += cell;
});
if (isHeader) {
self._header.setContent(text);
} else {
self.addItem(text);
}
});
this._header.setFront();
this.select(0);
};
ListTable.prototype._select = ListTable.prototype.select;
ListTable.prototype.select = function(i) {
if (i === 0) {
i = 1;
}
if (i <= 1) {
this.setScroll(0);
}
return this._select(i);
};
ListTable.prototype._cellLength = function(text) {
if (!this.options.tags && !this.options.parseTags) {
return text.length;
}
return text.replace(/{(\/?)([\w\-,;!#]*)}/g, '').length;
};
ListTable.prototype.render = function() {
var self = this;
var coords = this._render();
if (!coords) return;
if (!this._maxes) return coords;
var lines = this.screen.lines
, xi = coords.xi
, xl = coords.xl
, yi = coords.yi
, yl = coords.yl
, rx
, ry
, i;
var battr = this.sattr(
this.style.border,
this.style.border.fg,
this.style.border.bg);
var width = coords.xl - coords.xi - this.iwidth / 2
, height = coords.yl - coords.yi - this.iheight / 2;
if (!this.border) return coords;
// Draw border with correct angles.
ry = 0;
for (i = 0; i < height + 1; i++) {
rx = 0;
self._maxes.slice(0, -1).forEach(function(max, i) {
rx += max;
// center
if (ry === 0) {
// top
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u252c'; // '┬'
} else if (ry === height) {
// bottom
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u2534'; // '┴'
} else {
// middle
++rx;
}
});
ry += 1;
}
// Draw internal borders.
for (ry = 1; ry < height; ry++) {
rx = 0;
self._maxes.slice(0, -1).forEach(function(max, i) {
rx += max;
lines[yi + ry][xi + ++rx][0] = battr;
lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
});
}
return coords;
};
/**
* Terminal
*/
@ -7658,7 +8043,7 @@ function generateTags(style, text) {
open = '{' + val + '-' + key + '}' + open;
close += '{/' + val + '-' + key + '}';
} else {
if (val) {
if (val === true) {
open = '{' + key + '}' + open;
close += '{/' + key + '}';
}
@ -7798,6 +8183,7 @@ exports.Listbar = exports.listbar = Listbar;
exports.Log = exports.log = Log;
exports.Table = exports.table = Table;
exports.ListTable = exports.listtable = ListTable;
exports.Terminal = exports.terminal = Terminal;
exports.Image = exports.image = Image;

54
test/widget-listtable.js Normal file
View File

@ -0,0 +1,54 @@
var blessed = require('../')
, screen;
screen = blessed.screen({
dump: __dirname + '/logs/listtable.log',
autoPadding: false
});
var table = blessed.listtable({
parent: screen,
top: 'center',
left: 'center',
data: null,
border: 'line',
align: 'center',
tags: true,
keys: true,
height: 4,
vi: true,
mouse: true,
style: {
border: {
fg: 'red'
},
header: {
fg: 'blue',
bold: true
},
cell: {
fg: 'magenta',
selected: {
bg: 'blue'
}
}
}
});
var data = [
[ 'Animals', 'Foods', 'Times' ],
[ 'Elephant', 'Apple', '1:00am' ],
[ 'Bird', 'Orange', '2:15pm' ]
];
data[1][0] = '{red-fg}' + data[1][0] + '{/red-fg}';
table.setData(data);
table.focus();
screen.key('q', function() {
return process.exit(0);
});
screen.render();

View File

@ -3,7 +3,7 @@ var blessed = require('../')
screen = blessed.screen({
dump: __dirname + '/logs/table.log',
autoPadding: true
autoPadding: false
});
var table = blessed.table({
@ -13,6 +13,7 @@ var table = blessed.table({
data: null,
border: 'line',
align: 'center',
tags: true,
style: {
border: {
fg: 'red'
@ -27,11 +28,15 @@ var table = blessed.table({
}
});
table.setData([
var data = [
[ 'Animals', 'Foods', 'Times' ],
[ 'Elephant', 'Apple', '1:00am' ],
[ 'Bird', 'Orange', '2:15pm' ]
]);
];
data[1][0] = '{red-fg}' + data[1][0] + '{/red-fg}';
table.setData(data);
screen.key('q', function() {
return process.exit(0);