UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

224 lines (193 loc) 4.8 kB
/** * 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; }