In one of my views I apply a layout to a block of code:
# In app/views/sessions/new.html.erb
<% render :layout => 'home/shadow_box' do %>
#... code for sign in form here
<% end %>
The layout is a div that has png shadows on all four sides.
Since I use this layout all over my site, I want to pass a variable to the layout that specifies the width of the shadowed div. I tried using content for in the code block:
# In app/views/sessions/new.html.erb
<% render :layout => 'home/shadow_box' do %>
<% content_for :box_width %>640<% end %>
#... code for sign in form here
<% end %>
# In app/views/home/_shadow_box.html.erb
<div class="shadow-one" style="width:<%= yield :box_width %>;">
<div class="corner-a"></div>
<div class="corner-b"></div>
<div class="shadow-two">
<div class="shadow-three">
<div class="shadow-four">
<%= yield %>
</div>
</div>
</div>
</div>
This didn't work and instead resulted in a double render of the entire code block.
What's the best way to tackle this problem?
Figured it out.
From the API: "You can also yield multiple times in one layout and use block arguments to differentiate the sections."
Solution:
# In app/views/sessions/new.html.erb
<% render :layout => 'home/shadow_box' do | section | %>
<%- case section when :box_width -%>
#width goes here. I.e., 640px
<%- when :content -%>
#code block goes here
<% end -%>
<% end %>
#In app/views/home/_shadow_box.html.erb
<div class="shadow-one" style="width:<%= yield :box_width %>;">
<div class="corner-a"></div>
<div class="corner-b"></div>
<div class="shadow-two">
<div class="shadow-three">
<div class="shadow-four">
<%= yield :content %>
</div>
</div>
</div>
</div>
First you need to know the difference between layouts and partials. Partials are generally from the view but can also be used from the controller if you are using ajax. Layouts are almost always used in the controller.
First create a file in a shared folder such as application/ and in this folder put a file call it whatever you want but it will contain the material that you want to include all over your site. Then when you pass a variable to a partial it's called in the partial as a local variable. Also with partials you don't need to say render :partial => you just put render 'application/some_file'
So from the view you want this:
<%= render 'application/your_file', :div_size => '600' %>
And then from the partial in the folder such as application/your_file.html.erb do this:
<div style="width:<%= div_width %>px;">
content
</div>
Related
On my views I use 1 form that includes a block that renders comments. I do not want to run it when creating a new record. So, I tried conditions like so...
<% unless #annotation_id.nil? %>
<hr>
<div class="row">
<div class="col-md-8">
<h4>Comments</h4>
<%= render #annotation.comments %>
</div>
<div class="col-md-4">
<%= render 'comments/form' %>
</div>
</div>
<% end %>
This however results in never displaying the block - also when the annotation record exists. What am I doing wrong?
You don't show that you have actually set #annotation_id to something.
A simpler way might be to use the .new_record? method instead, like:
<% unless #annotation.new_record? %>
...
<% end %>
use if #annotation.persisted? or unless #annotation.new_record?
I am trying to insert content on my page with yield but every time action removes whole content from the page. I have one main yield which is working fine:
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
But inside that new content which is displayed on one page I have another yield:
<div class="container">
<%= render 'admins/menu' %>
<%= yield :admin %>
</div>
When user clicks on the menu which is rendered, new content should be displayed below that menu.
admins/_menu.html.erb
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<ul class="nav">
<li><%= link_to "Users", :controller => "admins", :action => "test" %></li>
<li><%= link_to "1", ... %></li>
<li><%= link_to "2", ... %></li>
<li><%= link_to "3", ... %></li>
</ul>
</div>
</div>
</div>
Controller:
class AdminsController < ApplicationController
def index
end
def test
#users = User.paginate(page: params[:page])
end
end
test.html.erb
<% content_for :admin do %>
<h1>All users</h1>
...
<% end %>
When I click on the option 'Users' from menu, page refreshes, menu disappears and nothing is displayed inside `body'. I want the content to be displayed below menu. How to use that second yield and accomplish this functionality?
I hope the question is not confusing. If question is confusing, please write me in comments and I will edit it immediately.
Thank you :)
So, when you go to the index page you will get the piece of html that will be placed in the main layout, and this piece of html look like this:
<div class="container">
<%= render 'admins/menu' %>
<%= yield :admin %>
</div>
This code will yield :admin properly.
When you go to the test page you do not have this html code anymore (since it only belongs to the index method). So, anything you put in the content_for(:admin) block will be ignored since no-one is printing it.
What you probably want to do is creating a shared layout for all your admin pages. Follow this guide and you'll have your solution.
Solution
Edit the application.html.erb layout using this:
<%= content_for?(:content) ? yield(:content) : yield %>
instead of
<%= yield %>
Then create an admins.html.erb file inside the layouts folder to handle your admin pages' layout. Something like this:
<% content_for :content do %>
<div class="container">
<%= render 'admins/menu' %>
<%= yield %>
</div>
<% end %>
<%= render template: "layouts/application" %>
Will do fine. Then in the index.html.erb and test.html.erb just place regular HTML content, without using the content_for(:admin) block. Everything should work fine and you'll have your custom admin template, with a slightly different look from regular pages.
Calling yield doesn't work in helper modules, while content_for does, so you should replace your yield calls in the helper files.
Also noteworthy: using provide is recommended over content_for when you're only using the method in 1 place instead of multiple places. You'll get better performance since it won't leave the buffer open while looking for more content, and your intent will be clearer to other developers that may see your code. (see http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html#method-i-provide)
I found that you have to add an yield (without actually outputing) before the namespaced tags.
<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>
...
I have a Rails app, in which I fit my entire website into a 980width container with the following code in my 'application.html.erb' file:
<div class="container_980 white shadow-horizontal">
<div class="container">
<%= render 'layouts/flashes' %>
<%= yield %>
</div>
</div>
Now, I want to make 2 file exceptions for fitting the content within the container. I want the index page and another page to expand across the entire page, so I need to get those two pages outside of the common 'yield' set above.
I tried doing so with:
<% if current_page?(root_url) %>
<%= yield :index %>
<% elsif current_page?(:controller => "tracks", :action => "show", :id => params[:id])) %>
<%= yield :show_track %>
<% else %>
<div class="container_980 white shadow-horizontal">
<div class="container">
<%= render 'layouts/flashes' %>
<%= yield %>
</div>
</div>
and
<% content_for :show_track do %>
blah blah blah
<% do %>
THE PROBLEM: The show_track page doesn't load. I did some searching, and it seems like the above method should work, but it's not, and I was wondering if I needed to do something else as the "show" page was made through scaffoldaing(RESTful).
Is there a better way to take out the 2 pages from the container than using if..else conditions?
Is there a better way to take out the 2 pages from the container than using if..else conditions?
This is subjective, but I would use nested layouts, then define the layouts for each page type in the controller.
First your basic top level layout. I'm calling it "application", the default, but you could call it whatever. Note how if there's content_for? :application it will yield it, otherwise it will just yield. This is key to the setup. All nested layouts should follow a similar pattern; in this way they can render further nested child layouts, or be used as layouts themselves.
<!-- layouts/application.html.erb -->
<html>
<body>
<%= content_for?(:application) ? yield(:application) : yield %>
</body>
</html>
Then for the container, you'd define layout which can be nested inside "application", this one setting up your container HTML and rendering content inside.
<!-- layouts/container.html.erb -->
<%= content_for :application do %>
<div class="container_980 white shadow-horizontal">
<div class="container">
<%= render 'layouts/flashes' %>
<%= content_for?(:container) ? yield(:container) : yield %>
</div>
</div>
<% end %>
<%= render :file => "layouts/application" %>
Then just move your conditional logic to the controller, like:
layout :determine_layout
protected
function determine_layout
# pseudocode here, you get it
(index or tracks) ? "application" : "container"
end
You could stop there. Continue to see how you might further nest layouts.
However you could go further, and use the nested layout setup to nest arbitrary numbers of different layouts. Say, for example, that tracks had another content block you needed to fill. You could define another nested layout, like:
<!-- layouts/tracks.html.erb -->
<%= content_for :some_other_block do %>
// stuff that should be in some other block
<% end %>
<%= content_for :container do %>
// stuff that should be in the container
<% end %>
<%= render :file => "layouts/container" %>
Then in your controller, you'd change your determine_layout to set the "tracks" layout for tracks, e.g.:
function determine_layout
# pseudocode here, you get it
if index
"application"
elsif tracks
"tracks"
else
"container"
end
end
Problem: How Do You Render Multiple View Pages From Different Controllers?
In my views/layouts/application.html.erb, I call views/posts/index.html.erb with the yield method.
<div class='span10'>
<%= yield %>
</div>
On top of it, I wanted to call views/good_posts/index.html.erb with yield. So in that particular index file, I wrap around all the code with content_for method.
<%= content_for :good_post do %> all my content in good_post/index.html.erb <% end %>
I go back to application.html.erb, I tried to call the index file of good_post with yield method.
<div class='span10'>
<%= yield :good_post %>
</div>
<div class='span10'>
<%= yield %>
</div>
I thought this would result in good_post/index to be rendered on top of post/index, but it did not work; only post/index was correctly rendered as before. Could someone explain why this is, and tell me the correct way to approach this problem? I appreciate your help!
You should, as #cdesrosiers said, rename the index.html.erb file in good_posts to _index.html.erb. Then you can render this view in your application like this:
<div class='span10'>
<%= render 'good_posts/index' %>
<%= yield :good_post %>
</div>
<div class='span10'>
<%= yield %>
</div>
Personally, I would change the index.html.erb file in good_posts folder to _good_posts.html.erb file in app/views/posts folder. Your code will have better meaning, and you can know where to find it after, because it relates to posts. So, if you change this, use this code:
<div class='span10'>
<%= render 'posts/good_posts' %=
<%= yield :good_post %>
</div>
<div class='span10'>
<%= yield %>
</div>
Another you should change is content_for :good_post -> content_for :good_posts , because good posts maybe have many posts, so you should use post in pluralize.
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 %>