Monday, February 19, 2007

Sorta Nested Layouts

I was playing around with layouts in Rails the other day… I know, clearly I have too much time on my hands. But anyway, I found that you can fake a nested layout scheme in Rails by delegating view rendering to partials. As an added bonus, you don’t need to hack around with any of Rails’ internals to make it work.

For example, let’s say I’m working on an application that has both a public layout and an admin layout. The public layout consists of tabs and a sidebar. The admin layout has different tabs and no sidebar at all. Both layouts share a site header graphic.

With that in mind, here’s how we could structure our layouts and partials:

app/views/layouts/application.rhtml

<html>
<head>
<title>Layout Example</title>
</head>
<body>
<div id="header"><!-- shared header code --></div>

<%= render :partial=>"layouts/#{controller.sub_layout}" %>

<div id="footer><!-- shared footer code --></div>
</body>
</html>

Note: I’m getting the partial name from a controller method named sub_layout. I’ll explain that bit in just minute—In the meantime, knowing that it’ll return either ‘public’ or ‘admin’ is enough.

You’ll notice I don’t have a call to <%= yield %> in the layout itself…

So then, your partials (or sub layouts) will look something like this:

app/views/layouts/_public.rhtml

<div id="tabs"><!-- public tabset --></div>
<div id="public-content">
<div id="sidebar">
<!-- sidebar content here -->
</div>
<%= yield %>
</div>

app/views/layouts/_admin.rhtml

<div id="tabs"><!-- admin tabset --></div>
<div id="admin-content">
<%= yield %>
</div>

Ah, there’s the <%= yield %>! That’s how you can delegate the view rendering. Basically, you’re using a partial to wrap HTML around the call to <%= yield %>.

OK, in the application.rhtml listing above, I get the partial that works as a sub layout from the controller. To hook that up, in your ApplicationController, you can specify a default sub layout like this:

class ApplicationController < BaseController

# .. your actions

def sub_layout
"public"
end

end

Then in any administrative controllers, you can override it:

class UsersController < ApplicationController

# .. your admin-like actions

def sub_layout
"admin" 
end

end

I went ahead a threw together a little example application to better illustrate:

Perhaps the example of different tabs and sidebars isn’t most compelling reason to use sub layouts—Which is fine. The key point to all of this is that you can delegate the rendering of your view from a layout to a partial. Which I’m sure you can leverage in all kinds of cool ways…