/** * Copyright schukai GmbH and contributors 2023. All Rights Reserved. * Node module: @schukai/monster * This file is licensed under the AGPLv3 License. * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html */ import { isArray, isObject } from "../types/is.mjs"; import { Node } from "../types/node.mjs"; import { NodeList } from "../types/nodelist.mjs"; import { assembleParts } from "./buildmap.mjs"; import { extend } from "./extend.mjs"; export { buildTree }; /** * @private * @type {symbol} */ const parentSymbol = Symbol("parent"); /** * @private * @type {symbol} */ const rootSymbol = Symbol("root"); /** * @typedef {Object} buildTreeOptions * @property {array} options.rootReferences=[null, undefined] defines the values for elements without parents * @property {Monster.Data~exampleFilterCallback} options.filter filtering of the values * @memberOf Monster.Data */ /** * With the help of the function `buildTree()`, nodes can be easily created from data objects. * * @param {*} subject * @param {string|Monster.Data~exampleSelectorCallback} selector * @param {string} idKey * @param {string} parentIDKey * @param {buildTreeOptions} [options] * @return {*} * @memberOf Monster.Data * @throws {TypeError} value is neither a string nor a function * @throws {TypeError} the selector callback must return a map * @throws {Error} the object has no value for the specified id * @license AGPLv3 * @since 1.26.0 */ function buildTree(subject, selector, idKey, parentIDKey, options) { const nodes = new Map(); if (!isObject(options)) { options = {}; } options = extend( {}, { rootReferences: [null, undefined], filter: undefined, }, options, ); const filter = options?.filter; let rootReferences = options.rootReferences; if (!isArray(rootReferences)) { rootReferences = [rootReferences]; } const childMap = assembleParts(subject, selector, filter, function (o, k, m) { const key = o?.[idKey]; let ref = o?.[parentIDKey]; if (rootReferences.indexOf(ref) !== -1) ref = rootSymbol; if (key === undefined) { throw new Error("the object has no value for the specified id"); } o[parentSymbol] = ref; const node = new Node(o); this.has(ref) ? this.get(ref).add(node) : this.set(ref, new NodeList().add(node)); nodes.set(key, node); }); nodes.forEach((node) => { let id = node?.["value"]?.[idKey]; if (childMap.has(id)) { node.childNodes = childMap.get(id); childMap.delete(id); } }); const list = new NodeList(); childMap.forEach((s) => { if (s instanceof Set) { s.forEach((n) => { list.add(n); }); } }); return list; }