diff --git a/js/term/widgets/node.js b/js/term/widgets/node.js new file mode 100644 index 0000000..3d9e26c --- /dev/null +++ b/js/term/widgets/node.js @@ -0,0 +1,308 @@ +/** + ** ============================== + ** 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 and contributors, Stefan Bosse + ** $INITIAL: (C) 2013-2015, Christopher Jeffrey and contributors + ** $MODIFIED: sbosse (2017-2021). + ** $VERSION: 1.2.1 + ** + ** $INFO: + * + * node.js - base abstract node for blessed + * + ** $ENDOFINFO + */ + +/** + * Modules + */ +var Comp = Require('com/compat'); + +var EventEmitter = Require('term/events').EventEmitter; + +/** + * Node + */ + +function Node(options) { + var self = this; + var Screen = Require('term/widgets/screen'); + + if (!instanceOf(this,Node)) { + return new Node(options); + } + + EventEmitter.call(this); + + options = options || {}; + this.options = options; + + this.screen = this.screen || options.screen; + + if (!this.screen) { + if (this.type === 'screen') { + this.screen = this; + } else if (Screen.total === 1) { + this.screen = Screen.global; + } else if (options.parent) { + this.screen = options.parent; + while (this.screen && this.screen.type !== 'screen') { + this.screen = this.screen.parent; + } + } else if (Screen.total) { + // This _should_ work in most cases as long as the element is appended + // synchronously after the screen's creation. Throw error if not. + this.screen = Screen.instances[Screen.instances.length - 1]; + process.nextTick(function() { + if (!self.parent) { + throw new Error('Element (' + self.type + ')' + + ' was not appended synchronously after the' + + ' screen\'s creation. Please set a `parent`' + + ' or `screen` option in the element\'s constructor' + + ' if you are going to use multiple screens and' + + ' append the element later.'); + } + }); + } else { + throw new Error('No active screen.'); + } + } + + this.parent = options.parent || null; + this.children = []; + this.$ = this._ = this.data = {}; + this.uid = Node.uid++; + this.index = this.index != null ? this.index : -1; + + if (this.type !== 'screen') { + this.detached = true; + } + + if (this.parent) { + this.parent.append(this); + } + + (options.children || []).forEach(this.append.bind(this)); +} + +Node.uid = 0; + +//Node.prototype.__proto__ = EventEmitter.prototype; +inheritPrototype(Node,EventEmitter); + +Node.prototype.type = 'node'; + +Node.prototype.insert = function(element, i) { + var self = this; + + if (element.screen && element.screen !== this.screen) { + throw new Error('Cannot switch a node\'s screen.'); + } + + element.detach(); + element.parent = this; + element.screen = this.screen; + + if (i === 0) { + this.children.unshift(element); + } else if (i === this.children.length) { + this.children.push(element); + } else { + this.children.splice(i, 0, element); + } + + element.emit('reparent', this); + this.emit('adopt', element); + + (function emit(el) { + var n = el.detached !== self.detached; + el.detached = self.detached; + if (n) el.emit('attach'); + el.children.forEach(emit); + })(element); + + if (!this.screen.focused) { + this.screen.focused = element; + } +}; + +Node.prototype.prepend = function(element) { + this.insert(element, 0); +}; + +Node.prototype.append = function(element) { + this.insert(element, this.children.length); +}; + +Node.prototype.insertBefore = function(element, other) { + var i = this.children.indexOf(other); + if (~i) this.insert(element, i); +}; + +Node.prototype.insertAfter = function(element, other) { + var i = this.children.indexOf(other); + if (~i) this.insert(element, i + 1); +}; + +Node.prototype.remove = function(element) { + if (element.parent !== this) return; + + var i = this.children.indexOf(element); + if (!~i) return; + + element.clearPos(); + + element.parent = null; + + this.children.splice(i, 1); + + i = this.screen.clickable.indexOf(element); + if (~i) this.screen.clickable.splice(i, 1); + i = this.screen.keyable.indexOf(element); + if (~i) this.screen.keyable.splice(i, 1); + + element.emit('reparent', null); + this.emit('remove', element); + + (function emit(el) { + var n = el.detached !== true; + el.detached = true; + if (n) el.emit('detach'); + el.children.forEach(emit); + })(element); + + if (this.screen.focused === element) { + this.screen.rewindFocus(); + } +}; + +Node.prototype.detach = function() { + if (this.parent) this.parent.remove(this); +}; + +Node.prototype.free = function() { + return; +}; + +Node.prototype.destroy = function() { + this.detach(); + this.forDescendants(function(el) { + el.free(); + el.destroyed = true; + el.emit('destroy'); + }, this); +}; + +Node.prototype.forDescendants = function(iter, s) { + if (s) iter(this); + this.children.forEach(function emit(el) { + iter(el); + el.children.forEach(emit); + }); +}; + +Node.prototype.forAncestors = function(iter, s) { + var el = this; + if (s) iter(this); + while (el = el.parent) { + iter(el); + } +}; + +Node.prototype.collectDescendants = function(s) { + var out = []; + this.forDescendants(function(el) { + out.push(el); + }, s); + return out; +}; + +Node.prototype.collectAncestors = function(s) { + var out = []; + this.forAncestors(function(el) { + out.push(el); + }, s); + return out; +}; + +Node.prototype.emitDescendants = function() { + var args = Array.prototype.slice(arguments) + , iter; + + if (typeof args[args.length - 1] === 'function') { + iter = args.pop(); + } + + return this.forDescendants(function(el) { + if (iter) iter(el); + el.emit.apply(el, args); + }, true); +}; + +Node.prototype.emitAncestors = function() { + var args = Array.prototype.slice(arguments) + , iter; + + if (typeof args[args.length - 1] === 'function') { + iter = args.pop(); + } + + return this.forAncestors(function(el) { + if (iter) iter(el); + el.emit.apply(el, args); + }, true); +}; + +Node.prototype.hasDescendant = function(target) { + return (function find(el) { + for (var i = 0; i < el.children.length; i++) { + if (el.children[i] === target) { + return true; + } + if (find(el.children[i]) === true) { + return true; + } + } + return false; + })(this); +}; + +Node.prototype.hasAncestor = function(target) { + var el = this; + while (el = el.parent) { + if (el === target) return true; + } + return false; +}; + +Node.prototype.get = function(name, value) { + if (this.data.hasOwnProperty(name)) { + return this.data[name]; + } + return value; +}; + +Node.prototype.set = function(name, value) { + return this.data[name] = value; +}; + +/** + * Expose + */ + +module.exports = Node;