Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Fast HTML and XML markup in Ruby

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 dev
Octocat-spinner-32 lib
Octocat-spinner-32 spec
Octocat-spinner-32 .gitignore
Octocat-spinner-32 README.textile
Octocat-spinner-32 TODO.textile
Octocat-spinner-32 leaf.gemspec
README.textile

Leaf

Fast HTML and XML markup in Ruby

Synopsis


  class MyView
    include Leaf::View
  end
  
  MyView.new(:title => "Demo").render {
    html {
      div {
        div(:id => "header") {
          h1 { @title }}
        div {
          h2 { "Important Info" }
          a(:href => "/about") { "Learn More" }}}}}
  
  <html>
    <div>
      <div id="header">
        <h1>Demo</h1>
      </div>
      <div>
        <h2>Important Info</2>
        <a href="/about">Learn More</a>
      </div>
    </div>
  </html>

Leaf::View – Core Principles

Leaf views are pure Ruby, with method names corresponding to tags. Tag
methods take a hash of tag attributes. Methods for non-self-closing tags also
take a block, which can call other methods or evaluate to a literal
value to be added directly to the output.


  render {
    div(:id => "header") {
        h1 { "Title" }}}
      
  <div id="header">
    <h1>Title</h1>
  </div>

Leaf takes special care to render readable, indented
markup. How Leaf manages whitespace actually depends on the type of the
specific tags involved. There are 3 types of tags: self closing tags,
inline wrapping tags, and block wrapping tags. Examples of each:


  render { 
    img(:source => "http://google.com/logo.png")}
    
  <img source="http://google.com/logo.png" />
  
  render {
    span(:class => "mood") { _ "I am "; em(:class => "bad") { "hungry" }}}
    
  <span class="mood">I am <em class="bad">hungry</em></span>
  
  render {
    div(:class => "content") { p { "thinking" }}}
    
  <div class="content">
    <p>thinking</p>
  </div>

You can see a list of the default tags and their types in
/leaf/lib/leaf/defalt_tags.rb, which includes all HTML tags. You can
also define your own tags with .define_self_closing_tags,
.define_inline_wrapping_tags, and .define_block_wrapping_tags, all of which
are class methods on the classes including Leaf::View.


  class RssView
    include Leaf::View
    
    define_block_wrapping_tags  :rss, :channel, :item
    define_inline_wrapping_tags :guid, :link, :pubDate
  end

You can create dynamic templates using #assign, which sets instance
variables for the rendering template to consume.


  view = View.new
  view.assign(:foo => "bar", :biz => "bat")
  # or View.new(:foo => "bar", :biz => "bat")
  view.render { 
    div { "#{@foo}-#{@biz}" }}
    
  <div>
    bar-bat
  </div>

Sometimes you may want to just print values directly to the output. You may
need to do this when inserting text before other text that is surrounded by
tags, or when adding non-tagged text at the top of a template:


  render {
    span { _ "Some text "; em { "Some important text" }}}
  
  <span>Some text <em>Some important text</em><span>
  
  render {  
    _ %{<?xml version="2.0" ?>}
    rss {
      channel { "etc" }}}
    
  <?xml version="2.0" ?>
  <rss>
    <channel>
      etc
    </channel>
  </rss>

In both of the above examples above, the text in question would have been
omitted if #_ had not been used.

You also have to be careful about literal values being accidentally leaked into
the output. In particular, Enumerable methods like #each can give
unexpected results.


  render { 
    [1,2,3].each { |i| 
      em { i }}}
  
  <em>1</em>
  <em>2</em>
  <em>3</em>
  123

Notice the “123” at the bottom. You can avoid this problem by using the
built-in helper #guard, which wraps the inner block to prevent return
values from leaking.


  render {
    guard { 
      [1,2,3].each { |i| 
        em { i }}}}
  
  <em>1</em>
  <em>2</em>
  <em>3</em>

Note that multiple tagged blocks within a single outer block work just fine:


  render {
    div {
      span { "span"}
      span { "the" }
      span { "gap"}}}
    
  <div>
    <span>span</span>
    <span>the</span>
    <span>gap</span>
  </div>

Notice also the use of #render. This is necessary to start the rendering
process and to get a regular String returned out at the end. This should be
called only once, outside of all tag methods in the view.

The methods described above must be called in the context of an instance of
a class including the Leaf::View module. You can either do this directly,
as we do above, or by using the built-in template management system discussed
below.

Leaf::TemplateSpace – Template Inlining

Writing view methods inside Ruby class files is useful for small views,
but if you are writing lots of views you will want a system that lets you write
templates as stand-alone source files. Leaf provides such a system with
Leaf::TemplateSpace.

A template space is a namespace for views that brings together the built-in
Leaf methods, your helper methods, your template code, and the supporting
template framework code.

To use template spaces, get a new instance, add any appropriate helper
modules and template source modules, and render a template:


  # e.g. app/views/bar/bat.html.rb
  div {
    span { helper_method } 
    span { "my template" }
    span { @assigned     }}
  
  # e.g. app/helpers/my_helper.rb
  class MyHelper
    def helper_method
     "this helped"
    end
  end
  
  # your app file
  template_space = Leaf::TemplateSpace.new
  template_space.add_helper(MyHelper)
  template_space.add_template("app/views, "bar/bat.html.rb")
  template_space.render_template("bar/bat", :html, :assigned => "to render")
  
  <div>
    <span>this helped</span>
    <span>my template</span>
    <span>to render</span>
  </div>

#add_helper takes a literal Ruby helper module. Adding a helper like this
provides all of its instance methods to the other view code in the template
space.

#add_template takes two arguments that when joined give the full path to a
template to be added to the template space. The source of this template is read
in and then inlined into the template space for quick invocation later.

The second argument to #add_template determines the
name with which you can refer to this template from other methods. For example,
The call template_space.add_template("app/views, "bar/bat.html.rb") makes
available a template that we can refer to with the path "bar/bat" and the
format :html, as we do with our call to #render_template.

To access another template while rendering, use #template_method_name, with
takes the path argument mentioned above and returns the string corresponding to
the method name to call to invoke that view code.


  # app/views/biz/outer.html.rb
  span { "Some header text" }
  send(template_method_name("biz/inner"))
  span { "Some footer text"}
  
  # app/views/biz/inner.html.rb
  span { "Some inner text" }

  template_space.add_template("/app/views", "biz/outer.html.rb")
  template_space.add_template("/app/views", "biz/inner.html.rb")
  template_space.render_template("biz/outer", :html)
  
  <span>Some header text</span>
  <span>Some inner text</span>
  <span>Some footer text</span>

The methods alone are purposefully quite primitive, and mean to serve mostly as
the foundation for higher-level view frameworks. For example, you might start
by defining a #partial method:


  def partial(path, assigns = {})
    assing(assigns)
    send(template_method_name(path))
  end

Performance

Leaf achieves relatively high performance by using macro-like code generation
and by avoiding method_missing. Also, since the templates are pure Ruby, there
is no compilation needed, just eval’ing.

/dev/markaby_bench.rb indicates that Leaf is about 10 times faster than
Markaby.

Stability

Leaf is experimental alpha software; there may be bugs and the interface will
probably change in the future.

Something went wrong with that request. Please try again.