simple-tree-utils
Advanced tools
Comparing version
@@ -5,4 +5,12 @@ ### Changelog | ||
#### [3.1.0](https://github.com/Raiper34/simple-tree-utils/compare/3.0.1...3.1.0) | ||
- feat(deleteby): add deleteBy method to delete nodes by given callback [`c62afc9`](https://github.com/Raiper34/simple-tree-utils/commit/c62afc9059b2f7c6a81ef488ede2c419f43a948c) | ||
- feat(computepaths): add computePaths method to compute paths for all nodes [`e30e6a8`](https://github.com/Raiper34/simple-tree-utils/commit/e30e6a8713471a0fc9976f6e5b16248fec767198) | ||
- feat(foreach): add forEach moethod to iterate over each nodes [`1352f44`](https://github.com/Raiper34/simple-tree-utils/commit/1352f44a409d7726ea2e68a3983b188f555bf912) | ||
#### [3.0.1](https://github.com/Raiper34/simple-tree-utils/compare/3.0.0...3.0.1) | ||
> 11 April 2025 | ||
- fix(tree2list): ability to parse tree with nodes that does not contain children property [`30e8f49`](https://github.com/Raiper34/simple-tree-utils/commit/30e8f4957a9ef10cb63d367b3121299d4f491b8f) | ||
@@ -9,0 +17,0 @@ |
@@ -79,2 +79,8 @@ /** | ||
/** | ||
* Method to iterate over all nodes | ||
* @param tree - tree structure to iterate over | ||
* @param fn - callback function to perform | ||
*/ | ||
forEach(tree: any[], fn: (item: any) => any): void; | ||
/** | ||
* Method to find all nodes in tree structure by given callback function | ||
@@ -94,5 +100,17 @@ * @param tree - tree structure to search in | ||
* @param id - identifier of node to delete | ||
* @returns deleted node, if nothing deleted then returns null | ||
*/ | ||
delete(tree: any[], id: any): any; | ||
/** | ||
* Method to delete node in tree by given callback function (mutable operation!) | ||
* @param tree - tree structure for node deleting | ||
* @param fn - callback function to remove all nodes | ||
* @returns deleted nodes | ||
* @example | ||
* ```ts | ||
* utils.deleteBy(tree, item => item.id === myId); | ||
* ``` | ||
*/ | ||
deleteBy(tree: any[] | undefined, fn: (item: any) => boolean): any[]; | ||
/** | ||
* Method to add new node to tree, node will be added as last child (mutable operation!) | ||
@@ -106,3 +124,3 @@ * @param tree - tree structure for node adding | ||
/** | ||
* Method to add new node to tree, node will be added as last child (mutable operation!) | ||
* Method to add new node to tree, node will be added as first child (mutable operation!) | ||
* @param tree - tree structure for node adding | ||
@@ -298,2 +316,13 @@ * @param parentId - identifier of parent node, null if new node should be on root level | ||
/** | ||
* Method to compute paths for nodes (mutable operation) | ||
* path property will be added into each node | ||
* e.g. {path: "parent/child"...} | ||
* @param tree - tree structure | ||
* @param pathComputationProperty - property to use for path computation | ||
* @param delimiter - to delimit path | ||
* @param pathProperty - property where path will be stored | ||
* @param originPath - path of top level nodes | ||
*/ | ||
computePaths(tree: any[], pathComputationProperty: string, delimiter?: string, pathProperty?: string, originPath?: string): void; | ||
/** | ||
* Helper method to deep clone object | ||
@@ -300,0 +329,0 @@ * @param obj - object to be cloned |
@@ -1,1 +0,1 @@ | ||
var simpleTreeUtils=function(l){"use strict";const u="id",g="parentId",P="children";class o{constructor(e){this.idProp=(e==null?void 0:e.idProp)||u,this.parentIdProp=(e==null?void 0:e.parentIdProp)||g,this.childrenProp=(e==null?void 0:e.childrenProp)||P}list2Tree(e,t=null){return e.filter(r=>r[this.parentIdProp]===t).map(r=>({...r,[this.childrenProp]:this.list2Tree(e,r[this.idProp])}))}tree2List(e){return this._tree2List(o.deepCopy(e))}_tree2List(e,t=null){return o.deepCopy(e).reduce((r,i)=>{const{[this.childrenProp]:s,...n}=i;return[...r,{...n,[this.parentIdProp]:t},...s!=null&&s.length?this._tree2List(s,n[this.idProp]):[]]},[])}get(e,t){return this.find(e,r=>r[this.idProp]===t)}find(e,t){const r=e.find(i=>t(i));return r||e.reduce((i,s)=>i||this.find(s[this.childrenProp]||[],t),null)}filter(e,t){const r=e.filter(i=>t(i));return e.reduce((i,s)=>[...i,...s[this.childrenProp].length?this.filter(s[this.childrenProp],t):[]],r)}delete(e,t){const r=e.findIndex(i=>i[this.idProp]==t);return r!=-1?e.splice(r,1)[0]:e.reduce((i,s)=>i||this.delete(s[this.childrenProp],t),null)}add(e,t,r,...i){this._add("push",e,t,r,...i)}addUnshift(e,t,r,...i){this._add("unshift",e,t,r,...i)}_add(e,t,r,i,...s){if(r==null){t[e](i,...s);return}const n=t.findIndex(h=>h[this.idProp]==r);if(n!=-1){t[n][this.childrenProp][e]({[this.childrenProp]:[],...i},...s.map(h=>({[this.childrenProp]:[],...h})));return}t.forEach(h=>this.add(h[this.childrenProp],r,i,...s))}edit(e,t,r){const i=e.findIndex(s=>s[this.idProp]==t);if(i!=-1){e[i]={[this.idProp]:e[i][this.idProp],[this.childrenProp]:[],...r};return}e.forEach(s=>this.edit(s[this.childrenProp],t,r))}getDescendants(e,t){const r=this.get(e,t);return r?this._getDescendants(r):[]}_getDescendants(e){return[...e[this.childrenProp],...e[this.childrenProp].reduce((t,r)=>[...t,...this._getDescendants(r)],[])]}getAncestors(e,t){const r=[];let i=this.getParent(e,t);for(;i;)r.push(i),i=this.getParent(e,i[this.idProp]);return r.reverse()}getPathNodes(e,t){return this.getAncestors(e,t)}getParent(e,t){return this._getParent(e,t)}_getParent(e,t,r=null){return e.find(s=>s[this.idProp]===t)?r:e.reduce((s,n)=>s||this._getParent(n[this.childrenProp]||[],t,n),null)}getChildren(e,t){var r;return((r=this.get(e,t))==null?void 0:r[this.childrenProp])||[]}getNeighbours(e,t){return[this.getParent(e,t),...this.getChildren(e,t)].filter(r=>r)}getSiblings(e,t){var r;return(((r=this.getParent(e,t))==null?void 0:r[this.childrenProp])||[]).filter(i=>i[this.idProp]!==t)}getLeafs(e,t){return this.filter(this.getSubTree(e,t),r=>!r[this.childrenProp].length)}getSubTree(e,t){return this.getChildren(e,t)}getSize(e,t){return this.tree2List(this.getSubTree(e,t)).length+1}getBreath(e,t){return this.getLeafs(e,t).length}getDepth(e,t){return this.getPathNodes(e,t).length}getLevel(e,t){return this.getDepth(e,t)+1}getDegree(e,t){return this.getChildren(e,t).length}getTreeDegree(e){return e.reduce((t,r)=>Math.max(t,r[this.childrenProp].length,this.getTreeDegree(r[this.childrenProp])),0)}getNodesAtLevel(e,t){return this._getNodesAtLevel(e,t)}_getNodesAtLevel(e,t,r=0){return e.reduce((i,s)=>[...i,...t===r?[s]:[],...r<t?this._getNodesAtLevel(s[this.childrenProp],t,r+1):[]],[])}getWidth(e,t){return this.getNodesAtLevel(e,t).length}getHeight(e,t){return this.getHeightNode(this.getSubTree(e,t))}getHeightNode(e,t=0){return e.reduce((r,i)=>Math.max(r,this.getHeightNode(i[this.childrenProp],t+1)),t)}getDistance(e,t,r){const i=[...this.getPathNodes(e,t),this.get(e,t)],s=[...this.getPathNodes(e,r),this.get(e,r)],n=[...i].reverse().find(d=>s.includes(d));if(!n)return-1;const h=i.findIndex(d=>d.id===n.id),p=s.findIndex(d=>d.id===n.id);return i.length-h-1+(s.length-p-1)}static deepCopy(e){return JSON.parse(JSON.stringify(e))}}return l.TreeUtils=o,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"}),l}({}); | ||
var simpleTreeUtils=function(l){"use strict";const u="id",P="parentId",g="children";class o{constructor(e){this.idProp=(e==null?void 0:e.idProp)||u,this.parentIdProp=(e==null?void 0:e.parentIdProp)||P,this.childrenProp=(e==null?void 0:e.childrenProp)||g}list2Tree(e,t=null){return e.filter(r=>r[this.parentIdProp]===t).map(r=>({...r,[this.childrenProp]:this.list2Tree(e,r[this.idProp])}))}tree2List(e){return this._tree2List(o.deepCopy(e))}_tree2List(e,t=null){return o.deepCopy(e).reduce((r,s)=>{const{[this.childrenProp]:i,...h}=s;return[...r,{...h,[this.parentIdProp]:t},...i!=null&&i.length?this._tree2List(i,h[this.idProp]):[]]},[])}get(e,t){return this.find(e,r=>r[this.idProp]===t)}find(e,t){const r=e.find(s=>t(s));return r||e.reduce((s,i)=>s||this.find(i[this.childrenProp]||[],t),null)}forEach(e,t){e==null||e.forEach(r=>{t(r),this.forEach(r[this.childrenProp],t)})}filter(e,t){const r=e.filter(s=>t(s));return e.reduce((s,i)=>[...s,...i[this.childrenProp].length?this.filter(i[this.childrenProp],t):[]],r)}delete(e,t){return this.deleteBy(e,r=>r[this.idProp]===t)[0]||null}deleteBy(e=[],t){const s=e.filter(i=>t(i)).map((i,h)=>h).reverse().reduce((i,h)=>[...i,...e.splice(h,1)],[]);return e.reduce((i,h)=>[...i,...this.deleteBy(h[this.childrenProp],t)],s)}add(e,t,r,...s){this._add("push",e,t,r,...s)}addUnshift(e,t,r,...s){this._add("unshift",e,t,r,...s)}_add(e,t,r,s,...i){if(r==null){t[e](s,...i);return}const h=t.findIndex(n=>n[this.idProp]==r);if(h!=-1){t[h][this.childrenProp][e]({[this.childrenProp]:[],...s},...i.map(n=>({[this.childrenProp]:[],...n})));return}t.forEach(n=>this.add(n[this.childrenProp],r,s,...i))}edit(e,t,r){const s=e.findIndex(i=>i[this.idProp]==t);if(s!=-1){e[s]={[this.idProp]:e[s][this.idProp],[this.childrenProp]:[],...r};return}e.forEach(i=>this.edit(i[this.childrenProp],t,r))}getDescendants(e,t){const r=this.get(e,t);return r?this._getDescendants(r):[]}_getDescendants(e){return[...e[this.childrenProp],...e[this.childrenProp].reduce((t,r)=>[...t,...this._getDescendants(r)],[])]}getAncestors(e,t){const r=[];let s=this.getParent(e,t);for(;s;)r.push(s),s=this.getParent(e,s[this.idProp]);return r.reverse()}getPathNodes(e,t){return this.getAncestors(e,t)}getParent(e,t){return this._getParent(e,t)}_getParent(e,t,r=null){return e.find(i=>i[this.idProp]===t)?r:e.reduce((i,h)=>i||this._getParent(h[this.childrenProp]||[],t,h),null)}getChildren(e,t){var r;return((r=this.get(e,t))==null?void 0:r[this.childrenProp])||[]}getNeighbours(e,t){return[this.getParent(e,t),...this.getChildren(e,t)].filter(r=>r)}getSiblings(e,t){var r;return(((r=this.getParent(e,t))==null?void 0:r[this.childrenProp])||[]).filter(s=>s[this.idProp]!==t)}getLeafs(e,t){return this.filter(this.getSubTree(e,t),r=>!r[this.childrenProp].length)}getSubTree(e,t){return this.getChildren(e,t)}getSize(e,t){return this.tree2List(this.getSubTree(e,t)).length+1}getBreath(e,t){return this.getLeafs(e,t).length}getDepth(e,t){return this.getPathNodes(e,t).length}getLevel(e,t){return this.getDepth(e,t)+1}getDegree(e,t){return this.getChildren(e,t).length}getTreeDegree(e){return e.reduce((t,r)=>Math.max(t,r[this.childrenProp].length,this.getTreeDegree(r[this.childrenProp])),0)}getNodesAtLevel(e,t){return this._getNodesAtLevel(e,t)}_getNodesAtLevel(e,t,r=0){return e.reduce((s,i)=>[...s,...t===r?[i]:[],...r<t?this._getNodesAtLevel(i[this.childrenProp],t,r+1):[]],[])}getWidth(e,t){return this.getNodesAtLevel(e,t).length}getHeight(e,t){return this.getHeightNode(this.getSubTree(e,t))}getHeightNode(e,t=0){return e.reduce((r,s)=>Math.max(r,this.getHeightNode(s[this.childrenProp],t+1)),t)}getDistance(e,t,r){const s=[...this.getPathNodes(e,t),this.get(e,t)],i=[...this.getPathNodes(e,r),this.get(e,r)],h=[...s].reverse().find(d=>i.includes(d));if(!h)return-1;const n=s.findIndex(d=>d.id===h.id),c=i.findIndex(d=>d.id===h.id);return s.length-n-1+(i.length-c-1)}computePaths(e,t,r="/",s="path",i="/"){e==null||e.forEach(h=>{h[s]=i,this.computePaths(h[this.childrenProp],t,r,s,`${h.path}${h[t]}${r}`)})}static deepCopy(e){return JSON.parse(JSON.stringify(e))}}return l.TreeUtils=o,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"}),l}({}); |
@@ -1,2 +0,2 @@ | ||
const g = "id", p = "parentId", P = "children"; | ||
const p = "id", P = "parentId", u = "children"; | ||
class o { | ||
@@ -8,3 +8,3 @@ /** | ||
constructor(e) { | ||
this.idProp = (e == null ? void 0 : e.idProp) || g, this.parentIdProp = (e == null ? void 0 : e.parentIdProp) || p, this.childrenProp = (e == null ? void 0 : e.childrenProp) || P; | ||
this.idProp = (e == null ? void 0 : e.idProp) || p, this.parentIdProp = (e == null ? void 0 : e.parentIdProp) || P, this.childrenProp = (e == null ? void 0 : e.childrenProp) || u; | ||
} | ||
@@ -39,8 +39,8 @@ /** | ||
_tree2List(e, t = null) { | ||
return o.deepCopy(e).reduce((r, i) => { | ||
const { [this.childrenProp]: s, ...n } = i; | ||
return o.deepCopy(e).reduce((r, s) => { | ||
const { [this.childrenProp]: i, ...h } = s; | ||
return [ | ||
...r, | ||
{ ...n, [this.parentIdProp]: t }, | ||
...s != null && s.length ? this._tree2List(s, n[this.idProp]) : [] | ||
{ ...h, [this.parentIdProp]: t }, | ||
...i != null && i.length ? this._tree2List(i, h[this.idProp]) : [] | ||
]; | ||
@@ -69,5 +69,5 @@ }, []); | ||
find(e, t) { | ||
const r = e.find((i) => t(i)); | ||
const r = e.find((s) => t(s)); | ||
return r || e.reduce( | ||
(i, s) => i || this.find(s[this.childrenProp] || [], t), | ||
(s, i) => s || this.find(i[this.childrenProp] || [], t), | ||
null | ||
@@ -77,2 +77,12 @@ ); | ||
/** | ||
* Method to iterate over all nodes | ||
* @param tree - tree structure to iterate over | ||
* @param fn - callback function to perform | ||
*/ | ||
forEach(e, t) { | ||
e == null || e.forEach((r) => { | ||
t(r), this.forEach(r[this.childrenProp], t); | ||
}); | ||
} | ||
/** | ||
* Method to find all nodes in tree structure by given callback function | ||
@@ -88,4 +98,4 @@ * @param tree - tree structure to search in | ||
filter(e, t) { | ||
const r = e.filter((i) => t(i)); | ||
return e.reduce((i, s) => [...i, ...s[this.childrenProp].length ? this.filter(s[this.childrenProp], t) : []], r); | ||
const r = e.filter((s) => t(s)); | ||
return e.reduce((s, i) => [...s, ...i[this.childrenProp].length ? this.filter(i[this.childrenProp], t) : []], r); | ||
} | ||
@@ -96,8 +106,22 @@ /** | ||
* @param id - identifier of node to delete | ||
* @returns deleted node, if nothing deleted then returns null | ||
*/ | ||
delete(e, t) { | ||
const r = e.findIndex((i) => i[this.idProp] == t); | ||
return r != -1 ? e.splice(r, 1)[0] : e.reduce((i, s) => i || this.delete(s[this.childrenProp], t), null); | ||
return this.deleteBy(e, (r) => r[this.idProp] === t)[0] || null; | ||
} | ||
/** | ||
* Method to delete node in tree by given callback function (mutable operation!) | ||
* @param tree - tree structure for node deleting | ||
* @param fn - callback function to remove all nodes | ||
* @returns deleted nodes | ||
* @example | ||
* ```ts | ||
* utils.deleteBy(tree, item => item.id === myId); | ||
* ``` | ||
*/ | ||
deleteBy(e = [], t) { | ||
const s = e.filter((i) => t(i)).map((i, h) => h).reverse().reduce((i, h) => [...i, ...e.splice(h, 1)], []); | ||
return e.reduce((i, h) => [...i, ...this.deleteBy(h[this.childrenProp], t)], s); | ||
} | ||
/** | ||
* Method to add new node to tree, node will be added as last child (mutable operation!) | ||
@@ -109,7 +133,7 @@ * @param tree - tree structure for node adding | ||
*/ | ||
add(e, t, r, ...i) { | ||
this._add("push", e, t, r, ...i); | ||
add(e, t, r, ...s) { | ||
this._add("push", e, t, r, ...s); | ||
} | ||
/** | ||
* Method to add new node to tree, node will be added as last child (mutable operation!) | ||
* Method to add new node to tree, node will be added as first child (mutable operation!) | ||
* @param tree - tree structure for node adding | ||
@@ -120,4 +144,4 @@ * @param parentId - identifier of parent node, null if new node should be on root level | ||
*/ | ||
addUnshift(e, t, r, ...i) { | ||
this._add("unshift", e, t, r, ...i); | ||
addUnshift(e, t, r, ...s) { | ||
this._add("unshift", e, t, r, ...s); | ||
} | ||
@@ -132,16 +156,16 @@ /** | ||
*/ | ||
_add(e, t, r, i, ...s) { | ||
_add(e, t, r, s, ...i) { | ||
if (r == null) { | ||
t[e](i, ...s); | ||
t[e](s, ...i); | ||
return; | ||
} | ||
const n = t.findIndex((h) => h[this.idProp] == r); | ||
if (n != -1) { | ||
t[n][this.childrenProp][e]( | ||
{ [this.childrenProp]: [], ...i }, | ||
...s.map((h) => ({ [this.childrenProp]: [], ...h })) | ||
const h = t.findIndex((n) => n[this.idProp] == r); | ||
if (h != -1) { | ||
t[h][this.childrenProp][e]( | ||
{ [this.childrenProp]: [], ...s }, | ||
...i.map((n) => ({ [this.childrenProp]: [], ...n })) | ||
); | ||
return; | ||
} | ||
t.forEach((h) => this.add(h[this.childrenProp], r, i, ...s)); | ||
t.forEach((n) => this.add(n[this.childrenProp], r, s, ...i)); | ||
} | ||
@@ -155,8 +179,8 @@ /** | ||
edit(e, t, r) { | ||
const i = e.findIndex((s) => s[this.idProp] == t); | ||
if (i != -1) { | ||
e[i] = { [this.idProp]: e[i][this.idProp], [this.childrenProp]: [], ...r }; | ||
const s = e.findIndex((i) => i[this.idProp] == t); | ||
if (s != -1) { | ||
e[s] = { [this.idProp]: e[s][this.idProp], [this.childrenProp]: [], ...r }; | ||
return; | ||
} | ||
e.forEach((s) => this.edit(s[this.childrenProp], t, r)); | ||
e.forEach((i) => this.edit(i[this.childrenProp], t, r)); | ||
} | ||
@@ -193,5 +217,5 @@ /** | ||
const r = []; | ||
let i = this.getParent(e, t); | ||
for (; i; ) | ||
r.push(i), i = this.getParent(e, i[this.idProp]); | ||
let s = this.getParent(e, t); | ||
for (; s; ) | ||
r.push(s), s = this.getParent(e, s[this.idProp]); | ||
return r.reverse(); | ||
@@ -226,4 +250,4 @@ } | ||
_getParent(e, t, r = null) { | ||
return e.find((s) => s[this.idProp] === t) ? r : e.reduce( | ||
(s, n) => s || this._getParent(n[this.childrenProp] || [], t, n), | ||
return e.find((i) => i[this.idProp] === t) ? r : e.reduce( | ||
(i, h) => i || this._getParent(h[this.childrenProp] || [], t, h), | ||
null | ||
@@ -259,3 +283,3 @@ ); | ||
var r; | ||
return (((r = this.getParent(e, t)) == null ? void 0 : r[this.childrenProp]) || []).filter((i) => i[this.idProp] !== t); | ||
return (((r = this.getParent(e, t)) == null ? void 0 : r[this.childrenProp]) || []).filter((s) => s[this.idProp] !== t); | ||
} | ||
@@ -352,6 +376,6 @@ /** | ||
_getNodesAtLevel(e, t, r = 0) { | ||
return e.reduce((i, s) => [ | ||
...i, | ||
...t === r ? [s] : [], | ||
...r < t ? this._getNodesAtLevel(s[this.childrenProp], t, r + 1) : [] | ||
return e.reduce((s, i) => [ | ||
...s, | ||
...t === r ? [i] : [], | ||
...r < t ? this._getNodesAtLevel(i[this.childrenProp], t, r + 1) : [] | ||
], []); | ||
@@ -385,3 +409,3 @@ } | ||
getHeightNode(e, t = 0) { | ||
return e.reduce((r, i) => Math.max(r, this.getHeightNode(i[this.childrenProp], t + 1)), t); | ||
return e.reduce((r, s) => Math.max(r, this.getHeightNode(s[this.childrenProp], t + 1)), t); | ||
} | ||
@@ -396,9 +420,24 @@ /** | ||
getDistance(e, t, r) { | ||
const i = [...this.getPathNodes(e, t), this.get(e, t)], s = [...this.getPathNodes(e, r), this.get(e, r)], n = [...i].reverse().find((d) => s.includes(d)); | ||
if (!n) | ||
const s = [...this.getPathNodes(e, t), this.get(e, t)], i = [...this.getPathNodes(e, r), this.get(e, r)], h = [...s].reverse().find((d) => i.includes(d)); | ||
if (!h) | ||
return -1; | ||
const h = i.findIndex((d) => d.id === n.id), l = s.findIndex((d) => d.id === n.id); | ||
return i.length - h - 1 + (s.length - l - 1); | ||
const n = s.findIndex((d) => d.id === h.id), l = i.findIndex((d) => d.id === h.id); | ||
return s.length - n - 1 + (i.length - l - 1); | ||
} | ||
/** | ||
* Method to compute paths for nodes (mutable operation) | ||
* path property will be added into each node | ||
* e.g. {path: "parent/child"...} | ||
* @param tree - tree structure | ||
* @param pathComputationProperty - property to use for path computation | ||
* @param delimiter - to delimit path | ||
* @param pathProperty - property where path will be stored | ||
* @param originPath - path of top level nodes | ||
*/ | ||
computePaths(e, t, r = "/", s = "path", i = "/") { | ||
e == null || e.forEach((h) => { | ||
h[s] = i, this.computePaths(h[this.childrenProp], t, r, s, `${h.path}${h[t]}${r}`); | ||
}); | ||
} | ||
/** | ||
* Helper method to deep clone object | ||
@@ -405,0 +444,0 @@ * @param obj - object to be cloned |
{ | ||
"name": "simple-tree-utils", | ||
"version": "3.0.1", | ||
"version": "3.1.0", | ||
"description": "Simple Tree Utils is the library to convert and manipulate with tree-like structures.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -38,3 +38,3 @@ [](https://badge.fury.io/js/simple-tree-utils) | ||
```html | ||
<script src="https://cdn.jsdelivr.net/npm/simple-tree-utils@3.0.1/dist/simple-tree-utils.iife.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/simple-tree-utils@3.1.0/dist/simple-tree-utils.iife.js"></script> | ||
``` | ||
@@ -102,3 +102,3 @@ | ||
```html | ||
<script src="https://cdn.jsdelivr.net/npm/simple-tree-utils@3.0.1/dist/simple-tree-utils.iife.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/simple-tree-utils@3.1.0/dist/simple-tree-utils.iife.js"></script> | ||
<script> | ||
@@ -105,0 +105,0 @@ const utils = new simpleTreeUtils.TreeUtils(); |
@@ -135,2 +135,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
/** | ||
* Method to iterate over all nodes | ||
* @param tree - tree structure to iterate over | ||
* @param fn - callback function to perform | ||
*/ | ||
forEach(tree: any[], fn: (item: any) => any): void { | ||
tree?.forEach(item => { | ||
fn(item); | ||
this.forEach(item[this.childrenProp], fn); | ||
}); | ||
} | ||
/** | ||
* Method to find all nodes in tree structure by given callback function | ||
@@ -156,12 +168,28 @@ * @param tree - tree structure to search in | ||
* @param id - identifier of node to delete | ||
* @returns deleted node, if nothing deleted then returns null | ||
*/ | ||
delete(tree: any[], id: any): any { | ||
const index = tree.findIndex(item => item[this.idProp] == id); | ||
if (index != -1) { | ||
return tree.splice(index, 1)[0]; | ||
} | ||
return tree.reduce((acc, curr) => acc || this.delete(curr[this.childrenProp], id), null); | ||
return this.deleteBy(tree, item => item[this.idProp] === id)[0] || null; | ||
} | ||
/** | ||
* Method to delete node in tree by given callback function (mutable operation!) | ||
* @param tree - tree structure for node deleting | ||
* @param fn - callback function to remove all nodes | ||
* @returns deleted nodes | ||
* @example | ||
* ```ts | ||
* utils.deleteBy(tree, item => item.id === myId); | ||
* ``` | ||
*/ | ||
deleteBy(tree: any[] = [], fn: (item: any) => boolean): any[] { | ||
const indexesToRemove = tree | ||
.filter(item => fn(item)) | ||
.map((_item, index) => index) | ||
.reverse(); | ||
const removedItems = indexesToRemove.reduce<any>((acc, curr) => ([...acc, ...tree.splice(curr, 1)]), []); | ||
return tree.reduce((acc, curr) => [...acc, ...this.deleteBy(curr[this.childrenProp], fn)], removedItems) | ||
} | ||
/** | ||
* Method to add new node to tree, node will be added as last child (mutable operation!) | ||
@@ -178,3 +206,3 @@ * @param tree - tree structure for node adding | ||
/** | ||
* Method to add new node to tree, node will be added as last child (mutable operation!) | ||
* Method to add new node to tree, node will be added as first child (mutable operation!) | ||
* @param tree - tree structure for node adding | ||
@@ -493,2 +521,19 @@ * @param parentId - identifier of parent node, null if new node should be on root level | ||
/** | ||
* Method to compute paths for nodes (mutable operation) | ||
* path property will be added into each node | ||
* e.g. {path: "parent/child"...} | ||
* @param tree - tree structure | ||
* @param pathComputationProperty - property to use for path computation | ||
* @param delimiter - to delimit path | ||
* @param pathProperty - property where path will be stored | ||
* @param originPath - path of top level nodes | ||
*/ | ||
computePaths(tree: any[], pathComputationProperty: string, delimiter = '/', pathProperty = 'path', originPath = '/'): void { | ||
tree?.forEach(item => { | ||
item[pathProperty] = originPath; | ||
this.computePaths(item[this.childrenProp], pathComputationProperty, delimiter, pathProperty, `${item.path}${item[pathComputationProperty]}${delimiter}`); | ||
}); | ||
} | ||
/** | ||
* Helper method to deep clone object | ||
@@ -495,0 +540,0 @@ * @param obj - object to be cloned |
@@ -110,3 +110,3 @@ import {beforeEach, describe, expect, it} from 'vitest' | ||
it('should delete node by given customId', () => { | ||
treeUtils.delete(mock, 6); | ||
const deleted = treeUtils.delete(mock, 6); | ||
const out = [ | ||
@@ -126,6 +126,86 @@ { | ||
}, | ||
] | ||
]; | ||
expect(deleted).toEqual({customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []}); | ||
expect(mock).toEqual(out); | ||
}); | ||
it('should delete node returns null when not found', () => { | ||
const deleted = treeUtils.delete(mock, 100); | ||
expect(deleted).toEqual(null); | ||
}); | ||
it('should delete node when there is no children property', () => { | ||
const input = [{customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [ | ||
{customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [ | ||
{customId: 6, parentCustomId: 3, name: 'Node 6'}]}, | ||
{customId: 4, parentCustomId: 1, name: 'Node 4'}]}, | ||
]; | ||
const deleted = treeUtils.deleteBy(input, item => item.customId === 6); | ||
expect(deleted).toEqual([{customId: 6, parentCustomId: 3, name: 'Node 6'}]); | ||
}); | ||
it('should delete node by given callback', () => { | ||
const deleted = treeUtils.deleteBy(mock, item => item.customId === 5 || item.customId === 6); | ||
const out = [ | ||
{ | ||
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [ | ||
{ | ||
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [] | ||
}, | ||
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: []}, | ||
] | ||
}, | ||
{customId: 2, parentCustomId: null, name: 'Node 2', customChildren: []}, | ||
]; | ||
expect(deleted).toEqual([ | ||
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []}, | ||
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []}, | ||
]); | ||
expect(mock).toEqual(out); | ||
}); | ||
it('should iterate over each node', () => { | ||
treeUtils.forEach(mock, item => item.name = `I${item.name}`); | ||
const out = [ | ||
{ | ||
customId: 1, parentCustomId: null, name: 'INode 1', customChildren: [ | ||
{ | ||
customId: 3, parentCustomId: 1, name: 'INode 3', customChildren: [ | ||
{customId: 6, parentCustomId: 3, name: 'INode 6', customChildren: []}, | ||
] | ||
}, | ||
{customId: 4, parentCustomId: 1, name: 'INode 4', customChildren: []}, | ||
] | ||
}, | ||
{ | ||
customId: 2, parentCustomId: null, name: 'INode 2', customChildren: [ | ||
{customId: 5, parentCustomId: 2, name: 'INode 5', customChildren: []}, | ||
] | ||
}, | ||
]; | ||
expect(mock).toEqual(out); | ||
}); | ||
it('should compute paths for nodes', () => { | ||
treeUtils.computePaths(mock, 'name'); | ||
const out = [ | ||
{ | ||
customId: 1, parentCustomId: null, name: 'Node 1', path: '/', customChildren: [ | ||
{ | ||
customId: 3, parentCustomId: 1, name: 'Node 3', path: '/Node 1/', customChildren: [ | ||
{customId: 6, parentCustomId: 3, name: 'Node 6', path: '/Node 1/Node 3/', customChildren: []}, | ||
] | ||
}, | ||
{customId: 4, parentCustomId: 1, name: 'Node 4', path: '/Node 1/', customChildren: []}, | ||
] | ||
}, | ||
{ | ||
customId: 2, parentCustomId: null, name: 'Node 2', path: '/', customChildren: [ | ||
{customId: 5, parentCustomId: 2, name: 'Node 5', path: '/Node 2/', customChildren: []}, | ||
] | ||
}, | ||
]; | ||
expect(mock).toEqual(out); | ||
}); | ||
it('should add node to tree as first child of given node', () => { | ||
@@ -132,0 +212,0 @@ treeUtils.addUnshift(mock, 1, {customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
90945
11.15%1752
12.09%