Collect data in views and output in head - ruby-on-rails

I have a helper that collects data from multiple views:
# application_helper.rb
def data
#data ||= []
end
In my HTML, I want to add data to this helper. This should be possible from within the layout, views and partials:
# layout.html.erb
<html>
<head></head>
<body>
<section><% data << "section data" %></section>
<%= yield %>
<footer><% data << "footer data" %></footer>
</body>
</html>
# my_view.html.erb (which is rendered with yield)
<h1>My View</h1>
<% data << "view data" %>
The problem is, that the data should be rendered as a data attribute on the body tag, e.g.
<body data-test="<%= data.to_json %>">
Of course, this would render an empty array, because at this point of time, the array is not filled yet.
I tried it with
<body data-test="<%= yield(:data) %>">
...
<% content_for(:data) do %>
<%= data.to_json %>
<% end %>
</body>
but this also doesn't work. It only works, when I put the content_for call inside a view. And furthermore, I only get view data but not section data or footer data. So it seems that only data that is added within my view is rendered, not the data that I add within my layout.
How would something like this be done? Rendering data at the top of an html page, that is collected later in the document?

<html>
<head></head>
<body data-test="<%= content_for(:data) %>">
<section></section>
<%= yield %>
<footer>></footer>
</body>
</html>
content_for can be used to both store content for later use and provide dynamic blocks since subsequent calls to content_for concatenate.
Lets say we are rendering users/show:
<h1><%= #user.name %></h1>
<% content_for(:data) { #user.name } %>
<% content_for(:data) { ", " + #user.username } %>
This would give
<body data-test="john doe, john_doe">
However its use for passing data structures is questionable since its just a string buffer.
A better alternative is to gather the data beforehand and use JSON to pass the data into javascript.

Related

How can I use yield :content before content_for in Rails templates?

I need to use yield/content_for in <head>, but assign the value in <body>. I have found that this works fine when the value is being assigned from within a template that is being yielded to, but not one that is being rendered. Templates that are being rendered are compiled after <head>, so my value is already set in stone. Is there a way to achieve what I am trying to do?
I tried making application.html.erb look like this:
<%= render layout: 'application_template' do %>
<!-- <body> content here -->
<% end %>
and _application_template.html.erb look like:
<!doctype>
<html>
<head>
<%= content_for :my_value %>
</head>
<body>
<%= yield %>
</body
</html>
but the same problem happens, the value is nil when _application_template.html.erb is rendered.
You should be able to do the following.
In your head check if there is any content to yield and yield it if it is.
<% if content_for?(:my_value) %>
<%= yield :my_value %>
<% else %>
And then define your content somewhere in your body.
<% content_for :my_value do %>
# your contents
<% end %>
I figured out a solution.
# layouts/application.html.erb
<% content_for(:body_content) { render partial: 'layouts/body_content' } %>
<!doctype html>
<html>
<head>
<%= yield :my_value if content_for?(:my_value) %>
</head>
<body>
<%= yield :body_content %>
</body>
</html>
and
# layouts/_body_content.html.erb
# everything that used to be in <body></body> goes here and is maintained here.
This makes sure that all of the #content_for calls I need to happen will happen before <head> is compiled in my layout. It is also easier to maintain than some of the other hacks I had thought of trying.

Rails what does this "yield" do?

What does the yield do in this snippet?
<body data-spy="scroll" data-target=".sidebar">
<!-- Your timezone is <%= Time.zone %> -->
<!-- <%= "Ruby Version is #{RUBY_VERSION}" if Rails.env =~ /test|development/ %> -->
<%= render partial:'shared/account_status' %>
<%= render partial:"shared/session_timeout" %>
<div class="container">
<%= render partial:"shared/branding" %>
<%= render partial:"shared/nav", locals:{icons:icons, actionable_urls:actionable_urls, top_level_items:MenuItem.top_level_items_with_access_rights_for_user(current_user).sort{|a, b| a.sequence <=> b.sequence}, current_item:current_navigation_item} %>
<div style="clear:both"></div>
<div id="content">
<%= render partial:"shared/flash", object:flash %>
<%= yield %>
</div>
</div>
<%= render partial:"shared/ldap_user_menu" if signed_in_as_ldap_user? %>
</body>
SOLUTION
see #Christian_Rolle 's answer below
The reserved Ruby key word yield is for processing closures (like a Proc or lamda). That said it is some kind of placeholder for processing some logic.
In Ruby on Rails view templating it is used for merging in partials. In the case of a layout file like the application.html.erb it merges in controller response templates like index.html.erb or show.html.erb.
Think of it as a placeholder for your controller response HTML in a global layout environment.
Read more at: Understanding yield
or about Ruby closures:
Do the Proc! ... a Ruby closure and
Have a lambda! ... a Ruby closure
okay ... lets talk about it in easy way , yield is like a placeholder or like a container . And while you make different parts of view and want to show it on any specific layout file , then you can just call that part on the yield section . And this is all it does .

How do I remove a heading yield a specific page?

I am providing the h1 into my header, but I don't want it on products/show. How do I remove it just for that page?
_header.html
<header>
<h1><%= yield(:heading) %></h1>
</header>
Homepage
<% provide(:heading, 'This is the homepage heading') %>
products/show
<% provide(:heading, '') %>
Obviously, I could just not provide any heading for the products/show page but there is CSS styling applied to my H1 that is screwing up my design, whether there is content in the h1 tag or not.
Only render the element if it has content:
<header>
<% if content_for?(:heading) %>
<h1><%= yield(:heading) %></h1>
<% end %>
</header>
Use a different partial? Put an if-ish statement around the header?
As-is the elements themselves will always be there, because there's no reason for them not to be.

switch layout columns based on controller

so basically my application looks like this
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div id="content">
<div id="content_left">
// Some Stuff
</div>
<div id="content_right">
<%= yield %>
</div>
</div>
</body>
</html>
now i want to switch between a two column layout and a single column layout based on the controller (at best: also based on the method i'm using).
between the body and the content and also in there head there is way too much stuff for simply creating a second layout and adding this as a layoutcall in my controller without too much code duplication.
what i would love to do is something like this:
all should use this
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
// much stuff
<%= yield %>
</body>
</html>
and now i can switch between two layouts, f.e
single_col
<div id="content">
<%= yield %>
</div>
or two_column
<div id="content">
<div id="content_left">
// Some Stuff
</div>
<div id="content_right">
<%= yield %>
</div>
</div>
and then in the last yield there should be the view that is related to my controller and method.
is there an easy way to achieve this?
thanks for all hints.
PLEASE leave a comment if something is unclear.
Put the different parts of the layouts in partials! Then you render the partials based on what is in
params[:controller] and params[:action]. For example:
<% if params[:controller] == "controller_name" %>
<%= render 'partial_name1' %>
<% else %>
<%= render 'partial_name2' %>
<% end %>
The params[:controller] and params[:action] are always available! This is an example to show you how it works. Of cause there shjouldnt be any logic in the view aswell!
You could use two layouts that would both render header and footer partials containing what's common to both.
OR, you use a <%= yield :sidebar %> and then inject something
<% content_for(:sidebar) do %>
some stuff here
<% end %>
Take a look at Rails guide section about those here.
I think you looking for a way to use a shared layout with two sub layouts inside, here you go:
add this to your application_helper.rb
# Allows easy using nested layouts
def inside_layout(layout = 'application', &block)
render :inline => capture_haml(&block), :layout => "layouts/#{layout}"
end
layouts/application.html.haml
!!!
%html
%head
-# your header content
%body
.content
= yield
layouts/single_column.html.haml
= inside_layout do
.middle
= yield
layouts/two_column.html.haml
= inside_layout do
.left
-# your shared left content
.right
= yield
the column layouts can now be used like normal layouts, so you can just set them in your controller
layout "single_column"
Note: All markup is in HAML a gem which I can highly suggest.
hope it helps :)
You can use multiple layouts in a rails project. You could have a 2 column and a single column one.
To specify the layout used, provide it in the method in the controller.
def index
respond_to do |format|
if current_user
format.html {render 'pages/index', :layout => 'home'}# index.html.erb
else
format.html {render 'pages/landingpage', :layout => 'landingpage'}# index.html.erb
end
end
end
For example the controller above, will render the the views index.html.erb and landingpage.html.erb based on the value of current_user. You can also only provide the layout parameter to render like render :layout => 'home'
You could also have a look at nested layouts but I have never worked with those.
You could use sub_layouts
Class IndexController < ApplicationController
def index
def sub_layout
"left"
end
end
end
Add sub layouts to /app/view/shared/layouts/sub/_mysublayout.haml or .erb
in /app/views/layouts/application.haml/.erb * your main layout file*
= render :partial=>"layouts/sub/#{controller.sub_layout}"
With some checking for nils with rescue nil you can make this sub template load the layout for say a right or a left col, use this with great benefit in my apps. Its a big time saver and gives you the extra flexibility to set the layout on an action base.
Im assuming that in your single column layout you have no information for your content_left.
And in two column layout you have information for both content_left and content_right.
There are lots of ways but im recommending something completely controlled by CSS.
CSS classes will be like following
#content_left{
float: left;
background-color: red;
}
#content_right{
background-color: green;
}
#content {
margin: 0 auto;
width: 80%;
}
Now notice... if content_left div is empty then the content_right div will expand to full width. And you are ready with single column layout. And if you have data in content_left it will be showing accordingly.
I am using helper method to set Dynamically sidebar,you can use as per your requirement.
<div id="main_content" class="<%= main_content_css_class %>">
<%= yield %>
</div>
<div id="sidebar" class="<%= sidebar_css_class %>">
<%= yield :sidebar %>
</div>
In Helper
def sidebar_enabled?
current_page = "#{controller.controller_name}.#{controller.action_name}"
current_controller = controller.controller_name
pages = %w(home)
return pages.include?(current_page) || pages.include?(current_controller)
end
def main_content_css_class
sidebar_enabled? ? "grid_12" : "grid_16"
end
# Returns the CSS class for the 'sidebar' div depending on sidebar requirement
def sidebar_css_class
sidebar_enabled? ? "grid_4" : "dont-show"
end
Using your code is also cleaner and maintainable.
I usually put classes on the body element for the controller and action name, as it comes in handy in many ways. Here's an example based on that strategy:
...
<body class="<%= controller_name %> <%= action_name %>">
<div id="content">
<div id="sidebar">
// Some Stuff
</div>
<div id="main">
<%= yield %>
</div>
</div>
</body>
...
then it's a matter of getting the right CSS. If you had a 'posts' controller, the sidebar is usually visible, and you want to hide it:
body.posts { #sidebar { display: none; } };
If you want it to only show up sometimes, you could invert the logic so it's usually hidden, then override with a more specific scope to show it. Or, you could have one column on all 'index' actions, two columns for everything else, etc.
The downside to this, and it's not insignificant, is that your views are coupled to the name of the controller. I.e. this strategy violates the "tell, don't ask" principle. I still think it's worth it, since I haven't been bit by a nasty refactoring involving this code yet. Usually it's a simple matter of changing a couple instances of the controller name in ex. "posts.css.scss" when renaming the file; no big deal. Just something to weigh.
Also, note that I picked more descriptive class names. "Left" and "right" are ephemeral, as your example demonstrates. "Sidebar" and "main" might not be what you have, but I'd make an attempt to describe what goes in them if possible.

Creating a generic HTML header with blocks in Rails

What I want is to do something like this in my views:
<% page_header "Your Posts" do %>
<div class="add">
<%= link_to 'Add a new post', new_posts_path %>
</div>
<% end %>
And have the HTML render something like this:
<div class="page_header">
<h2>Your Posts</h2>
<div class="add">
Add a new post
</div>
</div>
However, sometimes I don't want to have any extra content and just have the rendered HTML be:
<div class="page_header">
<h2>Your Posts</h2>
</div>
Instead of having two methods I want to use a block to render the extra content if it's given, or just the header if it's not; this way I can use a generic call in all of my views to keep my code DRY.
I have the following code in my Application Helper, but it doesn't seem to be rendering anything:
# Renders a div for the page header with an H2 tag representing the page title
# If a block is provided, renders that content within the page header DIV
def page_header(title, &block)
concat(content_tag(:div, :class => "page_header") do
content_tag(:h2, title)
end)
block.call if block_given?
end
However, this doesn't work. When I give a block, it renders properly. Without the block, though, it doesn't render anything, not even the default .
I'm missing something simple to fix this, but I'm not sure what.
Shouldn't the block call be inside the content_tag?, like so:
# Renders a div for the page header with an H2 tag representing the page title
# If a block is provided, renders that content within the page header DIV
def page_header(title, &block)
concat(content_tag(:div, :class => "page_header") do
content_tag(:h2, title) +
block_given? ? block.call : ''
end)
end
You can do something like this
def page_header(title, &block)
concat( render :partial=>"shared/page_header",:locals=>{:title=>title,:body=> capture(&block)})
end
inside the partial _page_header.erb
<div class='page-header'>
<h2> <%= title %> </h2>
<%= body %>
</div>

Resources