From 686dc0c94c8643887752ee121d75ff5f7e972389 Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:11:32 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- js/term/widgets/form.js | 293 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 js/term/widgets/form.js diff --git a/js/term/widgets/form.js b/js/term/widgets/form.js new file mode 100644 index 0000000..85e04e5 --- /dev/null +++ b/js/term/widgets/form.js @@ -0,0 +1,293 @@ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Christopher Jeffrey, Stefan Bosse + ** $INITIAL: (C) 2013-2015, Christopher Jeffrey and contributors + ** $MODIFIED: by sbosse + ** $REVESIO: 1.2.1 + ** + ** $INFO: + ** + ** form.js - form element for blessed + ** + ** $ENDOFINFO + */ + +/** + * Modules + */ +var Comp = Require('com/compat'); + +var Node = Require('term/widgets/node'); +var Box = Require('term/widgets/box'); + +/** + * Form + */ + +function Form(options) { + var self = this; + + if (!instanceOf(this,Node)) { + return new Form(options); + } + + options = options || {}; + + options.ignoreKeys = true; + Box.call(this, options); + + if (options.keys) { + this.screen._listenKeys(this); + this.on('element keypress', function(el, ch, key) { + if ((key.name === 'tab' && !key.shift) + || (el.type === 'textbox' && options.autoNext && key.name === 'enter') + || key.name === 'down' + || (options.vi && key.name === 'j')) { + if (el.type === 'textbox' || el.type === 'textarea') { + if (key.name === 'j') return; + if (key.name === 'tab') { + // Workaround, since we can't stop the tab from being added. + el.emit('keypress', null, { name: 'backspace' }); + } + el.emit('keypress', '\x1b', { name: 'escape' }); + } + self.focusNext(); + return; + } + + if ((key.name === 'tab' && key.shift) + || key.name === 'up' + || (options.vi && key.name === 'k')) { + if (el.type === 'textbox' || el.type === 'textarea') { + if (key.name === 'k') return; + el.emit('keypress', '\x1b', { name: 'escape' }); + } + self.focusPrevious(); + return; + } + + if (key.name === 'escape') { + self.focus(); + return; + } + }); + } +} + +//Form.prototype.__proto__ = Box.prototype; +inheritPrototype(Form,Box), + +Form.prototype.type = 'form'; + +Form.prototype._refresh = function() { + // XXX Possibly remove this if statement and refresh on every focus. + // Also potentially only include *visible* focusable elements. + // This would remove the need to check for _selected.visible in previous() + // and next(). + 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._visible = function() { + return !!this._children.filter(function(el) { + return el.visible; + }).length; +}; + +Form.prototype.next = function() { + this._refresh(); + + if (!this._visible()) return; + + if (!this._selected) { + this._selected = this._children[0]; + if (!this._selected.visible) return this.next(); + if (this.screen.focused !== this._selected) return this._selected; + } + + var i = this._children.indexOf(this._selected); + if (!~i || !this._children[i + 1]) { + this._selected = this._children[0]; + if (!this._selected.visible) return this.next(); + return this._selected; + } + + this._selected = this._children[i + 1]; + if (!this._selected.visible) return this.next(); + return this._selected; +}; + +Form.prototype.previous = function() { + this._refresh(); + + if (!this._visible()) return; + + if (!this._selected) { + this._selected = this._children[this._children.length - 1]; + if (!this._selected.visible) return this.previous(); + if (this.screen.focused !== this._selected) return this._selected; + } + + var i = this._children.indexOf(this._selected); + if (!~i || !this._children[i - 1]) { + this._selected = this._children[this._children.length - 1]; + if (!this._selected.visible) return this.previous(); + return this._selected; + } + + this._selected = this._children[i - 1]; + if (!this._selected.visible) return this.previous(); + return this._selected; +}; + +Form.prototype.focusNext = function() { + var next = this.next(); + if (next) next.focus(); +}; + +Form.prototype.focusPrevious = function() { + var previous = this.previous(); + if (previous) previous.focus(); +}; + +Form.prototype.resetSelected = function() { + this._selected = null; +}; + +Form.prototype.focusFirst = function() { + this.resetSelected(); + this.focusNext(); +}; + +Form.prototype.focusLast = function() { + this.resetSelected(); + this.focusPrevious(); +}; + +Form.prototype.submit = function() { + var out = {}; + + this.children.forEach(function fn(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(fn); + }); + + this.emit('submit', out); + + return this.submission = out; +}; + +Form.prototype.cancel = function() { + this.emit('cancel'); +}; + +Form.prototype.reset = function() { + this.children.forEach(function fn(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': + delete el.value; + 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 'list-bar': + //el.select(0); + break; + case 'dir-manager': + el.refresh(el.options.cwd); + return; + case 'terminal': + el.write(''); + return; + case 'image': + //el.clearImage(); + return; + } + el.children.forEach(fn); + }); + + this.emit('reset'); +}; + +/** + * Expose + */ + +module.exports = Form;