Knockout.js: Filter array on subscribe - javascript

I have an observableArray that in its subscribe callback I need to filter the new array passed.
For example:
myArray.subscribe(function(elements) {
ko.utils.arrayFilter(elements, function(element) {
return element.x > 10
})
})
This, of course, doesn't work since arrayFilter nor the native filter() method doesn't change the original array. The problem is that i can't do this:
myArray.subscribe(function(elements) {
var newArray = ko.utils.arrayFilter(elements, function(element) {
return element.x > 10
})
myArray(newArray)
})
because this would be an infinite loop. How would I filter the array inside the subscription function?

The best solution is the one suggested by nemesv: Don't alter the array itself, but instead create a new computed observable that encapsulates the filter behavior.
var filteredArray = ko.computed(function () {
return ko.utils.arrayFilter(myArray(), function(element) {
return element.x > 10;
});
});
The simplest solution, if you're feeling lazy, would be to replace
myArray(newArray)
with
if (newArray.length !== elements.length) {
myArray(newArray);
}
as suggested in the comment by James Thorpe. The flaw here is that every subscriber might run twice, including the filter operation itself.

Your problem implies that you really want to encapsulate your data and not to expose it directly over the view-model.
Try something like this:
var arr = ko.observableArray();
this.AddItem = function(element) {
if (element.x > 10)
arr.push(element);
};
this.GetItems = function() {
return arr();
};

Related

How do I make 'foo()' and 'foo().bar()' output completely different things?

So I'm trying to make it so foo() returns anything from a string to object, then have foo().bar() return something completely different and even have foo().bar().test() return another thing completely different. For example, databases() would return something like a list of databases, then databases().users() would return an object of all the users and finally databases().users().ids() would return an array of IDs.
I think it's called function/method chaining but I haven't found anything related to what I need.
All I find is examples like var number = new math(10) number.add(5).subtract(3).value but I don't want it to chain it like that, I want it very linear (I don't know how to explain). I don't want to be able to use number.subtract(3).add(5).value or databases().ids().users() and I don't want to have to have .value at the end.
var math = function (n) {
this.value = n;
this.add = function (x) {
this.value += x;
return this;
};
this.subtract = function (x) {
this.value -= x;
return this;
};
};
var number = new math(10);
console.log(number.add(5).subtract(3).value);
The node js module, moment, works the way I'd like my project to work. moment() returns Moment<...>, moment().hours() returns the hour and moment().format("YYYY/MM/DD") returns 2020/10/22.
You can do something like this if you want number capacity and limited string capacity:
class Database extends String {
valueOf() { return 2 }
users() { return { ids: () => [] } }
};
const database = (val) => new Database(val || "")
Then you will be able to do this database() + 2 === 4, all string methods will be available and you will be able to do this database().users().ids()

Javascript Array.prototype.filter() not working

I have this piece of code running on the client that filters a list of events:
if (res)
{
eventList.filter(function(event) {
const out = res.find(function(visibility) { return visibility.ID == event.id; }) == undefined;
return out;
});
alert(eventList);
}
displayEvents(eventList);
The problem is that even when out is false the element is not filtered out.
Just for debug I tried to return false in any case and the resulting array still had all the initial elements:
eventList.filter(function(event) {
return out;
});
What am I doing wrong here??
EDIT:
res is an array of JSON objects (containg only ID field) returned by the server, while eventList is a list of Facebook events, passed to this callback function from a Facebook API request
Array.prototype.filter does not change array inplace, it returns new array made of items that satisfies the provided predicate. It should look like this
var result = eventList.filter(function(event) {
return res.find(function(visibility) { return visibility.ID == event.id; }) === undefined;
});
You don't need to declare and assign variable and then return it from function, you can simply return expression

Best way to remove the object from an array using AngularJs

I have an array of objects. I want to remove multiple objects from that array.
I have used below code which is working absolutely fine, but I need to check with you guys if there is better way to do that or its fine.
I have done it with angularjs and js.
Orders is the main array on which operations are performed.
Order is array of selected items to remove from main array Orders
$scope.Order = {};
$scope.removeOrders = function () {
angular.forEach($scope.Order, function (data) {
for (var i = $scope.Orders.length - 1; i >= 0; i--) {
if ($scope.Orders[i].Name == data.Name) {
$scope.Orders.splice(i, 1);
}
}
});
}
You can make it quite a bit shorter using filter:
$scope.removeOrders = function () {
$scope.Orders = $scope.Orders.filter(function(order){
return !$scope.Order.some(function(remove){
return remove.Name === order.Name;
});
}); // remove the order from $scope.Orders, if it's name is found in $scope.Order
};

_.each find value in array return true or false. using underscore js

//find value in array using function checkValue using underscoreJS _.each.
//return true, else false.
var helloArr = ['bonjour', 'hello', 'hola'];
var checkValue = function(arg) {
_.each(helloArr, function(helloArr, index) {
if (arg[index] === index) {
return true;
}
return false;
});
};
alert(checkValue("hola"));
The problem with your code is that, _.each will iterate through all the elements of the array and call the function you pass to it. You will not be able to come to a conclusion with that, since you are not getting any value returned from it (unless you maintain state outside _.each).
Note that the values returned from the function you pass to _.each will not be used anywhere and they will not affect the course of the program in any way.
But, instead, you can use _.some as an alternate, like this
var checkValue = function(arg) {
return _.some(helloArr, function(currentString) {
return arg === currentString;
});
};
But, a better solution would be, _.contains function for this purpose. You can use it like this
var checkValue = function(arg) {
return _.contains(helloArr, arg);
};
But, since you have only Strings in the Array, the best solution would be to use Array.prototype.indexOf, like this
var checkValue = function(arg) {
return helloArr.indexOf(arg) !== -1;
};
Try this:
var helloArr = ['bonjour', 'hello', 'hola'];
var checkValue = function(arr, val) {
_(arr).each(function(value) {
if (value == val)
{return console.log(true);}
else {return console.log(false);}
});
};
console.log(checkValue(helloArr,'hello'));
/* Output
false
true
false*/

Subscribe to observable array for new or removed entry only

So yes I can subscribe to an observable array:
vm.myArray = ko.observableArray();
vm.myArray.subscribe(function(newVal){...});
The problem is the newVal passed to the function is the entire array. Is there anyway I can get only the delta part? Say the added or removed element?
As of KnockoutJS 3.0, there's an arrayChange subscription option on ko.observableArray.
var myArray = ko.observableArray(["Alpha", "Beta", "Gamma"]);
myArray.subscribe(function(changes) {
// For this example, we'll just print out the change info
console.log(changes);
}, null, "arrayChange");
myArray.push("newitem!");
In the above callback, the changes argument will be an array of change objects like this:
[
{
index: 3,
status: 'added',
value: 'newitem!'
}
]
For your specific problem, you want to be notified of new or removed items. To implement that using Knockout 3, it'd look like this:
myArray.subscribe(function(changes) {
changes.forEach(function(change) {
if (change.status === 'added' || change.status === 'deleted') {
console.log("Added or removed! The added/removed element is:", change.value);
}
});
}, null, "arrayChange");
Since I couldn't find any info on this elsewhere, I'll add a reply for how to use this with TypeScript.
The key here was to use the KnockoutArrayChange interface as TEvent for subscribe. If you don't do that, it'll try to use the other (non-generic) subscribe and will complain about status, index, and value not existing.
class ZoneDefinition {
Name: KnockoutObservable<String>;
}
class DefinitionContainer
{
ZoneDefinitions: KnockoutObservableArray<ZoneDefinition>;
constructor(zoneDefinitions?: ZoneDefinition[]){
this.ZoneDefinitions = ko.observableArray(zoneDefinitions);
// you'll get an error if you don't use the generic version of subscribe
// and you need to use the KnockoutArrayChange<T> interface as T
this.ZoneDefinitions.subscribe<KnockoutArrayChange<ZoneDefinition>[]>(function (changes) {
changes.forEach(function (change) {
if (change.status === 'added') {
// do something with the added value
// can use change.value to get the added item
// or change.index to get the index of where it was added
} else if (change.status === 'deleted') {
// do something with the deleted value
// can use change.value to get the deleted item
// or change.index to get the index of where it was before deletion
}
});
}, null, "arrayChange");
}
In order to only detect push() and remove() events, and not moving items, I put a wrapper around these observable array functions.
var trackPush = function(array) {
var push = array.push;
return function() {
console.log(arguments[0]);
push.apply(this,arguments);
}
}
var list = ko.observableArray();
list.push = trackPush(list);
The original push function is stored in a closure, then is overlayed with a wrapper that allows me do do anything I want with the pushed item before, or after, it is pushed onto the array.
Similar pattern for remove().
I am using a similar but different approach, keep track whether an element has been instrumented in the element itself:
myArray.subscribe(function(array){
$.each(array, function(id, el) {
if (!el.instrumented) {
el.instrumented = true;
el.displayName = ko.computed(function(){
var fn = $.trim(el.firstName()), ln = $.trim(el.lastName());
if (fn || ln) {
return fn ? (fn + (ln ? " " + ln : "")) : ln;
} else {
return el.email();
}
})
}
});
})
But it is really tedious and the pattern repeated across my code
None that I know of. Wanna know what I do? I use a previous variable to hold the value, something called selectedItem
vm.selectedItem = ko.observable({});
function addToArray(item) { vm.selectedItem(item); vm.myArray.push(item); }
So that way, when something happens to my observable array, I know which item was added.
vm.myArray.subscribe(function(newArray) { var addedItem = vm.selectedItem(item); ... }
This is really verbose, and assuming your array holds many kinds of data, you would need to have some sort of flags that helps you know what to do with your saved variables...
vm.myArray.subscribe(function(newArray) {
if ( wasUpdated )
// do something with selectedItem
else
// do whatever you whenever your array is updated
}
An important thing to notice is that you might know which item was added if you know whether push or unshift was used. Just browse the last item of the array or the first one and voila.
Try vm.myArray().arrayChanged.subscribe(function(eventArgs))
That has the added value when an item is added, and the removed value when an item is removed.

Categories