jQuery Mobile presents a bit of a challenge when trying to keep your layouts DRY. In a general HTML document layout you have the head content, a header, a footer, and the page specific content. In other words, an HTML document is a single “page”. With jQuery Mobile you sometimes have several “pages” in a single HTML document for simplicity and/or performance reasons. Since each “page” often needs to follow the same layout, we need an approach to keep our UI code DRY.

Using the general Rails layout

Rails layouts assume the general HTML document structure. This works fine for an HTML document with a single jQuery Mobile “page”:

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <%= stylesheet_link_tag 'jquery-mobile-min', :media => 'screen' %>
    <%= javascript_include_tag 'jquery' %>
    <%= javascript_include_tag 'jquery.mobile.min' %>
  </head>
  <body>
    <div id="<%= page_id %>" data-role="page">
      <div data-role="header" class="main-header">
        <%= image_tag 'solutionsfit-logo.png' %>
      </div>

      <div data-role="header" data-position="inline">
        <%= yield :header %>
      </div>

      <% flash.each do |key, value| %>
        <%= content_tag(:div, value, :class => "flash #{key}") %>
      <% end %>

      <div data-role="content">
        <%= yield %>
      </div>

      <div data-role="footer">
        <h4>© <%= Date.today.year %> solutionsfit</h4>
      </div>
    </div>
  </body>
</html>

This works fine as long as you only have a single jQuery Mobile “page” in your HTML. Unfortunately, this is often not the case. When you want to include multiple jQuery Mobile “pages” in a single HTML document while keeping your layout DRY, you have to be a little more creative.

Using the CaptureHelper for multiple “pages”

Fortunately there is a solution using the CaptureHelper. From the Rails documentation:

CaptureHelper exposes methods to let you extract generated markup which can be used in other parts of a template or layout file.

This allows us to create a partial that describes our general “page” structure, shared/_page.html.erb:

<div id="<%= page_id %>" data-role="page">
  <div data-role="header" class="main-header">
    <%= image_tag 'solutionsfit-logo.png' %>
  </div>

  <div data-role="header" data-position="inline">
    <%= header %>
  </div>

  <% flash.each do |key, value| %>
    <%= content_tag(:div, value, :class => "flash #{key}") %>
  <% end %>

  <div data-role="content">
    <%= content %>
  </div>

  <div data-role="footer">
    <h4>© <%= Date.today.year %> solutionsfit</h4>
  </div>
</div>

As you can see we define variables for header and content. To use the partial we simply use the capture method from the CaptureHelper:

<% @home_header = capture do %>
  <h2>solutionsfit</h2>
<% end %>

<% @home_content = capture do %>
  <p>Welcome to solutionsfit!</p>

  <ul data-role="listview" data-inset="true">
    <li><a href="#postings">Blog Postings</li>
  </ul>
<% end %>

<%= render 'shared/page', :page_id => "home_page", 
  :header => @home_header, :content => @home_content %>

<% @blog_header = capture do %>
  <h2>Blog Postings</h2>
<% end %>

<% @blog_content = capture do %>
  <!-- Logic to list postings here -->
<% end %>

<%= render 'shared/page', :page_id => "postings", 
  :header => @blog_header, :content => @blog_content %>

Refactoring the general layout

Now that we have our partial defined, we can refactor our general layout to include just the basic HTML document structure:

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <%= stylesheet_link_tag 'jquery-mobile-min', :media => 'screen' %>
    <%= javascript_include_tag 'jquery' %>
    <%= javascript_include_tag 'jquery.mobile.min' %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

While it’s not as pretty as a general template, it allows us to DRY up our jQuery Mobile views. Another option is using a partial layout, but this has the disadvantage of requiring you to make both your header and content partials themselves. This tradeoff wasn’t worth the additional files in my case.