UNPKG

@vahaha/treejs

Version:
240 lines (205 loc) 5.98 kB
const _ = require('lodash') const TreeNode = require('./lib/TreeNode') function Option({ key = 'id', parentKey = 'parentId', childrenKey = 'children', clone = false, sort, }) { this.childrenKey = childrenKey this.clone = clone this.key = key this.parentKey = parentKey this.sort = sort return this } /** * Innit TreeJS from a tree json * @param {Object} tree tree json * @param {Option} [options] options for building tree * * Option object: {key: 'id', parentKey: 'parentId', childrenKey: 'children', sort} */ function TreeJS( tree, options = { key: 'id', parentKey: 'parentId', childrenKey: 'children', } ) { if (typeof tree !== 'object' || Array.isArray(tree)) { throw new Error('tree must be an object') } const _options = new Option(options) const mapIdNodes = new Map() this.tree = new TreeNode(tree, _options) scanTree(mapIdNodes, [this.tree], _options) const allNodes = [] mapIdNodes.forEach(val => { allNodes.push(val) }) /** * Get branch from a node * @param {any} id Node id * @return a TreeNode */ this.getBranch = function (id) { return mapIdNodes.get(id) } this.getNodesOfBranch = function (id) { const root = mapIdNodes.get(id) if (!root) { return root } const nodes = [root] mapIdNodes.forEach(node => { const path = node.getPath() || new Set() if (path.has(id)) { nodes.push(node) } }) return nodes } /** * Get a node. This function is same getBranch * @param {any} id Node id * @return a TreeNode */ this.getNode = function (id) { return this.getBranch(id) } /** * Get all nodes of tree * @returns Array<TreeNode> */ this.getAllNodes = function () { return allNodes } return this } TreeJS.TreeNode = TreeNode /** * Build trees from array of nodes. * @param {Array} nodes array of nodes. * @param {String} key identity field name of the node. 'id' is default * @param {String} parentKey identify field name of the parent (foreign key). 'parentId' is default * @param {Option} [options] options for building trees * * Option object: {key: 'id', parentKey: 'parentId', childrenKey: 'children', sort} * * @return {Array} array of TreeJS object */ TreeJS.fromNodes = ( nodes, options = { key: 'id', parentKey: 'parentId', childrenKey: 'children', } ) => { if (!Array.isArray(nodes)) { throw new Error('nodes must be an array') } const _options = new Option(options) const { key, parentKey } = _options // always clean nodes let availableNodes = nodes.filter(node => node) availableNodes = _.cloneDeep(availableNodes) // create index by id const mapIdNodes = new Map() availableNodes.forEach(entity => { const node = new TreeNode(entity, _options) mapIdNodes.set(node[key], node) }) // attach parent to child node mapIdNodes.forEach(node => { const parentId = node[parentKey] if ([undefined, null].includes(parentId)) { return } const parent = mapIdNodes.get(parentId) if (parent) { node.setParent(parent) } }) // build trees const trees = build(mapIdNodes, [...mapIdNodes.keys()], _options) trees.forEach(root => root.visitDescendants()) return trees.map(tree => new TreeJS(tree, _options)) } /** * Building trees from array of nodes. * @param {Array} nodes array of nodes. * @param {String} key field name, which is id of node. * @param {String} parentKey parent field name (foreign key), which is parent id of node * @param {Object} [options] options for building trees * * Option object: {childrenFieldName: 'children', cloneDeep: false, sort} */ TreeJS.buildTree = ( nodes, key = 'id', parentKey = 'parentId', options = { childrenFieldName: 'children', cloneDeep: false } ) => { if (!Array.isArray(nodes)) { throw new Error('nodes must be an array') } if (!key) { throw new Error('unknown key name') } if (!parentKey) { throw new Error('unknown parent field name') } let availableNodes = nodes.filter(node => node) availableNodes = options.cloneDeep ? _.cloneDeep(availableNodes) : availableNodes const mapIdNodes = new Map() availableNodes.forEach(node => { node.__proto__.__path = new Set() const parentId = node[parentKey] if (parentId) { node.__proto__.__path.add(parentId) } mapIdNodes.set(node[key], node) }) return build(mapIdNodes, [...mapIdNodes.keys()], { key, parentKey, childrenKey: options.childrenFieldName, sort: options.sort, }) } const build = (mapIdNodes, nodeIds, options) => { const { parentKey, sort } = options const trees = [] mapIdNodes.forEach(node => { const parentId = node[parentKey] const parent = mapIdNodes.get(parentId) if ([undefined, null].includes(parentId) || !parent) { return trees.push(node) } node.setParent(parent) }) if (typeof sort === 'function') { trees.sort(sort) } return trees } function scanTree(mapIdNodes, nodes, options) { const { key, childrenKey } = options nodes.forEach(node => { mapIdNodes.set(node[key], node) let children = node[childrenKey] if (children && children.length) { children = children.map(child => new TreeNode(child, options)) children.forEach(child => child.setParent(node)) node[childrenKey] = children scanTree(mapIdNodes, children, options) } }) } module.exports = TreeJS