More Ruby in Prototype

—Tuesday, September 26 2006

It’s obvious that Ruby has been a big influence to Prototype. We’ve already talked about Enumerables, Arrays, and Hashes, and how powerful they are. Now, lets look at some new tools: inGroupsOf and eachSlice.

Convenience matters

Have you ever tried to build a table programatically with a certain number of cells per row? The resulting code can look a bit ugly and certainly leaves something to be desired. Back in March, the Rails team introduced in_groups_of to Ruby. Since that time, I’ve put it to good use. Here is an RHTML snippet that we use in the Mephisto admin:

<table id="files">
<% @assets.in_groups_of(5) do |assets_line| -%>
<tr>
  <% assets_line.each do |asset| %>
   <td>
     <%= render :partial => 'asset', :object => asset if asset %>
    </td>
   <% end %>
</tr>
<% end %>
</table>

The code above builds a table of assets in rows of 5. So basically, it takes 5 assets at a time and passes them to the block. When inside the block, each individual asset is looped over creating the columns for the tables.

in_groups_of goes Javascript

In the latest SVN version of Prototype, Sam introduced inGroupsOf. It’s similar to it’s Ruby counterpart, with the exception that you don’t pass an iterator to it. Instead you have to run it through each. Assuming we were doing the same thing–building a table, how would we accomplish that with inGroupsOf? Lets see.

<table>
  <thead>
    <th>First</th>
    <th>Second</th>
    <th>Third</th>
  </thead>
  <tbody id="numbers"></tbody>
</table>

We’re gonna take the HTML above, and programatically build the table from a group of numbers. Here is a look at the Javascript:

var tbody = $('numbers');
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
numbers.inGroupsOf(3, 0).each(function(group) {
 var tr = document.createElement('tr');
 group.each(function(num) {
   var td = document.createElement('td');
   td.innerHTML = num;
   tr.appendChild(td);
 });
 tbody.appendChild(tr);
});

When you run the code above, it will spit out a table like this:

table

Basically we’re taking the numbers array, and dividing it into groups of 3. The 0 passed as the second argument is a filler string that is used to fill the final group if it doesn’t contain enough entries. Next, we create a tr element, and then do an inner loop which creates the td elements with the correct number, and appends it to the tr. Finally, we break out of the inner loop and append our tr onto our tbody element.

each_slice goes Javascript

inGroupsOf uses eachSlice to do some of it’s dirty work. Both of the methods are very similar, with the exception that eachSlice takes an iterator, and doesn’t pad the last slice. If we had an ObjectRange object (better known at $R in Prototype) and wanted to divide it up into slices of two, we’d do this:

$R(0, 10).eachSlice(2, function(slice) {
  console.log(slice);
});

//Output
[0,1]
[2,3]
[4,5]
[6,7]
[8,9]
[10]

The first argument is the size of the slices, and the second one is the iterator, or block. Note how the last slice isn’t padded? Finally, the iterator function can also pass the loop index if you need it:

$R(0, 10).eachSlice(2, function(slice, index) {
  console.log(slice, index);
});

//Output
[0,1] 0
[2,3] 1
[4,5] 2
[6,7] 3
[8,9] 4
[10] 5

And there you have it, the new features freshly baked into Prototype.

I also want to mention that I spoke to soon about XPath in Prototype. While no one has concocted a solution for IE (which was my main opposition to XPath support), document.getElementsByClassName now uses XPath in browsers that support it and it’s significantly faster than before.