Ruby on Rails
Nested

Unless someone can convince me otherwise, I hereby formally wish for nested layouts without using inheritance. For example:

class <span class="newWikiWord">FooController<a href="http://wiki.rubyonrails.org/rails/pages/FooController">?</a></span> < <a href="http://wiki.rubyonrails.org/rails/pages/ActionController" class="existingWikiWord">ActionController</a>::Base
    layout 'layout/outside'
    layout 'layout/inside'
end

Thoughts?


This is one of my wishes too.

Currently I handle this by wrapping a template that requires an inside layout with:

<% @content_for_inner_layout = capture do %>

and

<% end %>

<%= render 'layouts/inner_layout', { 'content_for_inner_layout' => @content_for_inner_layout } %>

Then do the actual wrapping of the template by including:

<%= @content_for_inner_layout %>

where it needs to be within the inner layout.

Is there a better way to handle this?


I use a little bit of black magic that can be found in CaptureHelper (the way it manipulate output buffer):


module ActionView
  module Helpers
    module NestedLayoutsHelper
      def inside_layout(layout, &block)
        layout = layout.include?('/') ? layout : "layouts/#{layout}" 

        concat(@template.render_file(layout, true, '@content_for_layout' => capture(&block)), block.binding)
      end
    end
  end
end

ActionView::Base.class_eval do
  include ActionView::Helpers::NestedLayoutsHelper
end

In controller write

class FooController < ApplicationController
  layout 'inner'
end

Then inside your ‘layouts/inner.rhtml’ do

<% inside_layout 'outer' do %>
Inner layout header
<%= @content_for_layout %>
Inner layout footer
<% end %>

The outer layout can also nest itself inside a higher level layout.

I took that idea from Microsoft ASP.NET v2.0 Master pages implementation: for every master page (that is “layout” in Rails terms) you can specify parent master page.
In my implementation it goes even further: the master page can be changed by changing the value of first parameter to “inside_layout” method. You can specify a method call there that will return the proper “outer” layout name.

—Maxim Kulkin

Corrected error in concat line

—Iktorn

I’ve beat my head over the same problem, though I took a different route. Since you have access to _erbout in the views, you can build something to manipulate it.

<% pre_content = _erbout.dup %>

<%= "stuff" %>
<%= "more stuff" %>

<%
post_content = _erbout.dup

_erbout = pre_content

post_content.slice!(pre_content)
%>

<p><%= post_content %></p>

I made a plugin that wraps that up in a stack, so you can have nested buffers.
svn://ahgsoftware.com/erb_buffer/trunk

I tried in vain at finding a clean way to use the Erb capture method!

—MikeC

I use this method which allows me to infinitely nest layouts that at any given level can be used. By this I mean if you have layout 2 nested in layout 1 and layout 3 nested in layout 2, all 3 layouts are usable. Heres a concrete example.

application.rhtml:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
    <head>
        <title>Title</title>
        <meta http-equiv="Content-Type" content="text/html; charset=us-ascii"/>
        <%= stylesheet_link_tag "main", :media => "all" %>
    </head>
    <body>
        <%= yield :layout %>
    </body>
</html>

inner.rhtml

<% @content_for_layout = capture do %>
<div id="test">
<%= yield :layout %>
<div class="blah"></div>
</div>
<% end %>
<%= render 'layouts/application', { 'content_for_layout' => @content_for_layout } %>

In this situation both ‘application’ and ‘inner’ are usable layouts.

The results of running render :text => ‘Hello World’, :layout => ‘inner’ would be:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
    <head>
        <title>Title</title>
        <meta http-equiv="Content-Type" content="text/html; charset=us-ascii"/>
        <%= stylesheet_link_tag "main", :media => "all" %>
    </head>
    <body>
        <div id="test">
Hello World
<div class="blah"></div>
</div>
    </body>
</html>

—David Genord II