Ruby on Rails
Nested (Version #16)

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 = precontent

post_content.slice!(pre_content)
%>

<%= post_content %>

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”>


Title</p>

<%= stylesheet_link_tag “main”, :media => “all” %>

<%= yield :layout %>

inner.rhtml


<% @content_for_layout = capture do >

<= yield :layout %>

<% 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”>


Title</p>

<%= stylesheet_link_tag “main”, :media => “all” %>

Hello World

_— David Genord II

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 = precontent

post_content.slice!(pre_content)
%>

<%= post_content %>

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”>


Title</p>

<%= stylesheet_link_tag “main”, :media => “all” %>

<%= yield :layout %>

inner.rhtml


<% @content_for_layout = capture do >

<= yield :layout %>

<% 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”>


Title</p>

<%= stylesheet_link_tag “main”, :media => “all” %>

Hello World

_— David Genord II