From 91bc189ba3fd6ef4fc78b2f832fe2b8b494a876d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 30 Mar 2015 04:58:15 -0700 Subject: [PATCH] add Log and Table elements. update README. --- README.md | 186 +++++++++++++++++++++++++++++++++++++++---- lib/widget.js | 152 ++++++++++++++++++++++++++++++++++- test/widget-table.js | 39 +++++++++ 3 files changed, 361 insertions(+), 16 deletions(-) create mode 100644 test/widget-table.js diff --git a/README.md b/README.md index 245be7f..b20946b 100644 --- a/README.md +++ b/README.md @@ -544,14 +544,16 @@ A box with scrollable content. - __baseLimit__ - a limit to the childBase. default is `Infinity`. - __alwaysScroll__ - a option which causes the ignoring of `childOffset`. this in turn causes the childBase to change every time the element is scrolled. -- __scrollbar__ - object enabling a scrollbar. allows `ch`, `fg`, and `bg` - properties. +- __scrollbar__ - object enabling a scrollbar. +- __scrollbar.style__ - style of the scrollbar. +- __scrollbar.track__ - style of the scrollbar track if present (takes regular + style options). ##### Properties: - inherits all from Box. - __childBase__ - the offset of the top of the scroll content. -- __childOffset__ - the offset of the chosen item (if there is one). +- __childOffset__ - the offset of the chosen item/line. ##### Events: @@ -605,16 +607,8 @@ A scrollable list which can display selectable items. ##### Options: - inherits all from Box. -- __selectedFg, selectedBg__ - foreground and background for selected item, - treated like fg and bg. (can be contained in style: e.g. `style.selected.fg`). -- __selectedBold, selectedUnderline__ - character attributes for selected item, - treated like bold and underline. (can be contained in style: e.g. - `style.selected.bold`). -- __itemFg, itemBg__ - foreground and background for unselected item, - treated like fg and bg. (can be contained in style: e.g. `style.item.fg`). -- __itemBold, itemUnderline__ - character attributes for an unselected item, - treated like bold and underline. (can be contained in style: e.g. - `style.item.bold`). +- __style.selected__ - style for a selected item. +- __style.item__ - style for an unselected item. - __mouse__ - whether to automatically enable mouse support for this list (allows clicking items). - __keys__ - use predefined keys for navigating the list. @@ -790,8 +784,7 @@ A progress bar allowing various styles. This can also be used as a form input. - inherits all from Input. - __orientation__ - can be `horizontal` or `vertical`. -- __barFg, barBg__ - (completed) bar foreground and background. - (can be contained in `style`: e.g. `style.bar.fg`). +- __style.bar__ - style of the bar contents itself. - __pch__ - the character to fill the bar with (default is space). - __filled__ - the amount filled (0 - 100). - __value__ - same as `filled`. @@ -920,6 +913,169 @@ A radio button which can be used in a form element. - inherits all from Checkbox. +#### Prompt (from Box) + +A prompt box containing a text input, okay, and cancel buttons. + +##### Options: + +- inherits all from Box. + +##### Properties: + +- inherits all from Box. + +##### Events: + +- inherits all from Box. + +##### Methods: + +- inherits all from Box. + + +#### Question (from Box) + +A question box containing okay and cancel buttons. + +##### Options: + +- inherits all from Box. + +##### Properties: + +- inherits all from Box. + +##### Events: + +- inherits all from Box. + +##### Methods: + +- inherits all from Box. + + +#### Message (from Box) + +A box containing a message to be displayed. + +##### Options: + +- inherits all from Box. + +##### Properties: + +- inherits all from Box. + +##### Events: + +- inherits all from Box. + +##### Methods: + +- inherits all from Box. + + +#### Loading (from Box) + +A box with a spinning line to denote loading. + +##### Options: + +- inherits all from Box. + +##### Properties: + +- inherits all from Box. + +##### Events: + +- inherits all from Box. + +##### Methods: + +- inherits all from Box. + + +#### Listbar (from Box) + +A horizontal list. Useful for a main menu bar. + +##### Options: + +- inherits all from Box. +- __style.selected__ - style for a selected item. +- __style.item__ - style for an unselected item. + +##### Properties: + +- inherits all from Box. + +##### Events: + +- inherits all from Box. + +##### Methods: + +- inherits all from Box. + + +#### Log (from ScrollableText) + +A log permanently scrolled to the bottom. + +##### Options: + +- inherits all from ScrollableText. + +##### Properties: + +- inherits all from ScrollableText. + +##### Events: + +- inherits all from ScrollableText. +- __line__ - emitted on a log line. passes in line. + +##### Methods: + +- inherits all from ScrollableText. +- __log/add(text)__ - add a log line. + + +#### Table (from Box) + +A stylized table of text elements. + +##### 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 diff --git a/lib/widget.js b/lib/widget.js index 3698ad3..18f19b7 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -2180,6 +2180,7 @@ Element.prototype.setContent = function(content, noClear, noTags) { if (!noClear) this.clearPos(); this.content = content || ''; this.parseContent(noTags); + this.emit('set content'); }; Element.prototype.getContent = function() { @@ -6747,6 +6748,152 @@ Listbar.prototype.selectTab = function(index) { } }; +/** + * Log + */ + +function Log(options) { + var self = this; + + if (!(this instanceof Node)) { + return new Log(options); + } + + options = options || {}; + + ScrollableText.call(this, options); + + this.on('set content', function() { + nextTick(function() { + self.setScrollPerc(100); + }); + }); +} + +Log.prototype.__proto__ = ScrollableText.prototype; + +Log.prototype.type = 'log'; + +Log.prototype.log = +Log.prototype.add = function(text) { + this.emit('line', text); + return this.pushLine(text); +}; + +/** + * Table + * TODO: Draw custom border with proper angles. + */ + +function Table(options) { + var self = this; + + if (!(this instanceof Node)) { + return new Table(options); + } + + options = options || {}; + options.shrink = true; + + Box.call(this, options); + + this.parseTags = true; + + this.pad = options.pad != null + ? options.pad + : 2; + + this.setData(options.rows || options.data); +} + +Table.prototype.__proto__ = Box.prototype; + +Table.prototype.type = 'table'; + +Table.prototype.setRows = +Table.prototype.setData = function(rows) { + var self = this + , text = '' + , maxes = [] + , total = 0 + , line = ''; + + // TODO Pregenerate `generateTags` calls here! + + this.rows = rows || []; + + this.rows.forEach(function(row) { + row.forEach(function(cell, i) { + if (!maxes[i] || maxes[i] < cell.length + self.pad) { + maxes[i] = cell.length + self.pad; + } + }); + }); + + maxes.forEach(function(cell) { + 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); + + 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]; + + if (i !== 0) { + // text += '\u2502'; // '│' + text += generateTags(self.style.border, '\u2502'); // '│' + } + + while (cell.length < width) { + cell = ' ' + cell + ' '; + } + + if (cell.length > width) { + // cell = cell.slice(0, -1); + cell = cell.substring(1); + } + + // XXX Workaround + if (!self.options.tags) { + cell = helpers.escape(cell); + } + + if (isHeader) { + cell = generateTags(self.style.header, cell); + } else { + cell = generateTags(self.style.cell, cell); + } + + text += cell; + }); + text += '\n'; + if (!isFooter) { + text += line + '\n'; + } + }); + + if (text[text.length - 1] === '\n') { + text = text.slice(0, -1); + } + + return this.setContent(text); +}; + /** * Terminal */ @@ -7482,7 +7629,7 @@ function generateTags(style, text) { var open = '' , close = ''; - Object.keys(style).forEach(function(key) { + Object.keys(style || {}).forEach(function(key) { var val = style[key]; if (typeof val === 'string') { val = val.replace(/^light(?!-)/, 'light-'); @@ -7627,6 +7774,9 @@ exports.Message = exports.message = Message; exports.Loading = exports.loading = Loading; exports.Listbar = exports.listbar = Listbar; +exports.Log = exports.log = Log; +exports.Table = exports.table = Table; + exports.Terminal = exports.terminal = Terminal; exports.Image = exports.image = Image; diff --git a/test/widget-table.js b/test/widget-table.js new file mode 100644 index 0000000..6d1a357 --- /dev/null +++ b/test/widget-table.js @@ -0,0 +1,39 @@ +var blessed = require('../') + , screen; + +screen = blessed.screen({ + dump: __dirname + '/logs/table.log', + autoPadding: true +}); + +var table = blessed.table({ + parent: screen, + top: 'center', + left: 'center', + data: null, + border: 'line', + style: { + border: { + fg: 'red' + }, + header: { + fg: 'blue', + bold: true + }, + cell: { + fg: 'magenta' + } + } +}); + +table.setData([ + [ 'Animals', 'Foods', 'Times' ], + [ 'Elephant', 'Apple', '1:00am' ], + [ 'Bird', 'Orange', '2:15pm' ] +]); + +screen.key('q', function() { + return process.exit(0); +}); + +screen.render();