I would like to know what is the best way to update the member of the multilevel object collection in JavaScript
Here is the simplified version of my collection:
this.Steps = [
{ id: 1, text: "test", childSteps:
[
{ id: 2, text: "test"},
{ id: 3, text: "test"},
{ id: 4, text: "test", childSteps:
[
{ id: 10, text: "test"},
{ id: 11, text: "test"}
]}
},
{ id: 5, text: "test"},
{ id: 6, text: "test"},
{ id: 7, text: "test"},
{ id: 8, text: "test"},
{ id: 9, text: "test"}
]
}
];
The ideal would be to have a function to be called like:
updateObjectByID(11, 'string to be set');
This is easy to be done when we have only 1 level of objects. But when using recursion on multilevel collection its getting much harder.
I'm currently using a function that is parsing the whole collection and building string like :
this.Steps[0].childSteps[3].childSteps[1].text == "string to be set"
and then I do eval() on that string.
I'm sure there might be a much cleaner solution.
Using eval is making my class impossible to compress btw.
Any help would be greatly appreciated.
Thanks in advance
You can build a map of objects by id for direct access:
var map = {};
(function recurse(steps) {
for (var i=0; i<steps.length; i++) {
var step = steps[i];
map[ step.id ] = step;
if ("childSteps" in step)
recurse(step.childSteps);
}
})(this.Steps);
function updateObjectByID(id, string) {
map[id].text = string;
}
A comment on your code: You overcomplified a lot. When the condition if(obj.id == objId) is met, you have your obj reference already!!! Now there is absolutely no need to search for a path to it, build a string from that, and eval it. Just assign directly to its property!
function(steps, objId, value, whatParam, command) {
if (typeof whatParam == 'undefined')
whatParam = 'selected';
$.each(steps, function(index, obj){
if(obj.id == objId) {
// removed a lot of unecessary stuff
// not sure, looks like "value" was evaled sometimes as well
obj[whatParam] = value;
} // insert an "else" here if ids are unique
if (typeof obj.childSteps != 'undefined') {
this.setupObjectState(obj.childSteps, objId, value, whatParam, command);
}
});
}
Since you call updateObjectById() with an ID, the ID has to be unique. Why donĀ“t you change your structure like this then:
this.Steps = [
{ id: 1, text: "test" },
{ id: 2, parent: 1, text: "test" },
{ id: 3, parent: 1, text: "test" },
{ id: 4, parent: 1, text: "test" },
{ id: 10, parent: 4, text: "test" },
{ id: 11, parent: 4, text: "test" },
{ id: 5, parent: 1, text: "test"},
{ id: 6, parent: 1, text: "test"},
{ id: 7, parent: 1, text: "test"},
{ id: 8, parent: 1, text: "test"},
{ id: 9, parent: 1, text: "test"}
]
This way you can easily update your entries like this:
function updateObjectByID(id, text) {
for(var i = 0; i < this.Steps.length; i++) {
if(this.Steps[i].id === id) {
this.Steps[i].text = text;
break;
}
}
}
With the extra attribute parent you can still easily get all the children of an element like this:
function getChildrenByID(id) {
var array = [];
for(var i = 0; i < this.Steps.length; i++) {
if(this.Steps[i].parent === id) {
array.push(this.Steps[i]);
}
}
return array;
}
You must recursively descend your tree structure searching for the object with the target "id" and replace its text. For example:
function updateObjectByID(obj, id, text) {
if (!obj) return;
if (obj.id === id) {
obj.text = text;
} else if ((typeof(obj)==='object') && (obj.constructor===Array)) {
for (var i=0; i<obj.length; i++) {
updateObjectByID(obj[i], id, text);
}
} else if (obj.childSteps) {
updateObjectByID(obj.childSteps, id, text);
}
}
updateObjectByID(this.Steps, 11, 'String to be set');
However, this solution can be optimized by tree pruning.
function updateObjectByID(id, text) {
(function setText(steps) {
for(var i = 0; i < steps.length; i++){
var step = steps[i];
if(step.id === id){
step.text = text;
}
if ("childSteps" in step){
setText(step.childSteps);
}
}
})(this.Steps);
}
Here is demo. http://jsfiddle.net/qDWX8/
Related
Thanks i fixed some sentence by advice. my code is like that,
i wanna find object with id. but if not, I want to return 'null'
function ha7(arr, id) { // i wanna find object with id
let result = [];
for(let i = 0 ; i < arr.length ; i++) {
if(arr[i].id === id) {
return arr[i] // found id, then return included object
}
else if(Array.isArray(arr[i].children)){ // but , its array
// let ar = ha7(arr[i].children, id)
result.push(...arr[i].children) // i put 'arr[i].children' to variables
}
}
if (result.id === id) {
return result // find object with id in inner
} else {
return ha7(result, id) // cant find. then go ahead!
}
return null // all of none exist id is return null
}
it is testing array.
let input = [
{
id: 1,
name: 'johnny',
},
{
id: 2,
name: 'ingi',
children: [
{
id: 3,
name: 'johnson',
},
{
id: 5,
name: 'steve',
children: [
{
id: 6,
name: 'lisa',
},
],
},
{
id: 11,
},
],
},
{
id: '13',
},
];
output = ha7(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = ha7(input, 99);
console.log(output); // --> null
I tried a lot of trial, like that. i wanna know.
how can i treat maximum call stack ?
and i wanna return 'null' value.
function ha7(arr, id) { // i wanna find object with id
let result = [];
for(let i = 0 ; i < arr.length ; i++) {
if(arr[i].id === id) {
return arr[i] // found id, then return included object
}
else if(Array.isArray(arr[i].children)){ // but , its array
// let ar = ha7(arr[i].children, id)
result.push(...arr[i].children) // i put 'arr[i].children' to variables
}
}
if (result.id === id) {
return result // find object with id in inner
} else {
return ha7(result, id) // cant find. then go ahead!
}
return null // all of none exist id is return null
}
let input = [
{
id: 1,
name: 'johnny',
},
{
id: 2,
name: 'ingi',
children: [
{
id: 3,
name: 'johnson',
},
{
id: 5,
name: 'steve',
children: [
{
id: 6,
name: 'lisa',
},
],
},
{
id: 11,
},
],
},
{
id: '13',
},
];
output = ha7(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = ha7(input, 99);
console.log(output); // --> null
This code is the problem:
if (result.id === id) {
return result // find object with id in inner
} else {
return ha7(result, id) // cant find. then go ahead!
}
Two lines above this you initialize result as an array. Then in this conditional test you treat the array result as if it were an object. So, since result.id does not equal id, the else condition recurses for ever and ever.
I've taken a different, more functional approach to the task.
filter the array on the id
If there is a length then at least one was found
Return the first one
Next filter out all the objects with children
Then create an array (with .map() that only includes the children
This will create an array of arrays, so must flatten it
If there are no children, then id was not found
Return null
Recurse the children
let input=[{id:1,name:"johnny"},{id:2,name:"ingi",children:[{id:3,name:"johnson"},{id:5,name:"steve",children:[{id:6,name:"lisa"}]},{id:11}]},{id:"13"}];
function ha7(arr, id) {
let found = arr.filter(o => o.id === id);
if (found.length) return found[0]; // return first match
let children = arr.filter(o=>!!o.children).map(c=>c.children).flat();
if(!children.length) return null;
return ha7(children, id);
}
output = ha7(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = ha7(input, 99);
console.log(output); // --> null
var array = [
{id: 1, text: "one"},
{id: 2, text: "two"},
{id: 3, text: "three"},
{id: 4, text: "four"},
{id: 5, text: "five"}
];
var name = array.find(function(item){
return item.id == $localStorage.id;
});
returns me {id: 2, text: "two"}
expected two only string nothing else should print
You can first find the object and then get the text property by checking if the find() operation actually returned a object or it is undefined.
var array = [{
id: 1,
text: "one"
},
{
id: 2,
text: "two"
},
{
id: 3,
text: "three"
},
{
id: 4,
text: "four"
},
{
id: 5,
text: "five"
}
];
var findObj = array.find(function(item) {
return item.id == 2;
});
//check if findObj is defined or not
var name = findObj? findObj.text: null;
console.log(name);
You can also use destructuring to get that text value directly from find() if you are sure the object exist for that localStorage value. Otherwise, it will raise error.
var array = [{
id: 1,
text: "one"
},
{
id: 2,
text: "two"
},
{
id: 3,
text: "three"
},
{
id: 4,
text: "four"
},
{
id: 5,
text: "five"
}
];
var {text} = array.find(function(item) {
return item.id == 2;
});
console.log(text);
What you did returns the element at the position where item.id == $localStorage.id. If you want to get the text, then after the element is returned in var name, you just do name.text because array.find() returns the element that passed the logical operation.
You can use filter and map. This way you can customize your filtered result the way you want.
var array = [
{id: 1, text: "one"},
{id: 2, text: "two"},
{id: 3, text: "three"},
{id: 4, text: "four"},
{id: 5, text: "five"}
];
var name = array.filter(a=> a.id === 2).map(b=> {return b.text});
console.log(name)
You should retrieve property text of found object:
var object = array.find(function(item){
return item.id === $localStorage.id;
});
var name = object.text;
If you like to use es6 syntax, you can write like this.
You did everything good except, you needed to get specific object value.
const array = [
{ id: 1, text: "one" },
{ id: 2, text: "two" },
{ id: 3, text: "three" },
{ id: 4, text: "four" },
{ id: 5, text: "five" }
];
// So here i use same find as you did.
let object = array.find(item => {
return item.id == $localStorage.id;
});
// And assigning text property of object to variable 'name'
// since object, can be undefined, using OR empty object,
// so no error will be thrown if so.
let { text: name } = object || {};
console.log(name);
from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find:
The find() method returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.
Try like this way to get text attribute value while using find by id
var array = [{
id: 1,
text: "one"
},
{
id: 2,
text: "two"
},
{
id: 3,
text: "three"
},
{
id: 4,
text: "four"
},
{
id: 5,
text: "five"
}
];
var name = array.find(function(item) {
return item.id == 2;
}).text;
console.log(name);
find will return the object that satisfy the condition
var object = array.find(function(item) {
return item.id == $localStorage.id;
});
var name = object? object.text: null;
console.log(name);
I got the following array:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
}
}
]
}
];
The objects directly in the array are the groups. The 1: is the first language, 2: is second etc. The id is stored in every language object (due to the database I'm using). The children array is built the same way as the 'arr' array.
Example of multiple children:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
},
children: [
{
1: {
id: 3,
title: 'test3',
},
children: []
}
]
}
]
}
];
Now I need to delete items from this array. You can have unlimited children (I mean, children can have children who can have children etc.). I have a function which needs an ID parameter sent. My idea is to get the right object where the ID of language 1 is the id parameter. I got this:
function deleteFromArray(id)
{
var recursiveFunction = function (array)
{
for (var i = 0; i < array.length; i++)
{
var item = array[i];
if (item && Number(item[1].ID) === id)
{
delete item;
}
else if (item && Number(item[1].ID) !== id)
{
recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
}
However, I'm deleting the local variable item except for the item in the array. I don't know how I would fix this problem. I've been looking all over the internet but haven't found anything.
This proposal features a function for recursive call and Array.prototype.some() for the iteration and short circuit if the id is found. Then the array is with Array.prototype.splice() spliced.
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function splice(array, id) {
return array.some(function (a, i) {
if (a['1'].id === id) {
array.splice(i, 1)
return true;
}
if (Array.isArray(a.children)) {
return splice(a.children, id);
}
});
}
splice(arr, 2);
document.write('<pre>' + JSON.stringify(arr, 0, 4) + '</pre>');
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function deleteFromArray(id) {
function recursiveFunction(arr) {
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
if (item && Number(item[1].id) === id) {
arr.splice(i, 1);
} else if (item && Number(item[1].id) !== id) {
item.children && recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
};
deleteFromArray(2);
document.getElementById("output").innerHTML = JSON.stringify(arr, 0, 4);
<pre id="output"></pre>
jsfiddle: https://jsfiddle.net/x7mv5h4j/2/
deleteFromArray(2) will make children empty and deleteFromArray(1) will make arr empty itself.
I have an array of objects:
[{
id: 1,
name: 'kitten'
}, {
id: 2,
name: 'kitten'
},{
id: 3,
name: 'cat
}]
How do I remove the second kitten? Sorting into an array of names doesn't work, because I can't know if I am deleting id 1 or or id 2. So, I'm not quite sure how to do this.
You can use an additional hash-map to store names found so far. When you process a next object if it's name is already in the hash-map it is a duplicate and you can remove it.
var duplicates = {};
for (var i = 0; i < array.length) {
var obj = array[i];
if (! duplicates[obj.name]) {
duplicates[obj.name] = 1;
i++;
} else {
array.splice(i, 1);
}
}
there is the lodash library.
You could use the uniq
var array = [{
id: 1,
name: 'kitten'
}, {
id: 2,
name: 'kitten'
},{
id: 3,
name: 'cat'
}];
var asd = _.uniq(array,'name');
console.log(asd);
Gives an output:
[ { id: 1, name: 'kitten' }, { id: 3, name: 'cat' } ]
as it written in the documentation "only the first occurence of each element is kept".
var arr =[{
id: 1,
name: 'kitten'
}, {
id: 2,
name: 'kitten'
},{
id: 3,
name: 'cat'
}];
var results = [];
var idsSeen = {}, idSeenValue = {};
for (var i = 0, len = arr.length, name; i < len; ++i) {
name = arr[i].name;
if (idsSeen[name] !== idSeenValue) {
results.push(arr[i]);
idsSeen[name] = idSeenValue;
}
}
console.log(results);
I want to dinamically replace object inside parent object.
Object:
var obj = {
address: { id: 2, type: { id: 1, label: 'Test1' } },
id: 1,
name: 'test'
}
Selector:
var selector = "address.type";
New inner object:
var type = { id:2, label: 'Test2' }
Now, what is the best way to replace "obj.address.type" with "type"?
My attemtp
tmp = selector.split('.'), obj1 = obj;
for(prop in tmp) {
obj1 = obj1[tmp[prop]];
}
Selectors are given as strings, I presume.
function set(obj, selector, value) {
selector = selector.split(".");
selector.slice(0, -1).reduce(function(obj, s) {
return obj[s]
}, obj)[selector.pop()] = value;
}
set(obj, 'address.type.id', 'foobar');
If selectors are real pointers to objects, you can also replace them directly:
function replaceObject(obj, newObj) {
Object.keys(obj).forEach(function(k) {
delete obj[k];
});
Object.keys(newObj).forEach(function(k) {
obj[k] = newObj[k];
});
}
replaceObject(obj.address.type, {'foo':'bar'});
James Donnelly answered correctly but here is some quick code doing what I think you wanted to achieve... I apologise if I have missed your goal. I don't know what you want to do with the selector variable.
var objArray = [{
address: { id: 2, type: { id: 1, label: 'Test1' } },
id: 1,
name: 'test'
},{
address: { id: 4, type: { id: 3, label: 'Test2' } },
id: 1,
name: 'test'
},{
address: { id: 6, type: { id: 5, label: 'Test3' } },
id: 1,
name: 'test'
}]
var lastId=6;for (var i=0,l=objArray.length;i<l; i++){objArray[i].type={id:++lastId, label:'Test'+lastId}};
JSON.stringify(objArray);