tag:www.rhnh.net,2008:/hamlHaml - Xavier Shay's Blog2007-07-17T11:41:13ZEnkiXavier Shaynotreal@rhnh.nettag:www.rhnh.net,2008:Post/1182006-09-16T11:00:00Z2007-07-17T11:41:13ZHAML Tutorial<p><a href="http://dev.hamptoncatlin.com/haml"><span class="caps">HAML</span></a> is, and is an acronym for, an <span class="caps">HTML</span> Abstraction Markup Language. It is a replacement for the <span class="caps">RHTML</span> templates we are so used to in rails applications. If you are interested in <em>why</em> one would need such a thing, please read John Philip Green’s excellent <a href="http://unspace.ca/discover/haml"><span class="caps">HAML</span> introduction</a>. If you are more interested in <em>how</em> one would use such a thing, read on!</p>
<h3>Table of Contents</h3>
<ol>
<li><a href="#installation">Installation</a></li>
<li><a href="#fundamentals">Fundamentals</a></li>
<li><a href="#xhtml_techniques"><span class="caps">XHTML</span> techniques</a></li>
<li><a href="#ruby_techniques">Ruby techniques</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ol>
<p>h3.#installation). Installation</p>
<p>First things first, install the plugin:</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">./script/plugin install -x svn://hamptoncatlin.com/haml/trunk haml<tt>
</tt></pre></td>
</tr></table>
<p>This gives you a library to parse <span class="caps">HAML</span> templates, and also registers the .haml extension with rails. What this means is that to start using <span class="caps">HAML</span> you only need to rename your template from ‘index.rhtml’ to ‘index.haml’. Do that now (in a new test app, an existing app, whatever), as we are about to get our first taste of ham … (l).</p>
<p>h3.#fundamentals). Fundamentals</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt>15<tt>
</tt>16<tt>
</tt>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt>22<tt>
</tt>23<tt>
</tt>24<tt>
</tt>25<tt>
</tt>26<tt>
</tt>27<tt>
</tt>28<tt>
</tt>29<tt>
</tt><strong>30</strong><tt>
</tt>31<tt>
</tt>32<tt>
</tt>33<tt>
</tt>34<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">%h1 HAML Example<tt>
</tt>%div<tt>
</tt> %blockquote <tt>
</tt> Farewell, Emily. It was fun, but you were a robot. <tt>
</tt> You had no heart. <tt>
</tt>--- <tt>
</tt><tt>
</tt>In the same vein as YAML and Python, *indentation matters* in HAML. It allows the parser to cleverly close our tags without being explicitly told to do so. Equals less typing for us lazy sloths. 2 spaces per indent is the rule. The first non-whitespace character of each line is what is used to decide how to parse the line. As may be evident, the % character indicates an XHTML tag. There are only 5 others, which we will cover in due course. Lines that do not begin with a special character are treated as normal text.<tt>
</tt><tt>
</tt>h3.#xhtml_techniques). XHTML techniques<tt>
</tt><tt>
</tt>Being a prime requirement of a templating language, outputting XHTML is as simple as you would expect. I'm not even going to write a full paragraph, this annotated listing should suffice:<tt>
</tt>--- haml<tt>
</tt>/ The slash character specifies an XHTML comment,<tt>
</tt>/ but if after a tag name it self closes that tag<tt>
</tt>%br/<tt>
</tt><tt>
</tt>/ Attributes are specified by a hash provided directly after <tt>
</tt>/ the tag name. There is NO SPACE between the tag and the hash<tt>
</tt>%a{"name" => "top"}<tt>
</tt><tt>
</tt>/ "class" is such a common attribute that it has a shortcut syntax<tt>
</tt>%span.important Tada!<tt>
</tt><tt>
</tt>/ Combine the two to impress you friends<tt>
</tt>%span.extra{"style"=>"color: red"} Tada! Tada!<tt>
</tt><tt>
</tt>/ A div with id is also common, so it too has a shortcut syntax<tt>
</tt>#content<tt>
</tt> This is a div with id "content"<tt>
</tt><tt>
</tt>/ As does a div with class<tt>
</tt>.fancy<tt>
</tt> This is a div with class "fancy"<tt>
</tt></pre></td>
</tr></table>
<p>The one curly aspect of generating <span class="caps">XHTML</span> you only need to deal with once – the doctype. You can use three exclamation marks on the first line of a template (hopefully a layout template) to output a doctype declaration. The problem is that it makes your document <span class="caps">XHTML 1</span>.0 transitional. Always. It also forgets to give you an <span class="caps">XML</span> prolog, so for now I specify these without using <span class="caps">HAML</span>, which brings up another point – you can mix normal <span class="caps">XHTML</span> tags and <span class="caps">HAML</span> code (although why you would want to outside of this fix eludes me).</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">!!!<tt>
</tt>%html{"xmlns"=>"http://www.w3.org/1999/xhtml", "xml:lang"=>"en"}<tt>
</tt> %head<tt>
</tt> %title Layout Example<tt>
</tt> %body= @content_for_layout<tt>
</tt></pre></td>
</tr></table>
<table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><?xml version="1.0" encoding="UTF-8"?><tt>
</tt><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"<tt>
</tt> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><tt>
</tt>%html{"xmlns"=>"http://www.w3.org/1999/xhtml", "xml:lang"=>"en"}<tt>
</tt> %head<tt>
</tt> %title Layout Example<tt>
</tt> %body= @content_for_layout<tt>
</tt></pre></td>
</tr></table>
<p>Those on the edge may want to keep an eye on <a href="http://dev.hamptoncatlin.com/haml/ticket/13">this ticket</a> , which proposes a fix.</p>
<p>h3.#ruby_techniques). Ruby techniques</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">= link_to :controller => 'home'<tt>
</tt>= 1 + 2 # => 3<tt>
</tt>%span= 1 + 2<tt>
</tt></pre></td>
</tr></table>
<p>Text after an equals sign is evaluated as ruby code. It is roughly equivalent to
<code><%= 1 + 2 # => 3 %></code>, but with one fairly major caveat: Each is evaluated independent of the rest of the template. Meaning the follow <strong>will not</strong> work, because the first line is evaluated as an entire ruby snippet, and does not find the <code>end</code> it requires to be valid.</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">= for i in (1..10)<tt>
</tt>= i<tt>
</tt>= end<tt>
</tt></pre></td>
</tr></table>
<p>There is currently no way around this. There is a <a href="http://dev.hamptoncatlin.com/haml/ticket/16">ticket</a> on the <span class="caps">HAML</span> trac with a proposed fix, but at the time of authoring the patch has not been attached. This is not as shocking as it may first appear. Ask yourself why you are using a loop or an if block in your code. If it cannot be reduced to a one liner, maybe it should be moved it out into a partial.</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">= (1..10).inject('') { |buffer, i| buffer + i.to_s }<tt>
</tt>= render :partial => 'secret', :collection => @secrets if cia?<tt>
</tt></pre></td>
</tr></table>
<p>An alternative way to evaluate ruby code is to use a tilde instead of equals. This has the effect of searching in the evaluated string and replacing all newlines found in <code>pre</code>, <code>code</code> or <code>textarea</code> tags with an <span class="caps">XHTML</span> entity (<code>&#000A;</code>). This allows you to create neat markup even when displaying large chunks of preformatted text.</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"> ~ "<textarea>\n\n\n\n\n\nYo</textarea>"<tt>
</tt></pre></td>
</tr></table>
<p>Keep in mind that your ruby expression must not span more than one line – only the first line will be parsed and the rest will be treated as plain text. There is a <a href="http://dev.hamptoncatlin.com/haml/ticket/6">proposed fix</a> (that makes 3! I want a pony) on the <span class="caps">HAML</span> Trac, if you are in to that sort of thing.</p>
<p>h3.#conclusion). Conclusion</p>
<p><span class="caps">HAML</span> may not be quite as powerful as <span class="caps">RHTML</span> yet, but it drastically reduces the size of your views while greatly increasing readability and the quality of the markup. The best part is you can mix and match – you can start writing <span class="caps">HAML</span> templates in your existing project right now and keep all your old <span class="caps">RHTML</span> code hanging around.</p>