In Part 1, I explained how you can develop clean, DRY and encapsulated MVC code that allows for completely modular page assembly in Ruby on Rails.
In this follow up post, I explain how you can use a combination of layout
s and content_for
to apply title bars and consistent styles to your page components (or modules or portlets, or whatever you want to call them).
The code here is easy to follow and it pretty much speaks for itself. It consists of two layouts (which I called aggregate and snippet), a sample controller and two sample views. Let's start with the controller for one page in your site that, say, aggregates a couple of modular page components together to show a nice view of company information.
Controller code (app/controllers/company_controller.rb
):
def index
render :action => 'index', :layout => 'aggregate'
end
This controller simply delegates the rendering of the page to the index.html.erb
view and tells Rails to use a layout called aggregate
.
Now let's inspect the view.
View code (app/views/company/index.html.erb
):
<% content_for :left do %>
<%= embed_action :controller => 'company', :action => 'company_list' %>
<%= embed_action :controller => 'feed', :action => 'feed' %>
<% end %><% content_for :center do %>
<%= embed_action :controller => 'company', :action => 'detail' %>
<% end %><% content_for :right do %>
<%= embed_action :controller => 'home', :action => 'sponsors' %>
<% end %>
This view defines three content regions, with the end goal being to create a page with three columns of "portlets." The left column contains two portlets: a list of all companies (company_list
) and a news feed (feed
). The center column contains a company detail portlet and the right column contains a portlet with information about sponsors. (Note that the portlets come from three different functional areas of the site, so they're decomp'd appropriately into three different controllers.)
Now, let's take a look at some layout magic.
Here's the aggregate layout (app/views/layouts/aggregate.html.erb
):
<table class="main" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td id="column-left" class="column" valign="top">
<div id="region-left" class="region"><%= yield :left %></div>
</td>
<td id="column-center" class="column" valign="top">
<div id="region-center" class="region"><%= yield :center %></div>
</td>
<td id="column-right" class="column" valign="top">
<div id="region-right" class="region"><%= yield :right %></div>
</td>
</tr>
</tbody>
</table>
I chose a table (yes, I still use tables) with three div
s in it, one for each region of modules, but you could use pure div
s with floating layouts or any other approach.
The three content regions, left
, center
and right
, match up with the three content sections defined in the index
view above using content_for
. In case this isn't obvious, when the layout encounters a page-level definition of a content region in the view, it renders it. If there is no definition for a particular region, the containing column will just collapse on itself, which is the behavior we want.
This is a slight digression, but note how I used CSS classes to identify the columns and regions in a general way (using class
es) and a specific way (using id
s). This allows me to style the whole module-carrying region with CSS using table.main
as my selector, all the columns using table.main td.column
as my selector or all the regions using table.main td.column div.region
as my selector. I can also pick and choose different specifc areas (e.g. table.main td.column#column-right
) and define their style attributes using CSS. As you'll see in a minute, I can write CSS selectors to say if module A is in the left column, apply style X but if module A is in the center or right column, apply style Y. Pretty cool.
Now, let's explore the module layout. (Note that I've been calling page snippets portlets, modules or components, pretty much interchangeably. I think this illustrates that it doesn't make a difference what we call 'em -- e.g. portlets vs. gadgets -- the concept is fundamentally clear and fundamentally the same.)
Module layout (app/views/layouts/snippet.html.erb
):
<div id="<%= yield :id %>"><%= yield :id %>" class="snippet-container">
<div class="snippet-title"><%= yield :title %></div>
<div class="snippet-body"><%= yield :body %></div>
</div>
This layout expects three more content regions to be defined in the view: id
, title
and body
. Here are the matching content regions from one sample view (for sponsors) -- for brevity's sake, I didn't include all the views.
Sample module view (app/views/home/sponsors.html.erb
):
<% content_for :id do %>sponsors<% end %><% content_for :title do %>Our Sponsors<% end %>
<% content_for :body do %>
Please visit the sites of our wonderful sponsors!
<% end %>
Now, because of some nicely-placed classes and ids, I can once again use CSS selectors to give a common look-and-feel to all portlet containers (div.snippet-container
), portlet titles (div.snippet-title
) and to portlet bodies (div.snippet-body
). Of course, if I want to diverge from the main look-and-feel, I can call out specific portlets: div.snippet-body#sponsors
.
If I really want to get fancy, I can use CSS selectors to select, say, the sponsor portlet, but only when it's running in the right column: table.main td.column-right div.snippet-container#sponsors
.
So, in summary, using layouts
, content_for
and some crafty CSS, I can create a page of modules that can be styled generically or specifically. Combine this approach with what I described in Part 1, and you can "portal-ize" your Rails applications without using a portal!
Was this useful to you? If so, please leave a comment.