V1.3: HTML5


This helper makes available an HTML5 generator that is template engine independent. It’s a private method for views and layouts called #html.

Usage

This is how it will look used with a layout:

module Web
  module Views
    class ApplicationLayout
      include Web::Layout

      def sidebar
        html.aside(id: 'sidebar') do
          div 'hello'
        end
      end
    end
  end
end
<%= sidebar %>

It generates:

<aside id="sidebar">
  <div>hello</div>
</aside>

Features

  • It knows how to close tags according to HTML5 spec (1)
  • It accepts content as first argument (2)
  • It accepts builder as first argument (3)
  • It accepts content as block which returns a string (4)
  • It accepts content as a block with nested markup builders (5)
  • It builds attributes from given hash (6)
  • it combines attributes and block (7)
# 1
html.div # => <div></div>
html.img # => <img>

# 2
html.div('hello') # => <div>hello</div>

# 3
html.div(html.p('hello')) # => <div><p>hello</p></div>

# 4
html.div { 'hello' }
# =>
#<div>
#  hello
#</div>

# 5
html.div do
  p 'hello'
end
# =>
#<div>
#  <p>hello</p>
#</div>

# 6
html.div('hello', id: 'el', 'data-x': 'y') # => <div id="el" data-x="y">hello</div>

# 7
html.div(id: 'yay') { 'hello' }
# =>
#<div id="yay">
#  hello
#</div>

It supports complex markup constructs, without the need of concatenate tags. In the following example, there are two div tags that we don’t need link together.

html.section(id: 'container') do
  div(id: 'main') do
    p 'Main content'
  end
  
  div do
    ul(id: 'languages') do
      li 'Italian'
      li 'English'
    end
  end
end

# =>
#  <section id="container">
#    <div id="main">
#      <p>Main Content</p>
#    </div>
#
#    <div>
#      <ul id="languages">
#        <li>Italian</li>
#        <li>English</li>
#      </ul>
#    </div>
#  </section>

The result is a very clean Ruby API.

Custom tags

Hanami helpers support 100+ most common tags, such as div, video or canvas. However, HTML5 is fast moving target so we wanted to provide an open interface to define new or custom tags.

The API is really simple: #tag must be used for a self-closing tag, where #empty_tag does the opposite.

html.tag(:custom, 'Foo', id: 'next') # => <custom id="next">Foo</custom>
html.empty_tag(:xr, id: 'next')      # => <xr id="next">

The link_to helper, while appearing similar to tags generated by html, has different semantics and will not appear as part of the generator, as one might expect.

Since the return value of link_to is a string, it must be treated as such when combined with the generator offered by html.

Alone in a tag, the return value of link_to can be used as the content for the tag.

html.div do
  link_to('hello', routes.root_path, class: 'btn')
end
# => <div>
# =>   <a href="/" class="btn">hello</a>
# => </div>

Multiple link_to can be concatted together, which returns a string, and then acts as the content of the tag.

html.div do
  link_to('Users', routes.users_path, class: 'btn') +
    link_to('Books', routes.books_path, class: 'btn')
end

If you require multiple link_to inside the same tag, and there are other tags inside that tag, the return value of link_to will need to be passed to one of the html generator helpers.

This might be a block or inline tag such as div or span, but if no wrapping element is desired, the text helper can be used to render the link as-is.

html.div do
  text link_to('Users', routes.users_path, class: 'btn')
  hr
  text link_to('Books', routes.books_path, class: 'btn')
end

# => <div>
# =>   <a href="/users" class="btn">Users</a>
# =>   <hr>
# =>   <a href="/posts" class="btn">Books</a>
# => </div>

Auto escape

The tag contents are automatically escaped for security reasons:

html.div('hello')         # => <div>hello</div>
html.div { 'hello' }      # => <div>hello</div>
html.div(html.p('hello')) # => <div><p>hello</p></div>
html.div do
  p 'hello'
end # => <div><p>hello</p></div>



html.div("<script>alert('xss')</script>")
  # =>  "<div>&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</div>"

html.div { "<script>alert('xss')</script>" }
  # =>  "<div>&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</div>"

html.div(html.p("<script>alert('xss')</script>"))
  # => "<div><p>&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</p></div>"

html.div do
  p "<script>alert('xss')</script>"
end
  # => "<div><p>&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</p></div>"

HTML attributes aren’t automatically escaped, in case we need to use a value that comes from a user input, we suggest to use #ha, which is the escape helper designed for this case. See Escape Helpers for a deep explanation.

View Context

Local variables from views are available inside the nested blocks of HTML builder:

module Web
  module Views
    module Books
      class Show
        include Web::View

        def title_widget
          html.div do
            h1 book.title
          end
        end
      end
    end
  end
end
<div id="content">
  <%= title_widget %>
</div>
<div id="content">
  <div>
    <h1>The Work of Art in the Age of Mechanical Reproduction</h1>
  </div>
</div>