I’ve been working on a bit of a lazy load recursive tree view, and while I have the component level stuff working, I’m having a bit of difficulty trying to work out how to make it play nicely with redux.
For simplicity, let’s say I have the following data structure in redux
[
{
id: 'myId1',
value: 'Tree View top level 1',
fetchedChildren: []
},
{
id: 'myId2',
value: 'Tree View top level 2',
fetchedChildren: []
},
…and so on
]
I have my recursive component working so that when you click on an item, the relevant id is sent to the API and a collection of children is returned. Clicking myId2 would result in the data looking like this
[
{
id: 'myId1',
value: 'Tree View top level 1',
fetchedChildren: []
},
{
id: 'myId2',
value: 'Tree View top level 2',
fetchedChildren: [
{
id: 'myId3',
value: 'Tree View second level 1',
fetchedChildren: []
},
{
id: 'myId3',
value: 'Tree View second level 2',
fetchedChildren: []
},
]
},
]
and then the idea is that this could theoretically go on for infinity.
below is an example of my reducer that updates the store correctly from the above first example to the second (the tree is linked to data)
case FETCH_CHILDREN_SUCCESS:
return {
...state,
loading: false,
error: null,
data: state.data.map(leaf=> leaf.id === action.payload.id ? {
...leaf,
value: leaf.value,
fetchedChildren: action.payload.fetchedChildren.map( child => {
return {
...child,
value: child.value
}
})
} : leaf)
};
This will work to populate fetchedChildren on the second level, but how do you do this recursively for subsequent levels?
I took a look at the tree-view sample in the redux source code, and the way they are managing the data is to have a flat array that is linked by ids to identify parents.
Is the above the wrong way to look at it, and should I be looking at doing this in a flat array like the redux example?
Related
Can anyone tell me, how to create a tree view inside the drop down. The drop down values will be getting from rest api call as json as follows. And subchild may contains one more level of child as well.
I have to do auto suggestion here to perform the filter from parent as well as the child level too.
VehicleList = [
{
parent: "audi",
child: [{
type: 'G-audiA',
subchild: [{
id: 1,
name: 'type audi A1'
}, {
id: 2,
name: 'type audi A2'
}]
}, {
type: 'G-audiB',
subchild: [{
id: 1,
name: 'type audi B1'
}, {
id: 2,
name: 'type audi B2'
}]
}]
}, {
parent: "bmw",
child: [{
type: 'G-bmwA',
subchild: [{
id: 1,
name: 'type bmw A1'
}, {
id: 2,
name: 'type bmw A2'
}]
}, {
type: 'G-bmwB',
subchild: [{
id: 1,
name: 'type bmw B1'
}, {
id: 2,
name: 'type bmw B2'
}]
}]
}]
Anyone help will be appreciated!!!
Based on the first example from the Angular Material Tree docs I managed to build up a drop-down with a tree structure inside like so:
The trick for displaying the tree is to add a disabled/empty option. I used it as a label. The tree is taken from their examples so I did not modify it at all, you can modify the node structure to match your own.
In order to display the selected items in the label of the drop-down, you can create a method that will return the selected items a string as their SelectionModel object has the selected property which would return all selected nodes.
/** The selection for checklist */
checklistSelection = new SelectionModel<TodoItemFlatNode>(
true /* multiple */
);
And in order to get the selected items from the tree:
return this.checklistSelection.selected.map(s => s.item).join(",");
For the filtering part I think you can look over this answer.
Hope this is helpful!
Stackblitz
Edit: If you select a child the parent gets selected too and added in the SelectionModel even if all its children are not selected. If you don't want this behaviour comment on the function descendantsPartiallySelected. This will not check the checkbox and so parents will not be added in SelectionModel unless all children are selected
I have a few components that are to render dynamically based on values passed through props. My main component(1) is dynamically creating its subcomponents(2) and passing values into those components via props. Each component(2) created is then supposed to utilize the values in props to create new subcomponents(3) of this component.
The problem arises when I try to map the object array passed as props in component 2. Component 1 successfully creates components 2, but component 2 displays the error "this.props.section.map is not a function". I'm using the exact code from component one, the only difference is I am referencing the array via props.
this.state = {
menuSections: [
{
id: 1, title: 'Starters', menuItem: [{
id: 1,
name: 'Meatball',
description: 'Large Meatball',
price: '11 meatballs'
}]
},
{
id: 2, title: 'Starters', menuItem: [{
id: 2,
name: 'Meatball',
description: 'Large Meatball',
price: '11 meatballs'
}]
},
{
id: 3, title: 'Starters', menuItem: [{
id: 3,
name: 'Meatball',
description: 'Large Meatball',
price: '11 meatballs'
}]
},
],
{this.state.menuSections.map(menuSection => (<MenuSection
section={menuSection} mobileMode={this.state.mobileMode} />))}
This is the code from my App component, which works like a charm. the code for my 2nd component (MenuSection) is where the problem arises.
{this.props.section.map((itemContainer, i) => (<ItemContainer styles={styles}
image={imageFood1} title={itemContainer.menuItem[i].name}
description={itemContainer.menuItem[i].description}
price={itemContainer.menuItem[i].price} />))}
Ideally, the section.map function in the 2nd component will render each individual menu item based on the props passed from the Main app. Instead, I receive the error "this.props.section.map is not a function".
edit: I know variations of this question have been asked before, but I have tried numerous suggestions, none of which have worked.
this.props.section is going to be an Object like :
{
id: 1, title: 'Starters', menuItem: [{
id: 1,
name: 'Meatball',
description: 'Large Meatball',
price: '11 meatballs'
}]
}
So you can't .map it ,
either you pass the array :
{
this.state.menuSections.map(menuSection => (
<MenuSection
section={menuSection.menuItem}
id={menuSection.id}
title={menuSection}
mobileMode={this.state.mobileMode}
/>
))
}
Or, you map the array inside :
{
this.props.section.menuItem.map((itemContainer, i) => (
<ItemContainer
styles={styles}
image={imageFood1} title={itemContainer.menuItem[i].name}
description={itemContainer.menuItem[i].description}
price={itemContainer.menuItem[i].price}
/>
))
}
Section is an Object.
this.props.section.menuItem.map()
I need to display a acyclic directed graph looking somewhat like this:
I created a nested hierarchical Data Structure similar to this:
[
{
node: 'abs'
children: [
{
node: 'jhg',
children: [{...}]
{
node: 'AAA',
children: [{...}]
},
{
node: 'fer'
children: [
{
node: 'AAA',
children: [{...}]
{
node: 'xcv',
children: [{...}]
},
{
]
I am not sure if this is the best way to actually display the data since the nodes with multiple parents and their children would appear multiple times, but I had no other idea how to handle it yet.
I simply want to render those nodes to an imaginary grid. Therefore i need to parse my data structure and set their grid values. The problem is i dont know how to parse the data structure with hierarchy logic.
What I am doing right now is obviously causing problems for nodes with multiple parents:
for (const root of allRoots) {
currentLevel = 0;
if (root.node === 'VB8') {
getChildrenTree(root);
}
}
function getChildrenTree(node) {
currentLevel++;
node._gridX = currentLevel;
if (node.children.length > 0) {
for(const nextChild of children ) {
getChildrenTree(nextChild);
}
}
The Problem with this code is that it will only run through one path and then stop when there arent any children.
I just need an algorithm which runs through the tree and sets each nodes hierarchy level.
I hope that this is not too confusing.
If you want to reference the same node from two separate parents, you shouldn't define it more than once. I suggest listing all nodes in a flat array with a single 'invisible' root node and referencing children by id or array index:
[
{id: 0, name: "root", children: [1, 2]},
{id: 1, name: "abs", children: [3, 4]},
{id: 2, name: "fer", children: [5, 6]},
{id: 3, name: "jhg", children: [...]},
{id: 4, name: "AAA", children: [...]},
...
]
then you can recursively set their tree depths like so:
function setDepth(node, depth) {
if (node._gridX && node._gridX >= depth) {
// node has been visited already through a path of greater or equal length
// so tree depths wouldn't change
return
}
node._gridX = depth
node.children
.map(idx => nodeArray[idx]) // get the actual objects from indices
.forEach(child => setDepth(child, depth+1))
}
setDepth(nodeArray[0], 0) // start at root
... careful though, as this algorithm will get stuck in a loop if your nodes have any cycles
I'm getting headache avoiding databinding on nested arrays.
Let's say i have 2 objects:
Items, array of all items
Item, a single item object
I use
Object.assign({}, object)
to avoid databinding, bit this only works for non-nested array fields.
Example:
data: {
items: [
{
name: 'Pencil case',
contents: [
{title: "Red Pencil"}, {title: "Blue Pencil"}
]
},
{
name: 'Rubber container',
contents: [
{title: "Yellow Rubber"}, {title: "Green Rubber"}
]
},
],
selected_item: {
name: 'Pencil case',
contents: [
{title: "Red Pencil"}, {title: "Blue Pencil"}
]
}
},
mounted() {
this.selected_item = Object.assign({}, this.items[0]);
}
There's no databinding on name, but there is still binding on contents.title for example. I absolutely need to assign the object absolutely without databinding.
Here's a JSFIDDLE.
In the first input binding on "title" is real, while in the second input there are no binding on "name" as expected. I can't get over it, help me please.
I'm currently implementing my own commenting system. Unfortunately Disqus or any other comment platform doesn't meet my requirements.
I use NodeJS and MongoDB as backend. I need to run basically two queries on my database:
Get all comments by a topic/slug
Get all comments by a user
One can comment to an topic or reply to a comment.
Hey, cool post # top lvl comment
Thanks! # reply to comment
Foo Bar! # reply to reply
and so on...
So my database schema looks like
{
id: ObjectId,
text: string,
author: { id: ObjectId, name: string },
parent: nullable ObjectId,
slug: string/number/whatever
}
If parent is null it's a top level comment, otherwise it's a reply.
Pretty easy so far, right? The problem I do have now is displaying comments below posts. When there would be only top level comments it would be easy. Just get all comments for one specific slug, sort them by date/rating/... and compile them with my HTML View Engine.
But there are in fact replies and I'm just stuck at the point where I need to organize my structure. I want to nest replies into comments within my list
Original list (simplified)
[
{ id: 1, text: 'foo', parent: null },
{ id: 2, text: 'bar', parent: 1 },
// ...
]
Expected Output
[
{ id: 1, text: 'foo', replies: [
{ id: 2, text: 'bar' },
] },
]
I've tried creating my expected output with a recursive function which got very weird tho. Unless that it wouldn't be very efficient. So since I'm really getting frustrated and kinda feeling stupid not solving this problem I've decided to ask for your help SO.
The actual problem I want to solve: How do I render my comments, that they are properly nested etc.
The question I'm going to ask: How do I organize my flat structure in an efficient way to solve the above described problem?
Here's one approach with linear complexity:
var comments = [{
id: 3,
text: 'second',
parent: 1
}, {
id: 1,
text: 'root',
parent: null
}, {
id: 2,
text: 'first',
parent: 1
}, {
id: 5,
text: 'another first',
parent: 4
}, {
id: 4,
text: 'another root',
parent: null
}];
var nodes = {};
//insert artificial root node
nodes[-1] = {
text: 'Fake root',
replies: []
};
//index nodes by their id
comments.forEach(function(item) {
if (item.parent == null) {
item.parent = -1;
}
nodes[item.id] = item;
item.replies = [];
});
//put items into parent replies
comments.forEach(function(item) {
var parent = nodes[item.parent];
parent.replies.push(item);
});
//root node replies are top level comments
console.log(nodes[-1].replies);