If you haven’t given HAML a try, you might want to consider it. My initial impressions are very favorable, and I was actually excited about how smooth things went. Allow me to explain…
There is a 50-50 chance you will like haml. From what I can tell, it is not necessarily an either-or decision for your team. Although standardizing and being consistent is often a quality unto itself!
I am not that good with html, and I never liked all of the open/close syntax. Doing html in erb is not that much better either. Still looks like a lot of extra “cruft” just to make the browser happy. It’s almost like – in terms of nth-order programming languages – the world of html is still in the low single digits.
When I first tried haml, it felt awkward, after all, I was learning to map the new haml syntax to the html syntax I already knew. However, this has now changed, it no longer feels awkward due to my recent experience:
Version 2
We are building a new Version 2 of an app we have deployed, and we are re-building it from the ground up. What a perfect time to try out a little haml! All I was implementing at the time was a user authentication via Devise. For that, there are only a handful of views to customize: login, sign-up, etc.
Handy Dandy Converter
If you have existing HTML, you can easily convert it to haml:
html2haml some_existing.html.erb some_existing.html.haml
Note: this requires
gem install haml gem install hpricot gem install ruby_parser
And if you have an entire directory (I learned this from the page on converting Devise html files to haml), you can convert and purge HTML easily:
$ cd app/views/devise $ for i in `find . -name '*.erb'` ; do html2haml -e $i ${i%erb}haml ; done
And to clean up the existing html files:
$ for i in `find . -name '*.erb'` ; do rm $i ; done
Compare and Contrast
At this point in the V2 app’s life, we’re implementing a small piece of functionality for a new customer, and slowly bringing in the V1 functionality. The initial non-login UI created by my buddy, Lee, was to display a list of messages and patient info. Simple stuff.
Initial Code
Lee created a core html layout and corresponding CSS, and slammed it together in this single view page to try it out. Unlike our production apps, it wasn’t even using the view/layout yet. It was totally self-contained (Lee and I often try the quickest route to be able to explore if an approach is going to work, and then rip it out and do it again, only better):
<html> <head> <title>Message Logs</title> <%= stylesheet_link_tag :all, :recursive => true %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> <script> </script> </head> <body> <div class="main"> <div class="header"> <div class = "global"> <ul class="hmenu"> <%= render :partial => 'shared/top_menu' %> </ul> </div> header </div> <div class="content"> <div class="stats">stats</div> <div class="details">Total Messages <span class="details count"><%= @count_total %></span></div> <div class="logs">logs</div> <div class="details"> <span class="details table"> <div class="logs" class="pagination"><%= will_paginate %></div> <table width="100%"> <tr> <th>date</th> <th>last name</th> <th>first name</th> <th>mr id</th> <th>patient id</th> <th>visit date</th> </tr> <% @messages.each do |message| %> <tr class="<%= cycle("odd", "even", :name => "row_class") %>"> <td><%= message.created_at.localtime.strftime("%D") %></td> <td><%= message.last_name %></td> <td><%= message.first_name %></td> <td><%= message.medical_record_id %></td> <td><%= message.patient_number %></td> <td><%= message.visit_date %></td> </tr> <% end %> </table> </span> </div> </div> <div class="sidebar"> <div class="search"> search </div> <div class="filter"> filter </div> <div class="a_form"> <form> <span class="label">start date:</span><input type="text" class="datepicker" id="dd_start_date" name="start_date" /><br> <span class="label">end date:</span><input type="text" class="datepicker" id="dd_end_date" name="end_date" /><br> <input type="submit" value="submit"> </form> </div> </div> </div> <div class="footer"> <div class="content"> © Company 2011 </div> </div> </body> </html>
So I decided to tackle breaking this monolithic page into it’s constituent parts, and to do it in haml.
So my first task was to use html2haml and generate a hamlized version that I can refer back to in case I had haml-specific questions (kind of like having the answers to a crossword that you can refer to when you are stuck).
One thing I am looking forward to learning about, is how best to handle where the styling goes when breaking up partials.
My current feeling is that a partial should have it’s own styling when you are trying to present the chunk of UI as both content and layout. Hopefully, some UI experts will chime in and help me out here. Or, I’ll figure it out as I get more experience under my belt.
Application Layout
So I broke out the core chunks of the original html to form an application layout (some elements removed for brevity):
!!! %html %head %title Product Name = stylesheet_link_tag :all, :recursive => true = javascript_include_tag :defaults = csrf_meta_tag :javascript %body .main .header .global %ul.hmenu = render 'shared/top_menu' = yield :header .content = yield .sidebar = yield :sidebar .footer .content © Company 2011
I think it is easy to see the gist of our application layout now:
- Main
- Header—Provide content
- Global Menu Bar
- Content—view content goes here by default
- Sidebar—Provide content
- Header—Provide content
- Footer
New Message View
Then the core UI to display the content for messages was rather trivial:
= render 'shared/header' .stats = render 'stats' = render 'details_count' .logs = render 'logs' .details = render 'details', :collection => @messages = render 'messages/search'
Here too, I liked how easy it was to see what is going on. Sure, much of that is due to the simplification of using partials. Nonetheless, it is easy to see:
- header
- stats
- details count
- details
- message details
- message search
What I really enjoyed was how easy it was to see the relationship between the UI code and the CSS classes. As I switched back and forth between the html version and the haml version to iron out differences, it was easy to know exactly where to go into the code to make a change. Using something like WebDeveloper or Firebug plugins make it easy to see the CSS for any page element.
I placed the header into a partial:
- content_for :header do This is a Header
And the top menu:
= link_to "Home", "/" = render 'devise/menu/registration_items' = render :partial => 'devise/menu/login_items'
The ‘stats’ and ‘logs’ partials have single words… Here is where I wonder if I should move the css “.stats” class into the partial? This would simplify the layout/application.html.haml file:
= render 'shared/header' = render 'stats' = render 'details_count' = render 'logs' .details = render 'details', :collection => @messages = render 'messages/search'
(I might just go with this, now that I see it…)
I was able to tweak the details count partial:
- content_for :sidebar do .search search .filter filter .a_form %form %span.label start date: %input#dd_start_date.datepicker{:name => "start_date", :type => "text"}/ %br/ %span.label end date: %input#dd_end_date.datepicker{:name => "end_date", :type => "text"}/ %br/ %input{:type => "submit", :value => "submit"}/
I more or less left the details iterative/tabular approach to be improved another day. Nonetheless, it is far easier on the eyes in its current state:
%span.details.table .logs.pagination =will_paginate %table{:width => "100%"} %tr %th date %th last name %th first name %th mr id %th patient id %th visit date - @messages.each do |message| %tr{:class => cycle("odd", "even", :name => "row_class")} %td= message.created_at.localtime.strftime("%D") %td= message.last_name %td= message.first_name %td= message.medical_record_id %td= message.patient_number %td= message.visit_date.localtime.strftime("%b %d %Y %H:%M%P")
I wish I was better able to articulate just how easy things felt while doing this. I don’t recall it ever feeling this easy when doing css/html inside of html/erb. A lot had to do, I am sure, with the fact that Lee had laid out the overall page view and had created well-formed CSS classes. However, I feel the bulk of the pleasure was derived from the simple syntax and the ease with which I could correlate what is happening in the browser, with what the code looked like.
Is haml a magic elixir? No. Does it dramatically reduce the total LOCs? No. Is it easier to read and maintain? For me, yes.
Try out HAML, see if you like it!