knockout.js sorted observable array - javascript

I would like to have an observable array which will sort itself when an object is pushed into it (it would be even better if it would sort itself if any of the values it was using in the comparator function was changed).
Something where you could define the comparator function you want the array to sort on and then every time push was called it would add the pushed objects into the correct place in the array so the array remained sorted, like:
var sortedArray = ko.sortedObservableArray(
function (a,b) { return b - a;},
[1,7,4]
); // sortedArray will be [1,4,7]
sortedArray.push([5,2]); // sortedArray will now be [1,2,4,5,7]
Are there any libraries that will do this for me and if not what is the best way to go about implementing this?

I ended up creating a sorted observable array by extending knockout observable array:
ko.sortedObservableArray = function (sortComparator, initialValues) {
if (arguments.length < 2) {
initialValues = [];
}
var result = ko.observableArray(initialValues);
ko.utils.extend(result, ko.sortedObservableArray.fn);
delete result.unshift;
result.sort(sortComparator);
return result;
};
ko.sortedObservableArray.fn = {
push: function (values) {
if (!$.isArray(values)) {
values = [values];
}
var underlyingArray = this.peek();
this.valueWillMutate();
underlyingArray.push.apply(underlyingArray, values);
underlyingArray.sort(this.sortComparator);
this.valueHasMutated();
},
sort: function (sortComparator) {
var underlyingArray = this.peek();
this.valueWillMutate();
this.sortComparator = sortComparator;
underlyingArray.sort(this.sortComparator);
this.valueHasMutated();
},
reinitialise: function (values) {
if (!$.isArray(values)) {
values = [values];
}
var underlyingArray = this.peek();
this.valueWillMutate();
underlyingArray.splice(0, underlyingArray.length);
underlyingArray.push.apply(underlyingArray, values);
underlyingArray.sort(this.sortComparator);
this.valueHasMutated();
},
reverse: function () {
var underlyingArrayClone = this.peek().slice();
underlyingArrayClone.reverse();
return underlyingArrayClone;
}
};
Which can be used in the following way:
var sortedArray = ko.sortedObservableArray(
function (a,b) { return a - b;},
[1,7,4]
); // sortedArray will be [1,4,7]
sortedArray.push([5,2]); // sortedArray will now be [1,2,4,5,7]
sortedArray.sort(function (a,b){
return b - a;
}); // sortedArray will now be [7,5,4,2,1]
sortedArray.push(6); // sortedArray will now be [7,6,5,4,2,1]
The only problem I have is that when reinitialising the sorted observable array with a new array in the same way you would reinitialise an observable array the sorted observable array isn't being sorted. To get around this I have added a reinitialise function on the sorted observable array:
var sortedArray = ko.sortedObservableArray(
function (a,b) { return a - b;},
[1,7,4]
); // sortedArray will be [1,4,7]
sortedArray([3,2,8]); // this doesn't work correctly, sortedArray will be [3,2,8]
// instead of [2,3,8]
// To get around this you can use reinitialise()
sortedArray.reinitialise([3,2,8]); // sortedArray will be [2,3,8]

Try this:
var sortedArray = ko.observableArray();
sortedArray.subscribe(function () {
if (!sortedArray._isSorting) {
sortedArray._isSorting = true;
sortedArray.sort(function (a, b) { return b - a; });
sortedArray._isSorting = false;
}
});
You can wrap this up in a function to create new sorted observable arrays whenever you want.

Related

Javascript return reference to array index with primitive value

I want to return a reference to to the content of an array at index x in order to be able to change the content of the array using the returned index afterwards.
Here is an example of what I mean:
let testArr = [1,2,3]
const someFunct = arr => {
...
return {reference to arr[0], aka 1}
}
someFunct(testArr) = 0;
//should log [0,2,3]
console.log(testArr);
someFunct(testArr) should behave like arr[0] in this case.
The content of the array could be anything.
i dont think the exact implementation you are trying to achieve is possible in JavaScript.
https://medium.com/#naveenkarippai/learning-how-references-work-in-javascript-a066a4e15600
something similar:
const testArr = [1,2,3]
const changeArray = (array, index, newValue) => {
array[index] = newValue
return array
}
changeArray(testArr, 0, 0) // evaluates to [0,2,3]
I want to return a reference …
This is not possible, JavaScript doesn't have references as returnable values. The someFunct(testArr) = 0; syntax you are imagining is invalid.
Instead, take the new value as an argument:
function someFunct(arr, val) {
…
arr[0] = val;
}
someFunct(testArr, 0);
or return an object with a method that does the assigning:
function someFunct(arr) {
…
return {
set(v) {
arr[0] = v;
}
};
}
someFunct(testArr) = 0;
Try this:
function arrayWrapper(arr, index, newVal) {
let returnVal;
if(newVal) {
arr[index] = newVal;
returnVal = arr;
} else {
const obj = { ...arr };
returnVal = obj[index];
}
return returnVal;
};
console.log(arrayWrapper([1,2,3,4,5,6,7,8,9], 5)); /// to get value at index
console.log(arrayWrapper([1,2,3,4,5,6,7,8,9], 5, 'text')); // to set value on index
You can use the above method to get as well as set elements to your array.
The nature of operation depends on the third parameter.
Hope this helps :)

JavaScript changing object list to array. return undefined

function listToArray(list){
var newArray = [];
repeat();
function repeat(){
newArray.push(list.value);
if(list.rest == null){
return obj = newArray; // I don't know why It returns undefined here
}
else {
list = list.rest;
repeat();
}
}
}
// and this will return an array
function reTurn(){
var listVar = [5, 4, 3, 2, 1];
return obj = listVar;
}
This function does change my obj when I look for it in console, but returns undefined.
Anyone can help me? please
just return newArray - you can't return an assignment like that.
You're returning from the inner function, not the outer one. Add a return newArray to the bottom of listToArray. Change the return statement in repeat to just return;.
function listToArray(list){
var newArray = [];
repeat();
function repeat(){
newArray.push(list.value);
if (list.rest) {
list = list.rest;
repeat();
}
}
return newArray;
}
There's nothing really wrong with the recursive approach, but it's not necessary. You could just as easily write
function listToArray(list){
var newArray = [];
while (list) {
newArray.push(list.value);
list = list.rest;
}
return newArray;
}
However, even this code has the drawback that it is mixing the traversal of the list with the building of the array of values. It would be better to separate those:
function traverseList(list, fn) {
while (list) {
fn(list.value);
list = list.rest);
}
}
Now you can write listToArray as
function listToArray(list) {
var newArray = [];
traverseList(list, value => newArray.push(value));
return newArray;
}
I might also write this as a generator:
function* forList(list) {
while (list) {
yield list.value;
list = list.rest;
}
}
Now I don't even need a separate function to create the array of values, because I can just write
[...forList(list)]

Function trims values after being passed in another function

I would like have renderData() display the values from max. When I console.log(max) in calculateData() it displays all three maximum values from three JSON objects. However, when I return max in renderData() it only shows the first value. Why is this, and what can I do to make it display all three values instead of just one? Note: data is the json list of objects being passed. Thank you!
function calculateData(data) {
for (i in data) {
var arr = [];
var max;
var obj = data[i].tones;
obj.map(function(item) {
var data = item.score;
arr.push(data);
})
max = arr.reduce(function(a, b) {
return Math.max(a, b);
})
//Returns an array containing dominant [emotion_tone, language_tone, social_tone]
return renderData(max);
}
}
function renderData(max) {
console.log(max);
};
Maybe this is what you are intending? It will iterate through the entire data object calculating a max for each iteration, collect them in an array, and then finally call the renderData func with that array.
function calculateData(data) {
var maxes = [];
for (i in data) {
var arr = [];
var max;
var obj = data[i].tones;
obj.map(function(item) {
var data = item.score;
arr.push(data);
})
maxes.push(arr.reduce(function(a, b) {
return Math.max(a, b);
}));
}
return renderData(maxes);
}
function renderData(max) {
console.log(max);
};
I created a second array, finalArray and pushed each max into it:
function calculateData(data) {
var finalArr = [];
for (i in data) {
var arr = [];
var max;
data[i].tones.map(function(item) {
arr.push(item.score);
})
var max = arr.reduce(function(a, b) {
return Math.max(a, b);
});
finalArr.push(max);
//Returns an array containing dominant [emotion_tone, language_tone, social_tone]
// return renderData(max);
}
renderData(finalArr);
}
function renderData(finalArr) {
console.log(finalArr);
};
Thanks for your help guys!
Your error arises because the return statement inside the loop aborts the whole function, and therefore also the loop. But this whole function can be simplified/shortened quite a bit:
And as mentioned in the comments, don't define functions inside a loop (unless it is inevitable), define the before the loop. I did this in by defining the functions getScore and max in my code.
//ES6
function calculateData(data) {
var getScore = item => item.score,
max = (a,b) => Math.max(a,b),
maxima = Object.values(data)
.map(value => value.tones.map(getScore).reduce(max));
return renderData(maxima);
}
//Or maybe you're more comfortable without Arrow functions
function calculateData(data) {
function getScore(item){ return item.score }
function max(a,b){ return Math.max(a,b) }
var maxima = Object.values(data).map(function(value){
return value.tones.map(getScore).reduce(max)
});
return renderData(maxima);
}
The only difference to your code is that Object.values() returns only own values of the object, whereas for..in iterates over all values of the object, own and inherited.

Creation of array and append value inside closure in javascript

I am using this code -
var gArray = (function (value) {
var array = [];
return function () {
array.push(value);
return array;
}
}());
gArray(1);
gArray(2);
gArray(3);
I am expecting to this code snippet [1, 2, 3]
but i am getting [undefined, undefined, undefined]
The gArray function doesn't have an argument, the immediately invoked function does, but you pass nothing when you call it:
var gArray = (function (value) { //<- Argument of IIFE
var array = [];
return function () { //<- No arguments for gArray
array.push(value);
return array;
}
}()); //<- No arguments passed to IIFE
What you need is to define an argument for the returned function, which is gArray:
var gArray = (function () {
var array = [];
return function (value) {
array.push(value);
return array;
}
}());
Your outer function is a self-invoked function. That means that it will be executed as soon as () is reached. In this particular case, it's returning:
function () {
array.push(value);
return array;
}
which is taking value as undefined. To solve this issue, you can rewrite your code as follows:
var gArray = (function () {
var array = [];
return function (value) {
array.push(value);
return array;
}
}());
Change var array = []; to this.array = [];.
It needs to have the right scope.
var myArray = [];
function addToArray(value){
myArray.push(value);
console.log(value + " was added to " + myArray);
}
addToArray(1);
addToArray(2);
addToArray(3);
If you give back myArray console will say [1, 2, 3]
This is the best way to do it:
const array = [];
const pushArray = function(value, array) {
array.push(value);
return array;
};
pushArray(1, array);
pushArray(2, array);
pushArray(3, array);

Compare array slow

I use a standard way of comparing two arrays, but its as slow as just doing a standard O(n^2)
Can you guys see the problem?
diffArray: function (arr1, arr2, observableArray, mapping) {
//We cant sort orginal arrays
var o = arr1.slice(0);
var n = arr2.slice(0);
// sort both arrays (or this won't work)
var sorter = function (left, right) {
return mapping.key(left) - mapping.key(right);
};
o.sort(sorter); n.sort(sorter);
// declare temporary variables
var op = 0; var np = 0;
var added = []; var removed = [];
// compare arrays and add to add or remove lists
while (op < o.length && np < n.length) {
if (mapping.key(o[op]) < mapping.key(n[np])) {
// push to diff?
removed.push(o[op]);
op++;
}
else if (mapping.key(o[op]) > mapping.key(n[np])) {
// push to diff?
added.push(n[np]);
np++;
}
else {
this.diffMembers(o[op], n[np]);
op++; np++;
}
}
// add remaining items
if (np < n.length)
added = added.concat(n.slice(np, n.length));
if (op < o.length)
removed = removed.concat(o.slice(op, o.length));
ko.utils.arrayForEach(removed, function (item) {
this.itemDeleted(item, mapping);
}.bind(this));
ko.utils.arrayForEach(added, function (item) {
if (observableArray.concurrencyExtendOptions) {
this.itemAdded(observableArray, item, mapping);
}
} .bind(this));
}
Note:
The mapping object is just a helper object the user supplies to compare objects in the array, arr1 and arr2 are standard JS arrays while observableArray is the unwrapped Knockout array
It's based on a C# code example so maybe the sort algorithm in JS isn't as good?
Not sure it will answer you, really, but here is the diff I'm using for arrays:
Array.prototype.diff = function( arr ) {
return arr.map( function( v ) {
if ( !~this.indexOf( v ) ) return v;
}, this ).filter( Boolean );
};
Usage:
arr.diff( otherArr );
PS: Too long to be posted as a comment... Not sure it deserves an answer.
PS2: Original author of the function: #Esailija.

Categories

Resources