I have an array of objects and I'm wondering how I can format it into a select box, using hyphens to represent each level deeper. Here's my object -
let elements = {
0: {
id: 1,
name: 'Parent folder',
parent_id: null
},
1: {
id: 2,
name: 'Another parent folder',
parent_id: null
},
2: {
id: 3,
name: 'Child folder 1',
parent_id: 1
},
3: {
id: 4,
name: 'Child folder 2',
parent_id: 1
},
4: {
id: 5,
name: 'Child of a child',
parent_id: 4,
},
}
I would like elements to then be re-formatted like below -
let elements = {
0: {
id: 1,
name: 'Parent folder',
parent_id: null,
depth: 0
},
1: {
id: 3,
name: 'Child folder 1',
parent_id: 1,
depth: 1
},
2: {
id: 4,
name: 'Child folder 2',
parent_id: 1,
depth: 1
},
3: {
id: 5,
name: 'Child of a child',
parent_id: 4,
depth: 2
},
4: {
id: 2,
name: 'Another parent folder',
parent_id: null,
depth: 0
},
}
This way I could loop through the object and generate a select in the following structure based on the depth variable -
Parent folder
- Child folder 1
- Child folder 2
-- Child of a child
Another parent folder
Currently I am looping my object through a process and getting a multi level object, so maybe I just need to work out how to convert this back into a single depth array of objects?
if(elements.length > 0) {
for (let i = 0; i < elements.length; i++) {
let obj = Object.assign({}, elements[i]);
let depth = 0;
obj.items = [];
map[obj.id] = obj;
let parent = obj.parent_id || '-';
if (!map[parent]) {
map[parent] = { items: [] }
}
map[parent].items.push(obj);
}
console.log(map);
return map['-'].items;
}
I feel like there is a relatively simple answer to this but I'm struggling! Look forward to your thoughts, thanks!
You could create a tree first, to reflect the relationship and then build a flat array which later became an object.
function getTree(array) {
var o = {};
array.forEach(function ({ id, name, parent_id }) {
Object.assign(o[id] = o[id] || {}, { id, name, parent_id });
o[parent_id] = o[parent_id] || {};
o[parent_id].children = o[parent_id].children || [];
o[parent_id].children.push(o[id]);
});
return o.null.children;
}
function getFlat(array = [], level = 0) {
return array.reduce((r, { id, name, parent_id, children }) =>
r.concat({ id, name, parent_id, level }, getFlat(children, level + 1)), []);
}
var elements = { 0: { id: 1, name: 'Parent folder', parent_id: null }, 1: { id: 2, name: 'Another parent folder', parent_id: null }, 2: { id: 3, name: 'Child folder 1', parent_id: 1 }, 3: { id: 4, name: 'Child folder 2', parent_id: 1 }, 4: { id: 5, name: 'Child of a child', parent_id: 4 } },
tree = getTree(Object.assign([], elements)),
flat = getFlat(tree);
console.log(flat.map(({ name, level }) => '-'.repeat(level) + name).join('\n'));
console.log(Object.assign({}, flat));
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I'm trying to build a small React.js clone,
In the code snippet below, i made a simple component tree with a succession of functional components
function Text(props) {
return createElement('p', null, props.content)
}
function Hello(props) {
return createElement(Text, props.content, null)
}
function Home() { // this is the root element
return createElement('div', null,
createElement(Hello, {content: "hello world 1"}, null),
createElement(Hello, {content: "hello world 2"}, null)
)
}
The createElement function checks the type of the current node, assigning it an id and pushes it into the data Array. But to reconstitute the component tree, i need to get the parentId of each components that have been pushed into data.
I assume that if the value of i is zero, it means that the current element is the root element. But if not, how to find the id of the parent who created the current element ?
const data = [];
let i = 0;
function createElement(node, props, children) {
if(typeof node === "string") {
data.push({ name: node, id: i, parentId: i > 0 ? i : null });
i++;
};
if(typeof node === "function") {
let functionalComponent = constructFunctionComponent(node);
data.push({ name: node.name, id: i, parentId: i > 0 ? i : null });
i++;
createElement(functionalComponent(props)());
};
}
function constructFunctionComponent(fc) {
return (props) => (children) => fc(props, children);
}
Here is what a console.log displays if we execute the Home() function.
Here the parentId keys are obviously all false (except the first one because it is the root element)
// current output :
// note that here the parentId keys of each index are not correct (this is what i'm trying to resolve)
[
{ name: 'Home', id: 0, parentId: null },
{ name: 'Hello', id: 1, parentId: 1 },
{ name: 'Text', id: 2, parentId: 2 },
{ name: 'p', id: 3, parentId: 3 },
{ name: 'Hello', id: 4, parentId: 4 },
{ name: 'Text', id: 5, parentId: 5 },
{ name: 'p', id: 6, parentId: 6 },
{ name: 'div', id: 7, parentId: 7 }
]
// expected output:
// here, each parentId keys is a "reference" to the parent that added the index to the array
[
{ name: 'Home', id: 0, parentId: null },
{ name: 'Hello', id: 1, parentId: 7 },
{ name: 'Text', id: 2, parentId: 1 },
{ name: 'p', id: 3, parentId: 2 },
{ name: 'Hello', id: 4, parentId: 7 },
{ name: 'Text', id: 5, parentId: 4 },
{ name: 'p', id: 6, parentId: 5 },
{ name: 'div', id: 7, parentId: 0 }
]
I made a codeSandbox which contains the code. Any help would be greatly appreciated !
Here is a link to the codeSandbox example
Thanks,
As you're doing it now, the structure is being evaluated from the leaf nodes up, so the parent ID is not known at the time of each element's creation. You'll have to separate the generation of the IDs from the generation of the elements. Here's an example of what I mean (it's not pretty; you can probably come up with a more elegant way to do it):
function createElement(node, props, children) {
if(typeof node === "string") {
data.push({ name: node, id: props.id, parentId: props.parentId });
};
if(typeof node === "function") {
let functionalComponent = constructFunctionComponent(node);
data.push({ name: node.name, id: props.id, parentId: props.parentId });
createElement(functionalComponent(props)());
};
}
function Home() {
homeId = 0;
createElement
(
'div',
homeId,
createElement(Hello, {content: "hello 1", parentId: homeId, id: (hello1Id = ++homeId)}),
createElement(Hello, {content: "hello 2", parentId: homeId, id: (hello2Id = ++hello1Id)})
);
}
Now the ID is being created as part of the call to createElement, so it can be known and used in any further child creation.
var array = [
{id: 1, name: "Father", parent_id: null},
{id: 2, name: "Child", parent_id: 1},
{id: 3, name: "Child", parent_id: 1},
{id: 4, name: "ChildChild", parent_id: 2},
{id: 5, name: "ChildChildChild", parent_id: 4}
]
for(var i in array){
if(array[i].parent_id == null){
console.log(array[i].name);
} else {
for(var j in array){
if(array[i].parent_id == array[j].id && array[j].parent_id == null){
console.log(">" + array[i].name);
for(var x in array){
if(array[i].id == array[x].parent_id){
console.log(">>" + array[x].name);
}
}
}
}
}
}
Output:
Father
>Child
>>ChildChild
>Child
I have this array which has id, name and parent_id. Right now it is fixed but it could have multiple arrays and can be nested for n amount of times.
What I am doing here is iterating through each array and trying to find which are the parents and which one is the child.
I want to know if there is a more efficient way to write this code. For instance, I added a fifth id but that would require another for loop and so on. The output would be the same just a printed out tree.
You can use a Map to key your nodes by id, and then use recursion to traverse them in depth first order:
var array = [{id: 1, name: "Father", parent_id: null},{id: 2, name: "Child", parent_id: 1},{id: 3, name: "Child", parent_id: 1},{id: 4, name: "ChildChild", parent_id: 2},{id: 5, name: "ChildChildChild", parent_id: 4}];
let map = new Map(array.map(({id}) => [id, []])).set(null, []);
array.forEach(node => map.get(node.parent_id).push(node));
function dfs(nodes, indent="") {
for (let node of nodes) {
console.log(indent + node.name);
dfs(map.get(node.id), indent+">");
}
}
dfs(map.get(null));
You could create a tree and then make the output.
const
print = ({ name, children = [] }) => {
console.log(name)
children.forEach(print);
},
array = [{ id: 1, name: "Father", parent_id: null }, { id: 2, name: "Child", parent_id: 1 }, { id: 3, name: "Child", parent_id: 1 }, { id: 4, name: "ChildChild", parent_id: 2 }, { id: 5, name: "ChildChildChild", parent_id: 4 }],
tree = function (data, root) {
var t = {};
data.forEach(o => {
Object.assign(t[o.id] = t[o.id] || {}, o);
t[o.parent_id] = t[o.parent_id] || {};
t[o.parent_id].children = t[o.parent_id].children || [];
t[o.parent_id].children.push(t[o.id]);
});
return t[root].children;
}(array, null);
tree.forEach(print);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm currently working through a problem that I'm having some trouble figuring out where I need to find a child node in an array of objects. The target could be one or many levels deep.
The issue is, once I find the object, I also need to push the path I took to get to that object into the resulting data array.
Currently, I have written code that can successfully find the child node:
const buildFullTree = (tree, cat, data = []) => {
let collection = [tree]
while (collection.length) {
let node = collection.shift()
if (node.id === cat.id) {
data.push(node)
}
collection.unshift(...node.children)
}
return data
}
However, this isn't sufficient in terms of getting the path taken to that object.
I'm pretty sure that I need to change this to a recursive depth-first search solution in order to achieve what I'm looking for, but I am not sure how to change the while loop to accomplish this.
If I understand your question correctly, then perhaps you could revise your path search function like so to achieve what you require:
const buildFullTree = (departmentTree, category, data = []) => {
const findPath = (node, category) => {
//If current node matches search node, return tail of path result
if (node.id === category.id) {
return [node]
} else {
//If current node not search node match, examine children. For first
//child that returns an array (path), prepend current node to that
//path result
for (const child of node.children) {
const childPath = findPath(child, category)
if (Array.isArray(childPath)) {
childPath.unshift(child)
return childPath
}
}
}
}
const foundPath = findPath(departmentTree, category)
// If search from root returns a path, prepend root node to path in
// data result
if (Array.isArray(foundPath)) {
data.push(departmentTree)
data.push(...foundPath)
}
return data
}
const departmentTree = {
id: 5,
title: 'department',
level: 1,
children: [{
id: 1,
parentId: 5,
title: 'category',
level: 2,
children: [{
id: 15,
parentId: 1,
title: 'subcategory',
level: 3,
children: []
}, {
id: 18,
parentId: 1,
level: 3,
title: 'subcategory',
children: []
}, {
id: 26,
parentId: 1,
level: 3,
title: 'subcategory',
children: [{
id: 75,
parentId: 26,
level: 4,
title: 'sub-subcategory',
children: []
}, {
id: 78,
parentId: 26,
level: 4,
title: 'sub-subcategory',
children: []
}]
}]
}, {
id: 23823,
title: 'category',
level: 2,
children: []
}, {
id: 9,
parentId: 5,
level: 2,
title: 'category',
children: [{
id: 48414,
parentId: 9,
level: 3,
title: 'subcategory',
children: []
}, {
id: 2414,
parentId: 9,
level: 3,
title: 'subcategory',
children: []
}, {
id: 42414,
parentId: 9,
level: 3,
title: 'subcategory',
children: [{
id: 2323213,
parentId: 42414,
level: 4,
title: 'sub-subcategory',
children: []
}, {
id: 322332,
parentId: 42414,
level: 4,
title: 'sub-subcategory',
children: []
}]
}]
}]
};
console.log('Path to 2323213:',
buildFullTree(departmentTree, {
id: 2323213
}).map(node => node.id).join(' -> '))
console.log('Path to 23823:',
buildFullTree(departmentTree, {
id: 23823
}).map(node => node.id).join(' -> '))
console.log('Path to -1 (non existing node):',
buildFullTree(departmentTree, {
id: -1
}).map(node => node.id).join(' -> '))
I have a flat array like this containing data objects with id and values. Every id will be unique
var data = [{
id: 1,
value: 'as',
parent: 2
}, {
id: 2,
value: 'sasa',
parent: 3
}, {
id: 3,
value: 'sasa',
parent:
}]
How can I create a hierarchical tree like "object" in JavaScript not an Array because I further want to access the object's elements like 3.2.value
{
id: 3,
value: 'sasa',
parent: '',
2: {
id: 2,
value: 'sasa',
parent: 3,
1: {
id: 1,
value: 'as',
parent: 2
}
}
}
You could take an iterative approach by using an object for collencting an create an object for id and parent at the same time to keep their relation.
At the end return the property which has the root as parent.
The result is lightly different, because you want to address the nodes by using their id as accessor.
var data = [{ id: 1, value: 'as', parent: 2 }, { id: 2, value: 'sasa', parent: 3 }, { id: 3, value: 'sasa', parent: '' }],
tree = function (data, root) {
return data.reduce(function (r, o) {
Object.assign(r[o.id] = r[o.id] || {}, o);
r[o.parent] = r[o.parent] || {};
r[o.parent][o.id] = r[o.id];
return r;
}, Object.create(null))[root];
}(data, '');
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
How do I use the following data properties to render a tree view using nested unordered lists(<ul>)?
// parentId value is always 0 for root nodes, otherwise this value corresponds to the id of its parent
// sequence represents the order of the element in the branch
// level represents the tree level of the element, root nodes will have a level of 1
var data = [
{ id: 'K66', level: 1, name: 'B', parentId: '0', sequence: 2 },
{ id: 'K65', level: 1, name: 'A', parentId: '0', sequence: 1 },
{ id: 'KK2', level: 2, name: 'Alan', parentId: 'K65', sequence: 1 },
{ id: 'KK22', level: 2, name: 'Bir', parentId: 'K66', sequence: 1 },
{ id: 'KK92', level: 2, name: 'Abe', parentId: 'K65', sequence: 2 },
{ id: 'KK77', level: 3, name: 'Boromir', parentId: 'KK22', sequence: 1 }
];
The result should look like the following:
A
Alan
Abe
B
Bir
Boromir
I have tried with for loops but soon I was repeating code to get the child nodes and I wasn't able to refactor it into a recursive function.
Here's a CodePen with the data: http://codepen.io/nunoarruda/pen/KgVPmv
I will answer following part of the OP's question ...
How do I use the following data properties to render a tree view using nested unordered lists()?
With the given data structure there is no need for a recursive solution. A combination of sorting the provided data list and then transforming it via a specific reduce callback already does the job ...
var
dataList = [
{ id: 'K66', level: 1, name: 'B', parentId: '0', sequence: 2 },
{ id: 'KK2', level: 2, name: 'Alan', parentId: 'K65', sequence: 1 },
{ id: 'K65', level: 1, name: 'A', parentId: '0', sequence: 1 },
{ id: 'KK77', level: 3, name: 'Boromir', parentId: 'KK22', sequence: 1 },
{ id: 'KK22', level: 2, name: 'Bir', parentId: 'K66', sequence: 1 },
{ id: 'KK92', level: 2, name: 'Abe', parentId: 'K65', sequence: 2 }
],
htmlListFragment = dataList.sort(function (a, b) {
// sort by `level` or, if both are equal (sub)sort by `sequence`
return (a.level - b.level) || (a.sequence - b.sequence);
}).reduce(function (collector, item) {
var
registry = collector.registry,
fragment = collector.htmlListFragment,
parentId = item.parentId,
id = item.id,
name = item.name,
member = registry[id];
if (!member) {
member = registry[id] = document.createElement("li");
member.id = id;
member.appendChild(document.createTextNode(name));
member.appendChild(document.createElement("ul"));
if ((item.level === 1) && (item.parentId === "0")) {
fragment.appendChild(member);
} else {
registry[parentId].getElementsByTagName("ul")[0].appendChild(member);
}
}
return collector;
}, {
registry : {},
htmlListFragment: document.createElement("ul")
}).htmlListFragment;
console.log("htmlListFragment : ", htmlListFragment)