@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
224 lines (193 loc) • 4.8 kB
JavaScript
/**
* Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
*
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
* For more information about purchasing a commercial license, please contact schukai GmbH.
*
* SPDX-License-Identifier: AGPL-3.0
*/
import { Base } from "./base.mjs";
import { isPrimitive } from "./is.mjs";
import { NodeList } from "./nodelist.mjs";
import { validateInstance } from "./validate.mjs";
import { instanceSymbol } from "../constants.mjs";
export { Node };
/**
* @private
* @type {symbol}
*/
const internalValueSymbol = Symbol("internalData");
/**
* @private
* @type {symbol}
*/
const treeStructureSymbol = Symbol("treeStructure");
/**
* You can create the instance via the monster namespace `new Monster.Types.Node()`.
*
* @license AGPLv3
* @since 1.26.0
* @copyright schukai GmbH
* @summary A Node Class
* @see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Iteration_protocols
*/
class Node extends Base {
/**
* @param {*} [value]
*/
constructor(value) {
super();
this[internalValueSymbol] = value;
this[treeStructureSymbol] = {
parent: null,
childNodes: new NodeList(),
level: 0,
};
}
/**
* This method is called by the `instanceof` operator.
* @return {symbol}
* @since 2.1.0
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/monster/types/node");
}
/**
* @property {*}
*/
get value() {
return this[internalValueSymbol];
}
/**
* @property {*}
*/
set value(value) {
this[internalValueSymbol] = value;
}
/**
* @property {Monster.Types.Node|null}
*/
get parent() {
return this[treeStructureSymbol].parent;
}
/**
* @property {integer}
*/
get level() {
return this[treeStructureSymbol].level;
}
/**
*
* @property {NodeList}
*/
get childNodes() {
return this[treeStructureSymbol].childNodes;
}
/**
*
* @property {NodeList}
*/
set childNodes(childNodes) {
this[treeStructureSymbol].childNodes = validateInstance(
childNodes,
NodeList,
);
setChildLevelAndParent.call(this, this, 1, new Set());
}
/**
* @return {Monster.Types.Node}
* @param {Node} node
*/
appendChild(node) {
this[treeStructureSymbol].childNodes.add(validateInstance(node, Node));
node[treeStructureSymbol].parent = this;
node[treeStructureSymbol].level = this.level + 1;
setChildLevelAndParent.call(this, node, 1, new Set());
return this;
}
/**
* @return {Monster.Types.Node}
* @param {Node} node
*/
removeChild(node) {
this[treeStructureSymbol].childNodes.remove(validateInstance(node, Node));
node[treeStructureSymbol].parent = null;
node[treeStructureSymbol].level = 0;
setChildLevelAndParent.call(this, node, -1, new Set());
return this;
}
/**
*
* @return {boolean}
*/
hasChildNodes() {
return this[treeStructureSymbol].childNodes.length > 0;
}
/**
* @return {Monster.Types.Node}
* @param {Node} node
*/
hasChild(node) {
return this[treeStructureSymbol].childNodes.has(
validateInstance(node, Node),
);
}
/**
* @since 1.28.0
* @return {string}
*/
toString() {
const parts = [];
if (this[internalValueSymbol]) {
let label = this[internalValueSymbol];
if (!isPrimitive(label))
label = JSON.stringify(this[internalValueSymbol]);
parts.push(label);
}
if (!this.hasChildNodes()) {
return parts.join("\n");
}
const count = this.childNodes.length;
let counter = 0;
for (const node of this.childNodes) {
counter++;
const prefix = (count === counter ? "└" : "├").padStart(
2 * node.level,
" |",
);
parts.push(prefix + node.toString());
}
return parts.join("\n");
}
}
/**
* @private
* @param {Node} node
* @param {int} operand
* @param {Set} visitedNodes
* @return {setChildLevelAndParent}
*/
function setChildLevelAndParent(node, operand, visitedNodes) {
const self = this;
if (visitedNodes.has(node)) {
throw new Error(
"the node has already been visited and cannot be traversed again",
);
}
visitedNodes.add(node);
if (node !== this) {
node[treeStructureSymbol].parent = this;
}
node[treeStructureSymbol].childNodes.forEach(function (child) {
child[treeStructureSymbol].parent = node;
child[treeStructureSymbol].level =
node[treeStructureSymbol].level + operand;
setChildLevelAndParent.call(self, child, operand, visitedNodes);
});
return this;
}