Fork me on GitHub

Make sure to check out the RDoc Documentation for more details on the API.

Erector User Guide

1. The Basics

The basic way to construct some HTML/XML with erector is to subclass Erector::Widget and implement a content method:

class Hello < Erector::Widget
  def content
    html {
      head {
        title "Hello"
      }
      body {
        text "Hello, "
        b "world!"
      }
    }
  end
end
<html>
  <head>
    <title>Hello</title>
  </head>
  <body>
  Hello, <b>world!</b>
  </body>
</html>

Once you have a widget class, you can instantiate it and then call its to_html method. If you want to pass in parameters (aka 'assigns' or 'locals' in Rails parlance), then do so in the constructor's default hash. This will make instance variables of the same name, with Ruby's '@' sign.

class Email < Erector::Widget
  def content
    a @address, :href => "mailto:#{@address}"
  end
end

>> Email.new(:address => "foo@example.com").to_html
=> "<a href=\"mailto:foo@example.com\">foo@example.com</a>"

(If you want control over which locals are valid to be passed in to a widget, use the needs macro.)

2. Mixin

If all this widget stuff is too complicated, just do

include Erector::Mixin

and then call erector { } from anywhere in your code. It will make an inline widget for you, pass in the block, and call to_html on it. And if you pass any options to erector, like :prettyprint => true, it'll pass them along to to_html!

Examples:

erector { a "lols", :href => "http://icanhascheezburger.com/" }
=> "<a href=\"http://icanhascheezburger.com/\">lols</a>"

erector(:prettyprint => true) do
  ol {
    li "bacon"
    li "lettuce"
    li "tomato"
  }
end
=> "<ol>\n  <li>bacon</li>\n  <li>lettuce</li>\n  <li>tomato</li>\n</ol>\n"

3. Pretty-printing

Erector has the ability to insert newlines and indentation to make the generated HTML more readable. Newlines are inserted before and after certain tags.

To enable pretty-printing (insertion of newlines and indentation) of Erector's output, do one of the following:

  • call to_pretty instead of to_html on your Erector::Widget
  • pass :prettyprint => true to to_html
  • call enable_prettyprint(true) on your Erector::Widget. Then subsequent calls to to_html will prettyprint
  • call Erector::Widget.prettyprint_default = true (for example, in environments/development.rb in a rails application, or anywhere which is convenient)

4. Classes and IDs

Because HTML tends to heavily use the class and id attributes, it is convenient to have a special syntax to specify them.

body.sample!.helpful "Hello, world!"
body class="helpful" id="sample"

Most CSS and javascript tends to write classes and IDs with hyphens (for example nav-bar instead of nav_bar). Therefore, erector has a setting to convert underscores to hyphens.

Erector::Widget.hyphenize_underscores = true
body.my_id!.nav_bar "Hello, world!"
body class="nav-bar" id="my-id"

You can put the setting of hyphenize_underscores anywhere it is convenient, for example config/application.rb in a rails application. For compatibility with erector 0.9.0, the default is false, but this is likely to change to true in a future version of erector, so explicitly set it to false if you are relying on the underscores.

5. Erector tool: Command-line conversion to and from HTML

We've written a little tool that will help you erect your existing HTML app. The 'erector' tool will convert HTML or HTML/ERB into an Erector class. It ships as part of the Erector gem, so to try it out, install the gem, then run

erector app/views/foos/*.html.erb

or just

erector app/views

and then delete the original files when you're satisfied.

See the Erector on Rails Guide for more details on converting a Rails app.

On the erector-to-html side, pass in the --to-htmloption and some file names and it will render the erector widgets to appropriately-named HTML files. We're actually using erector to build this Erector documentation web site that you're reading right now. Check out the 'web' directory and the 'web' task in the Rakefile to see how it's done.

6. Page Layout Inheritance

Erector replaces the typical Rails layout mechanism with a more natural construct, the use of inheritance. Want a common layout? Implement a layout superclass and have your page class inherit from it and override methods as needed.

For example:

class MyAppPage < Erector::Widget
  def content
    html {
      head {
        title "MyApp - #{@page_title}"
        css "myapp.css"
      }
      body {
        div.navbar {
          navbar
        }
        div.main {
          main
        }
        div.footer {
          footer
        }
      }
    }
  end

  def navbar
    a "MyApp Home", :href => "/"
  end

  def main
    p "This page intentionally left blank."
  end

  def footer
    p "Copyright (c) 2112, Rush Enterprises Inc."
  end
end
class Faq < MyAppPage
  def initialize
    super(:page_title => "FAQ")
  end

  def main
    p "Q: Why is the sky blue?"
    p "A: To get to the other side"
  end

  def navbar
    super
    a "More FAQs", :href => "http://faqs.org"
  end
end

Notice how this mechanism allows you to...

  • Set instance variables (e.g. title)
  • Override sections completely (e.g. render_body)
  • Append to standard content (e.g. render_navbar)
  • Use standard content unchanged (e.g. render_footer)

all in a straightforward, easily understood paradigm (OO inheritance). (No more weird yielding to invisible, undocumented closures!)

Check out Erector::Widgets::Page for a widget that does a lot of this for you, including rendering externals in the HEAD element.

7. Inline Widgets

Instead of subclassing Erector::Widget and implementing a content method, you can pass a block to Erector.inline and get back a widget instance you can call to_html on. For example:

hello = Erector.inline do
  p "Hello, world!"
end
hello.to_html          #=> <p>Hello, world!</p>
This lets you define mini-widgets on the fly.

If you're in Rails, your inline block has access to Rails helpers if you pass a helpers object to to_html:

image = Erector.inline do
  image_tag("/foo")
end
image.to_html(:helpers => controller)          #=> <img alt="Foo" src="/foo" />

Note that inline widgets are usually redundant if you're already inside an Erector content method. You can just use a normal do block and the Erector methods will work as usual when called back from yield. Inline widgets get evaluated with instance_eval which may or may not be what you want. See the section on blocks in this user guide for more detail.

One extra bonus feature of inline widgets is that they can call methods defined on the parent class, even though they're out of scope. How do they do this? Through method_missing magic. (But isn't method_missing magic against the design goals of Erector? Yes, some would say so, and that's why we're reserving it for a special subclass and method. For Erector::Widget and subclasses, if you pass in a block, it's a plain old block with normal semantics.) But they can't directly access instance variables on the parent, so watch it.

8. Needs

Named parameters in Ruby are fun, but one frustrating aspect of the 'options hash' technique is that the code is less self-documenting and doesn't 'fail fast' if you pass in the wrong parameters, or fail to pass in the right ones. Even simple typos can lead to very annoying debugging problems.

To help this, we've added an optional feature by which your widget can declare that it needs a certain set of named parameters to be passed in. For example:

class Car < Erector::Widget
  needs :engine, :wheels => 4
  def content
    text "My #{@wheels} wheels go round and round; my #{@engine} goes vroom!"
  end
end
This widget will throw an exception if you fail to pass :engine => 'V-8' into its constructor. (Actually, it will work with any engine, but a V-8 is the baddest.)

See the rdoc for Widget#needs for more details. Note that as of version 0.7.0, using needs no longer automatically declares accessor methods.

9. Externals

Erector's got some nice tags, like script and style, that you can emit in the content method of your widget. But what if your widget needs something, say a JavaScript library, that should be included not in the main page, but inside the head section?

Externals are a way for your widget to announce to the world that it has an external dependency. It's then up to another widget to emit that dependency while it's rendering the head.

Here's an example:

class HotSauce < Erector::Widget
  depends_on :css, "/css/tapatio.css"
  depends_on :css, "/css/salsa_picante.css", :media => "print"
  depends_on :js, "/lib/jquery.js"
  depends_on :js, "/lib/picante.js"

  def content
    p.hot_sauce {
      text "esta salsa es muy picante!"
    }
  end
end
Then when Page emits the head it'll look like this:
<head>
  <meta content="text/html;charset=UTF-8" http-equiv="content-type" />
  <title>HotPage</title>
  <link href="/css/tapatio.css" media="all" rel="stylesheet" type="text/css" />
  <link href="/css/salsa_picante.css" media="print" rel="stylesheet" type="text/css" />
  <script src="/lib/jquery.js" type="text/javascript"></script>
  <script src="/lib/picante.js" type="text/javascript"></script>
</head>

It also collapses redundant externals, so if lots of your widgets declare the same thing (say, 'jquery.js'), it'll only get included once.

Page looks for the following externals:
:js included JavaScript file
:css included CSS stylesheet
:script inline JavaScript
:style inline CSS style
It might be a little difficult to remember the difference between :js and :script, and between :css and :style, so I'm thinking of maybe unifying them and looking at the content to determine whether it's inline or not. (Something simple like, if it includes a space, then it's inline.) Good idea? Let us know on the erector mailing list!

Instead of a string, you can also specify a File object; the file's contents get read and used as text. This allows you to inline files instead of referring to them, for potential performance benefits. Example:

    depends_on :style, File.new("#{File.dirname(__FILE__)}/../public/sample.css")

10. Blocks

Erector is all about blocks (otherwise known as closures). Unfortunately, there are some confusing aspects to working with blocks; this section aims to clarify the issues so if you find yourself stuck on an 'undefined method' or a nil instance variable, at least you'll have some context to help debug it.

There are basically three cases where you can pass a block to Erector:

1. To an element method

This is the normal case that provides the slick HTML DSL. In the following code:

class Person < Erector::Widget
  def content
    div {
      h3 @name
      p {
        b "Birthday: "
        span @birthday
      }
    }
  end
end

the blocks passed in to div and p are evaluated using normal yield semantics, and the @name and @birthday instance variables are evaluated in the context of the Person instance being rendered.

So far, so good.

2. To the constructor of an Erector::Widget

In this case you can build a widget "on the fly" and have it render whatever it wants, then call your block. This is useful for widgets like Form which want to wrap your HTML in some of their own tags.

class PersonActions < Erector::Widget
  needs :user
  def content
    div {
      widget(Form.new(:action => "/person/#{@user.id}", :method => "delete") {
        input :type => "submit", :value => "Remove #{@user.name}"
      })
      widget(Form.new(:action => "/person/#{@user.id}/email", :method => "post") {
        b "Send message: "
        input :type => "text", :name => "message"
        input :type => "submit", :value => "Email #{@user.name}"
      })
    }
  end
end

In this case, you will get two form elements, each of which has some boilerplate HTML for emitting the form element, emitting the hidden _method input tag in the case of the delete method, then calling back into your widget to emit the contents of the form. In this case, as above, the @user instance variable will be sought inside the calling widget(PersonActions), not the called widget(Form).

A quirk of this technique is that methods inside the block will be called on the calling widget, not the called widget. This doesn't cause any problems for element methods (b and input above), but may be confusing if you want the block to be able to call methods on the target widget. In that case the caller can declare the block to take a parameter; this parameter will point to the nested widget instance.

widget(Form.new(:action => "/person/#{@user.id}", :method => "delete") do |f|
  span "This form's method is #{f.method}"
  input :type => "submit", :value => "Remove #{@user.name}"
end)

(As a variant of this case, note that thewidget method can accept a widget class, hash and block, instead of an instance; in this case it will set the widget's block and this code:

widget Form, :action => "/person/#{@user.id}", :method => "delete" do
  input :type => "submit", :value => "Remove #{@user.name}"
end
will work the same as the version above.)

3. To the constructor of an Erector::InlineWidget

This is where things get hairy. Sometimes we want to construct a widget on the fly, but we're not inside a widget already. So any block we pass in will not have access to Erector methods. In this case we have a special subclass called Erector::InlineWidget which uses two magic tricks: instance_eval and method_missing to accomplish the following:

  • inside the block, self points to the widget, not the caller.
  • methods will be looked for first on the inline widget, and then on the caller.
  • instance variables will be looked for on the inline widget only. This can be the source of many a nil! As a general rule, you should probably stay away from instance variables when using inline widgets. However...
  • Bound local variables will still be in scope. This means you can "smuggle in" instance variables via local variables. For example:
    local_name = @name
    Page.new do
      div local_name
    end.to_html
    

When using the mixin, you get an inline widget, so the above list of tricks applies.


One note for developers: when creating a widget like Form that needs to call back to its block, use the method call_block, which calls the block and passes in self as appropriate for both inline and normal widgets.