Remove parent in a nested object hierarchy (tree) retaining children - javascript

I am trying to delete an object in a nested object(hierarchical) array with a parent-child relationship. I want to delete an object from this which has the attribute 'required' as false and 'name' is empty or null. When I delete this object, if it is a parent object, I want the children to be attached to the grandparent (parent's parent object). But when I delete it in recursion, the whole element including the child gets deleted. Please help with the recursive approach.
The following is the structure
{
"name": "abc",
"nodeId": 1,
"parentNodeId": null,
"required": true,
"children": [
{
"name": "",
"nodeId": 2,
"parentNodeId": 1,
"required": false,
"children": [
{
"name": "",
"nodeId": 3,
"parentNodeId": 2,
"required": false,
"children": [
{
"name": "xyz",
"nodeId": 4,
"parentNodeId": 3,
"required": true,
"children": []
}
]
},
{
"name": "pqr",
"nodeId": 5,
"parentNodeId": 2,
"required": true,
"children": []
}
]
}
]
}
In the above scenario, the child with name 'xyz' should directly be attached to the nodeId of 1 (abc) as its immediate parents are to be deleted.
I have found this particular solution for deletion based on given condition, but it doesn't retain the child elements.
removeFromTree(root, parent, idx) {
if (!root.name && root.required === false) {
if (parent) {
parent.modelLines.splice(idx, 1);
}
else return null;
}
if (root.modelLines != null) {
for (const [i, e] of root.children.entries()) {
this.removeFromTree(e, root, i);
}
}
return tree;
};
Please help to address this scenario

You can use the below code snippet to achieve your requirement:
removeFromTree = function(node) {
var childrenHolder = []
var accumulator = []
for(const [i, child] of node.children.entries()) {
var holder = removeFromTree(child);
if (child.name || child.required === true) {
childrenHolder.push(child)
}
else {
accumulator = [...accumulator, ...holder]
}
}
node.children = [...childrenHolder,...accumulator]
for( var [i, child] of node.children.entries()) {
child.parentNodeId = node.nodeId
}
return node.children
}

Related

What is wrong with my recursive filter function?

I have a react n-level app where the delete function is a recursive function utilizing Array.prototype.filter(). The data is an array of objects and in each object there can be an array that contains more data.
Data:
[
{
"id": 1,
"hasParent": false,
"hasChildren": true,
"parentId": 0,
"childrenIds": [
3,
5
],
"text": "Opportunities don't happen, you create them.",
"childComments": [
{
"id": 3,
"hasParent": true,
"hasChildren": true,
"parentId": 1,
"childrenIds": [
4
],
"text": "one",
"childComments": [
{
"id": 4,
"hasParent": true,
"hasChildren": false,
"parentId": 3,
"childrenIds": [],
"text": "two",
"childComments": []
}
]
},
{
"id": 5,
"hasParent": true,
"hasChildren": false,
"parentId": 1,
"childrenIds": [],
"text": "one",
"childComments": []
}
]
},
{
"id": 2,
"hasParent": false,
"hasChildren": false,
"parentId": 0,
"childrenIds": [],
"text": "Just one small positive thought in the morning can change your whole day.",
"childComments": []
},
{
"id": 6,
"hasParent": false,
"hasChildren": false,
"parentId": 0,
"childrenIds": [],
"text": "More data one",
"childComments": []
},
{
"id": 7,
"hasParent": false,
"hasChildren": true,
"parentId": 0,
"childrenIds": [
8
],
"text": "More data two",
"childComments": [
{
"id": 8,
"hasParent": true,
"hasChildren": false,
"parentId": 7,
"childrenIds": [],
"text": "More data two-two",
"childComments": []
}
]
}
]
The delete function works for the top level of the data structure. For nested data, the delete function is not working. What am I doing wrong?
This is the code for the recursive delete function:
const HandleDelete = function(commentObj, objId) {
const filteredObj = commentObj.filter((ele) =>{
if (ele.childComments.length > 0 ) {
HandleDelete(ele.childComments, objId)
}
return ele.id !== objId;
})
return filteredObj;
}
As mentioned by #Pointy, you aren't doing anything with the result of the inner HandleDelete.
What I think you want is to map over the childComments first, then filter
const HandleDelete = function (commentArr, objId) {
const filteredArr = commentArr.filter((ele) => ele.id !== objId);
const filteredChildren = filteredArr.map((ele) => ({
...ele,
childComments: HandleDelete(ele.childComments, objId),
}));
return filteredChildren;
};
EDIT: changed order of map and filter, so it deletes top level items first, so it doesn't waste time recursing them only to throw away the result
Looks like you aren't updating the childComments property of the parent object after removing the child comment. Try updating the childComments property on the parent comment by calling your handleDelete function recursively, and setting the result as the new childComments value:
const HandleDelete = function(commentObj, objId) {
return commentObj.filter((ele) =>{
if (ele.childComments.length > 0 ) {
ele.childComments = HandleDelete(ele.childComments, objId)
}
return ele.id !== objId;
});
};
When you modify an element inside the filter function, it can cause unexpected behavior and may produce incorrect results. This is because modifying an element can change the truthiness of the condition you are using to filter, which can lead to unexpected filtering results.
A better way might be this
const HandleDelete = function (commentObj, objId) {
// create an empty array to store filtered objects
const filteredObj = [];
for (const ele of commentObj) {
// if the object id matches the id to be deleted
if (ele.id === objId) {
// if objId is unique, you can return here since you found the object to delete
return;
}
// if the object doesn't match the id to be deleted,
// check if it has child comments and recursively call the delete function
// passing in the child comments as the new commentObj
// otherwise, push the object into the filteredObj array
filteredObj.push(ele.childComments ? HandleDelete(ele.childComments, objId) : ele);
}
return filteredObj; // return the filtered array
};
Just filter without recursion, and only then apply recursion to childComments, where present:
const HandleDelete = function(commentObj, objId) {
const filteredObj = commentObj.filter((ele) =>{
return ele.id !== objId;
}).map((ele) =>{
if (ele.childComments.length > 0 ) {
return HandleDelete(ele.childComments, objId)
}
});
return filteredObj;
}

Trying to find element recursively in tree javascript

I'm trying to figure out how to search recursively element in json tree. For example I'm trying right now to reach 'Fruits' but can't figure out how to do it. Here's my json object
[
{
"_id": "604cab0acbdb8c1060698419",
"name": "Grocery",
"children": [
{
"children": [
{
"name": "Fruits",
"price": "200"
}
],
"_id": "604cad9b4ae51310c6f313f6",
"name": "Potatoes",
"price": "200"
},
{
"children": [],
"_id": "604cae721257d510e679a467",
"name": "Vegetables"
}
],
"date": "2021-03-13T12:07:38.186Z",
"__v": 0
} ]
function findName(name, tree) {
if(tree.children.name == name {
return tree;
}
if(tree.child == 0) {
return
}
return findName(name, tree);
};
There are a couple of issues with your implementation.
Your starting point is an array, but you treat it as though it were a node by trying to use its children property.
You're looking for name on children, but children is an array.
You're passing the same thing into findName at the end that you received. If you reach that point, you'll constantly call yourself until you run out of stack space.
Loop through the nodes in the array checking them and their children; see comments:
function findName(name, children) {
if (Array.isArray(children)) {
// Yes, check them
for (const childNode of children) {
if (childNode.name === name) {
// Found it
return childNode;
}
// Look in this node's children
const found = findName(name, childNode.children);
if (found) {
// Found in this node's children
return found;
}
}
}
}
Live Example:
const tree = [
{
"_id": "604cab0acbdb8c1060698419",
"name": "Grocery",
"children": [
{
"children": [
{
"name": "Fruits",
"price": "200"
}
],
"_id": "604cad9b4ae51310c6f313f6",
"name": "Potatoes",
"price": "200"
},
{
"children": [],
"_id": "604cae721257d510e679a467",
"name": "Vegetables"
}
],
"date": "2021-03-13T12:07:38.186Z",
"__v": 0
} ];
function findName(name, children) {
if (Array.isArray(children)) {
// Yes, check them
for (const childNode of children) {
if (childNode.name === name) {
// Found it
return childNode;
}
// Look in this node's children
const found = findName(name, childNode.children);
if (found) {
// Found in this node's children
return found;
}
}
}
}
console.log(findName("Fruits", tree));
const object = [
{
"_id": "604cab0acbdb8c1060698419",
"name": "Grocery",
"children": [
{
"children": [
{
"name": "Fruits",
"price": "200"
}
],
"_id": "604cad9b4ae51310c6f313f6",
"name": "Potatoes",
"price": "200"
},
{
"children": [],
"_id": "604cae721257d510e679a467",
"name": "Vegetables"
}
],
"date": "2021-03-13T12:07:38.186Z",
"__v": 0
} ]
function find(name, tree) {
// tree is undefined, return `undefined` (base case)
if (!tree) return
if (Array.isArray(tree)) {
// tree is an array
// so iterate over every object in the array
for (let i = 0; i < tree.length; i++) {
const obj = tree[i]
const result = find(name, obj)
// `find` returned non-undefined value
// means match is found, return it
if (result) return result
// no match found in `obj` so continue
}
} else if (tree.name === name) {
// `tree` is a key-value object
// and has matching `name`
return tree
}
// if no tree matching `name` found on the current `tree`
// try finding on its `children`
return find(name, tree.children)
}
console.log(find("Fruits", object))

Remove childless elements (except leafs) from nested array

I've got a table called mappings with id, leaf, parent_id, name and flip_parent columns. parent_id and flip_parent are both integers referring to the mapping.id column. flip_parent holds an id of a value that should be excluded from the tree. To do so I've got the following function ($mappings are all rows from the mappings table, flipParentIds are all values from the same table where the flip_parent value is not null)
private function removeFlipParents(array $mappings, array $flipParentIds)
{
foreach ($mappings as $key => $mapping) {
foreach ($flipParentIds as $id) {
if ($mapping['id'] === $id['flipParent']) {
unset($mappings[$key]);
}
}
}
return $mappings;
}
After those values have been removed, I need to build a tree with the remaining data (the tree goes 5/6 levels deep), which is done with the following piece of code;
private function buildTree(array $elements, $parentId)
{
$branch = [];
foreach ($elements as $element) {
if ($element['parentId'] == $parentId) {
$children = $this->buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
} else {
$element['children'] = [];
}
}
}
return $branch;
}
In this case elements is the same array as $mappings, but without those flip parents. The result of this function is returned as a JSON response and handled by Javascript to build a tree. The returned JSON has a structure similar to this;
[{
"id": 1, "name": "Node 1", "children": [{
"id": 2, "name": "Node 1.1", "children": [{
"id": 4, "name": "Node 1.1.1", "leaf": true, "children": [], "gls": [{
"id": 1000, "name": "GL1", "code": "0100"
}, {
"id": 1001, "name": "GL2", "code": "0200"
}]
}, {
"id": 5, "name": "Node 1.1.2", "leaf": true, "children": [], "gls": [{
"id": 2000, "name": "GL3", "code": "0300"
}, {
"id": 2001, "name": "GL4", "code": "0400"
}]
}]
}, {
"id": 3, "name": "Node 1.2", "children": [{
"id": 6, "name": "Node 1.2.1", "leaf": true, "children": [], "gls": [{
"id": 3000, "name": "GL5", "code": "0500"
}, {
"id": 3001, "name": "GL6", "code": "0600"
}]
}]
}]
},
{
"id": 7, "name": "Node 2", "children": [{
"id": 8, "name": "Node 2.1", "children": [{
"id": 9, "name": "Node 2.1.1", "leaf": true, "children": [], "gls": [{
"id": 4000, "name": "GL7", "code": "0700"
}, {
"id": 4001, "name": "GL8", "code": "0800"
}]
}]
}]
}
]
To build the tree in JS I've got the following two functions
parseNodes(nodes)
{
const ul = document.createElement('ul');
ul.style.listStyleType = 'none';
nodes.forEach((node) => {
const parsedNode = this.parseNode(node);
if (parsedNode) {
ul.appendChild(parsedNode);
}
});
return ul;
}
parseNode(node)
{
if (node.name && node.name !== '') {
const li = document.createElement('li');
li.className = node.leaf ? 'leaf' : 'parent';
li.setAttribute('data-mapping-id', node.id);
if (node.parentId) {
li.setAttribute('data-parent-id', node.parentId);
}
li.innerHTML = node.name;
if (node.children) {
li.appendChild(this.parseNodes(node.children));
}
return li;
}
return null;
}
This all works perfectly fine, but I now want to remove childless, non-leaf nodes from the tree, as only the leafs will actually be usable. I don't know at what level the childless nodes are, and not all childless nodes are at the same level.
To do so I've written a recursive PHP function, which is called after removeFlipParents and buildTree (this needs to be done as I need the tree structure to check whether branches have children of not);
private function removeChildlessBranches(array $nodes)
{
foreach ($nodes as $key => $node) {
if (empty($node['children']) && !$node['leaf']) {
unset($nodes[$key]);
} else {
$this->removeChildlessBranches($node['children']);
}
}
return $nodes;
}
This kinda does what I want it to do as it removes all childless root nodes that aren't leafs, but it doesn't go to the 2nd level and beyond. It also changes the structure of $nodes. Without the removeChildlessBranches call the JSON is wrapped in an array ([{"id": 1, "name": "Node 1", ...}]) and I can iterate over it in the parseNodes function, but if I do use the removeChildlessBranches call, $nodes no longer is wrapped in an array ({"id": 1, "name": "Node 1", ...}) and can't be iterated over.
So my two questions are, how can I make removeChildlessBranches actually recursive, and how can I make it so it doesn't change the structure of $nodes?
You're passing in a copy of the $node['children'] array on this line $this->removeChildlessBranches($node['children']); (If you haven't heard of it before lookup pass by reference and pass by value). So any subsequent changes are being made to that copy and not your original array (which is also a copy). The results of the changes are then being thrown away as you're not doing anything with them.
You could fix that issue by changing the line to this $nodes[$key]['children'] = $this->removeChildlessBranches($node['children']);.
Be aware however, that you may now have a node which has no children and is not a leaf but will not be removed correctly as you've already trimmed at that level. Trimming out children first and then unsetting should give you the desired result:
private function removeChildlessBranches(array $nodes)
{
foreach ($nodes as $key => $node) {
$nodes[$key]['children'] = $this->removeChildlessBranches($node['children']);
if (empty($nodes[$key]['children']) && !$nodes[$key]['leaf']) {
unset($nodes[$key]);
}
}
return $nodes;
}

How to delete object from an array of objects having relations with each arrays?

This Object have relationship as: childOne > childTwo > childThree > childFour > childFive > childSix.
{
"parentObj": {
"childOne": [
{
"name": "A",
"id": "1"
},
{
"name": "B",
"id": "2"
}
],
"childTwo": [
{
"name": "AB",
"parent_id": "1",
"id": "11"
},
{
"name": "DE",
"parent_id": "2",
"id": "22"
}
],
"childThree": [
{
"name": "ABC",
"parent_id": "22",
"id": "111"
},
{
"name": "DEF",
"parent_id": "11",
"id": "222"
}
],
"childFour": [
{
"name": "ABCD",
"parent_id": "111",
"id": "1111"
},
{
"name": "PQRS",
"parent_id": "111",
"id": "2222"
}
],
"childFive": [
{
"name": "FGRGF",
"parent_id": "1111",
"id": "11111"
},
{
"name": "ASLNJ",
"parent_id": "1111",
"id": "22222"
},
{
"name": "ASKJA",
"parent_id": "1111",
"id": "33333"
}
],
"childSix": [
{
"name": "SDKJBS",
"parent_id": "11111",
"id": "111111"
},
{
"name": "ASKLJB",
"parent_id": "11111",
"id": "222222"
}
]
}
}
Is there any way to delete an item by ID and the objects which are associated with that particular ID should get deleted(i.e., If I do delete parentObj.childTwo[1], then all the related object beneath it should also gets deleted).
Looping manually is too bad code, and generate bugs. There must be better ways of dealing with this kind of problems like recursion, or other.
The data structure does not allow for efficient manipulation:
By nature objects have an non-ordered set of properties, so there is no guarantee that iterating the properties of parentObj will give you the order childOne, childTwo, childThree, ... In practice this order is determined by the order in which these properties were created, but there is no documented guarantee for that. So one might find children before parents and vice versa.
Although the id values within one such child array are supposed to be unique, this object structure does not guarantee that. Moreover, given a certain id value, it is not possible to find the corresponding object in constant time.
Given this structure, it seems best to first add a hash to solve the above mentioned disadvantages. An object for knowing a node's group (by id) and an object to know which is the next level's group name, can help out for that.
The above two tasks can be executed in O(n) time, where n is the number of nodes.
Here is the ES5-compatible code (since you mentioned in comments not to have ES6 support). It provides one example call where node with id "1111" is removed from your example data, and prints the resulting object.
function removeSubTree(data, id) {
var groupOf = {}, groupAfter = {}, group, parents, keep = { false: [], true: [] };
// Provide link to group per node ID
for (group in data) {
data[group].forEach(function (node) {
groupOf[node.id] = group;
});
}
// Create ordered sequence of groups, since object properties are not ordered
for (group in data) {
if (!data[group].length || !data[group][0].parent_id) continue;
groupAfter[groupOf[data[group][0].parent_id]] = group;
}
// Check if given id exists:
group = groupOf[id];
if (!group) return; // Nothing to do
// Maintain list of nodes to keep and not to keep within the group
data[group].forEach(function (node) {
keep[node.id !== id].push(node);
});
while (keep.false.length) { // While there is something to delete
data[group] = keep.true; // Delete the nodes from the group
if (!keep.true.length) delete data[group]; // Delete the group if empty
// Collect the ids of the removed nodes
parents = {};
keep.false.forEach(function (node) {
parents[node.id] = true;
});
group = groupAfter[group]; // Go to next group
if (!group) break; // No more groups
// Determine what to keep/remove in that group
keep = { false: [], true: [] };
data[group].forEach(function (node) {
keep[!parents[node.parent_id]].push(node);
});
}
}
var tree = {"parentObj": {"childOne": [{"name": "A","id": "1"},{"name": "B","id": "2"}],"childTwo": [{"name": "AB","parent_id": "1","id": "11"},{"name": "DE","parent_id": "2","id": "22"}],"childThree": [{"name": "ABC","parent_id": "22","id": "111"},{"name": "DEF","parent_id": "11","id": "222"}],"childFour": [{"name": "ABCD","parent_id": "111","id": "1111"},{"name": "PQRS","parent_id": "111","id": "2222"}],"childFive": [{"name": "FGRGF","parent_id": "1111","id": "11111"},{"name": "ASLNJ","parent_id": "1111","id": "22222"},{"name": "ASKJA","parent_id": "1111","id": "33333"}],"childSix": [{"name": "SDKJBS","parent_id": "11111","id": "111111"},{"name": "ASKLJB","parent_id": "11111","id": "222222"}]}}
removeSubTree(tree.parentObj, "1111");
console.log(tree.parentObj);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Sure, the function you use to delete an entry should FIRST recurse, which means run itself on the linked entry, unless there is none. So, in psuedocode
function del(name, index)
{
if parent[name][index] has reference
Then del(reference name, reference ID)
Now del parent[name][index]
}
No loop needed.
And since we stop if there is no reference, we do not recurse forever.
Not sure what it is you want but maybe this will work:
const someObject = {
"parentObj": {
"childOne": [
{
"name": "A",
"id": "1"
},
{
"name": "B",
"id": "2"
}
],
"childTwo": [
{
"name": "AB",
"childOne": "1",
"id": "11"
},
{
"name": "DE",
"childOne": "2",
"id": "22"
}
]
}
};
const removeByID = (key,id,parent) =>
Object.keys(parent).reduce(
(o,k)=>{
o[k]=parent[k].filter(
item=>
!(Object.keys(item).includes(key)&&item[key]===id)
);
return o;
},
{}
);
const withoutID = Object.assign(
{},
someObject,
{ parentObj : removeByID("childOne","1",someObject.parentObj) }
);
console.log(`notice that childTwo item with childOne:"1" is gone`);
console.log("without key:",JSON.stringify(withoutID,undefined,2));
const otherExample = Object.assign(
{},
someObject,
{ parentObj : removeByID("childOne","2",someObject.parentObj) }
);
console.log(`notice that childTwo item with childOne:"2" is gone`);
console.log("without key:",JSON.stringify(otherExample,undefined,2));
const both = Object.assign(
{},
someObject,
{ parentObj : removeByID("childOne","1",otherExample.parentObj) }
);
console.log(`notice that childTwo items with childOne are both gone`);
console.log("without key:",JSON.stringify(both,undefined,2));

Creating a tree from a flat list using lodash

I am trying to create a category tree using the array of json objects below.
I want to set a category as a child of another category if its parent equals the id of the other, and I want the posts also to be a children of that category instead of having a separate field for posts, I'll add a flag field that if it is a category or not isParent.
It looks like its working alright, but as you may see, if a category has both category and post as child, it'll only show the categories. Another problem with that is if the post has a null value on its array, it will still push them as children.
What are the mistakes in my code, or is there a simpler or better solution to this?
var tree = unflatten(getData());
var pre = document.createElement('pre');
console.log(tree);
pre.innerText = JSON.stringify(tree, null, 4);
document.body.appendChild(pre);
function unflatten(array, parent, tree) {
tree = typeof tree !== 'undefined' ? tree : [];
parent = typeof parent !== 'undefined' ? parent : {
id: 0
};
_.map(array, function(arr) {
_.set(arr, 'isParent', true);
});
var children = _.filter(array, function(child) {
return child.parent == parent.id;
});
if (!_.isEmpty(children)) {
if (parent.id == 0) {
tree = children;
} else {
parent['children'] = children;
}
_.each(children, function(child) {
var posts = _.map(child.posts, function(post) {
return _.set(post, 'isParent', false);
});
child['children'] = posts;
delete child.posts;
unflatten(array, child);
});
}
return tree;
}
function getData() {
return [{
"id": "c1",
"parent": "",
"name": "foo",
"posts": [{
"id": "p1"
}]
}, {
"id": "c2",
"parent": "1",
"name": "bar",
"posts": [{
"id": "p2"
}]
}, {
"id": "c3",
"parent": "",
"name": "bazz",
"posts": [
null
]
}, {
"id": "c4",
"parent": "3",
"name": "sna",
"posts": [{
"id": "p3"
}]
}, {
"id": "c5",
"parent": "3",
"name": "ney",
"posts": [{
"id": "p4"
}]
}, {
"id": "c6",
"parent": "5",
"name": "tol",
"posts": [{
"id": "p5"
}, {
"id": "p6"
}]
}, {
"id": "c7",
"parent": "5",
"name": "zap",
"posts": [{
"id": "p7"
}, {
"id": "p8"
}, {
"id": "p9"
}]
}, {
"id": "c8",
"parent": "",
"name": "quz",
"posts": [
null
]
}, {
"id": "c9",
"parent": "8",
"name": "meh",
"posts": [{
"id": "p10"
}, {
"id": "p11"
}]
}, {
"id": "c10",
"parent": "8",
"name": "ror",
"posts": [{
"id": "p12"
}, {
"id": "p13"
}]
}, {
"id": "c11",
"parent": "",
"name": "gig",
"posts": [{
"id": "p14"
}]
}, {
"id": "c12",
"name": "xylo",
"parent": "",
"posts": [{
"id": "p15"
}]
}, {
"id": "c13",
"parent": "",
"name": "grr",
"posts": [{
"id": "p16"
}, {
"id": "p17"
}, {
"id": "p14"
}, {
"id": "p18"
}, {
"id": "p19"
}, {
"id": "p20"
}]
}]
}
<script src="//cdn.jsdelivr.net/lodash/3.10.1/lodash.min.js"></script>
Expected Output
So the expected output will be more like:
[
{
id: 'c1',
isParent: true,
children: [
{
id: 'c2',
isParent: true,
children: []
},
{
id: 'p1'
isParent: false
}
]
}
]
And so on..
Your code is very imperative. Try focusing on the "big picture" of data flow instead of writing code by trial-and-error. It's harder, but you get better results (and, in fact, usually it's faster) :)
My idea is to first group the categories by their parents. This is the first line of my solution and it actually becomes much easier after that.
_.groupBy and _.keyBy help a lot here:
function makeCatTree(data) {
var groupedByParents = _.groupBy(data, 'parent');
var catsById = _.keyBy(data, 'id');
_.each(_.omit(groupedByParents, ''), function(children, parentId) {
catsById['c' + parentId].children = children;
});
_.each(catsById, function(cat) {
// isParent will be true when there are subcategories (this is not really a good name, btw.)
cat.isParent = !_.isEmpty(cat.children);
// _.compact below is just for removing null posts
cat.children = _.compact(_.union(cat.children, cat.posts));
// optionally, you can also delete cat.posts here.
});
return groupedByParents[''];
}
I recommend trying each part in the developer console, then it becomes easy to understand.
I have made a small fidde that I think that is what you want.
http://jsfiddle.net/tx3uwhke/
var tree = buildTree(getData());
var pre = document.getElementById('a');
var jsonString = JSON.stringify(tree, null, 4);
console.log(jsonString);
pre.innerHTML = jsonString;
document.body.appendChild(pre);
function buildTree(data, parent){
var result = [];
parent = typeof parent !== 'undefined' ? parent : {id:""};
children = _.filter(data, function(value){
return value.parent === parent.id;
});
if(!_.isEmpty(children)){
_.each(children, function(child){
if (child != null){
result.push(child);
if(!_.isEmpty(child.posts)){
var posts = _.filter(child.posts, function(post){
return post !== null && typeof post !== 'undefined';
});
if(!_.isEmpty(posts)){
_.forEach(posts, function(post){
post.isParent = false;
});
}
result = _.union(result, posts);
delete child.posts;
}
ownChildren = buildTree(data, child);
if(!_.isEmpty(ownChildren)){
child.isParent = true;
child.children = ownChildren;
}else{
child.isParent = false;
}
}
});
}
return result;
}
EDIT: made a new fiddle to contain the isParent part you can find it here
While this problem looks simple, I can remember to have struggled achieving it in a simple way. I therefore created a generic util to do so
You only have to write maximum 3 custom callbacks methods.
Here is an example:
import { flattenTreeItemDeep, treeItemFromList } from './tree.util';
import { sortBy } from 'lodash';
const listItems: Array<ListItem> = [
// ordered list arrival
{ id: 1, isFolder: true, parent: null },
{ id: 2, isFolder: true, parent: 1 },
{ id: 3, isFolder: false, parent: 2 },
// unordered arrival
{ id: 4, isFolder: false, parent: 5 },
{ id: 5, isFolder: true, parent: 1 },
// empty main level folder
{ id: 6, isFolder: true, parent: null },
// orphan main level file
{ id: 7, isFolder: false, parent: null },
];
const trees = treeItemFromList(
listItems,
(listItem) => listItem.isFolder, // return true if the listItem contains items
(parent, leafChildren) => parent.id === leafChildren.parent, // return true if the leaf children is contained in the parent
(parent, folderChildren) => parent.id === folderChildren.parent // return true if the children is contained in the parent
);
console.log(trees);
/*
[
{
children: [
{
children: [{ data: { id: 3, isFolder: false, parent: 2 }, isLeaf: true }],
data: { id: 2, isFolder: true, parent: 1 },
isLeaf: false,
},
{
children: [{ data: { id: 4, isFolder: false, parent: 5 }, isLeaf: true }],
data: { id: 5, isFolder: true, parent: 1 },
isLeaf: false,
},
],
data: { id: 1, isFolder: true, parent: null },
isLeaf: false,
},
{ children: [], data: { id: 6, isFolder: true, parent: null }, isLeaf: false },
{
data: {
id: 7,
isFolder: false,
parent: null,
},
isLeaf: true,
},
]
*/
I did not check with your example as all cases are different, you however need to implement only 3 methods to let the algorithm build the tree for you:
If the item is a folder or a leaf (in your case just check if the children contain any non falsy item) i.e. listItem.posts.some((value)=>!!value)
if a parent contains the leaf child, (parent, child) => !!parent.posts.filter((val)=>!!val).find(({id})=>child.id === id)
if a parent contains the folder: optional if this is the same logic as for a leaf child.

Categories

Resources