Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Layouts in Rails

I'm having trouble finding a way to do the following:

Let's say in my application.html.erb I have the following

<div id="one" >
  <%= yield %>
</div>

Then I want to have another layout file asdf.html.erb

<div id="two">
  <%= yield %>
</div>

I want the final output to be

<div id="one">
  <div id="two">
     <%= yield %>
  </div>
</div>

Is it possible? Thanks.

like image 833
jhlu87 Avatar asked Dec 09 '13 21:12

jhlu87


People also ask

What is the default layout for a Rails application?

A generated Rails application has a default layout and it’s defined in the app/views/layouts/application.html.erb. On the screen above there is only one dynamic block - it’s the body, the footer, the header and the sidebar are common blocks for each page.

How does rails render a view as a response?

When Rails renders a view as a response, it does so by combining the view with the current layout, using the rules for finding the current layout that were covered earlier in this guide. Within a layout, you have access to three tools for combining different bits of output to form the overall response:

How do I get rails to render with a specific location?

You can use the :layout option to tell Rails to use a specific file as the layout for the current action: You can also tell Rails to render with no layout at all: You can use the :location option to set the HTTP Location header: Rails will automatically generate a response with the correct HTTP status code (in most cases, this is 200 OK ).

What is the problem of nested layout?

As you see each of these three steps includes common blocks: they are the progress bar, the “Submit” button and when you start to implement the steps you will see that it contains repetitive code. It may be, for example, form tags. This is the problem of nested layout.


3 Answers

The cleanest solution I found by far came from this repo : https://github.com/rwz/nestive

I did not want the whole gem. If you're like me, here's how I achieved what I wanted:

# application_helper.rb

  # From https://github.com/rwz/nestive/blob/master/lib/nestive/layout_helper.rb
  def extends(layout, &block)
    # Make sure it's a string
    layout = layout.to_s

    # If there's no directory component, presume a plain layout name
    layout = "layouts/#{layout}" unless layout.include?('/')

    # Capture the content to be placed inside the extended layout
    @view_flow.get(:layout).replace capture(&block)

    render file: layout
  end

Then you keep /layouts/application.html.erb unchanged!

And you can create other layouts. In my case /layouts/public.html.erb and /layouts/devise.html.erb:

# public.html.erb
<%= extends :application do %>
  <%= render 'partials/navbar' %>
  <div class="container margin-top">
    <%= yield %>
  </div>
<% end %>

# devise.html.erb
<%= extends :public do %>
  <div class="col-sm-6 col-sm-offset-3">
    <%= yield %>
  </div>
<% end %>

Works like a charm! I am still smiling I finally found a clean solution.

like image 83
Augustin Riedinger Avatar answered Oct 13 '22 00:10

Augustin Riedinger


By default, application.html.erb is your layout. You can render a default sub-layout by calling it as a partial from your application layout:

# app/views/layouts/application.html.erb
<div id="one" >
    <%= render "layouts/asdf" %>
</div>

# app/views/layouts/_asdf.html.erb
<div id="two">
    <%= yield %>
</div>

This will output the following:

<div id="one>
   <div id="two">
      <%= yield %>
   </div>
</div>

Alternatively, if you're looking to conditionally render layouts on a controller-by-controller basis, you should consider using nested layouts. From the documentation:

On pages generated by NewsController, you want to hide the top menu and add a right menu:

# app/views/layouts/news.html.erb
<% content_for :stylesheets do %>
  #top_menu {display: none}
  #right_menu {float: right; background-color: yellow; color: black}
<% end %>
<% content_for :content do %>
  <div id="right_menu">Right menu items here</div>
  <%= content_for?(:news_content) ? yield(:news_content) : yield %>
<% end %>
<%= render template: "layouts/application" %>

The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div.

like image 25
zeantsoi Avatar answered Oct 13 '22 00:10

zeantsoi


Rename asdf.html.erb to _asdf.html.erb and rewrite application.html.erb to this:

<div id="one">
  <%= render 'asdf' %>
</div>

More about partials here: http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials

like image 40
Kaleidoscope Avatar answered Oct 13 '22 00:10

Kaleidoscope