Sunday, November 30, 2008

Sharing a partial for both server-side and client-side HTML generation.

Say I have a page that shows a table of items (like a shopping cart), and I want to be able to generate the HTML for this table from the server-side (of course). Let's also say that I want to have client-side javascript be able to dynamically add items to this table without a round-trip to the server, based on data gathered on the client. What I end up with is some HTML code (actually, ERb or Haml) that defines the layout of a row in this table, and a duplicate piece of code in javascript that generates identical HTML. The server-side code uses a nice templating language like Haml to fill in the variable pieces, while the client-side code pieces together HTML embedded in strings concatenated with the dynamic data.

Not very DRY. Not easy to maintain. Not pretty to look at.

So...What's a good solution that allows me to write the HTML once, and have one place to maintain it, while still being able to use it to dynamically evaluate the template based on parameterized data on both the server and client sides?

Below is a solution that I came up with. (And I would love to hear any better ideas).

For brevity, I'll use a very simple HTML structure in these examples, but in practice I have used this with much more complex structure and logic. Here's what our example table looks like:

<table id="items">
<td class="name">Shirt</td>
<td class="desc">White short-sleeved shirt</td>
<td class="price">$5.99</td>

<td class="name">Pants</td>
<td class="desc">Blue pants with zipper</td>
<td class="price">$25.99</td>

Assume we have this built using Haml, and there are two partials: one for the top-level table, and one for an individual row. E.g.:


- items.each do |item|
= render :partial => 'item_row', :locals => {:item => item}


%td.desc= item.desc
%td.price= item.price

In the client-side javascript in our top-level Haml partial, assume we have some function that gets called from some user input action (like filling out a form and clicking a button). E.g.:

var addItem = function(name, desc, price) {
var html =
"<tr>" +
" <td class=\"name\">" + name + "</td>" +
" <td class=\"desc\">" + desc + "</td>" +
" <td class=\"price\">" + price + "</td>" +
$('items').insert(html); // using prototype.js
}; this doesn't seem so bad with such a simple example, but imagine that the HTML structure for a table row is something much more complex, with conditionals based on the item, and maybe even usage of additional sub-partials. This maintenance for this becomes a nightmare.

This first thing I am going to do is change the item_rows.haml partial. Instead of having it actually render the item's values, it's going to render template replacement variables using #{...} syntax. Note, these are NOT interpolated in the partial. We'll do that in a separate phase from rendering:


%tr #{item_name}
%td.desc #{item_desc}
%td.price #{item_price}

Next, let's make a couple helper methods. The first one returns this partial rendered as a string. (I do this because in the real world, this takes a number of parameters, and adding a helper layer makes things easier to manage than always calling render directly). The second one renders this partial and then interpolates the #{...} strings inside it, based on the properties of the item we want to render -- effectively making this equivalent to rendering the original version of this template.


module ItemHelper
# Make ActionController::Base#render_to_string available.
helper_method :render_to_string

def item_row_template
render_to_string :partial => item_row

def item_row(item)
# Get the template.
t = item_row_template

# Define the template replacement variables.
item_name =
item_desc = item.desc
item_price = item.price

# Evaluate the template
eval(t.inspect.gsub("\\#", "#"))

This, of course, means we change the top-level partial to use this new item_row helper, like so:


- items.each do |item|
= item_row(item)

At this point, we've got a functionally equivalent server-side. But the big benefit is that now the HTML template returned by the item_row_template helper method can also be used to do the same substitutions on the client side, thanks to the handy Template class in prototype.js. The Haml that defines the javascript addItem method now looks like this:

var addItem = function(name, desc, price) {
var html = new Template(
item_name : name,
item_desc : desc,
item_price : price

Now, when the markup for the item row changes, it only needs to change in one place, and both the server-side and client-side benefit from it. This certainly feels more DRY...but also seems like a hefty price to pay in terms of complexity...

Monday, November 17, 2008

Bug in Haml Partial Rendering?

I have stumbled across what I think is a bug in rendering a Haml partial in Ruby on Rails. The issue is that when I use render(:partial => '...', :locals => {...}), and one of the locals I define is a Boolean, it seems that if I pass a value of false, that value erroneously gets converted to nil. This causes a problem with a partial that wants to treat false, true, and nil as meaning different things.

Consider the following example:

I have a simple TestController:

class TestController < ApplicationController
def index

Then, the haml template for that controller:

%title haml partial test
%b Rendering a Haml Partial
= render :partial => 'test_haml', :locals => {:flag1 => true, :flag2 => false}

%b Rendering an RHTML Partial
= render :partial => 'test_rhtml', :locals => {:flag1 => true, :flag2 => false}

Notice that partial renders two partials. One is a haml partial, one is an rhtml partial. The rthml one behaves as I would expect, the haml one does not.

Here is what the haml partial looks like:

- if flag1 == false
flag1 is false
- elsif flag1 == true
flag1 is true
- elsif flag1 == nil
flag1 is nil
- else
= "flag1 is #{flag1.inspect}"


- if flag2 == false
flag2 is false
- elsif flag2 == true
flag2 is true
- elsif flag2 == nil
flag2 is nil
- else
= "flag2 is #{flag2.inspect}"

Here is what the rhtml partial looks like:

<% if flag1 == false %>
flag1 is false
<% elsif flag1 == true %>
flag1 is true
<% elsif flag1 == nil %>
flag1 is nil
<% else %>
flag1 is <%= flag1.inspect %>
<% end %>

<% if flag2 == false %>
flag2 is false
<% elsif flag2 == true %>
flag2 is true
<% elsif flag2 == nil %>
flag2 is nil
<% else %>
flag2 is <%= flag2.inspect %>
<% end %>

Here is what the output looks like:

Notice the discrepancy between the output of the haml partial versus the rhtml partial, with respect to flag2.

This is gonna turn out to be a real drag on a project that I am converting from rhtml to haml that has a number of partials that assume that false means false and not nil.

Anybody out there have any thoughts on this?