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…

6 comments:

  1. There's a plugin I often used to achieve nested layouts: http://nested-layouts.rubyforge.org/

    ReplyDelete
  2. Thanks Matt for writing this up in such a clear and detailed way! I will be using your method rather than using the nested-layouts plugin. Your method is pretty simple to understand. Second, I prefer using proprietary plugins only when they save significant amounts of time. I may be wrong in this case, but I believe having full control using what's out of the Rails box, will allow easier maintainability if the app grows and more Rails programmers are needed.

    ReplyDelete
  3. Hey matt,
    Thanks for the tip - using partials instead of a plugin is way simpler and 'update-proof'.
    I used "layouts/#{controller.controller_name}" in my render method instead of defining a new method in my controllers.
    wrote about it in my blog, "rubynerds.blogspot.com":url under "nested layouts for Rails". check it out!

    ReplyDelete
  4. Very cool. Exactly what I was looking for. Thanks bro!

    ReplyDelete
  5. thanks! this worked very well for me.
    several (but not all) of my views required a standard header and footer (separate from the overall site's header and footer) - a prime candidate for nested layouts.
    the sub_layout technique worked out very well ...

    ReplyDelete
  6. Nice solution! Cleanest one I've seen, thanks for posting it.

    ReplyDelete