I have an problem with building an recursive tree from another. The function seems to work but if you expand the first array you'll notice that there is an endless recursion at index 2.
My function which builds the tree:
var buildTree = function(data, idx, aparent){
var parent = aparent || [];
for(var i = 0, c = data.length ; i < c ; i++){
parent.push({
text: data[i].text,
items: []
});
if(typeof data[i].items !== 'undefined' && data[i].items.length > 0){
var t = buildTree(data[i].items, idx + 1, parent[parent.length - 1].items);
parent[parent.length - 1].items.push(t);
}
}
return parent;
};
And that's how my tree data looks like:
[{
text: "house",
groupId: "1",
type: "group",
items: [{
text: "basement",
groupId: "2",
type: "group"
},{
text: "flat-1",
groupId: "3",
type: "group",
items: [{
text: "computer"
}]
}]
},{
text: "other-house",
groupId: "4",
type: "group"
}];
I think i has something to do that javascript returns the value by reference...
Here's a plunk with the complete data, check the console to after clicking the button to get an idea what i mean.
I can't really get my head around your code. Maybe your problem has something to do with the fact that you pass in the items-array during your recursion.
I have fixed up your code - making it a bit more simple and easy to read. It relies on the property items being an array if present, so if that is not always the case you need to add error handling for this scenario.
function recursiveBuildTree(data) {
var result = [];
data.forEach(function(item) {
var newItem = {
text: item.text,
items: item.items ? recursiveBuildTree(item.items) : []
};
result.push(newItem);
});
return result;
}
Related
Good day,
I need to convert strings as such:
Process1_Cat1_Cat2_Value1
Process1_Cat1_Cat2_Value2
Process2_Cat1_Cat2_Value1
into a nested array as such:
var d = [{
text: 'Process1',
children: [{
text: 'Cat1',
children: [{
text: 'Cat2',
children: [{
text: 'Value1'
},
{
text: 'Value2'
}]
}]
}]
},
{
text: 'Process2',
children: [{
text: 'Cat1',
children: [{
text: 'Cat2',
children: [{
text: 'Value1'
}]
}]
}]
},
];
The reason why I need to do this is to make use of a treeview to display my data:
https://www.npmjs.com/package/bootstrap-tree-view
I have looked at the following solution but was not able to get it working due to lowdash library throwing errors on the findWhere function:
Uncaught TypeError: _.findWhere is not a function
http://brandonclapp.com/arranging-an-array-of-flat-paths-into-a-json-tree-like-structure/
See below for the code:
function arrangeIntoTree(paths, cb) {
var tree = [];
// This example uses the underscore.js library.
_.each(paths, function(path) {
var pathParts = path.split('_');
pathParts.shift(); // Remove first blank element from the parts array.
var currentLevel = tree; // initialize currentLevel to root
_.each(pathParts, function(part) {
// check to see if the path already exists.
var existingPath = _.findWhere(currentLevel, {
name: part
});
if (existingPath) {
// The path to this item was already in the tree, so don't add it again.
// Set the current level to this path's children
currentLevel = existingPath.children;
} else {
var newPart = {
name: part,
children: [],
}
currentLevel.push(newPart);
currentLevel = newPart.children;
}
});
});
cb(tree);
}
arrangeIntoTree(paths, function(tree) {
console.log('tree: ', tree);
});
Any help will be appreciated!
You could use an iterative by looking for the text at the actual level. If not found create a new object. Return the children array for the next level until the most nested array. Then add the leaf object.
var data = ['Process1_Cat1_Cat2_Value1', 'Process1_Cat1_Cat2_Value2', 'Process2_Cat1_Cat2_Value1'],
result = data.reduce((r, s) => {
var keys = s.split('_'),
text = keys.pop();
keys
.reduce((q, text) => {
var temp = q.find(o => o.text === text);
if (!temp) {
q.push(temp = { text, children: [] });
}
return temp.children;
}, r)
.push({ text });
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I am facing an algorithmic conception problem. With JavaScript language, I have an heavy JSON object of about 11 000 lines, that is the result of the conversion of an HTML file. The structure of the JSON is similar to the one of the DOM, which means that an Object can have a property children, a data structure composed of other similar Object. The goal is to search in the JSON and extract the information of the property itemprop of the Object that has that property. The itemprop attribute is in and Object inside the attributes attribute that some of the first mentioned Object have.
Object Structure
{ type: 'x',
tagName: 'y',
attributes: { "itemprop" : "valueWanted" },
children:
[ Object, Object, Object]
}
I thought of a recursive algorithm for solution. Unfortunately, I am not familiar with recursion and the next code is not working.
Recursive Algorithm
var searchAttributesRecursive = function(children) {
for (var i = 0; i < children.length; ++i) {
if (children[i].hasOwnProperty('children')) {
return searchAttributesRecursive(children[i].children);
}
else {
if (children[i].hasOwnProperty('attributes')) {
if (children[i].attributes.itemprop === "valueWanted") {
console.log('success')
}
}
}
return; // probably a problem that breaks the loop
}
};
searchAttributesRecursive(startingChildren);
There is maybe another more effective generic algorithms to get this task done. I am open to suggestions.
Update
Thank you for all solutions and explanation provided. More particularly, have a look to #ChrisG's simple solution. Now, I would like to add a special condition in the algorithm.
If I would like to retrieve the data from the next object, outside of the scope of the children where an object has the wantedValue2, do you have an idea how I can access this data? The algorithm would have a special case where it meets wantedValue2, and don't want to extract directly the data of itemprop.
Object Structure Special Case
{
"type": "",
"tagName": "",
"attributes": {
"itemprop": "wantedValue"
},
"children": [{
"type": "",
"content": ""
}
]
},
{
"type": "",
"content": ""
}]
},
{
"type": "",
"tagName": "",
"attributes": {},
"children": [
{
"type": "",
"content": "here"
}
]
Here's a shorter version:
Note that the function expects an array, so if your object is not an array, you have to use findItemprop([dom], "wanted")
function findItemprop(data, value, found) {
if (!found) found = [];
data.forEach((node) => {
if (node.attributes && node.attributes.itemprop == value)
found.push(node);
if (node.children) findItemprop(node.children, value, found);
});
return found;
}
var dom = [{
tag: "root",
children: [{
tag: "header",
children: [{
tag: "div"
}]
}, {
tag: "div",
id: "main",
children: [{
tag: "p",
attributes: {
itemprop: "wanted"
}
}]
}, {
tag: "footer",
children: [{
tag: "span",
content: "copyright 2017",
attributes: {
itemprop: "wanted"
}
}]
}]
}];
console.log(findItemprop(dom, "wanted"));
Your return will break the loop. You just want to return if it does return:
var searchAttributesRecursive = function(children) {
for (var i = 0; i < children.length; ++i) {
if (children[i].hasOwnProperty('children')) {
var result=searchAttributesRecursive(children[i].children);
if(result) return result;//if weve found sth, return
}
if (children[i].hasOwnProperty('attributes')) {
if (children[i].attributes.itemprop === "valueWanted1") {
console.log('success')
return children[i];//return sth useful
}
}
}
return false;//nothing found in this and in all childs
};
var elem=searchAttributesRecursive(startingChildren);
This returns the first found child. You may want to return an array instead:
var searchAttributesRecursive = function(children,result=[]) {
for (var i = 0; i < children.length; ++i) {
if (children[i].hasOwnProperty('children')) {
searchAttributesRecursive(children[i].children,result);
}
if (children[i].hasOwnProperty('attributes')) {
if (children[i].attributes.itemprop === "valueWanted1") {
console.log('success')
result.push(children[i]);//return sth useful
}
}
}
return result;//return all results found
};
var arr=searchAttributesRecursive(allElems);
arr.forEach(console.log);
Through passing an array as optional parameter it is fast and easy to store the traversal of multiple trees in one result:
var arr=[];
searchAttributesRecursive(allElems,arr);
searchAttributesRecursive(allElemsTwo,arr);
Give Jonas w the credit for their answer, I'm just tagging on to help correct some of the confusion surrounding the recursion, and hopefully make the it a little easier to understand and work with.
First, you're passing in the array of children. That's fine, but then you have to access each one from its array index as you check them. My recommendation is to make your function handle only one item at a time. (I'm going to use Jonas w's method of collecting nodes, because there may be more than one node with this attribute. I'm also going to add the attribute name as a parameter to make it a little more dynamic.)
function searchAttributesRecursive(currentNode, parameterName, results=[]){
}
Now you can concentrate on one and only one node at a time. Once it has passed the check, you can move on to the children.
function searchAttributesRecursive(currentNode, parameterName, results=[]){
if(currentNode.attributes && currentNode.attributes[parameterName]){
results.push(currentNode);
}
if(currentNode.children){
for(var i = 0, len = currentNode.children.length; i < len; ++i){
searchAttributesRecursive(currentNode.children[i], parameterName, results);
}
}
}
Calling it like this:
var results = [];
searchAttributesRecursive(yourJsObject, "itemprop", results);
...populates results with nodes which contain the "itemprop" attribute. You can now print the attribute values with a simple loop:
for(var i = 0, len = results.length; i < len; ++i){
console.log(i, results[i].attributes.itemprop);
}
You can do this by using the .some() function. What this does is it will return true if any iteration returns true, otherwise it returns false. So, for every key in the current object, you will check if the property is === 'attributes', and if so, you will check the itemprop property for the desired value. If the current key is not 'attributes', and is === 'children' it will recurse and check each child object in the same way.
var searchAttributesRecursive = function(obj, valueWanted) {
if (obj instanceof Object) {
if (obj.attributes && obj.attributes.itemprop === valueWanted) {
return true;
}
if (obj.children) {
return obj.children.some(function(_obj) {
return searchAttributesRecursive(_obj, valueWanted);
});
} else {
return false;
}
} else {
return false;
}
};
var obj = {
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue0"
},
children: [{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue1"
},
children: []
},
{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue2"
},
children: [{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue3"
},
children: []
}]
}
]
};
console.log("Found 'wantedValue0': " + searchAttributesRecursive(obj, "wantedValue0"));
console.log("Found 'wantedValue1': " + searchAttributesRecursive(obj, "wantedValue1"));
console.log("Found 'wantedValue2': " + searchAttributesRecursive(obj, "wantedValue2"));
console.log("Found 'wantedValue3': " + searchAttributesRecursive(obj, "wantedValue3"));
console.log("Found 'wantedValue4': " + searchAttributesRecursive(obj, "wantedValue4"));
EDIT - To make this work dynamically and search for itemprop === wantedValue in any nested property or nested child property, you can do the following:
var searchAttributesRecursive2 = function(data, valueWanted) {
if (Array.isArray(data)) {
return data.some(function(elem) {
return searchAttributesRecursive2(elem, valueWanted);
});
} else if (data instanceof Object) {
return Object.keys(data).some(function(key) {
var prop = data[key];
return prop.itemprop === valueWanted || searchAttributesRecursive2(prop, valueWanted);
});
} else {
return false;
}
};
var obj = {
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue0"
},
children: [{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue1"
},
children: []
},
{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue2"
},
children: [{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue3"
},
children: []
}]
}
]
};
console.log("Found 'wantedValue0': " + searchAttributesRecursive2(obj, "wantedValue0"));
console.log("Found 'wantedValue1': " + searchAttributesRecursive2(obj, "wantedValue1"));
console.log("Found 'wantedValue2': " + searchAttributesRecursive2(obj, "wantedValue2"));
console.log("Found 'wantedValue3': " + searchAttributesRecursive2(obj, "wantedValue3"));
console.log("Found 'wantedValue4': " + searchAttributesRecursive2(obj, "wantedValue4"));
This is my code..
var result = data.map(function(item){
return {
category:item.category,
key:item.key,
value:item.value
}
});
console.log(result);
This is what is getting printed out in console..
Array[4]
0: Object
category: "common"
key: "Food"
value: "food"
1: Object
category: "welcome"
key: "title"
value: "Welcome..."
2: Object
category: "welcome"
key: "app_description"
value: "In this App "
3: Object
category: "welcome"
key: "select_location"
value: "Select Location"
This is what I'm trying to achieve
{
common:{
"Food" : "food"
},
welcome:{
title : Welcome...,
app_description : "In this App",
select_location : "Select Location"
}
}
This is the code I'm trying .but it is not working..
return {
item.category:{
item.key:item.value;
}
Can anyone help me with this? I dont want to use GSON or any other third-party JS..How can i get this done using only core JS?
First of all, what you want as a result is an object, not an array. So you can't use .map() which only maps one array to another array.
You want .reduce().
var result = data.reduce(function (result, item) {
// Create the object the first time it arrives
result[item.category] = result[item.category] || {};
// Add the field
result[item.category][item.key]=item.value;
// Return the resulting object
return result;
}, {});
console.log(result);
.reduce()'s reduction function takes two (with 2 more optional) parameters. The cumulative result, and the current item. The returned value is the result after the current item has been processed on it.
The second parameter for .reduce() (the first being the reduction function), is the initial value, this value will get passed as result for the first iteration.
Array.prototype.reduce() comes to rescue.
var result = data.reduce(function (previousValue, currentValue) {
previousValue[currentValue.category] = previousValue[currentValue.category] || {};
previousValue[currentValue.category][currentValue.key] = currentValue.value;
return previousValue;
}, {});
you can also try:
var data = [
{
category: "common",
key: "Food",
value: "food"
},
{
category: "welcome",
key: "title",
value: "Welcome..."
},
{
category: "welcome",
key: "app_description",
value: "In this App "
},
{
category: "welcome",
key: "select_location",
value: "Select Location"
}
];
var obj = {};
for (var i in data) {
if(!obj[data[i].category]){
obj[data[i].category] = {};
}
obj[data[i].category][data[i].key] = data[i].value;
}
console.log(obj);
I am trying to dynamically form a nested tree object something like below using JavaScript, can someone please let me know the best way to achieve this?
var contextpath= {
text: "TreeRoot",
items: [ {
text: "subgroup1" ,
items: [ {
text: "subgroup2",
items: [ {
text: "subgroup3",
items: [ {
text: "subgroup4",
items: [ {
text: "subgroup5"
}]
}]
}]
}]
}]
};
I have delimited string that I am trying to convert to object (that can be used as dat source for tree component).
var path="TreeRoot|subgroup1|subgroup2";
Trying to implement something like below but with recursion / looping using less number of variables.
var contextpathText= {};
contextpathText.text ="TreeRoot";
var childObject={};
var items=[];
childObject.text ="subgroup1";
items.push(childObject);
contextpathText.items=(items);
You need a depth counter and to store the current levels of the object you're working with.
var obj = {text:''}, parent, child, i = 0;
obj.text = 'TreeRoot';
parent = obj;
while (++i <= 5) {
if (parent.items === undefined) parent.items = []; // because you don't have an items on the last subgroup you can't put it in the object literal
child = {text: 'subgroup'+i};
parent.items.push(child);
parent = child;
}
parent = child = null; // cleanup
obj;
jsbeautified JSON.stringify(obj) is now
{
"text": "TreeRoot",
"items": [{
"text": "subgroup1",
"items": [{
"text": "subgroup2",
"items": [{
"text": "subgroup3",
"items": [{
"text": "subgroup4",
"items": [{
"text": "subgroup5"
}]
}]
}]
}]
}]
}
Edit For delimited string
var path = 'TreeRoot|subgroup1|subgroup2';
var obj = {text:''}, parent, child, levelText = path.split('|').reverse();
obj.text = levelText.pop() || '';
parent = obj;
while (levelText.length > 0) {
child = {text: levelText.pop()};
if (!parent.items) parent.items = [];
parent.items.push(child);
parent = child;
}
obj;
Beat me to it, but I went with this code:
var contextpath = { text: "TreeRoot", items: []}
var items = contextpath.items;
for(var i = 1; i <= 5; i++) {
items.push({ text: "subgroup" + i, items: []});
items = items[0].items;
}
The parent & child nomenclature is definitely clearer, for this sort of thing, but I wanted to show that you didn't have to declare the new object as a variable first, you can just push the object literal.
Whoops, just now noticed that you don't have an items array in your desired structure. My code creates the spare, so you end up with
// snip
text: "subgroup4",
items: [ {
text: "subgroup5",
items: []
}]
// etc.
I have the following object and what I would like achieve is to get the index of theme if the name has match with a variable.
for example: I'm making a loop in the views and if my task (something1) variable has matches with the name element than to return the index of object.
By the given example I should have as result 0,
var views = [
{
name: "something1",
type: something1,
columns: something1
},
{
name: "something2",
type: something2,
columns: something2
},
{
name: "something3",
type: something3,
columns: something3
}
];
var task = 'something1';
$.each(views, function(index, value) {
if (value.name = task) {
alert(index);
}
});
You dont really need jQuery for this:
See: http://jsfiddle.net/enNya/2/
var views = [
{
name: "something1",
type: "something1",
columns: "something1"
},
{
name: "something2",
type: "something2",
columns: "something2"
}
];
var task = 'something2';
// Set a var and maintain scope
var i;
// Loop each element of the array
for (i = 0; i < views.length; i++) {
// If the X = Y the stop looping
if (views[i].name == task) {
break;
}
}
// Check if it was not found
i = i == views.length ? false : i;
// Log the result
console.log(i);
It's just a matter of syntax, as lgt said don't forget toseparate elements within your object with commas. Aslo the correct 'equal' operator is '=='.
'value.name=task' would be always true. It means can I affect the content of 'task' into 'value.name'.
Here is your valid js.
Note that in this example you'll get 2 alertbox.. ;)
var views=[
{
name:"something1",
type:'something1',
columns:'something1'
},
{
name:"something1",
type:'something1',
columns:'something1'
},
{
name:"something2",
type:'something2',
columns:'something2',
},
];
var task='something1';
$.each(views, function(index, value) {
if (value.name==task){
alert(index);
}
});
replace something1 variable for 0 and value.name == task (double =)
var views=[{
name:"something1",
type:0,
columns:0
}, {
name:"something1",
type:0,
columns:0
}, {
name:"something2",
type:0,
columns:0
}];
var task='something1';
$.each(views, function(index, value) {
if (value.name==task){
return index;
}
});