181

Following this question here :

Using the checked binding in knockout with a list of checkboxes checks all the checkboxes

I've created some checkboxes using knockout that allow selection from an array. working fiddle taken from above post:

http://jsfiddle.net/NsCXJ/

Is there a simple way of creating an array of just the fruit's ID's?

I'm more at home with C# where I would do something along the lines of selectedFruits.select(fruit=>fruit.id);

Is there some method/ready made function for doing something similar with javascript/jquery? Or would the simplest option be to loop through the list and create a second array? I intend to post the array back to the server in JSON so am trying to minimize the data sent.

Community
  • 1
  • 1
Chris Nevill
  • 5,922
  • 11
  • 44
  • 79

8 Answers8

284

Yes, Array.map() or $.map() does the same thing.

//array.map:
var ids = this.fruits.map(function(v){
    return v.Id;
});

//jQuery.map:
var ids2 = $.map(this.fruits, function (v){
    return v.Id;
});

console.log(ids, ids2);

http://jsfiddle.net/NsCXJ/1/

Since array.map isn't supported in older browsers, I suggest that you stick with the jQuery method.

If you prefer the other one for some reason you could always add a polyfill for old browser support.

You can always add custom methods to the array prototype as well:

Array.prototype.select = function(expr){
    var arr = this;
    //do custom stuff
    return arr.map(expr); //or $.map(expr);
};

var ids = this.fruits.select(function(v){
    return v.Id;
});

An extended version that uses the function constructor if you pass a string. Something to play around with perhaps:

Array.prototype.select = function(expr){
    var arr = this;

    switch(typeof expr){

        case 'function':
            return $.map(arr, expr);
            break;

        case 'string':

            try{

                var func = new Function(expr.split('.')[0], 
                                       'return ' + expr + ';');
                return $.map(arr, func);

            }catch(e){

                return null;
            }

            break;

        default:
            throw new ReferenceError('expr not defined or not supported');
            break;
    }

};

console.log(fruits.select('x.Id'));

http://jsfiddle.net/aL85j/

Update:

Since this has become such a popular answer, I'm adding similar my where() + firstOrDefault(). These could also be used with the string based function constructor approach (which is the fastest), but here is another approach using an object literal as filter:

Array.prototype.where = function (filter) {

    var collection = this;

    switch(typeof filter) { 

        case 'function': 
            return $.grep(collection, filter); 

        case 'object':
            for(var property in filter) {
              if(!filter.hasOwnProperty(property)) 
                  continue; // ignore inherited properties

              collection = $.grep(collection, function (item) {
                  return item[property] === filter[property];
              });
            }
            return collection.slice(0); // copy the array 
                                      // (in case of empty object filter)

        default: 
            throw new TypeError('func must be either a' +
                'function or an object of properties and values to filter by'); 
    }
};


Array.prototype.firstOrDefault = function(func){
    return this.where(func)[0] || null;
};

Usage:

var persons = [{ name: 'foo', age: 1 }, { name: 'bar', age: 2 }];

// returns an array with one element:
var result1 = persons.where({ age: 1, name: 'foo' });

// returns the first matching item in the array, or null if no match
var result2 = persons.firstOrDefault({ age: 1, name: 'foo' }); 

Here is a jsperf test to compare the function constructor vs object literal speed. If you decide to use the former, keep in mind to quote strings correctly.

My personal preference is to use the object literal based solutions when filtering 1-2 properties, and pass a callback function for more complex filtering.

I'll end this with 2 general tips when adding methods to native object prototypes:

  1. Check for occurrence of existing methods before overwriting e.g.:

    if(!Array.prototype.where) { Array.prototype.where = ...

  2. If you don't need to support IE8 and below, define the methods using Object.defineProperty to make them non-enumerable. If someone used for..in on an array (which is wrong in the first place) they will iterate enumerable properties as well. Just a heads up.

Johan
  • 35,120
  • 54
  • 178
  • 293
  • 2
    @ChrisNevill I added a string version as well in case you're intrested – Johan Sep 24 '13 at 18:59
  • @MUlferts Good catch, updated :). Nowadays I would suggest using lodash for these kinds of tasks. They expose the same interface as the code above – Johan Jan 31 '18 at 18:59
  • To support knockout observables: `return typeof item[property] === 'function' ? item[property]() === filter[property] : item[property] === filter[property];` – Linus Caldwell Feb 04 '18 at 23:02
  • @LinusCaldwell It's been a long time since I used knockout, but what about something like `return ko.unwrap(item[property]) === filter[property]`? – Johan Feb 05 '18 at 09:54
  • Well, I mentioned knockout, but of course this would cover all properties which are functions without required parameters. Besides, why would one break the generic style of your beautiful code? – Linus Caldwell Feb 05 '18 at 23:43
  • “map” is not the same as “select” in C#. Map returns a new array, Select returns an enumerable (delayed until iteration). – David Kiff Dec 04 '18 at 21:26
34

I know it is a late answer but it was useful to me! Just to complete, using the $.grep function you can emulate the linq where().

Linq:

var maleNames = people
.Where(p => p.Sex == "M")
.Select(p => p.Name)

Javascript:

// replace where  with $.grep
//         select with $.map
var maleNames = $.grep(people, function (p) { return p.Sex == 'M'; })
            .map(function (p) { return p.Name; });
Stefano Altieri
  • 4,550
  • 1
  • 24
  • 41
  • that's what i want..but what is more good in between your answer and Enumerable.From(selectedFruits).Select(function (fruit) { return fruit.id; }); – Bharat May 19 '16 at 06:58
20

The ES6 way:

let people = [{firstName:'Alice',lastName:'Cooper'},{firstName:'Bob',age:'Dylan'}];
let names = Array.from(people, p => p.firstName);
for (let name of names) {
  console.log(name);
}

also at: https://jsfiddle.net/52dpucey/

July.Tech
  • 1,336
  • 16
  • 20
18

Since you're using knockout, you should consider using the knockout utility function arrayMap() and it's other array utility functions.

Here's a listing of array utility functions and their equivalent LINQ methods:

arrayFilter() -> Where()
arrayFirst() -> First()
arrayForEach() -> (no direct equivalent)
arrayGetDistictValues() -> Distinct()
arrayIndexOf() -> IndexOf()
arrayMap() -> Select()
arrayPushAll() -> (no direct equivalent)
arrayRemoveItem() -> (no direct equivalent)
compareArrays() -> (no direct equivalent)

So what you could do in your example is this:

var mapped = ko.utils.arrayMap(selectedFruits, function (fruit) {
    return fruit.id;
});

If you want a LINQ like interface in javascript, you could use a library such as linq.js which offers a nice interface to many of the LINQ methods.

var mapped = Enumerable.from(selectedFruits)
    .select("$.id") // shorthand for `x => x.id`
    .toArray();
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
11

You can also try linq.js

In linq.js your

selectedFruits.select(fruit=>fruit.id);

will be

Enumerable.From(selectedFruits).Select(function (fruit) { return fruit.id;  });
Anik Islam Abhi
  • 25,137
  • 8
  • 58
  • 80
4

The most similar C# Select analogue would be a map function. Just use:

var ids = selectedFruits.map(fruit => fruit.id);

to select all ids from selectedFruits array.

It doesn't require any external dependencies, just pure JavaScript. You can find map documentation here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Kirill
  • 7,580
  • 6
  • 44
  • 95
3

I have build a Linq library for TypeScript under TsLinq.codeplex.com that you can use for plain javascript too. That library is 2-3 times faster than Linq.js and contains unit tests for all Linq methods. Maybe you could review that one.

Michael Baarz
  • 406
  • 5
  • 17
0

Take a peek at underscore.js which provides many linq like functions. In the example you give you would use the map function.

Gruff Bunny
  • 27,738
  • 10
  • 72
  • 59
  • 3
    If someone wants to know how they compare I made blog post which explains difference between more than 15 most popular LINQ/underscore.js functions: https://www.vladopandzic.com/javascript/comparing-underscore-js-with-linq/ – Vlado Pandžić Sep 13 '17 at 10:05