Prototype's Enumerable

My previous posts about Prototype were focused on shortcuts and functions to work on forms. This one is all about the Enumerable object.

Well, Enumerable is one of those terrific objects of Prototype. Basically it allows you to add enumeration to your objects or add prototype’s way to do it to standard javascript objects. But as javascript doesn’t allow you to redefine the way your object (or standard object) implements enumeration, it broke the standard javascript enumeration. As you should know, javascript allows you to enumerate through an object properties ( with the “for in” syntax) and takes care to step over unwanted properties, for example :

var myArray = [1,2,3,4]; // Let's build an array object

for ( i in myArray ) {
  console.info(i);
}

// Output to firebug console:
// 1 
// 2 
// 3
// 4

As you’ve seen, even if Array is an object, the enumeration step over methods to only gives you values. If you do the same thing with Prototype library loaded, you’ll get the following :

  var myArray = [1,2,3,4]; // Let's build an array object
  
  for ( i in myArray ) {
    console.info(i);
  }
  
  // Output to firebug console:
  // 0
  // 1
  // 2
  // 3
  // each
  // all
  // any
  // collect
  // detect
  // findAll
  // ...

So I let you imagine how this can break existing code. That’s why so many other javascript framework developers don’t like Prototype’s way of doing and in some way they are right, this is a constraint of Prototype you should be aware of : Prototype don’t play nicely with others.

Then, after this little precautions let’s examine what Prototype’s Enumerable objects can give us.
All you need, to add enumerability to your objects, is to define a function _each which takes a function as parameter and call this function with each iterable elements of your objects.

For example if your objects stores its elements in an Array, you can make your objects enumerable like this :

var MyObject = Class.create();
MyObject.prototype = {
  initialize: function() {
    this.elements = $A(arguments);
  }
  
  // The magic function :)
  _each: function(iterator) {
    for ( var i = 0; i < this.elements.length; i++)
      iterator(this.elements[i]);
  }
};
// Extends MyObject with Enumerable functions
Object.extend(MyObject.prototype, Enumerable);

Now our new object have the following methods :

  • each()
  • all()
  • any()
  • collect()
  • detect()
  • findAll()
  • grep()
  • include()
  • inject()
  • invoke()
  • max()
  • min()
  • partition()
  • pluck()
  • reject()
  • sortBy()
  • toArray()
  • zip()
  • inspect()

Nice for a single method added, isn’t it ?
Let’s see what we can do with this :

// Let's create a new MyObject object :)
var anObject = new MyObject(3,5,4);

// Iterate through values
anObject.each( function(element) {
  doSomething(element);
});

// Verify if all our values are lesser than 6
// return true
anObject.all( function(value) {
  return value < 6;
});

// Verify if any value is greater than 4
// return true
anObject.any( function(value) {
  return value > 4;
});

// Get an array containing all values as string prefixed by 'id_'
// return ['id_3','id_5','id_4']
valuesAsId = anObject.collect( function(value) {
  return 'id_' + value;
});

// Get the first element greater than 3
// return 5
anObject.detect( function(value) {
  return value > 3;
});

// Find all elements greater than 3
// return [5,4]
anObject.findAll( function(value) {
  return value > 3;
});

// Find all elements matching a given pattern
// here we assume our initialization was anObject = new MyObject('jonathan', 'tron');

// return ['jonathan']
anObject.grep(/h/)

// return ['JONATHAN']
anObject.grep(/h/, function(value){
  return value.toUpperCase();
});

// Find if 3 is an element of our object
// return true
anObject.include(3);

// Get the sum of our elements + 10
// return 22
anObject.inject(10, function(sum, value) {
  return sum + value;
});

// Want to call a method on each elements ?
// return ["3","5","4"]
anObject.invoke('toString');

// Want to call a method on each elements but this time with params ?
// return ["11","101","100"]
anObject.invoke('toString', 2);

// Get the greatest element
// return 5
anObject.max();

// Get the smallest element
// return 3
anObject.min();

// Separate the elements in two groups :
// those matching a condition and those no matching it
// return [ [3], [5,4] ]
anObject.partition( function(value) {
  return value <= 3;
});

// Get an array of a particular properties of each element
// here we assume our initialization was :
// anObject = new MyObject( {firstname: 'jonathan', lastname: 'tron'}, 
//                          {firstname: 'sam', lastname: 'stephenson'});
// return ['jonathan','sam']                            
anObject.pluck('firstname');

// Return all element for whose the given function return true
// return [3]
anObject.reject(function(value) {
  return value != 3;
});

// Sort using the given compare function
// here we assume our initialization was :
// anObject = new MyObject( {firstname: 'jonathan', lastname: 'tron'}, 
//                          {firstname: 'sam', lastname: 'stephenson'});
// return [{firstname: 'sam', lastname: 'stephenson'},{firstname: 'jonathan', lastname: 'tron'}]
anObject.sortBy(function(value) {
  return value.lastname.toLowerCase();
});

// Get a new array of elements
// return [3,5,4]
anObject.toArray();

// And the last one, I did not have any need for it by now, but I can imagine it could be helpful when dealing with some sort of columns and rows
// here we assume our initialization was :
// anObject = new MyObject( [1, 2, 3] );
// return [ [1,4,7], [2,5,8], [3,6,9] ]
anObject.zip([4,5,6], [7,8,9]);

But there’s more, what if you want to enumerate but stop the enumeration at some point or skip an iteration ?

Prototype defines two objects for this : $break and $continue.

// Iterate over our values but break if value is 2
[1,2,3].each( function(value) {
  if ( value == 2 ) throw $break;
});

// Iterate over our values but skip to next element if value is 2
[1,2,3].each( function(value) {
  if ( value == 2 ) throw $continue;
});

Thu, 12 Oct 2006 14:45 Posted in

  1. By Thoms 01/02/2007 at 11h43


    great, horny, nice, I’ll use it. ;-)

Comment Prototype's Enumerable


RSS