Partial with multiple yields in rails - ruby-on-rails

I'm looking for solution to have partial with multiple yields.
In real example I have this views structure:
Basic application.erb (/views/layouts/application.erb):
<!DOCTYPE html>
<head>
<title>Some title</title>
</head>
<body>
<div class="page">
<%= yield %>
</div>
</body>
</html>
Some partial to DRY my code (/views/shared/content.erb):
<div class="content">
<div class="sidebar">
<%= yield :sidebar %>
</div>
<div class="main">
<%= yield %>
</div>
</div>
And controller view (/views/home/index.erb):
<%= render :partial => 'layouts/header' %>
<%= render :partial => 'shared/navigation' %>
<% # It is close to what I want to do %>
<%= render :layout => 'shared/content' do %>
<% content_for :sidebar do %>
<%# This is will go to application.erb, not in content.erb %>
<%= render :partial => 'shared/menu' %>
<% end %>
<%= yield %>
<% end %>
<%= render :partial => 'layouts/footer' %>
So the main issue here is to have a template block with multiple yield areas and ability to pass custom html or render another partial.

This question is old, however, it's still relevant when I was searching for an answer on Google.
I've come up with a solution, while still not beautiful, works very well. The idea uses Rails' capture method, which takes a block and stores its contents into a variable:
controller.html.erb
<%= render 'shared/partial', body: capture { %>
My body content
<% }, footer: capture { %>
My footer content
<% } %>
shared/_partial.html.erb
<div id="body"><%= body %></div>
<div id="footer"><%= footer %></div>
Hope this helps someone!

In my case I've found solution like this.
On my controller view (/views/home/index.erb):
<% sidebar_content = render :partial => 'shared/slider' %>
<%= render :layout => 'shared/content', :locals => {:sidebar => sidebar_content} do %>
<%= yield %>
<% end %>
The partial with multiple areas (/views/shared/content.erb):
<div class="content">
<div class="sidebar">
<%= sidebar %>
</div>
<div class="main">
<%= yield %>
</div>
</div>
This solution doesn't look pretty, but it works. I hope to find something better in near future.

Here is my solution:
/views/shared/_content.erb
<div class="content">
<div class="sidebar">
<%= yield :sidebar %>
</div>
<div class="main">
<%= yield %>
</div>
</div>
views/home/index.erb
<%= my_content_tag do %>
<% content_for :sidebar do %>
<%= render :partial => 'shared/menu' %>
<% end %>
<%= yield %>
<% end %>
app/helpers/my_tags_helper.rb
module MyTagsHelper
def my_content_tag(&block)
sidebar_backup = #view_flow.content.delete(:sidebar)
x = capture(&block)
html = render('shared/content') { x }
#view_flow.content[:sidebar] = sidebar_backup if sidebar_backup
html
end
end
The key is using capture to extract content_for block and pass it into #view_flow

Related

Rails yield and content_for wieird behaviour, `yield :filter` only work if placed after default yield

Lets say i have this partial i am trying to render
#layouts/_subheader.html.erb
<div class="subheader">
<%= yield %>
<%= yield :filters %>
</div>
when i use this partial in a view like this
<%= render 'layouts/sub_header' do %>
<h2> Content For Yield </h2>
<% content_for :filters do %>
<h2> Content for Filters </h2>
<% end %>
<% end %>
i am getting the HTML output as
<div class="subheader">
<h2> Content For Yield </h2>
<h2> Content for Filters </h2>
</div>
this works as expected, but the problem arises when i change the order of the yield tags in the partial
instead of the above, if i rewrite the partial as
#layouts/_subheader.html.erb
<div class="subheader">
<%= yield :filters %>
<%= yield %>
</div>
i am getting output as
<div class="subheader">
<h2> Content For Yield </h2>
</div>
the content_for :filters is not being rendered.
what an i doing wrong here ? is this the correct behavior or am i doing something wrong ?
what should i do if i have to make the content of the yield :filters appear before the plain yield
EDIT:-
I have tried
#layouts/_subheader.html.erb
<div class="subheader">
<%= content_for :filters %>
<%= yield %>
</div>
but it seems to be not working as well.
Thanks everyone on reddit and #Ezhil i found a solution, this is what i did
what i did is i placed the content_for capture group before the render like this
#index.html.erb
<% content_for :filters do %>
<h2> Content for Filters </h2>
<% end %>
<%= render 'layouts/sub_header' do %>
<h2> Content For Yield </h2>
<% end %>
and in my partial
#layouts/_subheader.html.erb
<div class="subheader">
<%= content_for :filters %>
<%= yield %>
</div>
It's because the block isn't executed until the first yield, so there's no content captured until that runs.

Rails 4 - Multiple yield blocks on a partial

Is it possible to have a partial using more than one yield block? I wanted to use it to implement bootstrap modal boxes on my project, kinda like this:
<div class="modal fade" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<%= yield :header %>
</div>
<div class="modal-body">
<%= yield :body %>
</div>
<div class="modal-footer">
<%= yield :footer %>
</div>
</div>
</div>
</div>
And this is more or less how I was thinking of using it
<%= render partial: "shared/modal" do %>
<% content_for :header do %>
...
<% end %> %>
<% content_for :body do %>
...
<% end %> %>
<% content_for :footer do %>
...
<% end %> %>
<% end %>
Is there a way to do this? Is this maybe a bad approach for some reason?
Through much trial and error, I think I solved this in Rails 5 but needed to insert a blank yield in my partial in order to get it to work:
_partial.html.erb
<div class="partial">
<%= yield %> <!--Does not do anything-->
<div class="header">
<%= yield :header %>
</div>
<div class="text">
<%= yield :text %>
</div>
</div>
Implemented as:
full_layout.html.erb
<%= render "partial" do %>
<% content_for :header do %>
<h1>Header content</h1>
<% end %>
<% content_for :text do %>
<p>Text content</p>
<% end %>
<% end %>
I think you have an issue with your render partial. Notice you have all of your content_for blocks within the render partial block.
<%= content_for :thing do %>
Some content
<% end %>
<%= render partial: "blah" %>
It's no problem to use multiple yield blocks in your partial. Only thing is to make sure that many is actually needed. For an example content_for sections are basically placeholders for content that could vary based on the logic of the application. So, it's totally fine to use multiple yields in one page.
You can also just run put an yield there withouth the output. This way you can also use the default block.
<div>
<% yield %>
<div class="mt-3">
<div class="text-2xl tracking-wide font-bold text-gray-900">
heading
<%= yield :heading %>
</div>
</div>
<div class="relative bg-white rounded-xl shadow-xl mb-8 min-h-28">
<%= yield %>
</div>
...

Two content_for the same yield renders in all the views (Rails 3)

I have two separate views which are outputting content for a named yield block called :icons.
When I load the two pages, I always see in the :icons block the content of the 2 views... :-(
View 1 : Projects#Index
<% content_for :icons do %>
Project Icons ...
<% end %>
...
View 2 : Tree#Show
<% content_for :icons do %>
Expand
Collapse
Import
<div id="viewname_selector">
<%= form_tag({:controller => :loopview, :action => :show}, { :id => "viewname_form" ,:remote => true}) do %>
<%= collection_select(:viewname, :id, #viewnames, :id, :name, :selected => #current_viewname_id) %>
<% end %>
</div>
<% end %>
And the call in the application.html.erb is
<div id="icons">
<%= yield :icons %>
</div>
When I load Projects#index, I have only the Project Icons (so it is ok).
When I load Tree#index, I have the tree icons AND the project icons ...
What Am I doing wrong ? How to get only the tree icons in the tree view ?
Edit : Complete source of tree#index
<% content_for :head do %>
<title>Dynatree View <%= sicadea %></title>
<%= javascript_include_tag "leanModal/jquery.leanModal.min" %>
<%# For the dynatree %>
<%= stylesheet_link_tag "dynatree/skin/ui.dynatree.css" %>
<%= stylesheet_link_tag "dynatree/skin/ui.dynatree.custom.css" %>
<%= javascript_include_tag "cookie/jquery.cookie.js" %>
<%= javascript_include_tag "dynatree/jquery.dynatree-1.2.4.js" %>
<%= stylesheet_link_tag "contextMenu/jquery.contextMenu.css" %>
<%= javascript_include_tag "contextMenu/jquery.contextMenu-custom.js" %>
<%= javascript_include_tag "contextMenu/jquery.ui.position.js" %>
<%= javascript_include_tag "dynatree.js" %>
<%# for the poin'ts table %>
<%= stylesheet_link_tag "loopview.css" %>
<% end %>
<% content_for :icons do %>
Expand |
Collapse |
Import |
<div id="viewname_selector">
<%= form_tag({:controller => :loopview, :action => :show}, { :id => "viewname_form" ,:remote => true}) do %>
<%= collection_select(:viewname, :id, #viewnames, :id, :name, :selected => #current_viewname_id) %>
<% end %>
</div>
<% end %>
<div id="leftpanel">
<!-- Leftpanel Context menus -->
<ul id="myMenu" class="contextMenu">
<li class="rename">Rename</li>
<li class="new_child">New Child</li>
<li class="copy">Copy</li>
<li class="paste">Paste</li>
<li class="delete">Delete</li>
</ul>
<ul id="ProjectMenu" class="contextMenu">
<li class="new_child">New Child</li>
</ul>
<!-- Leftpanel Tree title -->
<div id='dynatree-title' data-project-id='<%= current_project.id %>'>
<span></span><%= current_project.name %>
</div>
<!-- Leftpanel Dynatree -->
<%= render_project_tree(current_project, "tree") %>
<!-- Leftpanel "New Child" Form -->
<%= render :partial => "lean_new_child" %>
</div>
<!-- Right Panel -->
<div id="dynatree-details">
<%= render :template => 'projects/show' %>
</div>
May I know why did you add following code in application.html.erb.
<div id="icons">
<%= yield :icons %>
</div>
I think this only loading in your Tree#index page too..

Passing markup into a Rails Partial

Is there any way of doing something equivilant to this:
<%= render partial: 'shared/outer' do %>
<%= render partial: 'shared/inner' %>
<% end %>
Resulting in
<div class="outer">
<div class="inner">
</div>
</div>
Obviously there would need to be a way of marking up 'shared/outer.html.erb' to indicate where the passed in partial should be rendered:
<div class="outer">
<% render Here %>
</div>
In my specific case I have a generic page header, consisting of a header and subheader, that is shared across all pages, but would like the option of passing in page-specific markup to that header to be rendered below the title and subtitle.
I'd use content_for :
<% content_for :subheader do %>
<%= render partial: 'shared/inner' do %>
<% end %>
<%= render partial: 'shared/outer' %>
Then in shared/outer :
<div class="outer">
<%= yield(:subheader) %>
</div>
You can put whatever you'd like in the content_for block and use it as many times as you'd like, just change the key name (here subheader)

Writing a helper method for page headers

I am currently integrating twitter bootstrap.
My application layout currently includes my flash messages right above the yield:
<div class="container">
<%= render 'layouts/header' %>
<div class="content" role="main">
<%= render :partial => 'shared/flash', :object => flash %>
<%= yield %>
</div>
<%= render 'layouts/footer' %>
</div> <!--! end of #container -->
However, I am also using the bootstrap Page Headers in my individual views
<div class="page-header">
<h1>Sign In <small>through one of these services:</small></h1>
</div>
I would like my flash messages to be below these page headers when page headers are set. So I'm thinking the best way to do this is to create a helper method and edit my application layout to be something like:
<%= render 'layouts/header' %>
<div class="content" role="main">
<%= pageHeader %>
<%= render :partial => 'shared/flash', :object => flash %>
<%= yield %>
</div>
How would I go about writing the helper for this. Would the best way to just have instance variables for #page_header and #page_header_small in my controller? Or is there a better rails way to handle this situation?
You can user content_for combined with yield.
In your layout you can put this block with headline above flash message
<%= yield :page_header %>
<%= render :partial => 'shared/flash', :object => flash %>
<%= yield %>
and in your template for action
<% content_for :page_header do %>
<div class="page-header">
<h1>Sign In <small>through one of these services:</small></h1>
</div>
<% end %>
Check it out at Ruby Guides

Resources