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) #### Terminal (from Box)
A box which spins up a pseudo terminal and renders the output. Useful for 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); 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', ['bg', 'fg', 'bold', 'underline',
'blink', 'inverse', 'invisible'].forEach(function(name) { 'blink', 'inverse', 'invisible'].forEach(function(name) {
options[name] = function() { options[name] = function() {
@ -6783,7 +6789,6 @@ Log.prototype.add = function(text) {
/** /**
* Table * Table
* TODO: Draw custom border with proper angles.
*/ */
function Table(options) { function Table(options) {
@ -6795,6 +6800,11 @@ function Table(options) {
options = options || {}; options = options || {};
options.shrink = true; 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); Box.call(this, options);
@ -6804,10 +6814,16 @@ function Table(options) {
? options.pad ? options.pad
: 2; : 2;
this.__align = options.align || 'center';
delete this.align;
this.setData(options.rows || options.data); 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; Table.prototype.__proto__ = Box.prototype;
@ -6820,16 +6836,20 @@ Table.prototype.setData = function(rows) {
, text = '' , text = ''
, maxes = [] , maxes = []
, total = 0 , 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 = rows || [];
this.rows.forEach(function(row) { this.rows.forEach(function(row) {
row.forEach(function(cell, i) { row.forEach(function(cell, i) {
if (!maxes[i] || maxes[i] < self._cellLength(cell) + self.pad) { var clen = self._cellLength(cell);
maxes[i] = self._cellLength(cell) + self.pad; 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; total += cell + 1 + self.pad;
}); });
// TODO Use these values to render the border
// elsewhere, in the usual border rendering.
this._maxes = maxes; this._maxes = maxes;
maxes.forEach(function(width, i) { // maxes.forEach(function(width, i) {
if (i !== 0) { // if (i !== 0) {
line += '\u253c'; // '┼' // line += '\u253c'; // '┼'
} // }
for (var i = 0; i < width; i++) { // for (var i = 0; i < width; i++) {
line += '\u2500'; // '─' // line += '\u2500'; // '─'
} // }
}); // });
line = generateTags(self.style.border, line); // line = sborder.open + line + sborder.close;
this.rows.forEach(function(row, i) { this.rows.forEach(function(row, i) {
var isHeader = i === 0; var isHeader = i === 0;
var isFooter = i === self.rows.length - 1; var isFooter = i === self.rows.length - 1;
row.forEach(function(cell, i) { row.forEach(function(cell, i) {
var width = maxes[i]; var width = maxes[i];
var clen = self._cellLength(cell);
if (i !== 0) { if (i !== 0) {
text += generateTags(self.style.border, '\u2502'); // '│' // text += sborder.open + '\u2502' + sborder.close; // '│'
text += ' ';
} }
while (self._cellLength(cell) < width) { while (clen < width) {
if (self.__align === 'center') { if (align === 'center') {
cell = ' ' + cell + ' '; cell = ' ' + cell + ' ';
} else if (self.__align === 'left') { clen += 2;
} else if (align === 'left') {
cell = cell + ' '; cell = cell + ' ';
} else if (self.__align === 'right') { clen += 1;
} else if (align === 'right') {
cell = ' ' + cell; cell = ' ' + cell;
clen += 1;
} }
} }
if (self._cellLength(cell) > width) { if (clen > width) {
if (self.__align === 'center') { if (align === 'center') {
// cell = cell.slice(0, -1); // cell = cell.slice(0, -1);
cell = cell.substring(1); cell = cell.substring(1);
} else if (self.__align === 'left') { clen--;
} else if (align === 'left') {
cell = cell.slice(0, -1); cell = cell.slice(0, -1);
} else if (self.__align === 'right') { clen--;
} else if (align === 'right') {
cell = cell.substring(1); cell = cell.substring(1);
clen--;
} }
} }
@ -6889,16 +6915,17 @@ Table.prototype.setData = function(rows) {
} }
if (isHeader) { if (isHeader) {
cell = generateTags(self.style.header, cell); cell = sheader.open + cell + sheader.close;
} else { } else {
cell = generateTags(self.style.cell, cell); cell = scell.open + cell + scell.close;
} }
text += cell; text += cell;
}); });
text += '\n'; text += '\n';
if (!isFooter) { if (!isFooter) {
text += line + '\n'; // text += line + '\n';
text += '\n';
} }
}); });
@ -6906,7 +6933,9 @@ Table.prototype.setData = function(rows) {
text = text.slice(0, -1); text = text.slice(0, -1);
} }
return this.setContent(text); delete this.align;
this.setContent(text);
this.align = align;
}; };
Table.prototype._cellLength = function(text) { Table.prototype._cellLength = function(text) {
@ -6916,6 +6945,362 @@ Table.prototype._cellLength = function(text) {
return text.replace(/{(\/?)([\w\-,;!#]*)}/g, '').length; 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 * Terminal
*/ */
@ -7658,7 +8043,7 @@ function generateTags(style, text) {
open = '{' + val + '-' + key + '}' + open; open = '{' + val + '-' + key + '}' + open;
close += '{/' + val + '-' + key + '}'; close += '{/' + val + '-' + key + '}';
} else { } else {
if (val) { if (val === true) {
open = '{' + key + '}' + open; open = '{' + key + '}' + open;
close += '{/' + key + '}'; close += '{/' + key + '}';
} }
@ -7798,6 +8183,7 @@ exports.Listbar = exports.listbar = Listbar;
exports.Log = exports.log = Log; exports.Log = exports.log = Log;
exports.Table = exports.table = Table; exports.Table = exports.table = Table;
exports.ListTable = exports.listtable = ListTable;
exports.Terminal = exports.terminal = Terminal; exports.Terminal = exports.terminal = Terminal;
exports.Image = exports.image = Image; 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({ screen = blessed.screen({
dump: __dirname + '/logs/table.log', dump: __dirname + '/logs/table.log',
autoPadding: true autoPadding: false
}); });
var table = blessed.table({ var table = blessed.table({
@ -13,6 +13,7 @@ var table = blessed.table({
data: null, data: null,
border: 'line', border: 'line',
align: 'center', align: 'center',
tags: true,
style: { style: {
border: { border: {
fg: 'red' fg: 'red'
@ -27,11 +28,15 @@ var table = blessed.table({
} }
}); });
table.setData([ var data = [
[ 'Animals', 'Foods', 'Times' ], [ 'Animals', 'Foods', 'Times' ],
[ 'Elephant', 'Apple', '1:00am' ], [ 'Elephant', 'Apple', '1:00am' ],
[ 'Bird', 'Orange', '2:15pm' ] [ 'Bird', 'Orange', '2:15pm' ]
]); ];
data[1][0] = '{red-fg}' + data[1][0] + '{/red-fg}';
table.setData(data);
screen.key('q', function() { screen.key('q', function() {
return process.exit(0); return process.exit(0);