10
X

Implementing Prototype’s Array Methods in jQuery

read 18 comments

One of the biggest concerns I've heard mentioned from users of the Prototype library about jQuery is the lack of support for various array methods. The robust features Prototype provides for arrays is of great benefit to developers that do a lot of array manipulation in their JavaScript.

However, I find that after moving to jQuery, I do less array manipulation than I had done with Prototype. Perhaps jQuery has altered my development pattern so I no longer need array manipulation, or perhaps I have shifted most of my data manipulation to the server. Whatever the case, I have only on occasion missed the Prototype array methods.

While there is some overlap in the ways that jQuery and Prototype handle array manipulation, jQuery does a few things Prototype doesn't do, and Prototype does a number of things that jQuery doesn't. I began writing an article about these differences, but soon got side-tracked writing a jQuery plugin to mimic the array methods Prototype provides.

Continue Reading Below

You can find the plugin at http://code.google.com/p/jquery-protify-js/

With this plugin, you can give a particular array all of the methods that Prototype adds to their Array and Enumerable objects. You can use the methods two different ways. The first does not extend the original array:

JavaScript:
  1. var arr = [1,2,3,4,5,6];
  2. var protArray = $.protify(arr);

This will return an array extended with the Prototype library's methods, but leave the original array untouched.

The second extends the original array by passing in true as the second parameter:

JavaScript:
  1. var arr = [1,2,3,4,5,6];
  2. $.protify(arr, true);

In either case, the JavaScript Array prototype is untouched. When you create new arrays, it will not have the new methods. This way of writing code adds the new methods when they are needed while leaving the underlying JavaScript prototypes untouched. The return value of the methods is an array with the extended methods so that they can be chained.

JavaScript:
  1. var arr = [1,null,2,3,4,5,6];
  2. var arr2 = $.protify(arr)
  3.               .compact()
  4.               .findAll(function(a) {
  5.                 return a>=3;
  6.               })
  7.               .first();  // 3

The above code first returns the arr array values in a new array with the Prototype methods, removes any null/undefined values (using compact), finds all the values in the array greater than or equal to 3 and returns an extended array of them (using findAll), and finally returns the first value of that array (using first). It does all of this without touching the prototype for all arrays.

Here are some useful Prototype array methods included in the plugin:

all()

returns true if every member of the array == true and returns false if even one member != true.

JavaScript:
  1. var arr = [1,2,3,4,5,6];
  2. $.protify(arr, true);
  3. arr.all();  // true

any()

returns true if even one member of the array == true and returns false if every member != true.

JavaScript:
  1. var arr = $.protify([1,2,3,4,5,6]);
  2. arr.any(); // true

map()

similiar to jQuery's $.map. It executes the function passed in on every of member of the array and returns an array of the results.

JavaScript:
  1. $.protify([1,2,3,4,5,6]).map(function(n) {
  2.   return n * 2;
  3. });  //  [2,4,6,8,10,12]

eachSlice(number)

slices the array into an array of arrays of the size passed in. There is an optional second parameter which is a function that acts on each array partition before placing it in the slice.

JavaScript:
  1. var arr = [1,2,3,4,5,6];
  2. $.protify(arr, true);
  3. arr.eachSlice(2);  // [[1,2],[3,4],[5,6]]
  4. arr.eachSlice(2, function (n) {
  5.   return n.map(function(item) {
  6.     return item * 2;
  7.   });
  8. }); // [[2,4],[6,8],[10,12]]

include(value)

returns true if the value is in the array (using == equality).

JavaScript:
  1. var arr = [1,2,3,4,5,6];
  2. $.protify(arr, true);
  3. arr.include(3); // true
  4. arr.include('pie'); // false

max()/min()

returns the maximum/minimum value of the array. Optional parameter is a function that can be used to define the comparison and what gets returned.

JavaScript:
  1. $.protify([{'name': 'frank', 'age': 10},{'name': 'joe', 'age': 12}])
  2.   .max(function(person) { return person.age });
  3. // returns 12

partition()

returns an array with two arrays in it. The first array contains the values that == true in the initial array, and the second all of the values != true. There is an optional parameter that is a function used to do the evaluation.

JavaScript:
  1. $.protify([1,2,3,4,5,6]).partition(function (n) {
  2.   return n <= 3;
  3. });
  4. // [[1,2,3],[4,5,6]]


pluck()

And finally, my favorite. The pluck() method iterates through every member of the array and returns an array of the value of the attribute name passed in.

JavaScript:
  1. var arr = [{'name': 'Frank', 'age': 10}, {'name': 'Joe', 'age': 12}];
  2. $.protify(arr).pluck('age'); // [10, 12]
  3. $.protify(arr).pluck('name'); // ['Frank, 'Joe']

For a full listing and description of all the methods, see the Prototype documentation on arrays and enumerables.

Arrays: http://prototypejs.org/api/array

Enumerables: http://prototypejs.org/api/enumerable

The toJSON method has not yet been implemented because it depends on object methods that Prototype provides. Can you guess what my next project is?


comment feed

18 comments

  1. Or you could just use Xavier Shay's excellent jQuery Enumerable plugin. Although it doesn't have all those methods, it's syntax is alot simplier, IMHO:

    squares = $([1,2,3]).collect(function () {
      return this * this;
    } // => [1, 4, 9]
    
    sum = $([1,2,3]).inject(0, function (accumulator) {
      return accumulator + this;
    } // => 6
    
    // Sum is actually provided as a convenience function
    $([1,2,3]).sum() // => 6
  2. This is awesome! Very nice piece of work - thank you!

  3. Very nice! I can definitely see quite a few of these being useful! Thank you.

    Not sure if it's open to contributions but here's a slightly terser version of your min and max methods:

    
        max: function(fn) {
          return Math.max.apply(null, $.map(this, fn || function(v){return v;}));
        },
      
        min: function(fn) {
          return Math.min.apply(null, $.map(this, fn || function(v){return v;}));
        }
    
  4. Josh Powell

    @ Chris Lloyd - The syntax is simpler once you know what it does, but would be very difficult for someone else to figure out from just looking at the code. I can just imagine someone seeing it and looking through the jQuery documentation to figure out what it does and not getting anywhere.

    @John - Thanks :)

    @James - I'm definitely open to contributions, thank you.

  5. Very nice piece of work - thank you!

  6. Lee Roberson

    Am I using this wrong?

    		
    var arr = [1,2,3,4,5,1,2,3,4,4,4,3,2,1];
    var protArray = $.protify(arr).uniq();
    

    Yields:
    array.include is not a function
    if (0 === index || (sorted ? arr...t() != value : !array.include(value))) {

    If I modify ln 297 to protify (array) permanently, it works fine.

    Great code. Thanks a million.

    • Josh Powell

      @Lee Roberson - That is the correct fix, that line wasn't doing anything before. Thanks for the contribution!

  7. Mina

    hi,I m neophytes in jquery so I have not much idea about array and I need to use indexes of array of data return by JSON, need some guidlines,,thanks

  8. If you find $.protify(...) a little long to sprinkle through your code, you might add this function:

    function $A(array) {
    return $.protify(array)
    }

    $A(myArray).findAll(...) looks a little better than $.protify(myArray).findAll(...)

  9. Birthday Flowers

    Thanks for sharing. I came across this website looking for solution about array created in JS and extraction data from this array in jquery. Question is: how to extract data in jquery from array like this: var tab = new Array(); tab['f1']="foo"; tab['f2']="bar"; is there any jquery function to read this array? I tried $.each, but not working ... :(

    • Hey Birthday Flowers,

      Two things. One, create arrays like this:

      var tab = [];

      instead of:
      var tab = new Array()

      Secondly, what you've created there looks like an associative array, but is actually an array with no members and the properties of f1 and f2. You can accomplish the same thing by creating an object and setting its properties.

      var tab = {};
      tab.f1 = "foo";
      tab.f2 = "bar";

      In other words the following two statements are identical:

      tab.f1 = "foo";
      tab['f1'] = "foo";

      The main difference in usage being that you can use a variable property name using the second notation.

      var baz = 'f1';
      var tab[bav] = 'foo'; //sets tab.f1 to "foo"

      Now, to answer your question, you can iterate through them by doing:

      for (each in tab) {
      console.log(each);
      console.log(tab[each]);
      }

      There are lots of quid pro quo's in doing that, just google around.

  10. Pilot

    Seems error:

    now:

    compact: function() {
    return $.protify(this.select(function(value) {
    return value !== null;
    }));
    },

    should be:

    compact: function() {
    return $.protify(this.select(function(value) {
    return value !== null && value !== undefined; <<<<!!!!!!!!!!!!
    }));
    },

  11. Pilot

    may be add:

    keys: function(obj) {
    var result = [];
    $.each(obj||{}, function(index, value) { result.push(index); });
    return $.protify(result);
    },

  12. klachko

    Hi... thanks, looks very useful.
    Little question: What is the advantage of "leaving the underlying JavaScript prototypes untouched" instead of extending the array prototype and make the calls simpler?

    • An excellent question. The most important difference for me is that extending the native prototype is changing the built in capabilities of a built in class. It would be like modifying a Java array and not calling it something else. Then, when other people who aren't familiar with your custom methods come in, they will not be able to understand or look up what is going on quite as easy. If you instead uses the array prototype to make a new kind of object with the array as a base and added your functions to that, I would be ok with it. Of course, that would mean you can't do var a = [];

      Another downside is that you can break other javascript libraries, you could be limiting your options as to what you can you and who can use your code. If you both implement a trim function on a string, then there is contention about which one gets used. They most might behave differently and it can break things.

      Another downside is that what about future browsers and versions of js? It could implement your function and now if you were not careful in implenting your code, you will overwrite the built in functionality. An early version of the Prototype library added methods to the base Object prototype and this ended up breaking code for people that used for each in loops to iterate over an objects attributes. They've subsequently removed that method.

      So, those are the primary reasons.

  13. Randy

    In jquery.protify_0.3.js

    flatten: function() {
      return this.inject([], function(array, value) {
          $.protify(value); 
          return $.protify(array.concat($.isArray(value) ?
          value.flatten() : [value]));
      });
    }
    

    Notice that in the inject iterator function body

    $.protify(value);

    This extends value.slice() but does not assign it to anything, the slice is discarded. What is actually needed is to extend value

    Change to

    $.protify(value,true);

    Code as stands was not assigning anything, and javascript error value.flatten is not a function was being thrown at the following statement.

    After the change outlined above

    $.protify([ [1,2,3], [4,5,6], [7,8,9] ]).flatten() correctly returned [1,2,3,4,5,6,7,8,9] with no error. Tested using Firefox 7.0.1

    • Randy
          invoke: function(method) {
            var args = $.makeArray(arguments).slice(1);
            return this.map(function(value) {
       //     return value[method].apply(value,args);
              return value[method].apply(value,$(args).clone());
            });
          },
      
      

      I love jQuery's .clone() method, and find myself using it over and over when working with DOM-attached nodes. The above snippet is the implementation of the invoke method, with a little fix-up I made. Consider:

      var node = document.createTextNode("TEST").cloneNode(false);
      $.protify($("#foo div")).invoke("appendChild", node);
      

      The result I wanted was for all the <div> nodes inside $("#foo") to be injected with TEST, but what I got was TEST inside the last <div> and nothing inside the previous ones. Looking closer I saw that my cloneNode() ahead of time was pretty useless, because it didn't matter. Any node (don't matter where it came from) that gets attached to the DOM becomes very self-centered, and you can only jump it around from place to place in the DOM, much like playing checkers :) In situations like that cloneNode and jQuery's clone method are very handy. But how to apply a native DOM method like appendChild against all nodes in a collection, using invoke()? appendChild is a node yanker, and you are not "seeding the row", you are pulling one seed along with you as you go down it :)

      So I scratch my head and look at the invoke implementation, to see just how my node argument was passing through it. And sure enough, there was no cloning, my node was a jumping bean through the DOM field. My node (within the invoke method it hides inside args which is a native Array) was simply hopping from element to element, and plopping itself into the final one. So I changed args to $(args).clone() within the map iteration function, and now it works as I think it should. It works for this case but I'm not so sure if the cloning I added in wouldn't break other's code.

      Any thoughts on that?

      • Randy

        In retrospect, I'm not so sure if my approach to this isn't a little flawed. appendChild should be a node mover, not creating new nodes and adding to the total node count. Not on it's own. And my change to invoke changes the expected behavior of appendChild, making it look like a node generator instead. I guess unless there is some other usefulness to calling appendChild with the same node across a collection of nodes through the DOM, $.protify(array_of_nodes).invoke("appendChild",node) just doesn't make sense.

10 Pings

  1. [...] Read the original post: Implementing Prototype’s Array Methods in jQuery [...]

  2. [...] Implementing Prototype’s Array Methods in jQuery [...]

  3. [...] Implementing Prototype’s Array Methods in jQuery [...]

  4. [...] the content loads into the relevant container instead of having to navigate to another page. 84. Implementing Prototype’s Array Methods in jQuery – This technique gives a particular array all the methods that Prototype adds to their Array and [...]

  5. 100 Popular jQuery Examples, Plugins and Tutorials

    [...] the content loads into the relevant container instead of having to navigate to another page. 84. Implementing Prototype’s Array Methods in jQuery – This technique gives a particular array all the methods that Prototype adds to their Array and [...]

  6. [...] the content loads into the relevant container instead of having to navigate to another page. 84. Implementing Prototype’s Array Methods in jQuery – This technique gives a particular array all the methods that Prototype adds to their Array and [...]

  7. Edno360 WordPress » Blog Archive » 100 Popular jQuery Examples, Plugins and Tutorials

    [...] the content loads into the relevant container instead of having to navigate to another page. 84. Implementing Prototype’s Array Methods in jQuery – This technique gives a particular array all the methods that Prototype adds to their Array and [...]

  8. [...] the content loads into the relevant container instead of having to navigate to another page. 84. Implementing Prototype’s Array Methods in jQuery – This technique gives a particular array all the methods that Prototype adds to their Array and [...]

  9. [...] Implementing Prototype’s Array Methods in jQuery – This technique gives a particular array all the methods that Prototype adds to their Array and [...]

  10. [...] 84. Implementing Prototype’s Array Methods in jQuery –  This technique gives a particular array all the methods that Prototype adds to their Array and Enumerable objects. [...]

Sorry, but comments for this entry are now closed.