add shrinkBox. add forms. add RadioSet. misc refactor.

This commit is contained in:
Christopher Jeffrey 2013-07-17 00:53:34 -05:00
parent cac1924b14
commit 7981f8d749
3 changed files with 638 additions and 51 deletions

125
README.md
View File

@ -91,6 +91,7 @@ The base node which everything inherits from.
##### Properties:
- inherits all from EventEmitter.
- **type** - type of the node (e.g. `box`).
- **options** - original options object.
- **parent** - parent node.
- **screen** - parent screen.
@ -241,7 +242,8 @@ The base element.
- **label** - a simple text label for the element.
- **align** - text alignment: `left`, `center`, or `right`.
- **valign** - vertical text alignment: `top`, `middle`, or `bottom`.
- **shrink** - shrink/flex/grow to content width during render.
- **shrink** - shrink/flex/grow to content width/height during render.
- **shrinkBox** - shrink/flex/grow to combined coordinates of all child boxes.
- **padding** - amount of padding on the inside of the element.
##### Properties:
@ -268,6 +270,7 @@ The base element.
- **rright** - calculated relative right offset.
- **rtop** - calculated relative top offset.
- **rbottom** - calculated relative bottom offset.
- **name** - name of the element. useful for form submission.
##### Events:
@ -456,6 +459,39 @@ pre-existing newlines and escape codes.
- inherits all from ScrollableBox.
#### Form (from Box)
A form which can contain form elements.
##### Options:
- inherits all from Box.
- **keys** - allow default keys (tab, vi keys, enter).
- **vi** - allow vi keys.
##### Properties:
- inherits all from Box.
- **submission** - last submitted data.
##### Events:
- inherits all from Box.
- **submit** - form is submitted. receives a data object.
- **cancel** - form is discarded.
- **reset** - form is cleared.
##### Methods:
- inherits all from Box.
- **focusNext()** - focus next form element.
- **focusPrevious()** - focus previous form element.
- **submit()** - submit the form.
- **cancel()** - discard the form.
- **reset()** - clear the form.
#### Input (from Box)
A form input.
@ -476,6 +512,9 @@ A box which allows text input.
##### Events:
- inherits all from Input.
- **submit** - value is submitted (enter).
- **cancel** - value is discared (escape).
- **action** - either submit or cancel.
##### Methods:
@ -502,6 +541,9 @@ A box which allows multiline text input.
##### Events:
- inherits all from Input/ScrollableText.
- **submit** - value is submitted (enter).
- **cancel** - value is discared (escape).
- **action** - either submit or cancel.
##### Methods:
@ -542,7 +584,7 @@ A button which can be focused and allows key and mouse input.
#### ProgressBar (from Input)
A progress bar allowing various styles.
A progress bar allowing various styles. This can also be used as a form input.
##### Options:
@ -551,6 +593,9 @@ A progress bar allowing various styles.
(can be contained in `style`: e.g. `style.bar.fg`).
- **ch** - the character to fill the bar with (default is space).
- **filled** - the amount filled (0 - 100).
- **value** - same as `filled`.
- **keys** - enable key support.
- **mouse** - enable mouse support.
##### Properties:
@ -566,6 +611,7 @@ A progress bar allowing various styles.
- inherits all from Input.
- **progress(amount)** - progress the bar by a fill amount.
- **setProgress(amount)** - set progress to specific amount.
- **reset()** - reset the bar.
@ -598,6 +644,81 @@ A very simple file manager for selecting files.
- **reset([cwd], [callback])** - reset back to original cwd.
#### Checkbox (from Input)
A checkbox which can be used in a form element.
##### Options:
- inherits all from Input.
- **checked** - whether the element is checked or not.
- **mouse** - enable mouse support.
##### Properties:
- inherits all from Input.
- **text** - the text next to the checkbox (do not use setContent, use
`check.text = ''`).
- **checked** - whether the element is checked or not.
- **value** - same as `checked`.
##### Events:
- inherits all from Input.
- **check** - received when element is checked.
- **uncheck** received when element is unchecked.
##### Methods:
- inherits all from Input.
- **check()** - check the element.
- **uncheck()** - uncheck the element.
- **toggle()** - toggle checked state.
#### RadioSet (from Box)
An element wrapping RadioButtons. RadioButtons within this element will be
mutually exclusive with each other.
##### Options:
- inherits all from Box.
##### Properties:
- inherits all from Box.
##### Events:
- inherits all from Box.
##### Methods:
- inherits all from Box.
#### RadioButton (from Checkbox)
A radio button which can be used in a form element.
##### Options:
- inherits all from Checkbox.
##### Properties:
- inherits all from Checkbox.
##### Events:
- inherits all from Checkbox.
##### Methods:
- inherits all from Checkbox.
### Positioning
Offsets may be a number, a percentage (e.g. `50%`), or a keyword (e.g.

View File

@ -119,8 +119,8 @@ Node.prototype.remove = function(element) {
if (this.type !== 'screen') {
i = this.screen.clickable.indexOf(element);
if (~i) this.screen.clickable.splice(i, 1);
i = this.screen.input.indexOf(element);
if (~i) this.screen.input.splice(i, 1);
i = this.screen.keyable.indexOf(element);
if (~i) this.screen.keyable.splice(i, 1);
}
if (this.type === 'screen' && this.focused === element) {
@ -267,7 +267,7 @@ function Screen(options) {
this.hover = null;
this.history = [];
this.clickable = [];
this.input = [];
this.keyable = [];
this.grabKeys = false;
this.lockKeys = false;
this.focused;
@ -372,6 +372,7 @@ Screen.prototype._listenMouse = function(el) {
var self = this;
if (el && !~this.clickable.indexOf(el)) {
el.clickable = true;
this.clickable.push(el);
}
@ -471,7 +472,8 @@ Screen.prototype._listenMouse = function(el) {
Screen.prototype._listenKeys = function(el) {
var self = this;
if (el && !~this.input.indexOf(el)) {
if (el && !~this.keyable.indexOf(el)) {
el.keyable = true;
// Listen for click, but do not enable
// mouse if it's not enabled yet.
if (el.options.autoFocus !== false) {
@ -480,7 +482,7 @@ Screen.prototype._listenKeys = function(el) {
el.on('click', el.focus.bind(el));
this._listenedMouse = lm;
}
this.input.push(el);
this.keyable.push(el);
}
if (this._listenedKeys) return;
@ -509,11 +511,11 @@ Screen.prototype._listenKeys = function(el) {
return;
}
if (~self.input.indexOf(focused)) {
if (~self.keyable.indexOf(focused)) {
focused.emit('keypress', ch, key);
focused.emit('key ' + key.full, ch, key);
// self.emit('element keypress', focused, ch, key);
// self.emit('element key ' + key.full, focused, ch, key);
self.emit('element keypress', focused, ch, key);
self.emit('element key ' + key.full, focused, ch, key);
}
});
};
@ -1075,25 +1077,25 @@ Screen.prototype.codeAttr = function(code) {
};
Screen.prototype.focus = function(offset) {
var shown = this.input.filter(function(el) {
var shown = this.keyable.filter(function(el) {
return el.visible;
});
if (!shown.length || !offset) return;
var i = this.input.indexOf(this.focused);
var i = this.keyable.indexOf(this.focused);
if (!~i) return;
if (offset > 0) {
while (offset--) {
if (++i > this.input.length - 1) i = 0;
if (!this.input[i].visible) offset++;
if (++i > this.keyable.length - 1) i = 0;
if (!this.keyable[i].visible) offset++;
}
} else {
offset = -offset;
while (offset--) {
if (--i < 0) i = this.input.length - 1;
if (!this.input[i].visible) offset++;
if (--i < 0) i = this.keyable.length - 1;
if (!this.keyable[i].visible) offset++;
}
}
return this.input[i].focus();
return this.keyable[i].focus();
};
Screen.prototype.focusPrev = function() {
@ -1359,6 +1361,8 @@ function Element(options) {
Node.call(this, options);
this.name = options.name;
this.position = {
left: options.left || 0,
right: options.right || 0,
@ -1408,7 +1412,7 @@ function Element(options) {
this.screen._listenMouse(this);
}
if (options.input) {
if (options.input || options.keyable) {
this.screen._listenKeys(this);
}
@ -1968,7 +1972,9 @@ Box.prototype.render = function(stop) {
, xll
, yll
, ret
, cci;
, cci
, el
, i;
if (this.position.width) {
xl = xi_ + this.width;
@ -1995,6 +2001,29 @@ Box.prototype.render = function(stop) {
}
}
// TODO: Possibly do both shrinkBox and shrink
// and use whichever values are higher.
// Slower than below, but more foolproof.
if (this.options.shrinkBox && this.children.length) {
xl = 0, yl = 0;
for (i = 0; i < this.children.length; i++) {
el = this.children[i];
// Recurse
el.options._shrinkBox = !!el.options.shrinkBox;
el.options.shrinkBox = true;
ret = el.render(true);
// Reset
el.options.shrinkBox = el.options._shrinkBox;
delete el.options._shrinkBox;
if (ret.xl > xl) xl = ret.xl;
if (ret.yl > yl) yl = ret.yl;
}
}
// TODO: Check for 'center', recalculate yi, and xi. Better
// yet, simply move this check into this.left/width/etc.
if (this.shrink) {
@ -2039,6 +2068,11 @@ Box.prototype.render = function(stop) {
}
}
// NOTE: Won't work because parent is rendered first.
//if (this.options.shrinkBox && this._lastPos) {
// xl = this._lastPos.xl, yl = this._lastPos.yl;
//}
// TODO:
// Calculate whether we moved/resized by checking the previous _lastPos.
// Maybe clear based on that. Possibly emit events here.
@ -2046,9 +2080,14 @@ Box.prototype.render = function(stop) {
xi: xi_,
xl: xl,
yi: yi_,
yl: yl
yl: yl,
//mxl: xl,
//myl: yl
};
//this.parent._lastPos.mxl = Math.max(this.parent._lastPos.mxl, xl);
//this.parent._lastPos.myl = Math.max(this.parent._lastPos.myl, yl);
if (stop) return ret;
battr = this.border
@ -2574,6 +2613,7 @@ function List(options) {
ScrollableBox.call(this, options);
this.value = '';
this.items = [];
this.ritems = [];
this.selected = 0;
@ -2782,6 +2822,9 @@ List.prototype.remove = function(child) {
this._remove(child);
};
List.prototype.appendItem = List.prototype.add;
List.prototype.removeItem = List.prototype.remove;
List.prototype.setItems = function(items) {
var i = 0
, original = this.items.slice()
@ -2825,6 +2868,7 @@ List.prototype.select = function(index) {
var diff = index - this.selected;
this.selected = index;
this.value = this.ritems[this.selected];
this.scroll(diff);
};
@ -2977,6 +3021,203 @@ ScrollableText.prototype._recalculateIndex = function() {
this.contentIndex = t;
};
/**
* Form
*/
function Form(options) {
var self = this;
if (!(this instanceof Form)) {
return new Form(options);
}
Box.call(this, options);
if (options.keys) {
this.screen._listenKeys(this);
this.screen.on('element keypress', function(el, ch, key) {
// Make sure we're not entering input into a textbox.
if (self.screen.grabKeys || self.screen.lockKeys) {
return;
}
// Make sure we're a form or input element.
if (el !== self && !el.hasAncestor(self)) {
return;
}
if ((key.name === 'tab' && !key.shift) || key.name === 'down' || (options.vi && key.name === 'j')) {
self.focusNext();
return;
}
if ((key.name === 'tab' && key.shift) || key.name === 'up' || (options.vi && key.name === 'k')) {
self.focusPrevious();
return;
}
if (key.name === 'escape') {
self.focus();
return;
}
});
}
}
Form.prototype.__proto__ = Box.prototype;
Form.prototype.type = 'form';
Form.prototype._refresh = function() {
if (!this._children) {
var out = [];
this.children.forEach(function fn(el) {
if (el.keyable) out.push(el);
el.children.forEach(fn);
});
this._children = out;
}
};
Form.prototype.next = function() {
this._refresh();
if (!this._selected) {
return this._selected = this._children[0];
}
var i = this._children.indexOf(this._selected);
if (!~i || !this._children[i + 1]) {
return this._selected = this._children[0];
}
return this._selected = this._children[i + 1];
};
Form.prototype.previous = function() {
this._refresh();
if (!this._selected) {
return this._selected = this._children[this._children.length - 1];
}
var i = this._children.indexOf(this._selected);
if (!~i || !this._children[i - 1]) {
return this._selected = this._children[this._children.length - 1];
}
return this._selected = this._children[i - 1];
};
Form.prototype.focusNext = function() {
this.next().focus();
};
Form.prototype.focusPrevious = function() {
this.previous().focus();
};
Form.prototype.submit = function() {
var self = this
, out = {};
(function get(el) {
if (el.value != null) {
var name = el.name || el.type;
if (Array.isArray(out[name])) {
out[name].push(el.value);
} else if (out[name]) {
out[name] = [out[name], el.value];
} else {
out[name] = el.value;
}
}
el.children.forEach(get);
})(this);
this.emit('submit', out);
return this.submission = out;
};
Form.prototype.cancel = function() {
this.emit('cancel');
};
Form.prototype.reset = function() {
(function clear(el) {
switch (el.type) {
case 'screen':
break;
case 'box':
break;
case 'text':
break;
case 'line':
break;
case 'scrollable-box':
break;
case 'list':
el.select(0);
return;
case 'form':
break;
case 'input':
break;
case 'textbox':
el.clearInput();
return;
case 'textarea':
el.clearInput();
return;
case 'button':
break;
case 'progress-bar':
el.setProgress(0);
break;
case 'file-manager':
el.refresh(el.options.cwd);
return;
case 'checkbox':
el.uncheck();
return;
case 'radio-set':
break;
case 'radio-button':
el.uncheck();
return;
case 'prompt':
break;
case 'question':
break;
case 'message':
break;
case 'info':
break;
case 'loading':
break;
case 'pick-list':
el.select(0);
break;
case 'list-bar':
//el.select(0);
break;
case 'dir-manager':
el.refresh(el.options.cwd);
return;
case 'passbox':
el.clearInput();
return;
}
el.children.forEach(clear);
})(this);
this.emit('reset');
};
/**
* Input
*/
@ -2997,6 +3238,8 @@ Input.prototype.type = 'input';
*/
function Textbox(options) {
var self = this;
if (!(this instanceof Textbox)) {
return new Textbox(options);
}
@ -3009,8 +3252,6 @@ function Textbox(options) {
this.secret = options.secret;
this.censor = options.censor;
var self = this;
this.on('resize', updateCursor);
this.on('move', updateCursor);
@ -3057,6 +3298,17 @@ Textbox.prototype.setInput = function(callback) {
self.screen.restoreFocus();
}
if (err) {
self.emit('error', err);
} else if (value != null) {
self.emit('submit', value);
} else {
self.emit('cancel', value);
}
self.emit('action', value);
if (!callback) return;
return err
? callback(err)
: callback(null, value);
@ -3072,7 +3324,6 @@ Textbox.prototype._listener = function(ch, key) {
if (key.name === 'escape' || key.name === 'enter') {
delete this._callback;
this.value = '';
this.removeListener('keypress', this.__listener);
delete this.__listener;
callback(null, key.name === 'enter' ? value : null);
@ -3140,7 +3391,7 @@ Textbox.prototype.readEditor =
Textbox.prototype.setEditor = function(callback) {
var self = this;
return this.screen.readEditor({ value: this.value }, function(err, value) {
if (err) return callback(err);
if (err) return callback && callback(err);
value = value.replace(/[\r\n]/g, '');
self.value = value;
return self.readInput(callback);
@ -3243,6 +3494,17 @@ Textarea.prototype.setInput = function(callback) {
self.screen.restoreFocus();
}
if (err) {
self.emit('error', err);
} else if (value != null) {
self.emit('submit', value);
} else {
self.emit('cancel', value);
}
self.emit('action', value);
if (!callback) return;
return err
? callback(err)
: callback(null, value);
@ -3315,7 +3577,7 @@ Textarea.prototype.readEditor =
Textarea.prototype.setEditor = function(callback) {
var self = this;
return this.screen.readEditor({ value: this.value }, function(err, value) {
if (err) return callback(err);
if (err) return callback && callback(err);
self.value = value;
self.setContent(self.value);
self._typeScroll();
@ -3390,17 +3652,61 @@ function ProgressBar(options) {
return new ProgressBar(options);
}
Input.call(this, options);
this.filled = options.filled || 0;
if (typeof this.filled === 'string') {
this.filled = +this.filled.slice(0, -1);
}
this.value = this.filled;
this.ch = options.ch || ' ';
if (!this.style.bar) {
this.style.bar = {};
this.style.bar.fg = options.barFg;
this.style.bar.bg = options.barBg;
}
this.orientation = options.orientation || 'horizontal';
if (options.keys) {
this.on('keypress', function(ch, key) {
var back, forward;
if (self.orientation === 'horizontal') {
back = ['left', 'h'];
forward = ['right', 'l'];
} else if (self.orientation === 'vertical') {
back = ['down', 'j'];
forward = ['up', 'k'];
}
if (key.name === back[0] || (options.vi && key.name === back[1])) {
self.progress(-5);
self.screen.render();
return;
}
if (key.name === forward[0] || (options.vi && key.name === forward[1])) {
self.progress(5);
self.screen.render();
return;
}
});
}
if (options.mouse) {
this.on('click', function(data) {
var x, y, m, p;
if (self.orientation === 'horizontal') {
x = data.x - self.left;
m = self.width - (self.border ? 2 : 0) - self.padding * 2;
p = x / m * 100 | 0;
} else if (self.orientation === 'vertical') {
y = data.y - self.top;
m = self.height - (self.border ? 2 : 0) - self.padding * 2;
p = y / m * 100 | 0;
}
self.setProgress(p);
});
}
}
ProgressBar.prototype.__proto__ = Input.prototype;
@ -3444,11 +3750,18 @@ ProgressBar.prototype.progress = function(filled) {
if (this.filled === 100) {
this.emit('complete');
}
this.value = this.filled;
};
ProgressBar.prototype.setProgress = function(filled) {
this.filled = 0;
this.progress(filled);
};
ProgressBar.prototype.reset = function() {
this.emit('reset');
this.filled = 0;
this.value = this.filled;
};
/**
@ -3467,6 +3780,8 @@ function FileManager(options) {
List.call(this, options);
this.cwd = options.cwd || process.cwd();
this.file = this.cwd;
this.value = this.cwd;
this.on('select', function(item) {
var value = item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, '')
@ -3476,6 +3791,8 @@ function FileManager(options) {
if (err) {
return self.emit('error', err, file);
}
self.file = file;
self.value = file;
if (stat.isDirectory()) {
self.emit('cd', file, self.cwd);
self.cwd = file;
@ -3497,8 +3814,10 @@ FileManager.prototype.refresh = function(cwd, callback) {
cwd = null;
}
var self = this
, cwd = cwd || this.cwd;
var self = this;
if (cwd) this.cwd = cwd;
else cwd = this.cwd;
return fs.readdir(cwd, function(err, list) {
if (err && err.code === 'ENOENT') {
@ -3636,30 +3955,32 @@ function Checkbox(options) {
Input.call(this, options);
this.value = options.value || '';
this.checked = options.checked || false;
this.text = options.content || options.text || '';
this.checked = this.value = options.checked || false;
this.on('keypress', function(ch, key) {
if (key.name === 'enter' || key.name === 'space') {
self.check();
self.toggle();
self.screen.render();
}
});
if (this.options.mouse) {
if (options.mouse) {
this.on('click', function() {
self.check();
self.screen.render();
});
}
this.on('focus', function() {
self.program.saveCursor();
self.program.cup(this.top, this.left + 1);
self.program.showCursor();
this.on('focus', function(old) {
self.screen.program.saveCursor();
self.screen.program.cup(this.top, this.left + 1);
self.screen.program.showCursor();
});
this.on('blur', function() {
self.program.hideCursor();
self.program.restoreCursor();
self.screen.program.hideCursor();
self.screen.program.restoreCursor();
});
}
@ -3669,19 +3990,23 @@ Checkbox.prototype.type = 'checkbox';
Checkbox.prototype._render = Checkbox.prototype.render;
Checkbox.prototype.render = function(stop) {
this.setContent('[' + (this.checked ? 'x' : ' ') + '] ' + this.value);
if (this.type === 'radio-button') {
this.setContent('(' + (this.checked ? '*' : ' ') + ') ' + this.text);
} else {
this.setContent('[' + (this.checked ? 'x' : ' ') + '] ' + this.text);
}
return this._render(stop);
};
Checkbox.prototype.check = function() {
if (this.checked) return;
this.checked = true;
this.checked = this.value = true;
this.emit('check');
};
Checkbox.prototype.uncheck = function() {
if (!this.checked) return;
this.checked = false;
this.checked = this.value = false;
this.emit('uncheck');
};
@ -3691,12 +4016,24 @@ Checkbox.prototype.toggle = function() {
: this.check();
};
Checkbox.prototype.setChecked = function(val) {
val = !!val;
if (this.checked === val) return;
this.checked = val;
this.emit('check', val);
};
/**
* RadioSet
*/
function RadioSet(options) {
var self = this;
if (!(this instanceof RadioSet)) {
return new RadioSet(options);
}
Box.call(this, options);
}
RadioSet.prototype.__proto__ = Box.prototype;
RadioSet.prototype.type = 'radio-set';
/**
* RadioButton
@ -3711,10 +4048,9 @@ function RadioButton(options) {
Checkbox.call(this, options);
self.group = options.group || [];
this.on('check', function() {
self.group.forEach(function(el) {
if (self.parent.type !== 'radio-set') return;
self.parent.children.forEach(function(el) {
if (el === self) return;
el.uncheck();
});
@ -3725,6 +4061,8 @@ RadioButton.prototype.__proto__ = Checkbox.prototype;
RadioButton.prototype.type = 'radio-button';
RadioButton.prototype.toggle = RadioButton.prototype.check;
/**
* Prompt
*/
@ -4075,7 +4413,7 @@ function PickList(options) {
PickList.prototype.__proto__ = List.prototype;
PickList.prototype.type = 'popup-menu';
PickList.prototype.type = 'pick-list';
PickList.prototype.pick = function(callback) {
this.screen.saveFocus();
@ -4109,7 +4447,7 @@ function Listbar(options) {
Listbar.prototype.__proto__ = Box.prototype;
Listbar.prototype.type = 'menubar';
Listbar.prototype.type = 'listbar';
Listbar.prototype.setOptions =
Listbar.prototype.setCommands =
@ -4387,6 +4725,7 @@ exports.Line = exports.line = Line;
exports.ScrollableBox = exports.scrollablebox = ScrollableBox;
exports.List = exports.list = List;
exports.ScrollableText = exports.scrollabletext = ScrollableText;
exports.Form = exports.form = Form;
exports.Input = exports.input = Input;
exports.Textbox = exports.textbox = Textbox;
exports.Textarea = exports.textarea = Textarea;
@ -4395,7 +4734,9 @@ exports.ProgressBar = exports.progressbar = ProgressBar;
exports.FileManager = exports.filemanager = FileManager;
exports.Checkbox = exports.checkbox = Checkbox;
exports.RadioSet = exports.radioset = RadioSet;
exports.RadioButton = exports.radiobutton = RadioButton;
exports.Prompt = exports.prompt = Prompt;
exports.Question = exports.question = Question;
exports.Message = exports.message = Message;

125
test/widget-form.js Normal file
View File

@ -0,0 +1,125 @@
var blessed = require('blessed')
, screen = blessed.screen();
var form = blessed.form({
parent: screen,
mouse: true,
keys: true,
vi: true,
left: 0,
top: 0,
width: '100%',
height: 5,
bg: 'green',
content: 'foobar'
});
form.on('submit', function(data) {
output.setContent(JSON.stringify(data, null, 2));
screen.render();
});
var set = blessed.radioset({
parent: form,
left: 0,
top: 0,
shrinkBox: true,
height: 1,
bg: 'magenta'
});
var radio1 = blessed.radiobutton({
parent: set,
mouse: true,
keys: true,
shrink: true,
bg: 'magenta',
height: 1,
left: 0,
top: 0,
name: 'radio1',
content: 'radio1'
});
var radio2 = blessed.radiobutton({
parent: set,
mouse: true,
keys: true,
shrink: true,
bg: 'magenta',
height: 1,
left: 15,
top: 0,
name: 'radio2',
content: 'radio2'
});
var text = blessed.textbox({
parent: form,
mouse: true,
keys: true,
bg: 'blue',
height: 1,
width: 20,
left: 1,
top: 1,
name: 'text'
});
text.on('focus', function() {
text.readInput();
});
var check = blessed.checkbox({
parent: form,
mouse: true,
keys: true,
shrink: true,
bg: 'magenta',
height: 1,
left: 24,
top: 1,
name: 'check',
content: 'check'
});
var submit = blessed.button({
parent: form,
mouse: true,
keys: true,
height: 1,
left: 30,
top: 0,
shrink: true,
bg: 'blue',
name: 'submit',
content: 'submit',
focusEffects: {
bg: 'red'
}
});
submit.on('press', function() {
form.submit();
});
var output = blessed.scrollabletext({
parent: screen,
mouse: true,
keys: true,
left: 0,
top: 5,
width: '100%',
bg: 'red',
content: 'foobar'
});
screen.key('q', function() {
return process.exit(0);
});
form.focus();
form.submit();
screen.render();