mattmccray

 
« Comatose 0.8 Released… | Home | Comatose 0.8.1 »

Sorta Nested Layouts

» Published February 19, 2007 under Rails, Software

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…


8 comments

02.19.07 @ 18:41 Aaron Pfeifer said...
There’s a plugin I often used to achieve nested layouts: http://nested-layouts.rubyforge.org/
08.02.07 @ 14:37 Chirag Patel said...
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.
06.20.08 @ 04:18 Asyraf said...
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 under “nested layouts for Rails”. check it out!
12.31.08 @ 00:11 Jacques said...
Very cool. Exactly what I was looking for. Thanks bro!
03.22.09 @ 02:06 prash said...
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 …
07.20.09 @ 23:26 Brian Armstrong said...
Nice solution! Cleanest one I’ve seen, thanks for posting it.
04.08.10 @ 20:24 Nate said...
Thanks for this! I love how clean it is. I modified it slightly by checking to see if the controller responds to the sub_layout method first. My layout looks like this now (I use haml):

– if controller.respond_to?(‘sub_layout’)
  = render :partial => “layouts/#{controller.sub_layout}”
-else
  = yield
04.22.10 @ 07:30 Rohan Dey said...
Exactly matching to my needs…...Thanks

No trackbacks

Trackback link:

Please enable javascript to generate a trackback url


You may use Textile, or simple html tags (B,I). Feel free to use Emoticons too. Oh, and please limit yourself to only five links per comment. Anything more and you'll probably get detained by the spam police.