I have a very simple requirement - I have a layout comprising of a header and body. It is a sub-layout of the page, not for the page itself.
This layout is repeated throughout multiple pages, and it is possible the structure around it will change. So I want to be able to separate the content of the header and the content of the body from the structure that contains it.
My first attempt was to use render a partial as a layout that used named yields to render a header and body:
<header class="Resource-header">
<%= yield :resource_header %>
</header>
<div class="Resource-body">
<%= yield :resource_body %>
</div>
Then render it from my templates like this:
<%= render layout: 'admin/resource' do %>
<% content_for :resource_header do %>
<% end %>
<% content_for :resource_body do %>
<% end %>
<% end %>
However, this renders nothing.
I started playing with the order of things, and discovered that if the content_for blocks are declared before the call to the partial, this approach does work:
<% content_for :resource_header do %>
<% end %>
<% content_for :resource_body do %>
<% end %>
<%= render layout: 'admin/resource' do %><% end %>
However this just feels incredibly hacky. It seems that content_for is scoped globally, and there is no association between the content_for block and the partial rendering.
So what is the correct way for me to achieve this?
I just happened to have exactly same problem.
Solution is:
in your partial layout file 'admin/resource' body:
<header class="Resource-header">
<%= yield resource, :resource_header %>
</header>
<div class="Resource-body">
<%= yield resource, :resource_body %>
</div>
in your templates do:
<%= render layout: 'admin/resource' do |resource, section| %>
<% case section %>
<% when :resource_header %>
Resource header shows here.
<% when :resource_body %>
Resource body shows here.
<% end %>
<% end %>
Take a look on rails presenters https://www.ruby-toolbox.com/categories/rails_presenters
Maybe your solution is cells gem.
Eventhough the question is quite old now, I had a similar issue today. I came up with sth. like this. No gem or custom class required, just some fancy block usage ;)
<!-- app/views/layouts/fancy-blocks.html.erb -->
<%
body, footer = nil
yield(
proc {|&blk| body = capture(&blk) },
proc {|&blk| footer = capture(&blk) }
)
%>
<section class="body"><%= body %></section>
<footer><%= footer %></footer>
<!-- app/views/some-other/view.html.erb -->
<%= render 'layout/fancy-blocks' do |body, footer| %>
<% body.call do %>
BODY
<% end %>
<% footer.call do %>
FOOTER
<% end %>
<% end %>
Related
I have a main page that is responsible for HTML/CSS styling, but some of the contents come from partials. A partial receives some locals or params, i.e. current_user or person, and displays information if any.
Is there a way for me to check if a partial rendered anything? My end goal is something like this:
<% if my_partial can render something %>
<div class="css_for_something">
<%= render(partial: 'my_partial', locals: {...} ) %>
<% else %>
<div class="css_for_no_info">
<%= render something else %>
<% end %>
I do not want the partials to handle styling logic; they just need to display content if any. Conversely, the main page should not know anything about the logic in the partial(s), such as checking values or querying the database.
Thank you
Unfortunately, Chris Peter's solution did not work for me on rails 4.2.4, as render_to_string seems to not be available in views.
However, the following worked (rails 4.2.4):
<% partial_content = render partial: 'my_partial' %>
<% if partial_content.present? %>
<%= partial_content %>
<% else %>
<%# rendered if partial is empty %>
<% end %>
Be aware that the present? check really only checks if what was rendered is empty. If, something, e.g. a HTML comment, is returned, the check returns false.
Try storing the value generated by render_to_string in a variable:
<% partial_content = render_to_string(partial: 'my_partial', locals: {...} ).strip %>
Then you can see if it contains any content:
<% if partial_content.present? %>
<%= partial_content %>
<% else %>
<div class="css_for_no_info">
<%= render something else %>
</div>
<% end %>
I have a partial called _main_nav.html.erb in views/layout folder, this is content file:
<% content_for :main_navigation do %>
<h1>Something must here</h1>
<% end %>
In my _header.html.erb file, i used yield to insert content:
...
<%= yield :main_navigation %>
<nav id="utility" class="nav-collapse">
...
But when i visit home page, it does'n appear h1 element, i have wrong somewhere?
The issue is that you are trying to yield content from a view that isn't currently being rendered.
You would need to do:
_header.html.erb
...
<%= render "layouts/main_nav" %>
<%= yield :main_navigation %>
<nav id="utility" class="nav-collapse">
...
_main_nav.html.erb
<% content_for :main_navigation do %>
<h1>Something must here</h1>
<% end %>
http://guides.rubyonrails.org/layouts_and_rendering.html#understanding-yield
If I have one <%= yield %> tag then all my views render in the same place in the layout. Can I have different <%= yield %> tags for different views? Is so how do I do this? Thanks
Look into ActionView::Helpers::CaptureHelper. You can do something like this in your views:
<% content_for :sidebar do %>
<!-- sidebar content specific to this page -->
<% end %>
This will run the template inside the content_for block, but will not output as part of the regular template yield buffer, it will be stored in a separate buffer for later. Then later on, including in the layout, you can use yield :content_name to output the content:
<div class="content">
<%= yield %>
</div>
<div class="sidebar">
<%= yield :sidebar %>
</div>
So in a sense you can have different yields for different views, you just have to give the differing content a name with content_for in the views, and yield it with that same name in the layout.
Consider your case, where you want different views in different places. Let's say you have three panels, panel1, panel2, and panel3. You can do this in your layout:
<div id="panel1"><%= yield :panel1 %></div>
<div id="panel2"><%= yield :panel2 %></div>
<div id="panel3"><%= yield :panel3 %></div>
You don't even need to include a plain <%= yield %> if you don't want to. Then in your views, you can choose which panel to display the content in by surrounding the entire view with the appropriate content_for. For example, one of your views might be changed like this:
<% content_for :panel2 do %>
<!-- Your View -->
<% end %>
To show in panel 2. Another one might be intended for panel 3, like this:
<% content_for :panel3 do %>
<!-- Your View -->
<% end %>
Yes, you can have multiple <%= yield %> tags. You can specify each yield tag with names like these in the base view.
<%= yield :head %>
<%= yield :footer %>
Then use the content_for tag in your individual views.
<% content_for :head do %>
<%= stylesheet_link_tag 'custom' %>
<% end %>
You can use yield and content for:
For example:
<%= yield :head %>
<% content_for :head do %>
<title>A simple page</title>
<% end %>
Refer :layout and rendering guide.
Hy,
I have a layout in the views/layout that has 2 cols and then in every view i have content_for :main_col and content_for :side_col. The problem is that i have more than 5 views with the same content in the content_for :side_col
You have a better idea on how to do this?thanks
Put it into a partial and render the partial where you need it.
app/views/shared/_sidebar.html.erb
<% content_for :sidebar do %>
Hello, I am your neighbourhood friendly sidebar!
<% end %>
app/views/somewhere/else.html.erb
<%= render :partial => "shared/sidebar" %>
If you need pass variables to your partials (what I guess you do), use a helper it's a very neat way to DRY your code:
# app/views/shared/_side_col.html.erb
<% content_for :side_col do %>
<ul>
<% menu_items.each do |item| %>
<li><%= item %></li>
<% end %>
</ul>
<% end %>
# app/helpers/application_helper.rb
def side_col(menu_items)
render 'shared/_side_col', :menu_items => menu_items
end
# app/views/your/view.html.erb
<%= side_col your_menu_items_for_this_view %>
My web pages consist of two parts, let's say top and bottom (except header and footer -- those are consistent across pages). What is the best practice to dynamically generate those parts depending on the action?
One approach I have come up with is to have view for the top and partial for the bottom; in the layout call yield for the top and render partial for the bottom. The name of the partial is dynamically substituted depending on the action.
Not sure it is the best way to do it.
I think your idea is fine. In your views you could do:
<%- content_for :top do -%>
[…]
<%- end -%>
<%- content_for :bottom do -%>
<%= render #partial_name %>
<%- end -%>
Of course you should check whether the partial exist and provide some default behavior. But I think you're aware of that anyway.
And then in your layout:
<div id="top">
<%= yield :top %>
</div>
<div id="bottom">
<%= yield :bottom %>
</div>
Here is a very simplified version of a view DSL I've used in the past. Worked well for us. In reality we parameterized the the helper methods so we could choose from many layout partials on the fly (to have pages with sidebars, multiple columns, etc).
# app/views/shared/_screen.erb
<div id="screen">
<div class="screen_header">
<%= yield :screen_header %>
</div>
<div class="screen_body">
<%= yield :screen_body
</div>
<div class="bottom">
<%= yield :footer %>
</div>
</div>
# app/helpers/screen_helper.rb
module ScreenHelper
def screen(&block)
yield block
concat(render :partial => 'shared/screen')
end
def screen_header
content_for :screen_header do
yield
end
end
def screen_body
content_for :screen_body do
yield
end
end
def footer
content_for :footer do
yield
end
end
end
# app/views/layouts/application.erb
# only showing the body tag
<body>
<%= yield :layout
<body>
# Example of a page
# any of the sections below (except screen) may be used or omitted as needed.
# app/views/users/index.html.erb
<% screen do %>
<% screen_header do %>
Add all html and/or partial renders for the header here.
<%end%>
<% screen_body do %>
Add all html and/or partial renders for the main content here.
<% end %>
<% footer do %>
Add all the html and/or partial renders for the footer content here.
<% end %>
<% end %>