Haml - The Rails 4 Way (2014)

The Rails 4 Way (2014)

Chapter 12. Haml

HAML gave us a great take on how views can also be done. It looks a little cryptic at first, but don’t let that shake you off. Once you internalize the meaning of %, #, and . it should be all good (and you already know most just from CSS). […] Additionally, I can’t help but have respect for a Canadian who manages to swear more than I did during my vendoritis rant and drink beer at the same time. A perfect example of the diversity in the Rails community. Very much part of what makes us special.66

—David (talking about Haml and Hampton Catlin in September 2006)

Haml67 is a “whitespace-sensitive” HTML templating engine that uses indentation to determine the hierarchy of an HTML document. Haml was created because its creator, Hampton Catlin, was tired of having to type markup, and wanted all of his output code to be beautifully formatted. What he invented was a new templating engine that removed a lot of noisy boilerplate, such as angle brackets (from ERb), and did away with the need to close blocks and HTML tags.

We love Haml because it’s truly minimal, allowing a developer to focus simply on the structure of the page, and not on the content. Today it’s a common to keep view logic out of your templates, but that directive has been a guiding principle of Haml since its beginning. According to the 2012 Ruby Survey68, 36.96% of Rubyists prefer Haml over ERb, and 15.84% demand it in their projects. Haml is also the standard templating engine at various professional Ruby agencies, such as Hashrocket, Envy Labs, Remarkable Labs, and Astrails.

In this chapter, we’ll cover the fundamentals of Haml, from creating HTML elements, to using filters to create other kinds of textual content embedded in your document.

12.1 Getting Started

To start using the Haml template language over ERb in your project, first add the haml-rails gem to your Gemfile and run bundle install.

# Gemfile

gem 'haml-rails'

The benefit of using haml-rails over simply the haml gem is it adds support for Rails specific features. For instance, when you use a controller or scaffold generator, haml-rails will generate Haml views instead of using the Rails default of ERb. The haml-rails gem also configures Haml templates to work with Rails 4 cache digests out of the box.

12.2 The Basics

In this section, we’ll cover how to create HTML elements and attributes using Haml.

12.2.1 Creating an Element

To create an HTML element in Haml, one simply needs to prefix the percent character % to an element name. The element name can be any string, allowing you to use newly added HTML5 elements, such as header.

Haml

%header content

HTML

<header>content</header>

Haml will automatically handle generating opening and closing tags for the element on compilation. Not only does this make templates more concise and clean, it also eliminates common errors such forgetting to not close an HTML tags.

12.2.2 Attributes

Attributes in Haml are defined using two styles. The first style involves defining attributes between curly braces ({}). These attribute “brackets” are really just Ruby hashes, and are evaluated as such. Because of this, local variables and ruby logic can be used when defining attributes.

%a{ title: @article.title, href: article_path(@article) } Title

The second style follows the more traditional way of defining HTML attributes using brackets. Note that attributes are separated by white space, not commas.

%a(title=@article.title href=article_path(@article)) Title

tip

Multiline Attributes

Attribute hashes can be separated on multiple lines for readability. All newlines must be placed right after the comma:

1 %a{ title: @article.title,

2 href: article_path(@article) } Title

12.2.2.1 Data Attributes

Introduced with HTML 5, data attributes allow custom data to be embedded in any HTML element by prefixing an attribute with data-. Instead of littering the attribute hash with multiple attribute keys prefixed with data-, one can define all their data attributes in a nested hash associated with the key :data, like this:

Haml

%article{ data: { author_id: 1 } } Lorem Ipsum...

HTML

<article data-author-id='123'>Lorem Ipsum...</article>

Note that underscores are automatically replaced with a hyphen. Not that you’d want to, but you can change this behavior by setting the Haml configuration option hyphenate_data_attrs to false. (Haml configuration options are covered in detail later in this chapter.)

It’s also possible to nest data hashes more than one level, to reduce verbosity when attributes share common roots.

Haml

%article{ data: { author: {id: 1, name: "Kevin Wu" } } Lorem Ipsum...

HTML

<article data-author-id='123' data-author-name='Kevin Wu'>Lorem Ipsum...</article>

12.2.2.2 Boolean Attributes

In HTML, there exists certain attributes that do not have a value associated with them, such as required.

<input type="text" required>

These are referred to as boolean attributes in Haml, since their value does not matter, only that they’re present. To represent these attributes in using the hash-style attribute syntax, set the value of the attribute to true.

%input{ type: 'text', required: true }

Otherwise, if you’re using the HTML attribute style syntax, a boolean value doesn’t have to be set at all.

%input(type="text" required)

XHTML

If the format of Haml is set to :xhtml, boolean attributes will be set to their name. To illustrate, given the above example, Haml would render the following HTML:

<input type="text" required="required" />

12.2.3 Classes and IDs

Haml was designed to promote the DRY principle (not repeating code unnecessarily.) As such, it provides a shorthand syntax for adding id and class attributes to an element. The syntax is borrowed from CSS, where ids are represented by a pound (#) and classes by a period (.). Both of these signs must be placed immediately after the element and before an attributes hash.

Haml

1 #content

2 .entry.featured

3 %h3.title Haml

4 %p.body Lorem Ipsum...

HTML

1 <div id='content'>

2 <div class='entry featured'>

3 <h3 class='title'>Haml</h3>

4 <p class='body'>Lorem Ipsum...</p>

5 </div>

6 </div>

As the above example shows, multiple class names can be specified in similarly to CSS, by chaining the class names together with periods. In a slightly more complicated scenario, the shortcut CSS style class and id syntax can be combined with long-hand attributes. Both values are merged together when compiled down to HTML.

Haml

%article.featured{ class: @article.visibility }

HTML

<article class='feature visible'>...</article>

Haml has some serious tricks up its sleeves for dealing with complex id and class attributes. For instance, an array of class names will automatically be joined with a space.

Haml

%article{ class: [@article.visibility, @article.category] }

HTML

<article class='visible breakingnews'>...</article>

Arrays of id values will be joined with an underscore.

Haml

%article{ id: [@article.category, :article, @article.id] }

HTML

<article id='sports_article_1234'>...</article>

Note that the array is flattened and any elements that evaluate to false or nil will be dropped automatically. This lets you do some pretty clever tricks at the possible expense of readability and maintainability.

1 %article{ class: [@article.visibility,

2 @article.published_at < 4.hours.ago && 'breakingnews'] }

In the example, if the article was published less than 4 hours ago, then breakingnews will be added a one of the CSS classes of the element. While we’re on the subject, remember that it is advisable to migrate this kind of logic into your Ruby classes. In this particular example, we might give the Article class (or one of its presenters or decorator classes) a breakingnews? method, and use it instead of inlining the business logic.

1 def breaking?

2 published_at < 4.hours.ago

3 end

%article{ class: [@article.visibility, @article.breaking? && 'breakingnews'] }

If breaking? returns false, then the Ruby expression short circuits to false, and Haml ignores that particular class name.

12.2.4 Implicit Divs

The default elements of Haml are divs. Since they are used so often in markup, one can simply define a div with a class or id using . or # respectively.

Haml

1 #container

2 .content Lorem Ipsum...

HTML

1 <div id="container">

2 <div class="content">

3 Lorem Ipsum...

4 </div>

5 </div>

Implicit Div Creation

Not having to specify div tags explicitly helps your markup to be more semantic from the start, placing focus on the intention of the div instead of treating it as just another markup container. It’s also one of the main reasons that we recommend Haml over ERB. We believe that Haml templates lessen mental burden by communicating the structure of your DOM in way that maps cleanly to the CSS that will be applied to the document.

12.2.5 Empty Tags

In HTML, there are certain elements that don’t require a closing tag, such as br. By default, Haml will not add a closing tag for the following tags:

· area

· base

· br

· col

· hr

· img

· input

· link

· meta

· param

To illustrate, consider the following example:

%hr

would render HTML

<hr>

or XHTML

<hr/>

Adding a forward slash character (/) at the end of a tag definition causes Haml to to treat it as being an empty element. The list of empty tags Haml uses can be overridden using the autoclose configuration setting. Haml configuration options are covered in detail later in this chapter.

12.3 Doctype

A doctype must be the first item in any HTML document. By including the characters !!! at the beginning of a template, Haml will automatically generate a doctype based on the configuration option :format, set to :html5 by default. Adding !!! to a template would result in the following HTML:

<!DOCTYPE html>

Haml also allows the specifying of a specific doctype after !!!. A complete listing of supported doctypes can be found on Haml’s reference website69.

12.4 Comments

There are two types of comments in Haml, those that appear in rendered HTML, and those that don’t.

12.4.1 HTML Comments

To leave a comment that will be rendered by Haml, place a forward slash (/) at the beginning of the line you want commented. Anything nested under that line will also be commented out.

Haml

/ Some comment

HTML

<!-- Some comment -->

You can use this feature to produce Internet Explorer conditional comments by suffixing the condition in square brackets like this:

/[if lt IE 9]

12.4.2 Haml comments

Besides conditional comments for targeting Internet Explorer, comments left in your markup are meant to communicate a message to other developers working with the template. These messages should not be rendered to the browser as they are specific to your team. In Haml, starting a line with -# ensures any text following the pound sign isn’t rendered at all.

Haml

-# Some important comment...

%h1 The Rails 4 Way

HTML

<h1>The Rails 4 Way</h1>

If any text is nested beneath this kind of silent comment, it will also be ommitted from the resulting output.

12.5 Evaluating Ruby Code

Somewhat similar to ERb, using = results in Haml evaluating Ruby code following the equals character and outputting the result into the document.

Haml

%p= %w(foo bar).join(' ')

HTML

<p>foo bar</p>

Alternatively, using the hyphen character - evaluates Ruby code, but doesn’t insert its output into the resulting document. This is commonly used in combination with if\else statements and loops.

- if flash.notice

.notice= flash.notice

Note that Ruby blocks don’t need to be explicitly closed in Haml. As seen in the previous example, any indentation beneath a Ruby evaluation command indicates a block.

tip

Kevin says…

Do not use - to set variables. If you find yourself doing so, this is an indication that you need to create some form of view object, such as a presenter or decorator.

Lines of Ruby code can be broken up over multiple lines as long as each line but the last ends with a comma.

= image_tag post.mage_url,

class: 'featured-image'

12.5.1 Interpolation

Ruby code can can be interpolated in two ways in Haml, inline with plain text using #{}, or using string interpolation in combination with =. To illustrate, the following two lines of Haml code samples are equivalent:

%p By: #{post.author_name}

%p= "By: #{post.author_name}"

12.5.2 Escaping/Unescaping HTML

To match the default Rails XSS protection scheme, Haml will sanitize any HTML sensitive characters from the output of =. This results in any = call to behave like &=.

Haml

&= "Cookies & Cream"

HTML

Cookies & Cream

Alternatively, to unescape HTML with Haml, simply use != instead of =. If the Haml configuration option escape_html is set to false, then any call to = will behave like !=. (You probably will never want to do that.)

Haml

!= "Remember the awful <blink> tag?"

HTML

Remember the awful <blink> tag?

12.5.3 Escaping the First Character of a Line

On rare occasion, you might want to start a line of your template with a character such as = that would normally be interpreted. You may escape the first character of a line using a backslash.

Haml

%p

\= equality for all =

HTML

<p>

= equality for all =

</p>

12.5.4 Multiline Declarations

Haml is meant to be used for layout and design. Although one can technically write multiline declarations within a template, the creators of Haml made this intentionally awkward to discourage people from doing so.

If you do for some reason do need to declarations that spans multiple lines in a Haml template, you can do so by adding multiline operator | to the end of each line.

1 #content

2 %p= h( |

3 "While possible to write" + |

4 "multiline Ruby code, " + |

5 "it is not the Haml way" + |

6 "as you should eliminate as much Ruby" + |

7 "in your views as possible") |

We highly recommend extracting multi-line Ruby code into helpers, decorators, or presenters.

12.6 Helpers

Haml provides a variety of helpers that are useful for day-to-day development, such as creating list items for each item in a collection, and setting CSS ids and classes based on a model or controller.

12.6.1 Object Reference []

Given an object, such as an Active Record instance, Haml can output an HTML element with the id and class attributes set by that object via the [] operator. For instance, assuming @post is an instance of a Post class, with an id value of 1 then the following template code

1 %li[@post]

2 %h4= @post.title

3 = @post.excerpt

renders

<li class='post' id='post_1'>...</li>

This is similar to using Rails helpers div_for and content_tag_for, covered in Chapter 11, All About Helpers.

12.6.2 page_class

Returns the name of the current controller and action to be used with the class attribute of an HTML element. This is commonly used with the body element, to allow for easy style targeting based on a particular controller or action. To illustrate, assuming the current controller isPostsController and action index

%body{ class: page_class }

renders

<body class='posts index'>

12.6.3 list_of(enum, opts = {}) { |item| ... }

Given an Enumerable object and a block, the list_of method will iterate and yield the results of the block into sequential <li> elements.

Haml

1 %ul

2 = list_of [1, 2, 3] do |item|

3 Number #{item}

HTML

1 <ul>

2 <li>Number 1</li>

3 <li>Number 2</li>

4 <li>Number 3</li>

5 </ul>

12.7 Filters

Haml ships with a collection of filters that allow you to pass arbitrary blocks of text content as input to another processor, with the resulting output inserted into the document. The syntax for using a filter is a colon followed by the name of the filter. For example, to use the markdown filter

1 :markdown

2 # The Rails 4 Way

3

4 Some awesome **Rails** related content.

renders

1 <h1>The Rails 4 Way</h1>

2

3 <p>Some awesome <strong>Rails</strong> related content.</p>

Here is a table of all the filters that Haml supports by default:

:cdata

Surrounds the filtered text with CDATA tags.

:coffee

Compiles filtered text into JavaScript using CoffeeScript.

:css

Surrounds the filtered text with style tags.

:ERb

Parses the filtered text with ERb. All Embedded Ruby code is evaluated in the same context as the Haml template.

:escaped

HTML-escapes filtered text.

:javascript

Surrounds the filtered text with script tag.

:less

Compiles filtered text into CSS using Less.

:markdown

Parses the filtered text with Markdown.

:plain

Does not parse filtered text. Can be used to insert chunks of HTML that will be inserted as is without going through Haml.

:preserve

Inserts filtered text with whitespace preserved.

:ruby

Parses the filtered text with the Ruby interpreter. Ruby code is evaluated in the same context as the Haml template.

:sass

Compiles filtered text into CSS using Sass.

:scss

Same as the :sass filter, except it uses the SCSS syntax to produce the CSS output.

Some filters require external gems to be added to your Gemfile in order to work. For instance, the :markdown filter requires a markdown gem, such as redcarpet.

12.8 Haml and Content

In Chris Eppstein’s blog post “Haml Sucks for Content” 70, he stated his opinions on why one shouldn’t use Haml to build content:

Haml’s use of CSS syntax for IDs and class names should make it very clear: The markup you write in Haml is intended to be styled by your stylesheets. Conversely, content does not usually have specific styling - it is styled by tags.

Essentially what Chris was trying to convey is to not use native Haml syntax for creating anything other than skeletal (or structural) HTML markup. Use filters to inline reader content, such as in this example using the :markdown filter.

1 %p

2 Do

3 %strong not

4 use

5 %a{ href: "http://haml.info" } Haml

6 for content

is equivalent to the following markdown within a filter

1 :markdown

2 Do **not* use [Haml](http://haml.info) for content

We like the idea, but admit that your mileage may vary. It really depends on the type of project you’re working on and the capabilities of the person that will be maintaining the Haml template source files.

12.9 Configuration Options

Haml provides various configuration options to control exactly how markup is rendered. Options can be set by setting the Haml::Template.options hash in a Rails initializer.

# config/initializers/haml.rb

Haml::Template.options[:format] = :html5

12.9.1 autoclose

The autoclose option accepts an array of all tags that Haml should self-close if no content is present. Defaults to [‘meta’, ‘img’, ‘link’, ‘br’, ‘hr’, ‘input’, ‘area’, ‘param’, ‘col’, ‘base’].

12.9.2 cdata

Determines if Haml will include CDATA sections around JavaScript and CSS blocks when using the :javascript and :css filters respectively.

When format is set to html, defaults to false. If the format is xhtml, cdata will always be set to true and cannot be overridden.

This option also affects the filters: * :sass * :scss * :less * :coffeescript

12.9.3 compiler_class

The compiler class to use when compiling Haml to HTML. Defaults to Haml::Compiler.

12.9.4 encoding

The default encoding for HTML output is Encoding.default_internal. If that is not set, the dfault is the encoding of the Haml template.

The encoding option can be set to either a string or an Encoding object.

12.9.5 escape_attrs

If set to true (default), will escape all HTML-sensitive characters in attributes.

12.9.6 escape_html

When Haml is used with a Rails project, the escape_html option is automatically set to true to match Rails’ XSS protection scheme. This causes = to behave like &= in Haml templates.

12.9.7 format

Specifies the output format of a Haml template. By default, it’s set to :html5.

Other options include:

· :html4

· :xhtml: Will cause Haml to automatically generate self closing tags and wrap the output of JavaScript and CSS filters inside CDATA.

12.9.8 hyphenate_data_attrs

Haml converts all underscores in all data attributes to use hyphens by default. To disable this functionality, set hyphenate_data_attrs to false.

12.9.9 mime_type

The mime type that rendered Haml templates are servered with. If this is set to text/xml then the format will be overridden to :xhtml even if it has set to :html4 or :html5.

12.9.10 parser_class

The parser class to use. Defaults to Haml::Parser.

12.9.11 preserve

The preserve option accepts an array of all tags should have their newlines preserved using the preserve helper. Defaults to [‘textarea’, ‘pre’].

12.9.12 remove_whitespace

Setting to true, causes all tags to be treated as if whitespace removal Haml operators are present. Defaults to false.

12.9.13 ugly

Haml does not attempt to format or indent the output HTML of a rendered template. By default, ugly is set to false in every Rails environment except production. This enables you to view the rendered HTML is a pleasing format when you’re in development, but yields higher performance in production.

12.10 Conclusion

In this chapter, we learned how Haml helps developers create clear, well-indented markup in your Rails applications. In the following chapter we will cover how to manage sessions with Active Record, memcached, and cookies.