![]() |
is a Software Apprentice at Obtiva, a Chicago based agile consultancy. He loves programming day and night, much to his girlfriend's dismay. While not digging through code, he can also be found drinking craft beers, being a slight coffee snob, and losing games of Settlers of Catan. |
If there's one thing that I dislike about Rails, it's ERB. It's not just ERB either, it's views in general. Often referred to as the ugly step-sister, views are neglected in MVC frameworks, Rails included. Enter Haml and Sass, two templating languages that aim to take the pain away from developing views and stylesheets.
Haml, short for XHTML Abstraction Markup Language, and Sass, short for Syntactically Awesome StyleSheets, are templating languages that express HTML and CSS in an outline form with a white space defined structure (think Python). They are capable of producing the same markup of their more verbose counterparts, but also add things like filters and css variables to the party.
Haml and Sass are based on a few primary principles:
- Markup should be beautiful
Let's face it, ERB looks like garbage. The nature of Haml and Sass' nested, whitespace defined structure ensures that line noise is kept to a minimum. The result is markup that is extremely easy to read, understand and change. - Markup should be meaningful
Since Haml and Sass are whitespace defined, every character matters. No keystroke goes to wasted markup. And, since Haml and Sass has nesting qualities, the frameworks know when an element or selector is closed. - Markup should be well indented
Have you ever opened up an ERB template only to find that it's completely unreadable due to its indentation? Various styles of closing tags, some on new lines, some on the same line, some missing all together. It can turn into a real mess. Thankfully, since Haml is whitespace defined, this kind of situations are nearly impossible. - Markup should be DRY
HTML and CSS are anything but DRY. HTML is incredibly verbose, with constant opening and closing of tags. CSS selectors are repeated, possibly multiple times in large projects. Every keystroke should be important, not meaningless fluff.
Installation
Haml and Sass come bundled together in one gem. It's important to note, however, that they are not dependent on each other. You can use them independently. To install, run:
sudo gem install haml
At this point, you can add Haml and Sass to your Rails project by either adding the gem to your environment.rb file, or by adding it as a plugin, like so:
haml --rails /path/to/project
Usage
Now that the gem is installed, go ahead and run
haml -help
sass -help
to see what options you have at the command line. Other than what you'll find listed there, you'll also have access to a couple of converters shown below:
html2haml /path/to/html /path/to/haml
css2sass /path/to/css /path/to/sass
These converters are great for playing around with syntax or porting over an older project.
You'll also have access to an interactive sass console, similar to irb. Here is an example of using Sass to subtract two color values in the interactive console
sass -i
>> #fffff - #111
#eeeeee
Haml
Syntax
The fundamentals that make up a Haml document are:
- % (percent character) - HTML Element
- . (period character) - Class
- # (pound character) - ID
Let's take a look at a really basic html file:
haml-syntax-example1.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Look, we're using Haml!</title>
</head>
<body></body>
</html>
And now, the equivalent Haml:
haml-syntax-example1.haml
!!!
%html
%head
%title Look, we're using Haml!
%body
I think it's important that you notice the things that I'm not doing in the Haml example. There's no crazy doc string that no one can remember, no needless closing tags. All we have is extremely easy to read markup.
Let's get a little more complex:
haml-syntax-example2.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Look, we're using Haml!</title>
</head>
<body>
<div id='content'>
<div class='post' id='first'></div>
</div>
</body>
</html>
And the equivalent Haml:
haml-syntax-example2.haml
!!!
%html
%head
%title Look, we're using Haml!
%body
%div#content
%div.post#first
As you can see, we've added a div with id "content" that contains a single div with a class of "post" and an id of "first". But, there's a little more we can do here. Since the div element is used so much, it is actually the default element. If you define a class or id without specifying an HTML element, a div is used. With that in mind, our previous example can be refactored to this:
haml-syntax-example3.haml
!!!
%html
%head
%title Look, we're using Haml!
%body
#content
.post#first
Ruby
Inserting Ruby evaluated code into a document is accomplished by using the equal character (=). The code is evaluated and then inserted, exactly like <%= %> in ERB. You can also use the = at the end of a HTML element tag.
Running Ruby is accomplished using the hyphen character (-). Ruby blocks also don't have to be closed in Haml, they are closed for you based on indentation. A block is evaluated whenever markup is indented past the evaluation character.
Interpolation can also be accomplished in plain text using #{}.
%span Hello my name is #{author.name}.
With that info in mind, let's see how you would write a common ERB file in Haml
haml-erb-example1.erb
ERB
<% @posts.each do |post| %>
<span class='title'><%= post.title %></span>
<div class='post'><%= post.content %></div>
<span class='author'><%= post.author %></span>
<% end %>
haml-erb-example1.haml
Haml
[email protected] do |post|
%span.title= post.title
.post= post.content
%span.author= post.author
Filters
In Haml, a colon character (:) signifies that a filter is being used. The filter takes the indented block of text and passes it to whatever filter program is being called. The result of the filter call is then added to the rendered html. Haml comes with several filters out of the box. Plain, javascript, cdata, ruby, erb, markdown just to name a few. It is also possible to write your own filters.
An example of using a Javascript filter:
filter-example.haml
:javascript
alert('Whoa! This is Javascript!');
Rendered result:
filter-example.html
<script type='text/javascript'>
//<![CDATA[
alert('Whoa! This is Javascript!');
//]]>
</script>
Sass
Syntax
Typically CSS is riddled with repeated names. Take a look at the following example:
sass-syntax-example1.css
#footer {width: 850px; padding: 5px; margin-bottom: 10px; font-weight: 900; font-size: 1.2em;}
#footer img {padding: 10px; float: right;}
#footer a {text-decoration: none;}
Now compare it to the equivalent Sass:
sass-syntax-example1.sass
#footer
width: 850px
padding: 5px
margin-bottom: 10px
font-weight: 900
font-size: 1.2em
a
text-decoration: none
img
padding: 10px
float: right
I find this a lot more easier to write and read. Since Sass is whitespace defined, the first selector isn't indented at all, and everything indented under it will either be a property on that selector, or a nested rule.
There are two different ways to write properties. Besides the example above, you may also move the colon to the beginning of the property. This may help you tell the difference between properties and nested rules.
sass-syntax-example2.sass
#footer
:width 850px
:padding 5px
:margin-bottom 10px
:font-weight 900
:font-size 1.2em
a
:text-decoration none
img
:padding 10px
:float right
For consistency sake, we will be using the 'attribute:' style for the rest of this article. It's also important to note that in the earlier versions of Sass, indentation had to be two spaces. However, with the 2.2 release of Sass, this is no longer true. The only requirement is that the spacing be consistent throughout the stylesheet.
Nested properties
In an effort to keep the markup DRY, Sass provides a way that we can clean up the previous example even more. Just like selectors, you can nest properties. That allows us to get rid of the hyphens in the font definitions.
sass-nesting-example-1.sass
#footer
width: 850px
padding: 5px
margin-bottom: 10px
font
weight: 900
size: 1.2em
a
text-decoration: none
img
padding: 10px
float: right
Variables
Easily one of the best features of Sass, is the ability to define variables for use throughout your stylesheet. Variables can be anything from font families to color definitions. Attributes are assigned a variable with the equal sign instead of the colon.
sass-variables-1.css
h1 {
color: #2887e5; }
#footer {
font-family: Arial;
font-color: #2887e5; }
Could be written like this:
sass-variables-1.sass
!font_family= Arial
!blue= #2887E5
h1
color= !blue
#footer
:font
:family= !font_family
:color= !blue
It's not hard to imagine all the uses for this. For example, being able to redefine a color scheme by only changing a couple of variables instead of hunting and pecking.
Variable math
Nope, you didn't read that wrong, you can actually perform basic math on variables! If you recall, I actually did that in the interactive sass example. Let's go back to the console for another look:
sass -i
>> !width = 5px
5px
>> !extra_width = 30px
30px
>> !extra_width - !width
25px
This gives a lot of flexibility when defining your stylesheets. Take defining a header height for instance.
!base_height= 40px
!tall_header_height= !base_height * 1.33
!short_header_height= !base_height * .66
By using those three variables while designing the header of my site, it allows for easy changes. All I would need to do is change the !base_height variable, and the rest of the header would scale accordingly. Pretty cool!
Mixins
Mixins allow you to reuse entire sections of your stylesheet.
sass-example-mixin-1.css
#header a{text-decoration: none; color: black;}
#footer a{text-decoration: none; color: black;}
Could be written like this:
sass-example-mixin-1.sass
=plain_a
color: black
text-decoration: none
#header
+plain_a
#footer
+plain_a
Mixins can also take parameters, and have the option for defaults. Expanding on our previous example, we could change the color of our a tags to "green" in the content area by passing a variable:
sass-example-mixin-2.sass
=plain_a(!color = black)
color= !color
text-decoration: none
#header
+plain_a
#content
+plain_a(green)
#footer
+plain_a
Note that we are setting a default value of color to be "black", and hence the a tag in the header and footer has a color of black whereas passing a value of "green" makes the a tag color for content to be green.
Conclusion
There you have it, everything you need to get started with an awesome set of templating languages. But in reality, I've only scratched the surface of what you can accomplish with Haml and Sass. I urge all of you to go out and read the fantastic documentation and start converting your current projects.
Just remember, with time and effort, app by app, we can get rid of the ERB plague once and for all!