Implementing Prototype’s Array Methods in jQuery
read 18 commentsOne 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.
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:
- var arr = [1,2,3,4,5,6];
- 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:
- var arr = [1,2,3,4,5,6];
- $.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.
- var arr = [1,null,2,3,4,5,6];
- var arr2 = $.protify(arr)
- .compact()
- .findAll(function(a) {
- return a>=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.
- var arr = [1,2,3,4,5,6];
- $.protify(arr, true);
- arr.all(); // true
any()
returns true if even one member of the array == true and returns false if every member != true.
- var arr = $.protify([1,2,3,4,5,6]);
- 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.
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.
- var arr = [1,2,3,4,5,6];
- $.protify(arr, true);
- arr.eachSlice(2); // [[1,2],[3,4],[5,6]]
- arr.eachSlice(2, function (n) {
- return item * 2;
- });
- }); // [[2,4],[6,8],[10,12]]
include(value)
returns true if the value is in the array (using == equality).
- var arr = [1,2,3,4,5,6];
- $.protify(arr, true);
- arr.include(3); // true
- 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.
- $.protify([{'name': 'frank', 'age': 10},{'name': 'joe', 'age': 12}])
- .max(function(person) { return person.age });
- // 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.
- $.protify([1,2,3,4,5,6]).partition(function (n) {
- return n <= 3;
- });
- // [[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.
- var arr = [{'name': 'Frank', 'age': 10}, {'name': 'Joe', 'age': 12}];
- $.protify(arr).pluck('age'); // [10, 12]
- $.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?
















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:
This is awesome! Very nice piece of work - thank you!
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:
@ 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.
Very nice piece of work - thank you!
Am I using this wrong?
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.
@Lee Roberson - That is the correct fix, that line wasn't doing anything before. Thanks for the contribution!
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
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(...)
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.
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; <<<<!!!!!!!!!!!!
}));
},
may be add:
keys: function(obj) {
var result = [];
$.each(obj||{}, function(index, value) { result.push(index); });
return $.protify(result);
},
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.
In jquery.protify_0.3.js
Notice that in the
injectiterator 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 extendvalueChange to
$.protify(value,true);Code as stands was not assigning anything, and javascript error
value.flatten is not a functionwas 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.1I 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 theinvokemethod, with a little fix-up I made. Consider: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 mycloneNode()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 thatcloneNodeand jQuery'sclonemethod are very handy. But how to apply a native DOM method likeappendChildagainst all nodes in a collection, usinginvoke()?appendChildis 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
invokeimplementation, to see just how mynodeargument was passing through it. And sure enough, there was no cloning, mynodewas a jumping bean through the DOM field. Mynode(within theinvokemethod it hides insideargswhich is a nativeArray) was simply hopping from element to element, and plopping itself into the final one. So I changedargsto$(args).clone()within themapiteration 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?
In retrospect, I'm not so sure if my approach to this isn't a little flawed.
appendChildshould be a node mover, not creating new nodes and adding to the total node count. Not on it's own. And my change toinvokechanges the expected behavior ofappendChild, making it look like a node generator instead. I guess unless there is some other usefulness to callingappendChildwith the same node across a collection of nodes through the DOM,$.protify(array_of_nodes).invoke("appendChild",node)just doesn't make sense.