I have the following data structure
menu: [ { id: 1, title: 'Test 1', children: [] },
{ id: 2, title: 'Test 2', children: [
{ id: 5, title: 'Test 5', children: [] },
{ id: 6, title: 'Test 6', children: [] },
{ id: 7, title: 'Test 7', children: [] },
{ id: 8, title: 'Test 8', children: [] },
] },
{ id: 3, title: 'Test 3', children: [
{ id: 9, title: 'Test 9', children: [] },
{ id: 10, title: 'Test 10', children: [] },
{ id: 11, title: 'Test 11', children: [] },
{ id: 12, title: 'Test 12', children: [] },
] },
{ id: 4, title: 'Test 4', children: [] },
]
How can remove object with Title 'Test 5'? Or from sub array in children arr?
onDeleteClick(item) {
const menuCopy = JSON.parse(JSON.stringify(this.menu));
const index = menuCopy.indexOf(item);
if (index !== -1) {
menuCopy.splice(index, 1);
} else {
menuCopy.map((el) => {
if (el.children.length) {
el.children.map((child) => {
if (child.Id === item.Id) {
console.log(child);
}
});
}
});
}
this.setMenu(menuCopy);
}
I am stuck at this point. I think that here should be used recursion but i have no idea how to implement this.
const menu = [ { id: 1, title: 'Test 1', children: [] },
{ id: 2, title: 'Test 2', children: [
{ id: 5, title: 'Test 5', children: [] },
{ id: 6, title: 'Test 6', children: [
{ id: 5, title: 'Test 5', children: [] },
{ id: 7, title: 'Test 7', children: [] },
{ id: 8, title: 'Test 8', children: [] }
] },
{ id: 7, title: 'Test 7', children: [] },
{ id: 8, title: 'Test 8', children: [] },
] },
{ id: 3, title: 'Test 3', children: [
{ id: 9, title: 'Test 9', children: [] },
{ id: 10, title: 'Test 10', children: [] },
{ id: 11, title: 'Test 11', children: [] },
{ id: 12, title: 'Test 12', children: [] },
] },
{ id: 4, title: 'Test 4', children: [] },
];
const excludeChildrenFromTitle = (arr, excludedChildTitle) => {
return arr.map((item) => {
const children = excludeChildrenFromTitle(item.children.filter((child) => child.title !== excludedChildTitle), excludedChildTitle);
return {
...item,
children
}
});
};
console.log(excludeChildrenFromTitle(menu, 'Test 5'))
Using a simple map for the whole menu array and then filtering every children array from each menu item can do the job.
I have updated the answer to remove the excluded child from sub array too.
You can filter first each first-level element and then second-level with map:
var l = [{ id: 1, title: 'Test 1', children: [] }, { id: 2, title: 'Test 2', children: [ { id: 5, title: 'Test 5', children: [] }, { id: 6, title: 'Test 6', children: [] }, { id: 7, title: 'Test 7', children: [] }, { id: 8, title: 'Test 8', children: [] }, ] }, { id: 3, title: 'Test 3', children: [ { id: 9, title: 'Test 9', children: [] }, { id: 10, title: 'Test 10', children: [] }, { id: 11, title: 'Test 11', children: [] }, { id: 12, title: 'Test 12', children: [] }, ] }, { id: 4, title: 'Test 4', children: [] },
{ id: 5, title: 'Test 5', children: [] }, ];
const removeTitleByValue = (arr, titleValue) => {
return arr
.filter(e => e.title !== titleValue)
.map((e2) => {
const children = e2.children.filter((ch) => ch.title !== titleValue);
return { ...e2, children }
});
};
console.log(removeTitleByValue(l, 'Test 5'))
Related
Data looks as follows:
Each node has a unique id (
Nodes have a children key which is either null or an array of ids.
Nodes can have one parent
Nodes do not have a parent or depth reference
Input:
const items = [
{
id: 1,
name: 'Item 1',
children: [ 2, 3 ]
},
{
id: 2,
name: 'Item 2',
children: null
},
{
id: 3,
name: 'Item 3',
children: null
},
{
id: 4,
name: 'Item 4',
children: [ 5 ]
},
{
id: 5,
name: 'Item 5',
children: [ 6 ]
},
{
id: 6,
name: 'Item 6',
children: null
},
}
]
Expected Output:
const tree = [
{
id: 1,
name: 'Item 1',
children: [
{
id: 2,
name: 'Item 2',
children: null
},
{
id: 3,
name: 'Item 3',
children: null
},
]
},
{
id: 4,
name: 'Item 4',
children: [
{
id: 5,
name: 'Item 5',
children: [
{
id: 6,
name: 'Item 6',
children: null
}
]
}
]
}
]
If this is in fact possible, would love to 1) see how it is done and 2) see if there are any libraries that handle this use case.
The resulting structure is more a forest than a tree, as not all nodes are connected and you have multiple "roots".
You can first key the nodes by their id in a Map, and then iterate all children arrays to replace their contents by the corresponding items found in the Map. At the same time keep track of all the children, so that at the end you can identify which items are not children, and therefore belong in the result array:
const items = [{id: 1,name: 'Item 1',children: [ 2, 3 ]},{id: 2,name: 'Item 2',children: null},{id: 3,name: 'Item 3',children: null},{id: 4,name: 'Item 4',children: [ 5 ]},{id: 5,name: 'Item 5',children: [ 6 ]},{id: 6,name: 'Item 6',children: null},];
const map = new Map(items.map(item => [item.id, item]));
const children = new Set;
for (const item of items) {
if (!item.children) continue;
for (const id of item.children) children.add(id);
item.children = item.children?.map(id => map.get(id));
}
const forest = items.filter(({id}) => !children.has(id));
console.log(forest);
I have an array of objects that I'm trying to rebuild without any success:
const data = [
{
ID: 1,
TemplateName: 'Template 1',
TemplateCategory: 'Category A',
},
{
ID: 2,
TemplateName: 'Template 2',
TemplateCategory: 'Category A',
},
{
ID: 3,
TemplateName: 'Template 3',
TemplateCategory: 'Category B',
},
]
I have the below code which produces the following undesired result:
result = [...data
.reduce((acc, {TemplateCategory, TemplateName, ID}) => {
const group = acc.get(TemplateCategory)
group ? group.options.push(ID, TemplateName) : acc.set(TemplateCategory, {TemplateCategory, "options":[ID, TemplateName]})
return acc
}, new Map)
.values()
]
console.log(result) // undesired result:
[
{
TemplateCategory: 'Category A',
options: [1, 'Template 1', 2, 'Template 2']
},
{
TemplateCategory: 'Category B',
options: [3, 'Template 3']
}
]
I am stuck on trying to convert options to an Array of Objects with value and label as properties. Also im struggling trying to reword TemplateCategory property to label.
My desired result is:
[
{
label: 'Category A',
options: [
{
value: 1,
label: 'Template 1'
},
{
value: 2,
label: 'Template 2'
}
]
},
{
label: 'Category B',
options: [
{
value: 3,
label: 'Template 3'
}
]
}
]
TIA
Like this
const data = [
{
ID: 1,
TemplateName: 'Template 1',
TemplateCategory: 'Category A',
},
{
ID: 2,
TemplateName: 'Template 2',
TemplateCategory: 'Category A',
},
{
ID: 3,
TemplateName: 'Template 3',
TemplateCategory: 'Category B',
},
]
const result = [...data
.reduce((acc, {TemplateCategory, TemplateName, ID}) => {
const group = acc.get(TemplateCategory)
group ? group.options.push({value: ID, label: TemplateName}) : acc.set(TemplateCategory, {label: TemplateCategory, "options":[{value: ID, label: TemplateName}]})
return acc
}, new Map)
.values()
]
console.log(result) // undesired result:
The Below code, not returning a flat array, is highly confusing, Need a flat array of deeply nested array of objects
have attached jsfiddle link https://jsfiddle.net/k6swuvox/
const arr = [{
id: 1,
name: 'XYZ 1'
}, {
id: 2,
name: 'XYZ 2',
children: [{
id: 5,
name: 'XYZ 5'
}, {
id: 6,
name: 'XYZ 6',
age: 29,
children: [{
id: 7,
name: 'XYZ 7'
}, {
id: 8,
name: 'XYZ 8'
}]
}]
}, {
id: 3,
name: 'XYZ 3'
}, {
id: 4,
name: 'XYZ 4'
}]
const flats = data => data.map(e => {
if (e.children) {
return [...flats(e.children), e]
} else {
console.log("E", e);
return e
}
})
console.log(flats(arr));
With your current code, you will sometimes return an array from the callback:
return [...flats(e.children),e]
and you'll sometimes return a plain object:
else {console.log("E",e);return e }
so the result will be a mix of arrays and plain objects, instead of an array of only objects.
Use flatMap instead, which will implement the flattening you're looking for for you. You'll also need to remove the .children property from items with children before returning them.
const arr=[{id:1,name:"XYZ 1"},{id:2,name:"XYZ 2",children:[{id:5,name:"XYZ 5"},{id:6,name:"XYZ 6",age:29,children:[{id:7,name:"XYZ 7"},{id:8,name:"XYZ 8"}]}]},{id:3,name:"XYZ 3"},{id:4,name:"XYZ 4"}];
const flats = data => data.flatMap(e=>{
const { children, ...objWithoutChildren } = e;
return children
? [...flats(children), objWithoutChildren]
: e;
});
console.log(flats(arr));
Here is an iterative solution using object-scan
// const objectScan = require('object-scan');
const arr = [{ id: 1, name: 'XYZ 1' }, { id: 2, name: 'XYZ 2', children: [{ id: 5, name: 'XYZ 5' }, { id: 6, name: 'XYZ 6', age: 29, children: [{ id: 7, name: 'XYZ 7' }, { id: 8, name: 'XYZ 8' }] }] }, { id: 3, name: 'XYZ 3' }, { id: 4, name: 'XYZ 4' }];
const flatten = objectScan(['**(^children$).id'], {
useArraySelector: false,
rtn: 'parent',
reverse: false,
afterFn: (state) => {
state.result = state.result.map(({ id, name }) => ({ id, name }));
}
});
console.log(flatten(arr));
// => [ { id: 1, name: 'XYZ 1' }, { id: 2, name: 'XYZ 2' }, { id: 5, name: 'XYZ 5' }, { id: 6, name: 'XYZ 6' }, { id: 7, name: 'XYZ 7' }, { id: 8, name: 'XYZ 8' }, { id: 3, name: 'XYZ 3' }, { id: 4, name: 'XYZ 4' } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#15.0.0"></script>
Disclaimer: I'm the author of object-scan
Need a flat array of deeply nested array of objects
In that case, I'd prefer recursive to get the flexible and high deep level array.
const arr = [{id:1,name:'XYZ 1'},{id:2,name:'XYZ 2',children:[{id:5,name:'XYZ 5'},{id:6,name:'XYZ 6',age:29,children:[{id:7,name:'XYZ 7'},{id:8,name:'XYZ 8'}]}]},{id:3,name:'XYZ 3'},{id:4,name:'XYZ 4'}];
const recursive = (array) => array.reduce((acc, {children = [], ...others}) => {
acc.push(others);
if(children.length > 0) // Base recurisve here.
acc = acc.concat(recursive(children));
return acc;
}, []);
console.log(recursive(arr));
==> As a result, the content structure will make sure like this
Supposed i have an array of personnel which has many offices.
var personnel = [
{
id: 1,
name: 'John Doe',
offices: [
{
id: 1,
name: 'Office 1',
isAssigned: false
},
{
id: 2,
name: 'Office 2',
isAssigned: true
},
{
id: 3,
name: 'Office 3',
isAssigned: false
}
]
},{
id: 2,
name: 'Jerry Smith',
offices: [
{
id: 2,
name: 'Office 2',
isAssigned: true,
},
{
id: 3,
name: 'Office 3',
isAssigned: false,
}
]
}
]
How can i get the count of the personnel which is assigned in a particular office? Or how many is assigned in office 2? Result: 2
Here a version that destructures the arguments:
personnel.filter( ({offices}) =>
offices.some( ({name, isAssigned}) => isAssigned && name === office)
).length
function countInOffice(personnel, office) {
return personnel.filter( ({offices}) =>
offices.some( ({name, isAssigned}) => isAssigned && name === office)
).length
}
var personnel = [
{
id: 1,
name: 'John Doe',
offices: [
{
id: 1,
name: 'Office 1',
isAssigned: false
},
{
id: 2,
name: 'Office 2',
isAssigned: true
},
{
id: 3,
name: 'Office 3',
isAssigned: false
}
]
},{
id: 2,
name: 'Jerry Smith',
offices: [
{
id: 2,
name: 'Office 2',
isAssigned: true,
},
{
id: 3,
name: 'Office 3',
isAssigned: false,
}
]
}
];
var count = countInOffice(personnel, 'Office 2');
console.log(count);
Using Array.prototype.filter() and Array.prototype.some():
const office2Personnel = personnel.filter(p =>
p.offices.some(o => o.name === 'Office 2' && o.isAssigned));
console.log(office2Personnel.length);
const personnel = [
{
id: 1,
name: 'John Doe',
offices: [
{
id: 1,
name: 'Office 1',
isAssigned: false
},
{
id: 2,
name: 'Office 2',
isAssigned: true
},
{
id: 3,
name: 'Office 3',
isAssigned: false
}
]
},{
id: 2,
name: 'Jerry Smith',
offices: [
{
id: 2,
name: 'Office 2',
isAssigned: true,
},
{
id: 3,
name: 'Office 3',
isAssigned: false,
}
]
}
];
const office2Personnel = personnel.filter(p => p.offices.some(o => o.name === 'Office 2' && o.isAssigned));
console.log(office2Personnel.length);
To get total counts(statistics for all offices: "how many employees are assigned to each office") use the following approach:
var personnel = [{id: 1,name: 'John Doe',offices: [{id: 1,name: 'Office 1',isAssigned: false},{id: 2,name: 'Office 2',isAssigned: true},{id: 3,name: 'Office 3',isAssigned: false}]},{id: 2,name: 'Jerry Smith',offices: [{id: 2,name: 'Office 2',isAssigned: true,},{id: 3,name: 'Office 3',isAssigned: false,}]}
],
counts = personnel.reduce(function (r, o) {
o.offices.forEach(function (i) {
(r[i.name])? ++r[i.name] : r[i.name] = 1;
});
return r;
}, {});
console.log(counts);
Iterating over the assigned offices and adding the count to an object
This will get a count for each office
var personnel = [{
id: 1,
name: 'John Doe',
offices: [{
id: 1,
name: 'Office 1',
isAssigned: false
},
{
id: 2,
name: 'Office 2',
isAssigned: true
},
{
id: 3,
name: 'Office 3',
isAssigned: false
}
]
}, {
id: 2,
name: 'Jerry Smith',
offices: [{
id: 2,
name: 'Office 2',
isAssigned: true,
},
{
id: 3,
name: 'Office 3',
isAssigned: false,
}
]
}];
let offices = {};
personnel.forEach(p=>{
p.offices.filter(o=>o.isAssigned).forEach(o=>{
offices[o.name] = offices[o.name] || 0;
offices[o.name]++;
});
});
console.log(offices);
let officeToCheck = "Office 2";
console.log(offices[officeToCheck]);
If you need to directly get the count for a specific office
var personnel = [{
id: 1,
name: 'John Doe',
offices: [{
id: 1,
name: 'Office 1',
isAssigned: false
},
{
id: 2,
name: 'Office 2',
isAssigned: true
},
{
id: 3,
name: 'Office 3',
isAssigned: false
}
]
}, {
id: 2,
name: 'Jerry Smith',
offices: [{
id: 2,
name: 'Office 2',
isAssigned: true,
},
{
id: 3,
name: 'Office 3',
isAssigned: false,
}
]
}];
let count = personnel.filter(p=>p.offices.some(o=>o.name==='Office 2'&&o.isAssigned)).length;
console.log(count);
You could use two nested Array#reduce and check for the given name.
function getCount(name) {
return personnel.reduce(function (r, a) {
return a.offices.reduce(function (s, b) {
return s + (b.name === name);
}, r);
}, 0);
}
var personnel = [{ id: 1, name: 'John Doe', offices: [{ id: 1, name: 'Office 1', isAssigned: false }, { id: 2, name: 'Office 2', isAssigned: true }, { id: 3, name: 'Office 3', isAssigned: false }] }, { id: 2, name: 'Jerry Smith', offices: [{ id: 2, name: 'Office 2', isAssigned: true, }, { id: 3, name: 'Office 3', isAssigned: false, }] }],
result = getCount('Office 2');
console.log(result);
If you have only one single name in one office, you could use Array#some and exit if one name is found.
function getCount(name) {
return personnel.reduce(function (r, a) {
return r + a.offices.some(function (b) {
return b.name === name;
});
}, 0);
}
var personnel = [{ id: 1, name: 'John Doe', offices: [{ id: 1, name: 'Office 1', isAssigned: false }, { id: 2, name: 'Office 2', isAssigned: true }, { id: 3, name: 'Office 3', isAssigned: false }] }, { id: 2, name: 'Jerry Smith', offices: [{ id: 2, name: 'Office 2', isAssigned: true, }, { id: 3, name: 'Office 3', isAssigned: false, }] }],
result = getCount('Office 2');
console.log(result);
ES6
function getCount(name) {
return personnel.reduce((r, a) => r + a.offices.some(b => b.name === name), 0);
}
var personnel = [{ id: 1, name: 'John Doe', offices: [{ id: 1, name: 'Office 1', isAssigned: false }, { id: 2, name: 'Office 2', isAssigned: true }, { id: 3, name: 'Office 3', isAssigned: false }] }, { id: 2, name: 'Jerry Smith', offices: [{ id: 2, name: 'Office 2', isAssigned: true, }, { id: 3, name: 'Office 3', isAssigned: false, }] }],
result = getCount('Office 2');
console.log(result);
Currently I have my scope set out in the following format.
$scope.test = [
{
group: 'Group1',
groupID:0,
items: [
{id: 0, description: 'Item 1', error: true, groupID:0},
{id: 1, description: 'Item 2', error: true, groupID:0},
{id: 2, description: 'Item 3', error: true, groupID:0},
{id: 3, description: 'Item 4', error: true, groupID:0}
]
},
{
group: 'Group2',
groupID:1,
items: [
{id: 0, description: 'Item 1', error: true, groupID:1},
{id: 1, description: 'Item 2', error: true, groupID:1},
{id: 2, description: 'Item 3', error: true, groupID:1},
{id: 3, description: 'Item 4', error: true, groupID:1}
]
},
{
group: 'Group3',
groupID:2,
items: [
{id: 0, description: 'Item 1', error: true, groupID:2},
{id: 1, description: 'Item 2', error: true, groupID:2},
{id: 2, description: 'Item 3', error: true, groupID:2},
{id: 3, description: 'Item 4', error: true, groupID:2}
]
}
What I want is too loop through all the item objects in each parent object inside the controller and get a total of all items which the value 'error' is equal to true and alert this total.
Currently inside the controller I can only loop through the 1st level objects in the array, so can only access 'group' and 'groupID'.
function myCtrl($scope) {
$scope.test = [{
group: 'Group1',
groupID: 0,
items: [{
id: 0,
description: 'Item 1',
error: true,
groupID: 0
}, {
id: 1,
description: 'Item 2',
error: true,
groupID: 0
}, {
id: 2,
description: 'Item 3',
error: true,
groupID: 0
}, {
id: 3,
description: 'Item 4',
error: true,
groupID: 0
}]
}, {
group: 'Group2',
groupID: 1,
items: [{
id: 0,
description: 'Item 1',
error: true,
groupID: 1
}, {
id: 1,
description: 'Item 2',
error: true,
groupID: 1
}, {
id: 2,
description: 'Item 3',
error: true,
groupID: 1
}, {
id: 3,
description: 'Item 4',
error: true,
groupID: 1
}]
}, {
group: 'Group3',
groupID: 2,
items: [{
id: 0,
description: 'Item 1',
error: true,
groupID: 2
}, {
id: 1,
description: 'Item 2',
error: true,
groupID: 2
}, {
id: 2,
description: 'Item 3',
error: true,
groupID: 2
}, {
id: 3,
description: 'Item 4',
error: false,
groupID: 2
}]
}];
$scope.errors = [];
function innerLoop(obj) {
return function (items) {
for (var i = 0; i < items.length; i++) {
if (items[i].error) {
$scope.errors.push(items[i].error);
}
}
}(obj.items);
}
function loop(obj) {
for (var i = 0; i < obj.length; i++) {
innerLoop(obj[i]);
}
return $scope.errors;
}
loop($scope.test);
alert($scope.errors.length);
}
You can use nested ng-repeat in view
see example
function myCtrl($scope){
$scope.multi = [1,2,[3,4,5]] ;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="myCtrl">
<div ng-repeat='i in multi'>
<span ng-hide="i.length>1">{{i}}</span>
<div ng-repeat='i2 in i'>
{{i2}}
</div>
</div>