_/**
* The base class which provides the implementation of the Composite design pattern.
* A composite object can form a tree-like structure, the nodes or leaves of which are also composites
*/
class Composite {
constructor() {
this._parent = null
this._children = []
}
/**
* Whether this has parent composite
* @type {boolean}
* @readonly
*/
get hasParent() {
return this._parent != null
}
/**
* Whether this has one or more child composites
* @type {boolean}
* @readonly
*/
get hasChild() {
return this._children.length > 0
}
/**
* The parent composite
* @type {Composite}
* @readonly
*/
get parent() {
return this._parent
}
/**
* The ancestor composites ordered by closest to furthest
* @type {Composite[]}
* @readonly
*/
get ancestors() {
let r = []
let item = this
while (item.hasParent) {
item = item._parent
r.push(item)
}
return r
}
/**
* The root of composition
* @type {Composite}
* @readonly
*/
get root() {
let r = this
while (r.hasParent) r = r._parent
return r
}
/**
* The number of child composites
* @type {number}
* @readonly
*/
get length() {
return this._children.length
}
/**
* Determines whether the specified composite can be added as a child
* @param {Composite} Cp The composite which is about to be added
* @return {boolean|string}
* `true` if `Cp` is valid. Any type other than `true` means invalid.
* If a string is returned, it is shown as an error message in the debug console
*/
verifyChild(Cp) {
return true
}
/**
* Adds a child composite
* @param {Composite} Cp The composite to add as a child
* @return {Composite} This
*/
addChild(Cp) {
if (Cp.hasParent) throw new Error('Parent already exists')
let verified = this.verifyChild(Cp)
if (verified !== true) {
console.error(typeof verified == 'string' ? verified : 'Invalid child')
return this
}
Cp._parent = this
this._children.push(Cp)
return this
}
/**
* Adds multiple child composites
* @param {Composite[]} Cps The array of the composites to add
* @return {Composite} This
*/
addChildren(Cps) {
for (let item of Cps) this.addChild(item)
return this
}
/**
* Performs tree traversal
* @param {function} Fn
* The callback that receives every descendant composite as the 1st parameter.
* If `false` is returned, the traversal will be aborted.
* The returned value other than `false` will be passed to the next traversal call of `Fn` as the 2nd parameter
* @param {number} Depth=-1
* The limit of traversal depth. Negative number means no-limit
* @param {mixed} Arg=null
* Additinal argument to pass to `Fn` as the 2nd parameter.
* @return {boolean}
* `true` if the traversal is successfully completed.
* `false` if the traversal is aborted
*/
traverse(Fn, Depth = -1, Arg = null) {
let r = true
let result = Fn(this, Arg)
if (result === false || Depth == 0) return !this.hasChild
for (let item of this._children) {
if (!item.traverse(Fn, Depth - 1, result) && r) r = false
}
return r
}
/**
* @ignore
*/
[Symbol.iterator]() {
return this._children[Symbol.iterator]()
}
}
export default Composite