Updating an object in array - javascript

I am trying to update a object in an array that is two-way binded to a directive but its not working at all.
I am doing the following to update it:
$filter('filter')
($scope.offers, {id: offer_id})[0].status = "active";
I get no console errors but if I console.log $scope.offers after the filter code is executed, the status is not updated.
What I am trying to accomplish is to update an object in $scope.offers array with a new value. Every object in the array has a id attribute that is unique which I am trying to select it by.
Hope this information is sufficient. Thanks!

Here you are updating the status of outcome of the filter expression.
That means $filter('filter')($scope.offers, {id: offer_id}) itself returns an array. And you are updating that array, not the actual array.
I believe, $scope.offers is two way binded and you are not updating it.
I hope this makes some sense. And if you can explain what exactly you want. Then I can put some more light.
EDIT
So, i think once you get the new status value and the id on which you need to update. You can simply iterate the array using simple for loop and check for id and if it matches update the status. Something like this.
for(var i=0; i<array.length; i++) {
if (array[i].id === requiredId) {
array[i].status = newStatus;
break;
}
}
Hope, this helps.

Using native JS methods the updateArrObj function below utilizes some to search for an object that matches the specified id and then using a more generic update approach will map an update object onto the original object. The main benefit of using some is twofold: you get a boolean value to indicate if an update occurred and also it will stop searching the array after it makes an update.
function updateArrObj(arr, id, update) {
return arr.some(function findObj(obj) {
if (obj.id === id) {
Object.keys(update).forEach(function updateObj(key) {
obj[key] = update[key];
});
return true;
}
});
}
var arrObj = [
{id: 1, status: 'inactive'},
{id: 2, status: 'inactive'},
{id: 3, status: 'inactive'},
{id: 4, status: 'inactive'},
{id: 5, status: 'inactive'}
];
var updated = updateArrObj(arrObj, 3, {status: 'active'});
document.write(
'<h3>Updated: ' + updated + '</h3>' +
'<pre>' + JSON.stringify(arrObj, null, 4) + '</pre>'
);
function updateArrObj(arr, id, update) {
return arr.some(function findObj(obj) {
if (obj.id === id) {
Object.keys(update).forEach(function updateObj(key) {
obj[key] = update[key];
});
return true;
}
});
}

Related

Find in JSON array by manually entered ID

in my angularjs app I get JSON object from API.
I have an input field, where I can input ID manually, and if an item with this ID exists, I need to change his property.
I also store this ID to sessionStorage, so when user refresh page, he get value like before refresh.
Here is json from API where I need to change ON THE FLY property SHOW to true if exist
$scope.tableParams.data = [
{id: 1015, type: "ssss", show: false},
{id: 1016, type: "ssss", show: false},
{id: 1017, type: "ssss", show: false},
{id: 1018, type: "ssss", show: false}
]
Function for getting input value and store to session storage, and also change SHOW property
$scope.getIndex = function (indexOfRow) { //indexOfRow is passed data from input field
//here I want to change show to true for passed id
sessionStorage.setItem("indexOfOpenedRow", JSON.stringify(indexOfRow));
}
I try answer from here but not working for me
I also try this, but allways get undefined
function findId(array, id) {
var i, found, obj;
for (i = 0; i < array.length; ++i) {
obj = array[i];
if (obj.id == id) {
console.log(obj);
return obj;
}
}
return undefined; // <= You might consider null or undefined here
}
$scope.getIndex = function (indexOfRow) { //indexOfRow is passed data from input field
//here I want to change show to true for passed id
angular.forEach($scope.tableParams.data, function(key, value){
var result = findId(key.id, indexOfRow);
console.log(result);
});
sessionStorage.setItem("indexOfOpenedRow", JSON.stringify(indexOfRow));
}
Filter is the function you are searching for. As i understood your question by reading your code you want to compare an seperate ID with the ID in the JSON? If I am wrong commend here and I will edit the answer:
function findIDAndSetShowingTrue(array, id) {
return array.filter(curr=> curr.id === id).map(curr => ({...curr, show: true}));
}
The filter function iterates over every child inside the array and gives you the specific value (array.filter).
Then you can use this child and compare it like an if statement with some other values (current).
In the end you compare current.id and your input id (current.id === id) and return the current object if it's true.

AngularJS: Merge object by ID, i.e. replace old entry when IDs are identical

I am using Ionic with AngularJS and I am using a localForage database and AJAX via $http. My app has a news stream that contains data like this:
{
"feed":[
{
"id":"3",
"title":"Ein Hund",
"comments:"1"
},
{
"id":"2",
"title":"Eine Katze",
"comments":"2"
}
],
"ts":"20150907171943"
}
ts stands for Timestamp. My app saves the feed locally via localForage.
When the app starts it first loads the locally saved items:
$localForage.getItem("feed").then(function(val) { vm.feed = val; })
Then, it loads the new or updated items (ts < current timestamp) and merges both the old and new data:
angular.extend(vm.feed, response.data.feed);
Updated items look like this:
{
"feed":[
{
"id":"2",
"title":"Eine Katze",
"comments":"4"
}
],
"ts":"20150907171944"
}
That is, the comments count on feed item 2 has changed from 2 to 4. When I merge the old and new data, vm.feed has two items with id = 2.
Does angularjs has a built-in "merge by id" function, i. e. copy from source to destination (if it is a new element), or otherwise replace the old element? In case angularjs does not have such a function, what's the best way to implement this?
Thanks in advance!
angular.merge(vm.feed, response.data.feed);
// EDIT
Probably, it will not merge correctly, so you have to update all properties manually. Update ts property and then find your object with id and replace it.
There is no builtin, I usually write my own merge function:
(function(){
function itemsToArray(items) {
var result = [];
if (items) {
// items can be a Map, so don't use angular.forEach here
items.forEach(function(item) {
result.push(item);
});
}
return result;
}
function idOf(obj) {
return obj.id;
}
function defaultMerge(newItem, oldItem) {
return angular.merge(oldItem, newItem);
}
function mergeById(oldItems, newItems, idSelector, mergeItem) {
if (mergeItem === undefined) mergeItem = defaultMerge;
if (idSelector === undefined) idSelector = idOf;
// Map retains insertion order
var mapping = new Map();
angular.forEach(oldItems, function(oldItem) {
var key = idSelector(oldItem);
mapping.set(key, oldItem);
});
angular.forEach(newItems, function(newItem) {
var key = idSelector(newItem);
if (mapping.has(key)) {
var oldItem = mapping.get(key);
mapping.set(key, mergeItem(newItem, oldItem));
} else {
// new items are simply added, will be at
// the end of the result list, in order
mapping.set(key, newItem);
}
});
return itemsToArray(mapping);
}
var olds = [
{ id: 1, name: 'old1' },
{ id: 2, name: 'old2' }
];
var news = [
{ id: 3, name: 'new3' },
{ id: 2, name: 'new2' }
];
var merged = mergeById(olds, news);
console.log(merged);
/* Prints
[
{ id: 1, name: 'old1' },
{ id: 2, name: 'new2' },
{ id: 3, name: 'new3' }
];
*/
})();
This builds a Map from the old items by id, merges in the new items, and converts the map back to list. Fortunately the Map object will iterate on the entries in insertion order, according to the specification. You can provide your idSelector and mergeItem functions.
Thanks hege_hegedus. Based on your code, I've written my own and tried to use less loops to speed things up a bit:
function updateCollection(localCollection, fetchedCollection) {
angular.forEach(fetchedCollection, function(item) {
var append = true;
for (var i = 0; i < localCollection.length; i++) {
if (localCollection[i].id == item.id) {
// Replace item
localCollection[i] = item;
append = false;
break;
} else if (localCollection[i].id > item.id) {
// Add new element at the right position, if IDs are descending check for "< item.id" instead
localCollection.splice(i, 0, item);
append = false;
break;
}
}
if (append) {
// Add new element with a higher ID at the end
localCollection.push(item);
// When IDs are descending use .unshift(item) instead
}
});
}
There is still room for improvements, i. e. the iteration through all the objects should use binary search since all items are sorted by id.

Toggling through values of a Json object using Jquery

I have a list of tasks which can have 4 possible states taken from a json object.
"Not Done", "Done", "Doing", "Later"
This states are stored in an object which is basically loaded via json.
var states = { status: ['doing','done', 'later' ] };
Tasks are loaded from another json object.
var tasks = [
{id: 1, text: 'Do something.', status: 'doing'},
{id: 2, text: 'Undo that thing.', status: 'done'},
{id: 3, text: 'Redo it again.', status: 'started'},
{id: 4, text: 'Responsive', status:'later'}
];
The html would be something like this.
<ul>
<li>Do something - <span class="status">Doing</span> </li>
<li>Undo that thing - <span class="status">Done</span> </li>
<li>Redo it again. - <span class="status">Started</span> </li>
</ul>
Each time user clicks on the link the status should toggle the value from the states object
and loop through them. For example, when use click on a task with status Done, its should become later and when they click again it should become Not Done. This should keep looping each time the user clicks.
What is the best way to do this. I felt if else won't be the right way to do as if the values of states increase or reduce that code will have to be revisited again.
Edit:
I didn't understand 100% of your question. But i think i do now. You want the code to work even on all cases at all time. I've made code a little safer now:
if a state contain a faulty entered text (like 'dOne') it will still compare.
if a task has a faulty (=non-existent) state, it will just reset itself to the first state available.
Here's an explosion of code, i'm a little tired so you might need to refactor some parts. Also thought it would be easier for you with the documentation included.
If you have more things to say, leave it in the comments and i will try again....!
Working jsfiddle: http://jsfiddle.net/kychan/62sUX/2/
//
// Retrieve the data with $.get()/AJAX or with an inline
// echo through PHP/ASP.
//
// Get states. Make the code extra fuzzy.
var states = { status: ['doing','dOne', 'Later' ] };
// Get the tasks.
// In this example we do it more quickly.
var tasks = [
{id: 1, text: 'Do something.', status: 'doing'},
{id: 2, text: 'Undo that thing.', status: 'done'},
{id: 3, text: 'Redo it again.', status: 'started'},
{id: 4, text: 'Responsive', status: 'later'}
];
// prepare the retrieved JSON data for fuzzy input.
states.status.forEach(function(e,i,a) {
states.status[i]=e.toUpperCase();
});
tasks.forEach(function(e,i,a) {
tasks[i].status=e.status.toUpperCase();
});
// create the li's.
for (var i in tasks) {
var item = '<li>'
+ '<a href="#" id="{id}">{text} - '
+ '<span class="status">{status}</span>'
+ '</a>'
+ '</li>'
;
item = item.replace(/{(\w+)}/g, function (m, n) {
return (typeof (tasks[i][n]) !== 'undefined') ? tasks[i][n] : '';
});
$('ul').append(item);
}
// Override the states with the button; will make it harder
// for the code and test it for future proofness.
$('.replace').click(function() {
// we will keep 'done' for testing.
states = {
status:['CONCEPT', 'ELABORATION', 'CONSTRUCTION', 'TESTING', 'PRODUCTION', 'DONE']
};
// remove the replace link, because after press it's useless.
$('.replace').detach();
});
//
// actual code.
//
// create listeners on the a tags of tag ul.
$('ul a').click(function (e) {
// fetch status DOM object and its text before.
var status = $(this).children('.status');
var text = status.html();
// iterate through states array.
for (var i in states.status) {
// if the task matches your text, then update it to the next.
if (text==states.status[i]) {
// get next status.
if ((i++)>=states.status.length-1) i=0;
// update. Don't forget to push the update to the database.
status.html(states.status[i]);
return;
}
}
// state not found. reset it to first. Don't forget to push the update to the DB.
status.html(states.status[0]);
});
How about something like this?
$('a').click(function(){
var id = parseInt($(this).attr('href').substring(1));
var task = tasks.filter(function(t){t.id == id});
if (task.length) {
var status = task[0].status;
var indexStatus = states.status.indexOf(status);
var nextStatus = null;
if (indexStatus === states.status.length-1) {
nextStatus = states.status[0];
} else if (indexStatus !== -1) {
nextStatus = states.status[indexStatus+1];
}
if (nextStatus !== null) {
task[0].status = nextStatus;
$(this).children('.status').text(nextStatus);
}
}
});
The idea is to look for the current status in the list, and just reset the index to 0 if it is the last one, otherwise increment it.
I have updated both JSON and HTML, as you did not precise if you want them in sync. The code can be made simpler if you don't need to sync data and view. Also I made all necessary bound checks, I don't know how robust you want the code to be and how much you trust the json data.

Get the lower integer id not already used in Javascript

I have a list of JS objects defined by an integer ID.
objects = [{
id: 0,
type: 'null'
}, {
id: 1,
type: 'foo'
}, {
id: 2,
type: 'bar'
}];
I implemented a function to remove an element from my list :
removeObject = function(o){
objects.splice(objects.indexOf(o), 1);
}
My problem is that I need to create a function to add a new item in my list with a id not already used (for example the lower positive integer not present in the list).
I tried to do something like that but it did not work when I remove the object 0 (for example).
addObject = function(type){
objects.push({
id: objects.length,
type: type
});
};
How can I do this ?
EDIT 1
According to your answers, I assume that the best solution in term of performance is to just use a topId which is always incremented when I add a new object in my list.
But that do not answer to my requierement. Actually I think that #X-Pippes response could be good.
Should I do someting like that :
objects = [{
id: 0,
type: 'null'
}, {
id: 1,
type: 'foo'
}, {
id: 2,
type: 'bar'
}];
// Init available ids list with the default value
availableIds = [objects.length];
removeObject = function(o){
// Remove the object from the list
objects.splice(objects.indexOf(o), 1);
// Add its id to the available ids list
availableIds.push(o.id);
}
addObject = function(type){
// Get lower id available
var newId = Math.min.apply(Math,availableIds);
// Push the new object with the id retrieved
objects.push({
id: newId,
type: type
});
// Remove used id from the available ids list
availableIds.splice(availableIds.indexOf(newId), 1);
// Add a default id if available list is empty
if(availableIds.length < 1) availableIds.push(objects.length);
};
if you remove for instance 0 and the next addObject is 0 you have to do something like:
keep a list [initial empty] with every ID removed. When you need to add a new one, pick the shorter, add and delete from list.
Also keep a var with the biggest ID added. If the previous list is empty, add +1 to the var and addObject with that id
Use the correct structures. A JavaScript object will do the job. It guarantees that you only get one item for key, you can look up and remove by key in probably O(1)ish. No point trying to re-implement it in a less efficient manner, which will be O(n) lookup.
var structure = {
objects : {},
topId : 0
}
structure.add = function(item) {
var id = this.topId ++;
structure.objects[id] = item;
}
structure.add("thing")
structure.add("other thing")
structure.add("another thing")
structure.objects
>>> Object {0: "thing", 1: "other thing", 2: "another thing"}
structure.objects[1]
>> "other thing"
Then the normal index operations to get/set/delete.
If you use that function then you have an invariant (guarantee) on your data structure that you won't use the same ID twice.
You need a function to find the first free number:
addObject = function(type){
objects.push({
id: firstOpenIndex(),
type: type
});
};
firstOpenIndex = function() {
for(var idx = 0; true; i++) {
var found = false;
for(var o in objects) {
if (objects[o].id == idx) {
found = true;
break;
}
}
if (!found) return idx;
}
}
In Javascript MaxInt is 9007199254740992. Why not just keep incrementing?
You can and probably should just use an array(s) like:
objects.type=['null','foo','bar'];
to add an object see:
How to append something to an array?
to find a value: var index = objects.type.indexOf('foo');
to find 1st empty field var index = objects.type.indexOf(''); which you can use to find the element for adding (if index is -1 use objects.type.length) if you "delete" an element by setting it to "" or... unless you have specific reason for keeping the "ID" static (in this case the array index), remove the element and only append new ones to the end
to remove an element see:
How do I remove a particular element from an array in JavaScript?
which will allow you to just push/append the next data.
if you need a new object array with empty fields to fill because you get new data to track:
object.newField=new Array(objects.type.length);
If you get to this point where your object contains multiple arrays, you will probably want to create functions for insert/add and delete/remove, so you don't do an operation on 1 and not the other.
Everything is already built in (read likely already pretty fast) and you don't need to reinvent constructors for your really cool object type.

Delete an element inside a JSON array by value with jQuery

Part of my json Array
var videos = $j.parseJSON('
[
{ "privacy":"public",
"id":"1169341693" },
{ "privacy":"private",
"id":"803641223" },
{ "privacy":"public",
"id":"1300612600" }, ......
When I console.log the element I'm getting
[Object, Object, Object, …]
0: Object
privacy: "public"
id: "1169341693"
1: Object
privacy: "private"
id: "803641223"
2: Object
privacy: "public"
id: "1300612600"
I also have a unique id I want to search for
var uniqueId = 803641223;
I want to find, in my videos array, the right id, and delete that whole array element. So In that case, I want my final videos array to contain only 2 object, instead of 3 :
var videos = $j.parseJSON('
[
{ "privacy":"public",
"id":"1169341693" },
{ "privacy":"public",
"id":"1300612600" }, ......
My problem is how to get in the array to do my splice. I prefer to do it with jQuery
Any help please?
You can use grep :
videos = $.grep(videos, function(e) { return e.id!='803641223' });
In vanilla JavaScript you could have used the similar filter function but it's not supported by IE8.
Please note that videos is a JavaScript array, it's not a JSON array, even if it was made by parsing a JSON string.
A non-jQuery solution that modifies the array in place:
var uniqueId = 803641223;
var videos = [
{ "privacy":"public",
"id":"1169341693" },
{ "privacy":"private",
"id":"803641223" },
{ "privacy":"public",
"id":"1300612600" }
];
function cleaner(arr, id) {
for (var i = 0; i < videos.length; i++) {
var cur = videos[i];
if (cur.id == uniqueId) {
arr.splice(i, 1);
break;
}
}
}
cleaner(videos, uniqueId);
http://jsfiddle.net/4JAww/1/
Note that this modifies the original array in place, such that the original videos array will have the items you want, and the one that matched the uniqueId will be gone (forever). So it depends on whether you want to be able to access the original array ever again, or are okay with modifying it.
It just loops through the elements of the array, compares the item's id property to the uniqueId value, and splices if they match. I use break; immediately after the splice because you seem to imply that the uniqueId can/should only appear once in the array since it's...unique.
Hello you can remove element with javascript splice function...
videos.items.splice(1, 3); // Removes three items starting with the 2nd,
It worker for me.
arrList = $.grep(arrList, function (e) {
if(e.add_task == addTask && e.worker_id == worker_id) {
return false;
} else {
return true;
}
});
It returns an array without that object.
Hope it helps.

Categories