Updating observable array in knockout - javascript

I have an observable array named administrators. I also have an object named adminToAdd. AdminToAdd contains a single item with the exact same properties as administrators. I'm able to push adminToAdd into administrators with no issue.
Unfortunately, I'm unable update an existing item in administrators. I'm receiving the item to update in adminToAdd. I've tried
vm.administrators.replace(vm.administrators.indexOf(vm.adminToAdd), vm.adminToAdd);
but although the structure is the same between them the index won't match. I've looked at several examples but none seem to work for my scenario. Any ideas?

The reason why your approach doesn't work is because when you use indexOf in trying to find an object rather than a value. When finding an object it uses the memory addresses to decide equality rather than the values of the object.
I love ko's utility function ko.utils.arrayFirst(array, predicate) for situations like these. This allows you to search for a specific item within an array and return it if found. You provide a predicate function that will return true in the event that the object is equal or false for anything else.
var existingItem = ko.utils.arrayFirst(vm.administrators(), function (item) {
return <true or false statement> //define what constitutes equal here;
});
if (existingItem) {
vm.administrators.remove(existingItem);
vm.administrators.push(vm.adminToAdd());
}
else {
//case where it wasn't in the array
}

Related

Why does using slice in MongoDB updateOne does not delete an item from an array but only replaces it with a weird object?

so there the array coursesFollowing
and inside it objects like this for example:
objInside = {
username:"SweetWhite"
courseTitle:"TOMATOaaaaa"
progress:0
}
and I have a function that finds the right object index in the array of objects of that kind of object
and it seems to work in terms of index found and object, however my update request works weirdly when I execute it:
User.updateOne({username: req.body.username}, {coursesFollowing:{$slice: [removeIndex , 1]}}, function(err,result){
if(err){return next(err)}
})
it find the right user to put it in, and also the right field, however it does not remove the object in that removeIndex index, but deletes all the items in the array and puts only one object in, with the array named $slice with the removeIndex as first value and the second value is 1, judging from the data it has not deleted all the other objects but simply replaced them with itself, the operation $slice pushed itself into the array and did not execute I think? I am mildly confused.
anyways, how do I fix this? how do I delete an index of the array without pushing the weird $slice object array thingi?
Thanks!
If you notice your updated document you might realize that you are not updating anything in the array actually using your $slice operator. Instead you are setting a new value for the array. And that is why you see the first element as $slice something and the second as 1.
$slice operator is used with $push and is used to limit the number of elements in the array field finally. The doc doesn't mention removing an array element with this, given its index.
According to this answer there is no simple way to do this.
There is $pull, but it does not work with the given index. It works based on a query condition for the object in the array. So if you are first figuring out the element index and then using it in the update query. You can do that directly.
If you want to use a JS function you can use splice, which does in-place updates. Use the $function
User.updateOne({username: req.body.username}, [{ $set:
{ "coursesFollowing": { $function: { body: function(coursesFollowing) { coursesFollowing.splice(removeIndex, 1); return coursesFollowing; }, args: ["$coursesFollowing"], lang: "js" }} }
}], function(err,result){
if(err){return next(err)}
})

How to delete object from array if value matched from other object?

I have already existed array of object values now when i delete dataItem dataItem has same properties that i have in selectedOwners so if dataItem selected value matched i want to delete that object from selectedOwners array.
How can i achieve that task using AngularJs or Javascript ?
ctrl.js
var selectedOwners = [{"fullName":"Johnson, Rocio","workerKey":3506},{"fullName":"Johnson, John S.","workerKey":571}];
$scope.deleteOwner = function(dataItem){
angular.forEach(selectedOwners,function(val,index){
if(val === dataItem){
selectedOwners.splice(index,1);
}
})
}
Unfortunately in Javascript you don't have many instrument for a good equality checking, and === isn't enough, === don't force javascript to convert the two operand in order to perform the equality check on the same type of object, we let say that in this way you have true if the two objects have the same memory reference false otherwise.
For this reason you should decide the your equality criteria and wrap this login in a function. I discourage to use something like this Object.prototype.equals because in the sway you will have the same behavior for all objects in your script
the rest of code that you had post is good in my opinion but you have implements a equality checking
I hope that this can help you

Eloquent Javascript: Uncaught TypeError: Cannot read property 'indexOf' of undefined

I am learning JavaScript based on Eloquent Javascript and during one of the chapters, came across this error. Not sure what I am doing wrong here. I am getting an error "Cannot read property 'indexOf' of undefined" against the code return journal.events.indexOf(event) != -1
Also, can someone explain how that line works? Isn't IndexOf supposed to return the first position of occurrence of the specified value (in this case, event)? But I see in the book that the line return journal.events.indexOf(event) != -1; returns either true or false.
var journal = [];
function addEntry(events, didITurnIntoASquirrel) {
journal.push({
events: events,
squirrel: didITurnIntoASquirrel
});
}
addEntry(["work", "touched tree", "pizza", "running",
"television"], false);
addEntry(["work", "ice cream", "cauliflower", "lasagna",
"touched tree", "brushed teeth"], false);
addEntry(["weekend", "cycling", "break", "peanuts",
"beer"], true);
function hasEvent(event, entry) {
return entry.events.indexOf(event) != -1;
}
console.log(hasEvent("pizza", journal));
In your sample code journal is an array
var journal = []; <--- Array
Therefore events should be accessed with an index like:
journal[0].events.indexOf(event)
^
|
|
Here you need to find the right index to get your events
I'm unsure how far along you are with learning javascript, so forgive me if some of this sounds condescending or obvious.
Let's break this down one step at a time. You begin with an empty array.
var journal = [];
console.log(journal); //[]
//it's defined. It's an empty Array.
By calling push on an array, you add something to the end of the array.
More on Array.push.
I don't like this example for beginners because it expects you to know already that you can define an object while you're passing it as an argument. This is done this way because you don't truly need a variable reference to an object that's only used once and is therefore a good way of reducing bloat in code. But verbosity is much better when teaching someone, imho.
//Wait, what am I pushing into the journal array?
journal.push({
events: events,
squirrel: didITurnIntoASquirrel
});
This should make more sense:
Create an object first. Then add that object to the "journal" array.
function addEntry(events, didITurnIntoASquirrel) {
var temporaryObject = {
events: events,
squirrel: didITurnIntoASquirrel
};
journal.push(temporaryObject);
}
Now journal is an array with an unnamed object at its first index.
1. console.log(journal); // [temporaryObject]
2. console.log(journal[0]); - //temporaryObject
The visibile difference is the lack of parens, but the difference is important.
On line 1 you have the array itself, on line 2 you have what's inside it (i.e. the object). You need to get the object (via the technique on line 2) before you can access properties of that object, such as "events" or "squirrel". Moving on.
addEntry(["work", "touched tree", "pizza", "running", "television"], false);
Next, we invoke the addEntry function. Same confusion here. I've rewritten it slightly to make the arguments more understandable.
var entry = ["work", "touched tree", "pizza", "running", "television"];
addEntry(entry, false);
//repeat 2 more times with different data
So first we define an array, then we pass it to the addEntry function. when the addEntry function runs (it will run right when we invoke it), the "entry" argument will be represented function as the "events" parameter (simple way: events = entry and didITurnIntoASquirrel = false). some notes on parameters vs arguments.
So you should be able to understand now that you're passing an array and a boolean to the addEntry function. That function creates an object based on those values referencing them via their parameters. That object is then added to the journal array.
What you end up with is 4 levels of depth. You have an array called journal, which has objects in it. Those objects have a property called events, which is a different array. That array has several strings inside it. To access the events array and use indexOf to see if it has a given string in it, you need to traverse that depth one level at a time.
//journal is the array, journal[0] is the object, journal[0].events is the property of that object
console.log(journal[0].events) //["work", "touched tree", "pizza", "running", "television"].
Note this is the same data that we originally put in the entry variable. This may seem unnecessarily complicated, but trust me when I say this type of structure is useful in real life when you need to manage data hierarchy or other logical relationships between "things" in Object Oriented programming.
Now, all the work we've done so far is to add to the journal array. We now want a function to see what's inside it. Why a function? So you don't have to rewrite the same code over and over.
function hasEvent(event, journal) {
return journal.events.indexOf(event) != -1;
}
By now I hope you can spot the error in this function. journal.events doesn't work, because journal is an array, not an object (you skipped a level, and your computer isn't smart enough to know what you mean) journal[0].events would work, because you are telling javascript ("from the journal array, I want the object in the first slot, and the events property of that object").
The simplest fix is to send journal[0] to the hasEvent function instead of journal. Beware, this will only check journals first index. Realistically you'd want a for loop inside the hasEvent function or wrapping the call to that function to check all indexes. For now we will hardcode them, since we know there are 3, but its not a good idea in real life, since later there may be more than 3 entries in the journal).
This funciton is returning the result of calling indexOf() (some number or -1) with -1. Let's again rewrite it so that it makes more sense.
New hasEvent function:
//I renamed the variable so it makes more sense what it really is. It's the object, not the journal array.
function hasEvent(event, journalEntry) {
var index = journalEntry.events.indexOf(event);
var result = (index != -1); //true if it was found, false if it wasn't found.
return result; //a boolean based on the above comparison.
}
//Ack! My kingdom for a "for loop". Don't worry about that right now.
console.log(hasEvent("pizza", journal[0]));
console.log(hasEvent("pizza", journal[1]));
console.log(hasEvent("pizza", journal[2]));
TL;DR
Here is a fiddle with working code:
http://jsfiddle.net/o8dg1ts6/1/
To answer your 2nd question:
"Isn't IndexOf supposed to return the first position of occurrence of the specified value"
Yes, and indexOf returns -1 if if the value is not found in the array.
So if the event is found, then the expression indexOf(event) != -1 will evaluate to true.

Can I make a "Virtual Array" in JavaScript?

I'm calling a JavaScript function that wants an array of things to display. It displays a count, and displays the items one by one. Everything works when I pass it a normal JavaScript array.
But I have too many items to hold in memory at once. What I'd like to do, is pass it an object with the same interface as an array, and have my method(s) be called when the function tries to access the data. And in fact, if I pass the following:
var featureArray = {length: count, 0: func(0)};
then the count is displayed, and the first item is correctly displayed. But I don't want to assign all the entries, or I'll run out of memory. And the function currently crashes when the user tries to display the second item. I want to know when item 1 is accessed, and return func(1) for item 1, and func(2) for item 2, etc. (i.e., delaying the creation of the item until it is requested).
Is this possible in JavaScript?
If I understand correctly, this would help:
var object = {length: count, data: function (whatever) {
// create your item
}};
Then, instead of doing array[1], array[2], et cetera, you'd do object.data(1), object.data(2), and so on.
Since there seems to be a constraint that the data must be accessed using array indexing via normal array indexing arr[index] and that can't be changed, then the answer is that NO, you can't override array indexing in Javascript to change how it works and make some sort of virtual array that only fetches data upon demand. It was proposed for ECMAScript 4 and rejected as a feature.
See these two other posts for other discussion/confirmation:
How would you overload the [] operator in Javascript
In javascript, can I override the brackets to access characters in a string?
The usual way to solve this problem would be to switch to using a method such as .get(n) to request the data and then the implementor of .get() can virtualize however much they want.
P.S. Others indicate that you could use a Proxy object for this in Firefox (not supported in other browsers as far as I know), but I'm not personally familiar with Proxy objects as it's use seems rather limited to code that only targets Firefox right now.
Yes, generating items on the go is possible. You will want to have a look at Lazy.js, a library for producing lazily computed/loaded sequences.
However, you will need to change your function that accepts this sequence, it will need to be consumed differently than a plain array.
If you really need to fake an array interface, you'd use Proxies. Unfortunately, it is only a harmony draft and currently only supported in Firefox' Javascript 1.8.5.
Assuming that the array is only accessed in an iteration, i.e. starting with index 0, you might be able to do some crazy things with getters:
var featureArray = (function(func) {
var arr = {length: 0};
function makeGetter(i) {
arr.length = i+1;
Object.defineProperty(arr, i, {
get: function() {
var val = func(i);
Object.defineProperty(arr, i, {value:val});
makeGetter(i+1);
return val;
},
configurable: true,
enumerable: true
});
}
makeGetter(0);
return arr;
}(func));
However, I'd recommend to avoid that and rather switch the library that is expecting the array. This solution is very errorprone if anything else is done with the "array" but accessing its indices in order.
Thank you to everyone who has commented and answered my original question - it seems that this is not (currently) supported by JavaScript.
I was able to get around this limitation, and still do what I wanted. It uses an aspect of the program that I did not mention in my original question (I was trying to simplify the question), so it is understandable that other's couldn't recommend this. That is, it doesn't technically answer my original question, but I'm sharing it in case others find it useful.
It turns out that one member of the object in each array element is a callback function. That is (using the terminology from my original question), func(n) is returning an object, which contains a function in one member, which is called by the method being passed the data. Since this callback function knows the index it is associated with (at least, when being created by func(n)), it can add the next item in the array (or at least ensure that it is already there) when it is called. A more complicated solution might go a few ahead, and/or behind, and/or could cleanup items not near the current index to free memory. This all assumes that the items will be accessed consecutively (which is the case in my program).
E.g.,
1) Create a variable that will stay in scope (e.g., a global variable).
2) Call the function with an object like I gave as an example in my original question:
var featureArray = {length: count, 0: func(0)};
3) func() can be something like:
function func(r) {
return {
f : function() {featureArray[r + 1] = func(r + 1); DoOtherStuff(r); }
}
}
Assuming that f() is the member with the function that will be called by the external function.

How do I sort a JSON object by a nested value?

I have an ajax call that returns a JSON object that is pretty complex and I'm having a hard time sorting it.
My call:
$.post('/reports-ajax',arguments, function(data) {}
The response:
{
"10001":{
"unitname":"Fort Worth",
"discounts":{"12-02-2012":"34.810000","12-03-2012":"20.810000","12-04-2012":"27.040000"},
"gross":{"12-02-2012":"56.730000","12-03-2012":"19.350000","12-04-2012":"66.390000"},
"net":{"12-02-2012":"61.920000","12-03-2012":"98.540000","12-04-2012":"39.350000"},
"discounts_total":82.66,
"gross_total":82.47,
"net_total":99.81,
"number":10001
},
"10002":{
"unitname":"Dallast",
"discounts":{"12-02-2012":"12.600000","12-03-2012":"25.780000","12-04-2012":"47.780000","12-05-2012":"45.210000"},
"gross":{"12-02-2012":"29.370000","12-03-2012":"91.110000","12-04-2012":"60.890000","12-05-2012":"51.870000"},
"net":{"12-02-2012":"16.770000","12-03-2012":"65.330000","12-04-2012":"13.110000","12-05-2012":"06.660000"},
"discounts_total":131.37,
"gross_total":33.24,
"net_total":101.87,
"number":10002
},
"32402":{
"unitname":"Austin",
"discounts":{"12-05-2012":"52.890000","12-02-2012":"22.430000","12-03-2012":"58.420000","12-04-2012":"53.130000"},
"gross":{"12-05-2012":"25.020000","12-02-2012":"2836.010000","12-03-2012":"54.740000","12-04-2012":"45.330000"},
"net":{"12-04-2012":"92.200000","12-05-2012":"72.130000","12-02-2012":"13.580000","12-03-2012":"96.320000"},
"discounts_total":186.87,
"gross_total":161.1,
"net_total":174.23,
"number":32402
}
}
I go over the function with a standard each call and do some awesome stuff with highcharts but now I'm trying to sort the responses by the net_total call and I can't figure it out.
I tried .sort() and it errors out that its not a function. I've been reading for a while but guess I'm not finding the right results. This looked promising: Sorting an array of JavaScript objects but it failed with the .sort is not a function. It seems most .sort are on [] arrays not full objects..
Any help would be greatly appreciated.
Sorting objects doesn't make sense since object keys have no positional value. For example, this:
{ a:1, b:2 }
and this:
{ b:2, a:1 }
are exactly the same object. They're not just similar, they're the same.
Nothing in javascript per se gives object keys any positional value. Some people perhaps are mistaken in the belief that:
for (var key in obj) {
iterates through the object keys in a specific sequence. But this is wrong. You should always assume that the for .. in loop processes object keys in random order, always, all the time.
Obviously, if you're going to write a web browser, you're not going to implement a random number generator to parse a for .. in loop. Therefore most web browsers have an accidental stability to how the for .. in loop processes object keys.
Developers who learn javascript by playing around with the browser may figure out that their browser iterates through objects in alphabetical order for example, or the order the keys were added to the object. But this is totally accidental and cannot be relied upon. The browser vendor may change this behavior in the future without violating any backwards compatability (except with buggy scripts written by people who believe objects have a sort order). Not to mention that different browsers have different implementations of javascript and therefore not necessarily have the same internal key ordering of objects.
All the above is besides the point. "Key sort order" does not make any sense in javascript and any behavior observed is merely implementation detail. In short, javascript object does not have key order, just assume it's random.
Solution
Now, what you're really trying to do is not sort the object (you can't, it doesn't make sense). What you're really trying to do is process the object attributes in a specific order. The solution is to simply create an array (which has sorting order) of object keys and then process the object using that array:
// First create the array of keys/net_total so that we can sort it:
var sort_array = [];
for (var key in Response) {
sort_array.push({key:key,net_total:Response[key].net_total});
}
// Now sort it:
sort_array.sort(function(x,y){return x.net_total - y.net_total});
// Now process that object with it:
for (var i=0;i<sort_array.length;i++) {
var item = Response[sort_array[i].key];
// now do stuff with each item
}
What you have there isn't an array and has no order, so you'll have to transform it into an array so you can give it order.
Vaguely:
var array = [];
$.each(data, function(key, value) {
array.push(value);
});
array.sort(function(a, b) {
return a.net_total - b.net_total;
});
Live Example | Source
As GolezTroi points out in the comments, normally the above would lose the key that each entry is stored under in data and so you'd add it back in the first $.each loop above, but in this case the entries already have the key on them (as number), so there's no need.
Or you can replace the first $.each with $.map:
var array = $.map(data, function(entry) {
return entry;
});
// ...and then sort etc.
...whichever you prefer.

Categories

Resources