Wednesday, January 17, 2007

Appending to ERB output from a block declaration

I've recently been writing up some material and using ERB to handle the majority of my formatting needs. For example, the code below shows what my template may start with.
<%= title "Appending to ERB output" %>
I use ERB to generate both .doc and .html files. If I generate an html file the above template will produce the html found below.
<title>Appending to ERB output</title>
This is very straightforward stuff easily handled by ERB. I use classes and bindings for my needs, but the example below is a simplified version that should get the point across.
require 'erb'

template = <<eos
<%= title "Appending to ERB output" %>
eos

def title(text)
"<title>#{text}</title>"
end

puts ERB.new(template).result
I recently ran into a different situation that took a bit more effort. In the material I was writing up I needed a sidebar. I thought that the best way to represent the sidebar in my template was to use a block. Using a block ensures that the sidebar, when generating html, will be properly closed by requiring an "end".
<%= title "Appending to ERB output" %>

<% sidebar do %>
sidebar content
<% end %>
Adding the content from the sidebar to the output is easily achieved by calling "yield".
require 'erb'

template = <<eos
<%= title "Appending to ERB output" %>

<% sidebar do %>
sidebar content
<% end %>
eos

def sidebar
yield
end

def title(text)
"<title>#{text}</title>"
end

puts ERB.new(template).result
However, the above implementation hasn't appended any tags to mark the beginning or end of the sidebar. Based on the way that the title method works you might assume that the following would work.
require 'erb'

template = <<eos
<%= title "Appending to ERB output" %>

<% sidebar do %>
sidebar content
<% end %>
eos

def sidebar
"<div border=1>#{yield}</div>"
end

def title(text)
"<title>#{text}</title>"
end
But, the above code does not produce any div tags. This is because <%= .. %> mark ruby expressions that are replaced with the result of the expression, and we used <% .. %> which is used to evaluate ruby code but not append.

A way to solve this issue is to tell ERB where to store the output. ERB allows you to specify, as a constructor argument, a variable to store the output in. This allows you to append to the output from within a block declaration.
require 'erb'

template = <<eos
<%= title "Appending to ERB output" %>

<% sidebar do %>
sidebar content
<% end %>
eos

def sidebar
@output << "<div border=1>"
yield
@output << "</div>"
end

def title(text)
"<title>#{text}</title>"
end

ERB.new(template, nil, nil, "@output").result

puts @output
The above code produces the following result.
  <title>Appending to ERB output</title>

<div border=1>
sidebar content
</div>

3 comments:

  1. I like the wat it was solved in Rails: it has capture helper that extracts output generated by block from ERb output buffer and returns it as string.

    Using this helper, sidebar method could look like this:

    def sidebar(&block)
    content = capture(&block)
    "<div>#{content}</div>"
    end

    It uses ERb black magic too, but it allows to keep this magic in a separate place.

    ReplyDelete
  2. Anonymous12:48 AM

    Sorry a little of topic but what blog tempelete are u using ?

    ReplyDelete
  3. Anonymous8:15 AM

    The template I'm using is a modified version of Herbert.

    ReplyDelete

Note: Only a member of this blog may post a comment.