I'm really struggling to sort a ko.observableArray. I've been searching for solutions for the past hour, and I'm pretty convinced I'm doing it by the book.
Basically the problem seems to come from the fact that the array of elements doesn't actually exist at sort time. Each item is represented by a function which I assume allows KnockoutJS to listen for mutations...but it's not helping me much :)
Link to JSfiddle | http://jsfiddle.net/farina/W7HJP/
Check out my fiddle and click the sort link. As you can see you'll get a bunch of NaN values instead of actual sorting.
Any help would be greatly appreciated!!
When you access an observable's value, you need call it as a function with zero arguments.
So:
var myObservable = ko.observable("Bob");
myObservable("Ted"); //set the value to something else
alert(myObservable()); //read the current value "Ted"
So, in your sort, you would do:
this.sortItems = function () {
this.incidents.sort(function (a, b) {
return b.id() - a.id();
});
};
http://jsfiddle.net/rniemeyer/W7HJP/10/
Related
The following code was used to create an array containing object groups(arrays) by date. I could understand the intention but could not understand the working of the code. This is from a course by "Neil Cummings" and unfortunately I could not find his SO handle to ask him directly. Also I borrowed the course so I couldn't ask him through Q & A either.
So please consider explaining the code to me.
#computed get activitiesByDate() {
return this.groupActivitiesByDate(Array.from(this.activityRegistry.values()));
}
groupActivitiesByDate(activites: IActivity[]){
const sortedActivities = activites.sort(
(a,b) => Date.parse(a.date) - Date.parse(b.date)
)
return Object.entries(sortedActivities.reduce((activities, activity) => {
const date = activity.date.split('T')[0];
activities[date] = activities[date] ? [...activities[date], activity]: [activity];
return activities;
}, {} as {[key: string]: IActivity[]}));
}
In the code above I could understand that a new array i.e. "sortedActivities" is made by sorting the activities array. Then again the reduce function is called on it where part of date from each activity is split to find objects having same date and grouping them - which is where Object.entries comes in. What I couldn't understand how the ordering of "activites" array is affecting "sortedActivities" when actually we are sorting the activities array and also the line when ternary operator is being used. can we compare two arrays directly like that? if so why get each object from the array?. I am totally confused I tried to search some similar code to get a nice and clear explanation but I couldn't find any. Can any body please help me out. I hope I have provided enough information for the question.
Well, let's go line by line:
const sortedActivities = activites.sort(
(a,b) => Date.parse(a.date) - Date.parse(b.date)
)
Here we sort an array of activities by date, pretty simple. By there is also a very rookie mistake here (not quite sure how course author could make it), is that .sort actually mutates original array. So it is quite bad to call it like that, you need to call .slice() first to create new separate copy.
sortedActivities.reduce((activities, activity) => {
const date = activity.date.split('T')[0];
activities[date] = activities[date] ? [...activities[date], activity]: [activity];
return activities;
}, {} as {[key: string]: IActivity[]})
Then we make map of array of activities grouped by same date, so it it will be something like that in the end:
const reduceResult = {
// Might be different format for date, but you see the point
'2020-08-10': [activity, activity],
'2020-09-10': [activity],
'2020-10-10': [],
// ...
}
So this line:
activities[date] = activities[date] ? [...activities[date], activity]: [activity];
just checks if array with date key already exists, if not it creates new array, if it exists then it just merges old array with current activity
Then we return Object.entries
Object.entries(...)
Basically just grabbing all values from our map.
But there is another possible mistake (or bug) here, because author of the code assumes that creating map from sorted array will always be sorted too, but it is not, Object.entries iterates over the properties of an object in an arbitrary order, so you should not depend on that, even if it work for this case right now.
I'm trying to group and add the similar elements in an array that I'm displaying in ng-repeat..
Plunker:Group Array Items
I tried to apply filter as:
<div ng-repeat="shape in shapes|filter:groupFilter">
{{shape.name}}- {{shape.value}}
</div>
$scope.groupFilter=function(item)=>{
return item.name===item.name;
}
where I'm not able to access the whole elements at a time so that I can compare the values and add them up..
The end result that I'm expecting is like this...
Circle- 17
Rectangle- 13
edit
Updated plunkr - http://jsfiddle.net/2s9ogyh8/1/
After reading the question again I realized that you were trying to group not only visually but you also wanted to aggregate values. If you were simply trying to group existing values, then you'd use my original answer.
Since you're aggregating the totals, I'd recommend doing so before the UI renders it, otherwise you encounter the $digest loop issue from #toskv's answer. His aggregating logic is pretty much the same though.
end edit
Here is a related post on this topic - orderBy multiple fields in Angular
Take a look at the built-in orderBy filter - https://docs.angularjs.org/api/ng/filter/orderBy
If you're looking for something more than just ordering by the name/type of shpae then you most likely won't be able to do this via a declarative object matcher but you can pass your groupFilter function back to it.
Updated plunker - http://jsfiddle.net/d13zmk3y/2/
A simple way to do it would be to make filter that uses reduce to group everything.
app.filter('group', function() {
return function(items) {
return items.reduce(function(aux, current) {
if (aux[current.name]) {
aux[current.name].count += parseInt(current.value, 10);
} else {
aux[current.name] = {
name: current.name,
count: parseInt(current.value, 10)
};
}
return aux;
}, {});
};
});
You can see a fully working example here.
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.
I'm trying to make a little damage calculator for the game Diablo 3 (I know, I know).
Basically the idea is that it has a "before" and "after" array of values that represent items for your character. The "after" array should duplicate the "before" array when that's updated. However, changes to the "after" array should not update the "before" array.
Each array then displays a DPS (more of this is better) total, and it shows you the difference between the two. The idea is then it makes for easy comparison of two items when using the in-game auction house.
I have the first bit set up - the "before" array is working great. However I'm at a loss as to how to create the "after" array, and I'm wondering if I've made this a different magnitude of complexity. Should I be using two view models, replicating it in jQuery, or using the mapping plugin? I can't quite find anything that's exactly what I'm after, the UI requirements especially seem a bit of a sticking point
Fiddle of where I'm up to: http://jsfiddle.net/kimadactyl/GuMuY/8/
Here's a solution that should get you started. I refactored your HeroItem to take a config object and an optional linked Hero.
I am assuming for the moment the array is fixed length. I create the after array from the items array by mapping it to a new HeroItem, using jquery extend to do a deep copy.
When a link is passed in the HeroItem will subscribe to changes on it's observables and update one-way only as specified.
function HeroItem(config, link) {
var self = this, prop;
self.item = config.item;
self.int = ko.observable(config.int);
self.ias = ko.observable(config.ias);
self.critdmg = ko.observable(config.critdmg);
self.critpc = ko.observable(config.critpc);
self.min = ko.observable(config.min);
self.max = ko.observable(config.max);
if (link) {
for (prop in link) {
if (link.hasOwnProperty(prop) && ko.isObservable(link[prop])) {
console.log("subscribing " + prop);
link[prop].subscribe((function(p) {
return function (newValue) {
console.log("updating " + p+ " to " + newValue);
self[p](newValue);
}
})(prop));
}
}
}
}
self.after = ko.observableArray(ko.utils.arrayMap(self.items(), function(i) {
return new HeroItem($.extend({}, ko.toJS(i)), i);
}));
http://jsfiddle.net/madcapnmckay/2MNFn/1/
No custom bindings needed, it just uses the subscription capabilities all KO observables have. If you want to extend this to cope with dynamic length arrays simple subscribe to the items array and cleanup the after array accordingly.
Hope this helps.
It might be a silly question but still i am facing problem with this.
var eformDetailIds = [];
eformDetailIds=$("[name=eform_id]").map(function(){ return $(this).val() }).get();
this is the code that i have written in js function and calling this function on button click.
But the problem is the list eformDetailIds containing the previous values also. could you please how to set this empty list for every function call? Thanks in advance.
Just set the length to zero:
eformDetailIds.length = 0;
Or allocate a new array:
eformDetailIds = [];
Now, that said, according to the code you posted the entire array will definitely be replaced each time that ".map()" call runs. In other words, the previous values will not remain in the array. Perhaps you should post more to explain what it is that makes you think the old values remain.
Don't forget you can always reset the array in this way:
myArray = new Array();
It is pretty easy.