How to do a recursive function for a tree view? - javascript

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)

Related

How to retrieve the parentId of an element created by a previous function?

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.

Treeview in javascript

I need to make something similar to treeview. It doesn't need collapsing it just needs to show some heirachy, but in a table view.
Flat data comes in from a database. I unflattened it and made a tree, but now that it's a tree, I wanted to turn it back into an array, so I can easily iterate using a for loop.
After looking at the source code of other treeviews my method was going to be like this:
From flat data from a db, unflatten:
[
{ id: 1, name: 'node1', parentId: 0 },
{ id: 2, name: 'node2', parentId: 1 },
{ id: 4, name: 'node4', parentId: 2 },
{ id: 5, name: 'node5', parentId: 2 },
{ id: 6, name: 'node6', parentId: 3 },
{ id: 3, name: 'node3', parentId: 1 },
]
The tree is now ordered and has a hierarchy (levels for indentation). Traverse the tree. I add level and children.
[
id: 1,
name: 'node1',
level: 0,
children: [
{
id: 2,
name: 'node2',
parentId: 1,
level: 1
children: [
{
id: 4,
name: 'node4',
parentId: 2,
level: 2,
children: []
},
{
id: 5,
name: 'node5',
parentId: 2,
children: []
},
]
},
{
id: 3,
name: 'node1',
parentId: 1,
children: [
{
id: 6,
name: 'node6',
parentId: 3,
children: []
},
]
},
]
]
Compress it back into an array form with order, level.
[
{ id: 1, name: 'node1', level: 0, parentId: 0, children: [...] },
{ id: 2, name: 'node2', level: 1, parentId: 1, children: [...] },
{ id: 4, name: 'node4', level: 2, parentId: 2, children: [...] },
{ id: 5, name: 'node5', level: 2, parentId: 2, children: [...] },
{ id: 3, name: 'node3', level: 1, parentId: 1, children: [...] },
{ id: 6, name: 'node6', level: 2, parentId: 3, children: [...] },
]
Of which I can easily create a table from.
I've gotten close with the following code:
var data = [
{ id: 1, name: 'node1', parentId: 0 },
{ id: 2, name: 'node2', parentId: 1 },
{ id: 4, name: 'node4', parentId: 2 },
{ id: 5, name: 'node5', parentId: 2 },
{ id: 6, name: 'node6', parentId: 3 },
{ id: 3, name: 'node3', parentId: 1 }
]
function unflatten (arr, parentId, level) {
let output = []
for (const obj of arr) {
if (obj.parentId === parentId) {
var children = unflatten(arr, obj.id, level+1)
obj.level = level
if (children.length) {
obj.children = children
}
output.push(obj)
}
}
// console.log(output)
return output
}
function flatten (tree) {
var output = []
for(const node of tree) {
if(node.children !== undefined){
var nodeChildren = flatten(node.children.reverse())
for(const child of nodeChildren){
output.push(child)
}
}
output.push(node)
}
return output
}
var dataCopy = Object.assign([], data)
console.log('data', dataCopy)
var res = unflatten(data, 0, 0)
console.log('tree', res)
var resCopy = Object.assign([], res)
var res2 = flatten(resCopy)
console.log('reflatten', res2)
Fiddle http://jsfiddle.net/ctd09r85/10/
That fiddle is the closest I've gotten, but it's a bit reversed and out of order.
How can I do this, and is this a reasonable way to build the tree view.

Finding a path to target object in nested array of objects in Javascript

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(' -> '))

How to create 'multi depth' select in JavaScript

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; }

postorder traversal a tree with javascript or jquery to create html codes

I want to create Html Codes like this
<ul>
<li>子菜单1
<ul>
<li>子菜单1.1</li>
<li>子菜单1.2
<ul>
<li>子菜单1.2.1</li>
<li>子菜单1.2.2</li>
</ul>
</li>
</ul>
</li>
<li>子菜单2
<ul>
<li>子菜单2.1</li>
<li>子菜单2.2
<ul>
<li>子菜单2.2.1</li>
</ul>
</li>
</ul>
</li>
<li>子菜单3</li>
</ul>
Now I have been transfer some json Data To HTML page like this
[{
id: 1,
name: '子菜单1',
parentid:
}, {
id: 2,
name: '子菜单2',
parentid:
}, {
id: 3,
name: '子菜单3',
parentid:
}, {
id: 4,
name: '子菜单1.1',
parentid: 1
}, {
id, 5,
name: '子菜单1.2',
parentid: 1
}, {
id: 6,
name: '子菜单1.2.1',
parentid: 5
}, {
id: 7,
name: '子菜单1.2.2',
parentid: 5
}, {
id, 8,
name: '子菜单2.1',
parentid: 2
}, {
id: 9,
name: '子菜单2.2',
parentid: 2
}, {
id: 10,
name: '子菜单2.2.1',
parentid: 9
}, {
id: 11,
name: '子菜单3',
parentid:
}]
So I want to write some javascript or jquery code to create the html as I want
maybe there are some ways to touch the goal
maybe postorder traversal the tree
when view the root node,then add the childnode lists like'<ul><li></li>....</ul>' to rootNode so they can be create to <li><ul>...</ul></li>
sorry my English is very poor
can you get it?
can you help me?
You're on the right track. We can print the tree by performing a depth-first traversal. For each node, we print the opening tag and the name value, then descend into each of the children. When the recursion is done, we print the closing tag.
The code belows builds the tree with the help of an index called id_to_node, which maps the id key values to previously built nodes. This assumes that the input array contains the objects ordered in such a way that each parentid refers to an earlier element in the array. If this is not true, you'll have to traverse the input twice: first to build the index, and the second time to build the tree.
var data = [ { id: 1, name: '子菜单1' },
{ id: 2, name: '子菜单2' },
{ id: 3, name: '子菜单3' },
{ id: 4, name: '子菜单1.1', parentid: 1 },
{ id: 5, name: '子菜单1.2', parentid: 1 },
{ id: 6, name: '子菜单1.2.1', parentid: 5 },
{ id: 7, name: '子菜单1.2.2', parentid: 5 },
{ id: 8, name: '子菜单2.1', parentid: 2 },
{ id: 9, name: '子菜单2.2', parentid: 2 },
{ id: 10, name: '子菜单2.2.1', parentid: 9 } ];
var id_to_node = {}, // Map id values to nodes.
tree = { children: [] }; // The root of the tree.
for (var i = 0; i < data.length; ++i) {
var object = data[i], // Iterate over the objects.
node = { id: object.id, name: object.name, children: [] };
id_to_node[object.id] = node;
if (object.parentid === null || object.parentid === undefined) {
var parent = tree; // Figure out the object's parent.
} else {
var parent = id_to_node[object.parentid];
}
parent.children.push(node); // Add the node to the parent.
}
// Makes whitespace for each level of the tree.
function indent(depth) {
var spaces = [];
for (var i = 0; i < depth; ++i) {
spaces.push(' ');
}
return spaces.join('');
}
// Recursively prints the tree.
function print(node, depth) {
document.write(indent(depth)+'<ul>\n');
for (var i = 0; i < node.children.length; ++i) {
var child = node.children[i];
document.write(indent(depth+1)+'<li>'+child.name);
if (child.children.length > 0) {
document.write('\n');
print(child, depth+2);
document.write(indent(depth+1));
}
document.write('<li>\n');
}
document.write(indent(depth)+'<ul>\n');
}
document.write('<pre>');
print(tree, 0);
document.write('</pre>');

Categories