UNPKG

@bigfishtv/cockpit

Version:

492 lines (452 loc) 14.6 kB
/** * Tree Utilities * @module Utilities/treeUtils */ import Immutable from 'immutable' import cloneDeep from 'lodash/cloneDeep' //// Immutable.js functions //// /** * Recursively iterates through tree looking for id * @param {Number} id * @param {Immutable.List} Branch * @param {String} [iteratorKey=children] * @return {Immutable.Map} */ export function getChildByIdImmutable(id, Branch, iteratorKey = 'children') { if ( typeof id !== 'number' || typeof Branch !== 'object' || typeof iteratorKey !== 'string' || !Immutable.List.isList(Branch) ) return false let found = false Branch.map(item => { if (!found) { if (item.get('id') === id) found = item else if (item.get(iteratorKey)) found = getChildByIdImmutable(id, item.get(iteratorKey)) } }) return found } /** * @deprecated This function is super-inefficient and should not be used. * * Recursively iterates through tree looking for parent of child's id * @param {Number} id * @param {Immutable.List} Branch * @param {String} [iteratorKey=children] * @return {Immutable.Map} */ export function getParentByChildIdImmutable(id, Branch, iteratorKey = 'children') { if ( typeof id !== 'number' || typeof Branch !== 'object' || typeof iteratorKey !== 'string' || !Immutable.List.isList(Branch) ) return false let found = false Branch.map(item => { if (!found) { if (item.get(iteratorKey)) item.get(iteratorKey).map(child => { if (!found) { if (child.get('id') === id) { found = item } else found = getParentByChildIdImmutable(id, item.get(iteratorKey)) } }) } }) return found } /** * Recursively iterates through tree using a checker function to determine whether or not to add a branch's key to an array of values * @param {Immutable.List} tree * @param {String} key * @param {Function} checker * @param {String} [iteratorKey=children] * @return {Array} - returns array of values */ export function collectValuesImmutable(tree, key, checker, iteratorKey = 'children') { if ( typeof tree !== 'object' || !Immutable.List.isList(tree) || typeof key !== 'string' || typeof checker !== 'function' || typeof iteratorKey !== 'string' ) return false let values = [] tree.map(branch => { if (checker(branch) && branch.get(key)) values.push(branch.get(key)) if (branch.get(iteratorKey) && branch.get(iteratorKey).size > 0) values = values.concat(collectValuesImmutable(branch.get(iteratorKey), key, checker, iteratorKey)) }) return values } /** * Prunes a tree's branches based on a key and an array of disallowed values * @param {Immutable.List} tree * @param {String} key * @param {Array} values - array of key values that determine if a branch is to be pruned * @param {String} [iteratorKey=children] * @return {Immutable.List} - returns pruned tree */ export function pruneTreeImmutable(tree, key, values, iteratorKey = 'children') { if (typeof tree !== 'object' || !Immutable.List.isList(tree) || typeof iteratorKey !== 'string') return false if (!(values instanceof Array)) values = [values] tree = tree.filter(branch => values.indexOf(branch.get(key)) < 0) tree = tree.map(branch => { if (branch.get(iteratorKey) && branch.get(iteratorKey).size > 0) { return branch.set(iteratorKey, pruneTreeImmutable(branch.get(iteratorKey), key, values, iteratorKey)) } return branch }) return tree } //// Regular array/object functions //// /** * Recursively iterates through tree looking for id * @param {Number} id * @param {Object[]} Branch * @param {String} [iteratorKey=children] * @return {Object[]} */ export function getParentByChildId(id, Branch, iteratorKey = 'children') { if ( typeof id !== 'number' || typeof Branch !== 'object' || typeof iteratorKey !== 'string' || !(Branch instanceof Array) ) return false let found = false Branch.map(item => { if (!found) { if (iteratorKey in item) item[iteratorKey].map(child => { if (!found) { if ('id' in child && child['id'] === id) { found = item } else { found = getParentByChildId(id, item[iteratorKey]) } } }) } }) return found } // /** * Takes a flat array and makes it multidimensional * @param {Object[]} FlatTree - flat array to be inflated * @param {String} [idTag=id] * @param {String} [parentTag=parent_id] * @param {String} [childrenTag=children] * @param {Number} [parent_id=null] * @param {Number} [level=0] * @return {Object[]} */ export function inflate( FlatTree, idTag = 'id', parentTag = 'parent_id', childrenTag = 'children', parent_id = null, level = 0 ) { if ( !(FlatTree instanceof Array) || typeof idTag !== 'string' || typeof parentTag !== 'string' || typeof childrenTag !== 'string' ) return false let branch = [] FlatTree.map(_item => { let item = cloneDeep(_item) if (idTag in item && parentTag in item && item[parentTag] === parent_id) { const children = inflate(FlatTree, idTag, parentTag, childrenTag, item[idTag], level + 1) item[childrenTag] = children && children instanceof Array ? children : [] // item.level = level; branch.push(item) } }) return branch } /** * Takes a multidimensional array and flattens it * @param {Object[]} Tree * @param {String} [iteratorKey=children] * @param {Number} [level=0] * @return {Object[]} */ export function flatten(Tree, iteratorKey = 'children', level = 0) { if (!(Tree instanceof Array) || typeof iteratorKey !== 'string') return false return Tree.reduce((list, branch) => { const rest = {} Object.keys(branch).map(key => { if (key !== iteratorKey) rest[key] = branch[key] }) list.push(rest) if (iteratorKey in branch) { list = list.concat(flatten(branch[iteratorKey], iteratorKey, level + 1)) } return list }, []) } /** * Takes a multidimensional array and flattens it, excluding certain ids * @param {Object[]} Tree * @param {Number[]} ignoreIds * @param {String} [iteratorKey=children] * @param {Number} [level=0] * @return {Object[]} */ export function flattenWithoutCollapsed(Tree, ignoreIds = [], iteratorKey = 'children', level = 0) { if (!(Tree instanceof Array) || !(ignoreIds instanceof Array) || typeof iteratorKey !== 'string') return false return Tree.reduce((list, branch) => { const rest = {} Object.keys(branch).map(key => { if (key !== iteratorKey) rest[key] = branch[key] }) list.push(rest) if (iteratorKey in branch && ignoreIds.indexOf(branch.id) < 0) { list = list.concat(flattenWithoutCollapsed(branch[iteratorKey], ignoreIds, iteratorKey, level + 1)) } return list }, []) } // takes a multidimensional array and flattens it /** * Takes a multidimensional array and flattens it with 'path' array * @param {Object[]} branches * @param {Array} path * @return {Object[]} */ export function flattenWithPath(branches, path = []) { let index = 0 return branches.reduce((list, branch) => { const { children, ...rest } = branch let newPath = path.slice() if (path.length) newPath.push('children') newPath.push(index++) const item = { item: { ...rest, children }, path: newPath, } list.push(item) if (children && children.length) { list = list.concat(flattenWithPath(children, newPath)) } return list }, []) } /** * @param {Object[]} branches * @param {Number} [parent_id=null] * @return {Object[]} */ export function flattenWithParentIds(branches, parent_id = null) { return branches.reduce((list, branch) => { const { children, ...rest } = branch const item = { ...rest, parent_id } list.push(item) if (children && children.length) { list = list.concat(flattenWithParentIds(children, item.id)) } return list }, []) } /** * Prunes a tree's branches based on a key and an array of disallowed values * @param {Object[]} _tree * @param {String} key * @param {String[]} values * @param {String} [iteratorKey=children] * @return {Object[]} */ export function pruneTree(_tree, key, values, iteratorKey = 'children') { if (!(_tree instanceof Array) || typeof key !== 'string' || typeof iteratorKey !== 'string') return false if (!(values instanceof Array)) values = [values] let tree = cloneDeep(_tree) tree = tree.filter(branch => values.indexOf(branch[key]) < 0) tree = tree.map(branch => { if (iteratorKey in branch && branch[iteratorKey] instanceof Array) branch[iteratorKey] = pruneTree(branch[iteratorKey], key, values, iteratorKey) return branch }) return tree } /** * Recursively iterates through tree using a checker function to determine whether or not to add a branch's key to an array of values * @param {Object[]} tree * @param {String} key * @param {Function} checker * @param {String} [iteratorKey=children] * @return {Array} - returns array of values */ export function collectValues(tree, key, checker, iteratorKey = 'children') { if ( !(tree instanceof Array) || typeof key !== 'string' || typeof checker !== 'function' || typeof iteratorKey !== 'string' ) return false let values = [] tree.map(branch => { if (checker(branch) && key in branch) values.push(branch[key]) if (iteratorKey in branch && branch[iteratorKey] instanceof Array) values = values.concat(collectValues(branch[iteratorKey], key, checker, iteratorKey)) }) return values } /** * Recursively iterates through tree to get a child by a key and a value * @param {Object[]} tree * @param {String} key * @param {*} value - Can be anything * @param {String} [iteratorKey=children] * @return {Object} */ export function getChildByKeyValue(tree, key, value, iteratorKey = 'children') { if (!(tree instanceof Array) || typeof key !== 'string' || typeof iteratorKey !== 'string') return false let found = false tree.map(item => { if (!found) { if (item[key] === value) found = item else if (item[iteratorKey]) found = getChildByKeyValue(item[iteratorKey], key, value, iteratorKey) } }) return found } /** * Recursively iterates through tree and get all values of a set key * @param {Object[]} _tree * @param {Number} id * @param {String} key * @param {String} [iteratorKey=children] * @return {String[]} */ export function collectChildrenKeyValues(_tree, id, key, iteratorKey = 'children') { if (!(_tree instanceof Array) || typeof key !== 'string' || typeof id !== 'number' || typeof iteratorKey !== 'string') return false const branch = getChildByKeyValue(_tree, 'id', id, iteratorKey) if (!branch[iteratorKey]) return [] return collectValues(branch[iteratorKey], key, item => true, iteratorKey) } /** * Recursively iterates through tree looking for a child and sets a value by key, returns new tree * @param {Object[]} _tree * @param {Number} id * @param {String} setKey * @param {*} setValue - Can be anything * @param {Object[]} */ export function setKeyValueById(_tree, id, setKey, setValue, iteratorKey = 'children') { let tree = cloneDeep(_tree) return tree.map(item => { if (item.id === id) item[setKey] = setValue if (item[iteratorKey]) item[iteratorKey] = setKeyValueById(item[iteratorKey], id, setKey, setValue, iteratorKey) return item }) } /** * Recursively iterates through tree and sorts all branches by key, returns new tree * @param {Object[]} _tree * @param {String} key * @param {Boolean} desc - Descending * @param {String} [iteratorKey=children] * @return {Object[]} */ export function sortByKey(_tree, key, desc = false, iteratorKey = 'children') { let tree = _tree.slice() tree.sort((a, b) => { if (typeof a[key] == 'string') { return a[key].localeCompare(b[key], undefined, { sensitivity: 'base' }) } else { return a[key] - b[key] } }) if (desc) tree.reverse() return tree.map(item => { if (!item[iteratorKey]) { return item } return { ...item, [iteratorKey]: sortByKey(item[iteratorKey], key, desc, iteratorKey), } }) } /** * Recursively iterates through tree and appends child to parent by parentId, returns new tree * @param {Object[]} _tree * @param {Number} parentId * @param {Object} child * @param {Number} [currentParentId=null] * @param {String} [iteratorKey=children] * @return {Object[]} */ export function appendChildToParent(_tree, parentId, child, currentParentId = null, iteratorKey = 'children') { if (!(_tree instanceof Array) || typeof iteratorKey !== 'string') return false let tree = cloneDeep(_tree) if (parentId === currentParentId) tree.push(child) else tree = tree.map(item => { if (parentId === item.id && !item[iteratorKey]) item[iteratorKey] = [] if (item[iteratorKey]) item[iteratorKey] = appendChildToParent(item[iteratorKey], parentId, child, item.id, iteratorKey) return item }) return tree } /** * Recursively iterates through tree looking to replace a specific child by given key (children are preserved), returns new tree * @param {Object[]} _tree * @param {Object} child * @param {String} [key=id] * @param {String} [iteratorKey=children] * @return {Object[]} */ export function replaceChild(_tree, child, key = 'id', iteratorKey = 'children') { if (!(_tree instanceof Array) || typeof iteratorKey !== 'string') return false let tree = cloneDeep(_tree) return tree.map(item => { if (item[key] === child[key]) { if (item[iteratorKey] && item[iteratorKey].length) return { ...child, [iteratorKey]: item[iteratorKey] } else return child } if (item[iteratorKey]) item[iteratorKey] = replaceChild(item[iteratorKey], child, key, iteratorKey) return item }) } /** * Recursively iterates through tree and concats branch chilren where supplied values match branch value * @param {Object[]} _tree * @param {Object[]} values * @param {String} valueKey * @param {String} key * @param {String} iteratorKey * @return {Object[]} */ export function mergeChildren(_tree, values, valueKey = 'folder_id', key = 'id', iteratorKey = 'children') { if (!(_tree instanceof Array) || !(values instanceof Array)) return false let tree = cloneDeep(_tree) const orphanedValues = values.filter(value => value[valueKey] === null) if (orphanedValues.length > 0) tree = [...tree, ...orphanedValues.map(value => ({ ...value, __injected: true }))] return tree.map(item => { if (item.__injected) return item const injectValues = values.filter(value => value[valueKey] == item[key]) if (item[iteratorKey] && item[iteratorKey].length > 0) { item[iteratorKey] = [...mergeChildren(item[iteratorKey], values, valueKey, key, iteratorKey), ...injectValues] } else { item[iteratorKey] = injectValues } return item }) }