Block handling view helpers

Over the past few years, my code has become much more DRY. Until recently though, I’ve found my HTML difficult to refactor. Inside, we’ll take a look at how to use view helpers to DRY up complex HTML.

In the past, I created a lot of HTML that looks like this:

 <div class="faq_entry">
  <div class="title">
    <%= faq.title %>
  </div>
  <div class="body">
    <%= faq.body %>
  </div>
 </div>

That doesn’t look so bad. It’s only a few lines of code and the markup is clean. Of course, it’s also somewhat hard to style. If I wanted the title to have nice rounded corners on my title, I might need to add a few more divs. That’s not too bad if I only use this display for FAQs, but what if I use it all over my application? This bit me in the past. It took me hours to find all of the places that used a certain style of box and then to add some additional divs. Now, I delegate to helpers.

I normally start simple. Most of my block helpers look something like this:

  <% fade_box "Title here","blue" do %>
    Content goes here
  <% end %>

Now, my view just tells what style of box is used. I am free to change the layout in just one place. It’s much cleaner. But how do we actually make that work? I’m going to jump right to the code, then we’ll walk through how it works.

    def fade_box(title,color="orange",&block)
      inner=capture(&block)
      title=content_tag :div,title,:class=>"title" 
      body=content_tag(:div,inner,:class=>"body")
      body=content_tag :div,"#{title}#{body}",:class=>"fade_box #{color}"
      concat body,block.binding
    end

I know that code is a little dense, but it isn’t as bad as it appears. The first and last lines are the tricky ones. In Rails, the capture helper grabs the contents of a block and returns it as a string. It executes the template code inside the body of the block and expands all of the templates. In our example above, that returns the “Content goes here” string.

Once we have the content for our block, we build a div for the title and then one for the body of our box. The Rails content_tag helper builds HTML tags with the passed in content. Attributes for the tags can be passed as the final parameter.

Once we have our title and our body, we wrap the whole thing in another &lt;div&gt; tag.

Finally, we use the Rails concat helper to add our content to the output. Concat is basically the opposite of capture. Instead of grabbing the content from a block, it adds content to the existing output. Any time you have a helper that is enclosed in <% %> tags (instead of <%= %>) you will need to use concat to display the output.

Once we have a helper function for displaying this style of box, we can easily change the way our HTML looks. We’ve seperated the meaning from the display, just as we wanted to do. We could easily replace our title div with 4 nested divs to give nice rounded corners, or to make our code look nice in IE6.

We can even build helpers on top of this code. For example, if we knew that all of our FAQs should be in an orage fade_box, we could write and FAQ helper like:

  def faq(title,&block)
    fade_box(title,"orange",&block)
  end

Later on, we could easily change all of our FAQs to use some other style of display if we wanted to.