I have the following array:
const items = [
{
"id": 1,
"name": "Menu 1",
"content": [
{
"id": 2,
"name": "Submenu 1 OF 1",
"url": "teste"
}
]
},
{
"id": 3,
"name": "Menu 2",
"content": [
{
"id": 4,
"name": "Submenu 1 OF 2",
"url": "teste"
},
{
"id": 5,
"name": "Submenu 2 OF 2",
"content": [
{
"id": 6,
"name": "Sub submenu 1 OF 2",
"url": "teste"
},
{
"id": 7,
"name": "Sub submenu 2 OF 2",
"url": "teste"
},
]
}
]
}
]
I need a function that given the id gets the closest parent object. For example, if I give the id 2 it returns {"id: 1, "name": "Menu 1", "content": [...]}. If I give the id 6 it returns {"id": 5, "name": "Submenu 2 OF 2", content: [...]}.
I have tried but i can only get the top-level parent and not the closest one.
EDIT: the code that i have tested so far:
let findDeep = function(data, id) {
return data.find(function(e) {
if(e.id == id) return e;
else if(e.content) return findDeep(e.content, id);
}
})
You can use a recursive depth-first search:
function findParent(items, id, parent=null) {
for (let item of items) {
let res = item.id === id ? parent
: item.content && findParent(item.content, id, item);
if (res) return res;
}
}
// demo: find parent of id=6 in example tree:
const items = [{"id": 1,"name": "Menu 1","content": [{"id": 2,"name": "Submenu 1 OF 1","url": "teste"}]},{"id": 3,"name": "Menu 2","content": [{"id": 4,"name": "Submenu 1 OF 2","url": "teste"},{"id": 5,"name": "Submenu 2 OF 2","content": [{"id": 6,"name": "Sub submenu 1 OF 2","url": "teste"},{"id": 7,"name": "Sub submenu 2 OF 2","url": "teste"},]}]}]
console.log(findParent(items, 6));
You can do this with a recursive function that checks each child and returns the parent if the id matches:
const findParent = (id, obj) => {
for (const child of obj.content) {
if (child.id === id) return obj;
if (!child.content) continue;
const found = findParent(id, child);
if (found) return found;
}
return undefined;
}
const items = {content: [{
"id": 1,
"name": "Menu 1",
"content": [{
"id": 2,
"name": "Submenu 1 OF 1",
"url": "teste"
}]
},
{
"id": 3,
"name": "Menu 2",
"content": [{
"id": 4,
"name": "Submenu 1 OF 2",
"url": "teste"
},
{
"id": 5,
"name": "Submenu 2 OF 2",
"content": [{
"id": 6,
"name": "Sub submenu 1 OF 2",
"url": "teste"
},
{
"id": 7,
"name": "Sub submenu 2 OF 2",
"url": "teste"
},
]
}
]
}
]
}
console.log(findParent(2, items));
console.log(findParent(6, items));
Related
I have two arrays of objects that I need to combine where the nested ID (connect.id) of the second array matches the id of the first but can't see how to do it
can anyone help?
const arr1 = [
{ "id": 7619209572755, "title": "Title 1" },
{ "id": 7619209157372, "title": "Title 2" },
{ "id": 7619209921625, "title": "Title 3" }
];
const arr2 = [
{
"id": 7619217289192,
"connect": [
{ "id": 7619209157372, "title": "Title 1" }
],
"value": "1"
},
{
"id": 7619217242206,
"connect": [
{ "id": 7619209921625, "title": "Title 2" }
],
"value": "2"
}
];
const expectedResult = [
{ "id": 7619209572755, "title": "Title 1" },
{ "id": 7619209157372, "title": "Title 2", "value": "1" },
{ "id": 7619209921625, "title": "Title 3", "value": "2" }
]
Using Array#reduce, iterate over arr2 to map every connect element's id with the its object's value in a Map
Using Array#map, iterate over arr1, if an element's id is in the map, set its value
const
arr1 = [ { "id": 7619209572755, "title": "Title 1" }, { "id": 7619209157372, "title": "Title 2" }, { "id": 7619209921625, "title": "Title 3" } ],
arr2 = [
{
"id": 7619217289192,
"connect": [ { "id": 7619209157372, "title": "Title 1" } ],
"value": "1",
},
{
"id": 7619217242206,
"connect": [ { "id": 7619209921625, "title": "Title 2" } ],
"value": "2",
}
];
const connectValueMap = arr2.reduce((map, { connect = [], value}) => {
connect.forEach(({ id }) => map.set(id, value));
return map;
}, new Map);
const merged = arr1.map(e => {
const value = connectValueMap.get(e.id);
if(value) e.value = value;
return e;
});
console.log(merged);
I have a nested array of objects and want to get only the child property elements of an array with the title of the parent element prepended to the title in the child. This is just an example and the actual data will include a unique children property in separate indices in the array.
I would like to implement flatMap in my solution instead of using flattenDeep and map from lodash. Please advice.
const headers = [{
"id": "name1",
"title": "Name 1",
"children": [{
"title": "Children 1",
"child": [{
"title": "Child 1",
"onClick": "child1Click"
}, {
"title": "Child 2",
"onClick": "child2Click"
}]
}, {
"title": "CHildren 2",
"child": [{
"title": "Child 3",
"id": "child3Click"
}, {
"title": "Child 4",
"id": "child4Click"
}]
}]
}, {
"id": "name2",
"title": "Name 2",
"children": [{
"title": "Children 3",
"child": [{
"title": "Child 5",
"onClick": "child5Click"
}, {
"title": "Child 6",
"onClick": "child6Click"
}]
}, {
"title": "CHildren 4",
"child": [{
"title": "Child 7",
"id": "child7Click"
}, {
"title": "Child 8",
"id": "child8Click"
}]
}]
}, {
"id": "name3",
"title": "Name 3"
}, {
"id": "name4",
"title": "Name 4"
}]
console.log(_.flattenDeep(_.map(_.flattenDeep(_.compact(_.map(headers, item => item.children))), item => _.map(item.child, child => {
return {
...child,
title: `${item.title} ${child.title}`
}
}))))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
The output I expect is
[
{
"title": "Children 1 Child 1",
"onClick": "child1Click"
},
{
"title": "Children 1 Child 2",
"onClick": "child2Click"
},
{
"title": "CHildren 2 Child 3",
"id": "child3Click"
},
{
"title": "CHildren 2 Child 4",
"id": "child4Click"
},
{
"title": "Children 3 Child 5",
"onClick": "child5Click"
},
{
"title": "Children 3 Child 6",
"onClick": "child6Click"
},
{
"title": "CHildren 4 Child 7",
"id": "child7Click"
},
{
"title": "CHildren 4 Child 8",
"id": "child8Click"
}
]
Any help is greatly appreciated. Thanks
You can solve this with just reduce and two inner forEach loops for children and child.
const headers = [{"id":"name1","title":"Name 1","children":[{"title":"Children 1","child":[{"title":"Child 1","onClick":"child1Click"},{"title":"Child 2","onClick":"child2Click"}]},{"title":"CHildren 2","child":[{"title":"Child 3","id":"child3Click"},{"title":"Child 4","id":"child4Click"}]}]},{"id":"name2","title":"Name 2","children":[{"title":"Children 3","child":[{"title":"Child 5","onClick":"child5Click"},{"title":"Child 6","onClick":"child6Click"}]},{"title":"CHildren 4","child":[{"title":"Child 7","id":"child7Click"},{"title":"Child 8","id":"child8Click"}]}]},{"id":"name3","title":"Name 3"},{"id":"name4","title":"Name 4"}]
const result = headers.reduce((r, {children}) => {
children && children.forEach(({title: pTitle, child}) => {
child && child.forEach(({title, ...rest}) => r.push({
title: `${pTitle} ${title}`,
...rest
}))
})
return r;
}, [])
console.log(result)
I have a scenario were I need to iterate through each parent/child array in an object.
Each Grandparents can have multiple parents, in same fashion each parent can have multiple childs, each child can have multiple subChilds and so on.
I need to check if type is "parent" or "child" while iterating and then push name property to an array as mentioned in expected output.
Input Object:
var inputObject = {
"id": 1,
"name": "Grand Parent 1",
"type": "GrandParent",
"childType": [
{
"id": 2,
"type": "Parent",
"childType": [
{
"id": 3,
"type": "Child",
"childType": [],
"name": "Child 11"
},
{
"id": 4,
"type": "Child",
"childType": [],
"name": "Child 12"
}
],
"name": "Parent 1"
},
{
"id": 5,
"type": "Parent",
"childType": [
{
"id": 6,
"type": "Child",
"childType": [],
"name": "Child 21"
}
],
"name": "Parent 2"
},
{
"id": 7,
"type": "Parent",
"childType": [
{
"id": 8,
"type": "Child",
"childType": [],
"name": "Child 31"
}
],
"name": "Parent 3"
}
]
}
Code Tried:
function handleData({childType, ...rest}){
const res = [];
res.push(rest.name);
if(childType){
if(rest.type == "Child")
res.push(...handleData(childType));
}
return res;
}
const res = handleData(inputObject);
Expected Output:
If type selected is "Parent"
["Parent 1", "Parent 2, Parent 3"]
if type selected is "Child"
["Child 11", "Child 12", "Child 21", "Child 31"]
You can do a recursive function that uses flatMap():
const obj = {id:1,name:"Grand Parent 1",type:"GrandParent",childType:[{id:2,type:"Parent",childType:[{id:3,type:"Child",childType:[],name:"Child 11"},{id:4,type:"Child",childType:[],name:"Child 12"}],name:"Parent 1"},{id:5,type:"Parent",childType:[{id:6,type:"Child",childType:[],name:"Child 21"}],name:"Parent 2"},{id:7,type:"Parent",childType:[{id:8,type:"Child",childType:[],name:"Child 31"}],name:"Parent 3"}]};
const get = (o, t) => o.type === t ? [o.name] : o.childType.flatMap(c => get(c, t));
console.log(get(obj, 'GrandParent'));
console.log(get(obj, 'Parent'));
console.log(get(obj, 'Child'));
You can do that using recursion.
Steps:
Create a wrapper function in which you declare result array.
Inside that make a wrapper function create a function which would be called recursively
Check if type matches the given type add the name of object to result
Then check if elements exists in its childType. If yes yes then call function on each each of its element.
var inputObject = { "id": 1, "name": "Grand Parent 1", "type": "GrandParent", "childType": [ { "id": 2, "type": "Parent", "childType": [ { "id": 3, "type": "Child", "childType": [], "name": "Child 11" }, { "id": 4, "type": "Child", "childType": [], "name": "Child 12" } ], "name": "Parent 1" }, { "id": 5, "type": "Parent", "childType": [ { "id": 6, "type": "Child", "childType": [], "name": "Child 21" } ], "name": "Parent 2" }, { "id": 7, "type": "Parent", "childType": [ { "id": 8, "type": "Child", "childType": [], "name": "Child 31" } ], "name": "Parent 3" } ] }
function handleData(obj,type){
let res = [];
function recursive(obj){
if(type === obj.type) res.push(obj.name);
if(obj.childType.length){
obj.childType.forEach(a => recursive(a));
}
}
recursive(obj)
return res;
}
console.log(handleData(inputObject,"Child"))
console.log(handleData(inputObject,"Parent"))
Recursion is elegant but you could use es6 to do it , if the object childType was very large, recursion might not be applicable (stack overflow), here is a solution using reduce,
function getType({childType}, type) {
return childType.reduce( (acc, {type: objectType, name}) => {
if (objectType === type){
acc.push(name)
}
return acc
}, [])
}
You can solve this problem using recursion. You can try on this way !
var inputObject = {
"id": 1,
"name": "Grand Parent 1",
"type": "GrandParent",
"childType": [
{
"id": 2,
"type": "Parent",
"childType": [
{
"id": 3,
"type": "Child",
"childType": [],
"name": "Child 11"
},
{
"id": 4,
"type": "Child",
"childType": [],
"name": "Child 12"
}
],
"name": "Parent 1"
},
{
"id": 5,
"type": "Parent",
"childType": [
{
"id": 6,
"type": "Child",
"childType": [],
"name": "Child 21"
}
],
"name": "Parent 2"
},
{
"id": 7,
"type": "Parent",
"childType": [
{
"id": 8,
"type": "Child",
"childType": [],
"name": "Child 31"
}
],
"name": "Parent 3"
}
]
};
var resultValues = [];
function getResult(inputObject, propertyName, propertyValue) {
for (var objectProperty in inputObject) {
if (objectProperty == propertyName && inputObject[objectProperty] == propertyValue) {
resultValues.push(inputObject['name']);
console.log(resultValues);
} else {
if (objectProperty == 'childType') {
inputObject[objectProperty].forEach(function (element) {
getResult(element, propertyName, propertyValue)
});
}
}
}
//console.log(resultValues);
}
getResult(inputObject, 'type', 'GrandParent');
getResult(inputObject, 'type', 'Parent');
getResult(inputObject, 'type', 'Child');
I have array with following structure:
var topics = [
{
"id": 1,
"name": "topic title 1",
"sub_categories": [
{
"id": 1,
"name": "category title 1",
"indicators": [
{
"id": 1,
"name": "indicator 1",
"sub_category_id": 1
},
{
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}
]
},
{
"id": 6,
"name": "category title 6",
"indicators": [
{
"id": 8,
"name": "indicator 8",
"sub_category_id": 6
}
]
}
]
},
{
"id": 2,
"name": "topic title 2",
"sub_categories": [
{
"id": 2,
"name": "category 2",
"indicators": [
{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}
]
},
{
"id": 4,
"name": "category 4",
"indicators": [
{
"id": 5,
"name": "indicator 5",
"sub_category_id": 4
}
]
}
]
}
];
I need to get filtered array based on value of name property in indicators array, removing non-matched indicators and both topic and sub_categories with empty indicators. So for input of foo, result would be:
var topics = [
{
"id": 1,
"name": "topic title 1",
"sub_categories": [
{
"id": 1,
"name": "category title 1",
"indicators": [
{
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}
]
}
]
},
{
"id": 2,
"name": "topic title 2",
"sub_categories": [
{
"id": 2,
"name": "category 2",
"indicators": [
{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}
]
}
]
}
];
I tried to use lodash methods based on other similar SO question but all examples either have only one level of nesting or same keys on all levels (ie. children). I would be fine with either getting back new array or mutating existing one.
Here is an ES6 solution based on reduce, filter and Object.assign:
function filterTree(topics, find) {
return topics.reduce(function (acc, topic) {
const sub_categories = topic.sub_categories.reduce(function (acc, cat) {
const indicators = cat.indicators.filter( ind => ind.name.includes(find) );
return !indicators.length ? acc
: acc.concat(Object.assign({}, cat, { indicators }));
}, []);
return !sub_categories.length ? acc
: acc.concat(Object.assign({}, topic, { sub_categories }));
}, []);
}
// sample data
const topics = [
{
"id": 1,
"name": "topic title 1",
"sub_categories": [
{
"id": 1,
"name": "category title 1",
"indicators": [
{
"id": 1,
"name": "indicator 1",
"sub_category_id": 1
},
{
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}
]
},
{
"id": 6,
"name": "category title 6",
"indicators": [
{
"id": 8,
"name": "indicator 8",
"sub_category_id": 6
}
]
}
]
},
{
"id": 2,
"name": "topic title 2",
"sub_categories": [
{
"id": 2,
"name": "category 2",
"indicators": [
{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}
]
},
{
"id": 4,
"name": "category 4",
"indicators": [
{
"id": 5,
"name": "indicator 5",
"sub_category_id": 4
}
]
}
]
}
];
// Call the function
var res = filterTree(topics, 'foo');
// Output result
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use an iterative and recursive approach for filtering the given array, without hard wired properties.
const deepFilter = (array, indicator) => {
return array.filter(function iter(o) {
return Object.keys(o).some(k => {
if (typeof o[k] === 'string' && o[k].includes(indicator)) {
return true;
}
if (Array.isArray(o[k])) {
o[k] = o[k].filter(iter);
return o[k].length;
}
});
});
}
const topics = [{ id: 1, name: "topic title 1", sub_categories: [{ id: 1, name: "category title 1", indicators: [{ id: 1, name: "indicator 1", sub_category_id: 1 }, { id: 7, name: "indicator 7 - foo", sub_category_id: 1 }] }, { id: 6, name: "category title 6", indicators: [{ id: 8, name: "indicator 8", sub_category_id: 6 }] }] }, { id: 2, name: "topic title 2", sub_categories: [{ id: 2, name: "category 2", indicators: [{ id: 2, name: "indicator 2 - foo", sub_category_id: 2 }] }, { id: 4, name: "category 4", indicators: [{ id: 5, name: "indicator 5", sub_category_id: 4 }] }] }];
console.log(deepFilter(topics, 'foo'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
This can pretty much all be done with ES 5 array methods (no library or polyfill needed for IE 9+):
var passed = topics.filter(function(x) {
return x.subcategories.some(function(y) {
return y.indicators.some(function(z) {
return Boolean(z.name.match(/foo/));
});
});
});
While this is total one-off code, the situation is perhaps too complicated for an easily digestible general-purpose solution (although I'd love to see someone prove me wrong).
UPDATE
After taking a closer look at the output you will need to use reduce instead of filter:
var passed = topics.reduce((acc, x) => {
var hasfoo = x.subcategories.reduce((accum, y) => {
var ls = y.indicators.filter(z => z.name.match(/foo/));
if (ls.length) {
accum.push(Object.assign({}, y, {indicators: ls}));
}
return accum;
}, []);
if (hasfoo.length) {
acc.push(Object.assign({}, x, {subcategories: hasfoo}));
}
return acc;
}, []);
Astute readers will note the recursive pattern here. Abstracting that out is left as an exercise, I'm tapped out. Object.assign will need to be polyfilled for old browsers (trivial though).
this will also modify existing topics
var result = topics.filter(top =>
(top.sub_categories = top.sub_categories.filter(cat =>
(cat.indicators = cat.indicators.filter(i => i.name.match(/foo/))).length)
).length
);
Example
var topics = [{
"id": 1,
"name": "topic title 1",
"sub_categories": [{
"id": 1,
"name": "category title 1",
"indicators": [{
"id": 1,
"name": "indicator 1",
"sub_category_id": 1
}, {
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}]
}, {
"id": 6,
"name": "category title 6",
"indicators": [{
"id": 8,
"name": "indicator 8",
"sub_category_id": 6
}]
}]
}, {
"id": 2,
"name": "topic title 2",
"sub_categories": [{
"id": 2,
"name": "category 2",
"indicators": [{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}]
}, {
"id": 4,
"name": "category 4",
"indicators": [{
"id": 5,
"name": "indicator 5",
"sub_category_id": 4
}]
}]
}];
var result = topics.filter(top => (top.sub_categories = top.sub_categories.filter(cat => (cat.indicators = cat.indicators.filter(i => i.name.match(/foo/))).length)).length);
console.log(result);
One more implementation.
topics.forEach(function(topic, indexTopic, indexTopicArray) {
topic.sub_categories.forEach(function(subCat, indexsubCat, arraysubCat) {
subCat.indicators = subCat.indicators.filter(indic => indic.name.includes("foo"));
if(subCat.indicators.length === 0) {
indexTopicArray[indexTopic].sub_categories.splice(indexsubCat, 1);
}})});
console.log(topics);
Complete Code.
var topics = [
{
"id": 1,
"name": "topic title 1",
"sub_categories": [
{
"id": 1,
"name": "category title 1",
"indicators": [
{
"id": 1,
"name": "indicator 1",
"sub_category_id": 1
},
{
"id": 7,
"name": "indicator 7 - foo",
"sub_category_id": 1
}
]
},
{
"id": 6,
"name": "category title 6",
"indicators": [
{
"id": 8,
"name": "indicator 8",
"sub_category_id": 6
}
]
}
]
},
{
"id": 2,
"name": "topic title 2",
"sub_categories": [
{
"id": 2,
"name": "category 2",
"indicators": [
{
"id": 2,
"name": "indicator 2 - foo",
"sub_category_id": 2
}
]
},
{
"id": 4,
"name": "category 4",
"indicators": [
{
"id": 5,
"name": "indicator 5",
"sub_category_id": 4
}
]
}
]
}
];
topics.forEach(function(topic, indexTopic, indexTopicArray) {
topic.sub_categories.forEach(function(subCat, indexsubCat, arraysubCat) {
subCat.indicators = subCat.indicators.filter(indic => indic.name.includes("foo"));
if(subCat.indicators.length === 0) {
indexTopicArray[indexTopic].sub_categories.splice(indexsubCat, 1);
}})});
console.log(topics);
You can do it using _.filterDeep from deepdash extension for lodash:
var endsWith = 'foo';
var foundFoo = _.filterDeep(
obj,
function(value, key) {
return _.endsWith(value.name, endsWith);
},
{ tree: { children: ['sub_categories', 'indicators'] } }
);
Here is a full test for your case
I currently have a problem on my javascript code.
I want to create a treeview using this module:
jqm-tree
==Problem==
When I execute the demo code in index.html like this:
<script>
$("#tree").jqmtree({
title : 'Items',
collapsed: true,
data: [
{ "id": 1, "title": "Menu 1" },
{ "id": 2, "title": "Sub 1 1", "pid":1 },
{ "id": 3, "title": "Sub 1 2", "pid": 1 },
{ "id": 4, "title": "Sub 1 3", "pid": 1 },
{ "id": 5, "title": "Menu 2" },
{ "id": 6, "title": "Sub 2 1", "pid": 5 }
]
});
</script>
the result is correct.
but when I execute this same demo code after a ajax request like this :
$(document).ready(function() {
$.ajax({
type: "GET",
url: "data/data.csv",
dataType: "text",
success: function(data)
{
$("#tree").jqmtree({
title : 'Items',
collapsed: true,
data: [
{ "id": 1, "title": "Menu 1" },
{ "id": 2, "title": "Sub 1 1", "pid":1 },
{ "id": 3, "title": "Sub 1 2", "pid": 1 },
{ "id": 4, "title": "Sub 1 3", "pid": 1 },
{ "id": 5, "title": "Menu 2" },
{ "id": 6, "title": "Sub 2 1", "pid": 5 }
]
});
}
});
});
I have not the right result, the collapsible is no longer active on the menu 1 and menu 2. nok screenshot
Can you point me to the reasons for this difference ?
thanks for your help.
==Edit1=
I try to put the demo code without ajax :
$(document).on('pagecreate', '#page1' ,function(){
$("#tree").jqmtree({
title : 'Items',
collapsed: true,
data: [
{ "id": 1, "title": "Menu 1" },
{ "id": 2, "title": "Sub 1 1", "pid":1 },
{ "id": 3, "title": "Sub 1 2", "pid": 1 },
{ "id": 4, "title": "Sub 1 3", "pid": 1 },
{ "id": 5, "title": "Menu 2" },
{ "id": 6, "title": "Sub 2 1", "pid": 5 }
]
});
});
The result is not good, I don't understand why ;(