I am working on a treeview and I created a class Node, consisting of a name and a list of children.
class Node {
constructor(name, childNodes) {
this.name = name;
this.childNodes = childNodes;
}
}
I am trying to create an object looking like the object "tree" in the image below. Every Node has a attribute called "nodes" which is the childNode-Array.
I got very close to creating the same tree, but in my version there is an extra "Array"-Layer around each of the Child nodes (see the recTreeview()-Element):
I am not sure how to fix this problem. The array has to be created to contain the childnodes. Here is the code where the treeview Element is created:
function recTreeview(currentNode, treeview) {
var tempChildren = [];
currentNode.children.forEach(child => {
tempChildren.push(recTreeview(child, []));
});
treeview.push({
text: currentNode.name,
nodes: tempChildren
});
return treeview;
}
Is there anything I can do? Update: A Code-Snippet:
class Node {
constructor(name, children) {
this.children = children;
this.name = name;
}
}
function recTreeview(currentNode, treeview) {
var tempChildren = [];
currentNode.children.forEach(child => {
tempChildren.push(recTreeview(child, []));
});
treeview.push({
text: currentNode.name,
nodes: tempChildren
});
return treeview;
}
child1 = new Node("child1", []);
child2 = new Node("child2", []);
parent = new Node("Parent", [child1, child2]);
var tree = [
{
text: "Parent 1",
nodes: [
{
text: "Child 1"
},
{
text: "Child 2"
}
]
}
];
<button onClick="console.log(recTreeview(parent, []));">Wrong</button>
<button onClick="console.log(tree);">Right</button>
The extra array is comming from tempChildren.push(recTreeview(child, []));. Because you are pushing the return value of recTreeview (which is an array) to the children array tempChildren.
Instead of pushing the new array to tempChildren, you should pass tempChildren as the parameter to recTreeview:
currentNode.children.forEach(child => {
recTreeview(child, tempChildren);
});
So that the children will be pushed directly to the tempChildren array instead of getting wrapped in the extra array recTreeview is creating.
Example:
class Node {
constructor(name, children) {
this.children = children;
this.name = name;
}
}
function recTreeview(currentNode, treeview) {
var tempChildren = [];
currentNode.children.forEach(child => {
recTreeview(child, tempChildren);
});
treeview.push({
text: currentNode.name,
nodes: tempChildren
});
return treeview;
}
child1 = new Node("child1", []);
child2 = new Node("child2", []);
parent = new Node("Parent", [child1, child2]);
var tree = [
{
text: "Parent 1",
nodes: [
{
text: "Child 1"
},
{
text: "Child 2"
}
]
}
];
<button onClick="console.log(recTreeview(parent, []));">Not Wrong Anymore</button>
<button onClick="console.log(tree);">Right</button>
Note:
If you want to get rid of that empty nodes array if there are no children, then change this:
treeview.push({
text: currentNode.name,
nodes: tempChildren
});
To:
var node = {
text: currentNode.name
};
if(tempChildren.length) {
node.nodes = tempChildren;
}
treeview.push(node);
Which only adds the nodes property if the tempChildren array is not empty.
Related
Is there any way to get a key-value from an object's parent object? In the example below, I want to combine urlParent with section:
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
subItems: [
{
subId: 1,
name: 'Project 1',
section: '#project1',
get url() {
//output /home#project1
}
}
]
}
];
console.log(linkItems[0].subItems[0].url) // /home#project1;
You cannot reference an Object parent that way (Object(value) ← Array ← Object), and not even from an Object's parent Object.
What you can do instead is:
Create two Classes, one for the parent and one for the child.
When adding a child to the parent, just make a "linked list", by referencing the parent's this to the created child item parent property
class Child {
constructor(data) {
Object.assign(this, data);
}
get url() {
return this.parent.urlParent + this.section
}
}
class Parent {
constructor(data) {
Object.assign(this, data);
this.subItems = [];
}
addChild(item) {
this.subItems.push(new Child({...item, parent: this}));
}
}
// Example:
const parent = new Parent({id:1, name:"Home", urlParent:"/home"});
parent.addChild({subId:1, name:"Project 1", section:"#project1"});
console.log(parent.subItems[0].url) // /home#project1;
But hey! Nodes and trees
Your original idea and the above use too much complexity.
What I'd suggest is to treat all parent, child, whatever, as Page Nodes.
class Page {
constructor(data) {
Object.assign(this, data);
this.children = {};
}
addChild(page) {
page.parent = this; // Linked to parent!
this.children[page.id] = page;
}
get url() {
// Generate full URI by recursing the parents tree
return this.parent ? `${this.parent.url}/${this.slug}` : this.slug;
}
}
// Example:
// 1. Create pages:
const pageRoot = new Page({id:1, name:"Home page", slug:""});
const pageProj = new Page({id:3, name:"All projects", slug:"projects"});
const pageWebs = new Page({id:4, name:"Websites", slug:"websites"});
const pageStOv = new Page({id:6, name:"Stack Overflow", slug:"so"});
const pageSpec = new Page({id:9, name:"Stack Overflow Specs", slug:"specs"});
// 2. Create Tree:
pageRoot.addChild(pageProj);
pageProj.addChild(pageWebs);
pageWebs.addChild(pageStOv);
pageStOv.addChild(pageSpec);
// 3. Test
console.log(pageRoot.url); // "";
console.log(pageProj.url); // "/projects";
console.log(pageSpec.url); // "/projects/websites/so/specs";
console.log(pageRoot);
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
get subItems(){
console.log(this.name);
return ([
(() => {
console.log(this);
return {
subId: 1,
name: 'Project 1',
section: '#project1',
urlParentFromOuterScope: () => {
return this.urlParent;
},
sectionString(){
return this.section;
},
url(){
console.log('url', this);
return this.urlParentFromOuterScope() + this.sectionString();
}
}
})()
])
}
}
];
const subItems = linkItems[0].subitems;
console.log(linkItems[0].subItems[0].url());
Please feel free to remove the unnecessary 'console.log's after you understand the approach.
I took the liberty of adding a few methods.
This is a tricky one and has to do with the scope of this in array functions.
P.S.: I guess this can be simplified.
I am trying to understand how to recursively append a branching structure to an object.
I am trying to append children to an empty JSON object that when built out should look like the following.
nodeStructure: {
text: { name: "Parent node" },
children: [
{
text: { name: "First child" },
children: [
{
text: {name: "Grandchild"}
}
]
},
{
text: { name: "Second child" }
}
]
}
Here is the most succinct version of that code.
trackFraud = async (fraudID) => {
var root = chart_config.nodeStructure = newNode(fraudID);
await fraudClimb(root, 1);
var my_chart = new Treant(chart_config);
function newNode(node) { return {text:{name:"fraud " + node}}; }
async function fraudClimb(root, fraudID) {
var frauds = await findFraudByFromID.call(this, fraudID); // returns an array of "integers"
if (frauds.length == 0) return;
var children = root.children = [];
for (var i = 0; i < frauds.length; i++) {
children.push(newNode(frauds[i]));
fraudClimb(children[i], frauds[i]);
}
}
}
Now I am trying to wrap my head around how to traverse, or in this case append to, a structure that alternates every other level between arrays and objects
I guess the real question is how to pass an object around recursively and append to that original object.
I see two issues in your code:
The first call to fraudClimb ignores the fraudID parameter that is available. Instead of:
await fraudClimb(root, 1);
I think you need:
await fraudClimb(root, fraudID);
The recursive call to fraudClimb is not awaited, yet you need the asynchronous operation to complete before you go on. So change this:
fraudClimb(children[i], frauds[i]);
to:
await fraudClimb(children[i], frauds[i]);
Consider the chained object below and the given code:
let data = {
item: "0001",
child: {
item: "00011",
child: {
item: "000111",
child: {
item: "0001111",
child: {
item: "00011111",
child: null
}
}
}
}
};
// Add item to last child of data
let last = data.child;
while (last !== null) last = chain.child;
// Add this item as last in chain
last = {
item: "9999",
child: null
};
console.log(data); // Same original data. No effect at all!!!
How can I add a new item in the last object child ?
You need to check the next child element, because you need a child property to assign the object.
let data = {
item: "0001",
child: {
item: "00011",
child: {
item: "000111",
child: {
item: "0001111",
child: {
item: "00011111",
child: null
}
}
}
}
};
let last = data; // start with data
while (last.child !== null) { // check the child
last = last.child; // assig child for check of the child's child
}
last.child = { item: "9999", child: null }; // assign new object to a property for
// keeping the reference
console.log(data); // Same original data. No effect at all!!!
.as-console-wrapper { max-height: 100% !important; top: 0; }
You probably meant to do the other way:
while (last.child !== null) last = last.child;
This will set you up so that last.child will be null, and you will be able to properly assign with last.child = {item: "9999", child: null};
Since you want to keep last as a reference pointer, you don't want to reassign it to a new object. This way, you navigate to the last child, and assign its child to your object.
You created a new object here instead of editing the current object in the variable last:
last = {
item: "9999",
child: null
};
If you want to change an attribute you need to use the . notation like
last.child = {item:"9999",child:null};
You can use recursion:
let data = {
item: "0001",
child: {
item: "00011",
child: {
item: "000111",
child: {
item: "0001111",
child: {
item: "00011111",
child: null
}
}
}
}
};
let run = function(node, addNode){
if(node.child === null){
node.child = addNode
return;
}
return run(node.child, addNode)
}
last = {
item: "9999",
child: null
};
run(data, last)
console.log(data);
https://jsfiddle.net/q89vt6mn/3/
In my controller I have a function that recieves an object from Java controller. My AngularJS variable is simple:
var self = this;
self.item = {};
And my function where I get the object:
function getItem() {
MyService.getItem(REST_SERVICE_URI)
.then(
function (d) {
self.item = d;
},
function (errResponse) {
console.error('Error while getting item');
}
);
}
Object that's received has rather complicated structure. It has id, name and list of child objects, who have also id and name fields. How do I get into this object's fields and list in the AngularJS controller? I tried loop though list using fucntion below to even count duplicate values but it didn't work. I tried even to include one more loop into it with outputing result in console, no effect. It only returns zeros.
var i = "SOME TEST NAME VALUE TO CHECK";
function getCount(i) {
var iCount = iCount || 0;
for (var el in self.item) {
console.log("let me see what are you: " + el);
if (el == i) {
iCount++;
}
}
return iCount;
}
The object I recieve is ok, I can see it content in Chrome using F12 - Network - Response or Preview.
added later:
On my page I test it like this
<tr class="my_item" ng-repeat="p in ctrl.item.children">
<span>getCount {{p.name}}: {{ctrl.getCount(p.name)}}</span>
</tr>
It displays p.name in the span btw. Java object structure is
public class Item {
private int id;
private String name;
List<Child> children = new ArrayList<>();
}
Child class is simple
public class Child {
private int id;
private String name;
}
As per your question, the content is complex and has recursive properties inside child content.
So you need to iterate on content recursively, inside one forEach loop.
See this example working Demo:
var myApp = angular.module('myApp', []);
myApp.controller('ExampleController', function() {
var vm = this;
vm.count = 0;
vm.searchTxt = "";
vm.getCount = function() {
vm.count = 0; //clear count before search
recursive(vm.content);
}
function recursive(dataArray) { //recursive function
dataArray.forEach(function(data) {
if (vm.searchTxt == data.name) { //match name property
vm.count = vm.count + 1;
}
if (data.child.length > 0) {
recursive(data.child); // call recursive function
}
});
}
vm.content = [{ //example content
id: 1,
name: 'one',
child: [{
id: 1.1,
name: 'new one',
child: [{
id: 1,
name: 'one',
child: []
}]
}]
}, {
id: 2,
name: 'two',
child: [{
id: 1.1,
name: 'new two',
child: []
}]
}]
});
<script src="https://code.angularjs.org/1.5.2/angular.js"></script>
<div ng-app="myApp" ng-controller="ExampleController as vm">
<input ng-model="vm.searchTxt" placeholder="ender search.." />
<br>
<button ng-click="vm.getCount()">Search</button>
<br>
<span>Match 'Name' count : {{vm.count}}</span>
</div>
I currently have this object:
var obj = {
1: {
title: 'test',
children: {
2: {
title: 'test2',
children: {}
},
3: {
title: 'test3',
children: {}
}
}
}
};
The whole idea is I make a function to add an item to this object. As parameter I send the parent.
Now, I was wondering how I would get the right item object. For example if I send parent '2', it would get 2: from the children of 1:. The only way I can think of is a for loop, but I don't know if there's a more efficient way. The children can be extended even more, so a parent has children, those children have children endlessly. That's the whole idea at least.
I think with a few items a for loop is okay, but I think if I have over 50 items it's already slow, and it'll even be slower with more.
This solution use Object.keys() for getting all keys of the given object and an array iteration with short ciruit Array.prototype.some() looks for the key. If found the reference is returned, otherwise the item is checked for an object. If so the object reference is taken for a new search with getReference().
var obj = { 1: { title: 'test', children: { 2: { title: 'test2', children: {} }, 3: { title: 'test3', children: {} } } } };
function getReference(o, p) {
var r;
Object.keys(o).some(function (k) {
if (k === p) {
r = o[k];
return true;
}
if (typeof o[k] === 'object') {
r = getReference(o[k], p);
return !!r;
}
});
return r;
}
var x = getReference(obj, '2');
document.write(x.title);
If you want adding to be fast, you can preserve indexes of your child nodes in object or map (ES6). It could look like this:
function Tree() {
this.data = {};
this.indexes = {0: this.data};
}
Tree.prototype = {
addNode: function(parentIndex, index, node) {
// handle cases when parentIndex does not exist
// handle cases when index already exists
this.indexes[index] = node;
var parent = this.indexes[parentIndex];
parent.children = parent.children || {};
parent.children[index] = node;
}
}
var tree = new Tree();
tree.addNode(0, 1, { title: 'test' });
tree.addNode(1, 2, { title: 'test2' });
tree.addNode(1, 3, { title: 'test3' });
console.log(tree.data);