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;
});

[…]

Published on Thu, 12 Oct 2006 12:45
1 comment

BlogMarks Ruby Library

As said in title, I’m working on a ruby library to interact with BlogMarks.net API and I just release the first beta version (0.1.0).

This project amongst others is available on my projects dedicated website : Jonathan’s Projects, as well as on RubyForge under the name blogmarks. Publishing the project on rubyforge is a great thing because it will be automatically published on their gem server.

I don’t really know how many time it take for a new gem to be integrated in gem server, but as soon as it will be, you’ll be able to install it via :

sudo gem install blogmarks

As you should see on my projects page (and soon on this page), I did a simple Typo Sidebar to show how one can use the library to display his last BlogMarks.[…]

Published on Mon, 30 Jan 2006 10:33
0 comments

Prototype's Shortcuts

Prototype is a wonderful javascript library written by Sam Stephenson.

The more I use it, the more I love it, it makes writting javascript so less painful.

This post is the first of a series aiming to discover its
features.

So let’s start with the lovely shortcuts provided by Prototype.

Shortcuts are functions whose names are as concise as possible, mostly 1 or 2 letters and wrap some of the most repetitive tasks you end up to write when doing javascript.
[…]

Published on Fri, 20 Jan 2006 10:17
2 comments

RSS