I am using some of the Knockout utility functions described brilliantly here: http://www.knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
I want to do an arrayMap to select certain properties based on a condition, e.g.
return ko.utils.arrayMap(myObservableArray(), function (item) {
return item.Label;
});
Say for example this produces the following output:
[null, "", "SomeLabel", null, "SomeOtherLabel"]
I want to select the properties based on a condition, so I try:
return ko.utils.arrayMap(myObservableArray(), function (item) {
if (item.Label && item.Label !== "") {
return item.Label;
}
});
However then you end up with an array like:
[undefined, undefined, "SomeLabel", undefined, "SomeOtherLabel"]
I've also tried this:
return ko.utils.arrayMap(myObservableArray(), function (item) {
return (item.Label && item.Label !== "") ? item.Label : false;
});
but you get:
[false, false, "SomeLabel", false, "SomeOtherLabel"]
So I am then having to do:
var itemsWithLabels = ko.utils.arrayFilter(myObservableArray(), function (item) {
return (item.Label && item.Label !== "");
});
return ko.utils.arrayMap(itemsWithLabels, function (item) {
return item.Label;
});
Which will give me:
["SomeLabel", "SomeOtherLabel"]
Is there a more efficient way of accomplishing this, in one shot, using ko.utils or similar?
As you noticed, with ko.utils.arrayMap it is expected your callback returns something. SO this is a 'dumb function' that always appends the return value of the callback to the array. Returning undefined, null or false does not omit the value from the resulting array.
arrayFilter allows for no way to modify the filtered item: the original item will be pushed to the result array.
So in short, this cannot be done more efficiently with the ko.utils.array* functions. You could combine them and make the code a bit more verbose, perhaps even put them in a computed:
var itemsWithLabels = ko.computed(function () {
return ko.utils.arrayMap(ko.utils.arrayFilter(myObservableArray(), function (item) {
return item.Label && item.Label.length;
}), function (filteredItem) {
return filteredItem.Label;
});
});
But this is the best you can do. I chose to first apply the filter, and afterwards do the mapping, because it seems to me the mapping would be more expensive than the filtering. But that's just a hunch.
Maybe a library such as Underscore provides methods to do this directly.
It's also fairly easy to write such a method yourself (and possible put it in ko.utils if you wish)
ko.utils.arrayMapFilter = function (array, mapping) {
array = array || [];
var result = [], mapResult;
for (var i = 0, j = array.length; i < j; i++) {
mapResult = mapping(array[i]);
if (mapResult) {
result.push(mapResult);
}
}
return result;
},
your mapping callback can now return falsy values such as 0, "", false, null or undefined, and they won't end up in the array.
If you want to permit a few of the above values anyway (such as 0 or ""), then just change the line:
if (mapResult)
to something more strict, like:
if (mapResult !== undefined && mapResult !== null)
Hans's second method can be shortened a little to
ko.utils.arrayMapFilter = function (array, mapping) {
var result = [], mapResult;
ko.utils.arrayForEach(ko.unwrap(array || []), function (item) {
mapResult = mapping(item);
if (mapResult) {
result.push(mapResult);
}
});
return result;
};
which will work on either a plain array or an observableArray
(returning a plain array in both cases). It's worth timing it since some JS engines' internal implementations of foreach
(which ko.utils.arrayForEach
uses if available) are alleged to be slower than using a for
loop on the array, though the difference is likely to be small.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With