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.
Related
My goal is simple, I just want to know the best approach on how to to do this.
Scenario
book_controller
def new
#book = "book"
end
magazine_controller
def new
#magazine = "magazine"
end
HTML
application.html.erb
<body>
<div class="container">
<body of controller>
</div>
</body>
books/new.html.erb
<h1>hello book</h1>
magazines/new.html.erb
<h1>hello magazine</h1>
css
.background-black {
background: black
}
My goal is to add the background-black CSS class to the only book_controller for example
<div class="container background-black">
But it will affect magazine_controller as well. What if I have 4 controllers that needed the black background and the rest don't need that class. What is the best approach?
The best ways to achieve your goal is to use Controller Specific Assets as recommended in Rails asset pipeline docs ( you can find some implementation details here), but as you said what you want to do is simple so you can simply use erb syntax to dynamically control the value of the class attribute by calling a variable called controller populated by rails and containing information about the controller who render the view like the name of the controller.
<div class="container <%= controller.controller_name == 'book' ? 'background-black' : '' %>">
If you have 4 controllers that needed the black background you can use erb too.
<div class="container <%= ['book','magazine','user', 'newspaper'].grep(/#{controller.controller_name}/).empty? ? '' : 'background-black' %>">
The answer mentionned here use the same approch but have the advantage of helping you keep your css code clean and readable.
Hope that will help
First, You should delete some codes that //= require_tree in application.css,
Then <% self_css_path = "/assets/"+controller.controller_name %> <%= stylesheet_link_tag self_css_path, :media => "all" %> in application.html.erb
I have a situation in rails (version 4.04, ruby version 2.1) where I've been using the standard application.html.erb to define the main framework for my site, header, footer, nav bar, etc. When I got to an inner div, call it, inner-content, thats where I put a <% yield %> statement so that the sub template can take over and place its content in the correct place (for example products#show or products#index have show.html.erb and index.html.erb respectively which just the content for those actions).
The problem is I realized I was duplicated some code in those sub templates. In ever one of them (except one) I always was starting off like this:
<div class="columns large-6 medium-6 center-small">
<div class="inner_wrapper">
And I was always ending like this:
</div>
</div>
So I was thinking, I shouldn't be repeating all this code. I should move this into application.html.erb so that every template automatically gets the inner-content set up correctly.
The problem is that one action I was talking about. There is one action that has a different setup. I don't want to have to type in those extra 2 divs for every sub-layout except one. Is there a better way to do this?
One way could be to check which controller your currently using this in your application.html.erb
<% if params[:controller] == "controller name" %>
<div>
<%= yield %>
</div>
<% else %>
<div class="different div">
<%= yield %>
</div>
<% end %>
Not sure if this the best way, but its one way to do it.
Create a different layout file and call it maybe products_layout.html.erb.
Then in the controller
class ProductsController < ApplicationController
layout: 'products_layout'
....
end
Or do it on a per action
def show
render 'show', layout: 'products_layout'
end
http://guides.rubyonrails.org/layouts_and_rendering.html
I've watched this screencast to add a page title when in a view, is there a way I can do the same but add a class the body tag?
Not sure what you mean, you can do it the same way:
In a view:
<% content_for :body_class, "my_class" %>
In a layout file:
<body class="<%= yield (:body_class) %>">
I usually make a helper method for stuff like this so you can have defaults set up cleanly
application_helper.rb
def body_class(class_name="default_class")
content_for :body_class, class_name
end
view:
<% body_class "foo" %>
application.html.erb
<body class="<%= yield (:body_class) %>">
Sometimes using the current controller name as a class name we'll do:
<body class="<%= controller.controller_name %>">
I find this simpler and a bit more elegant, but of course thus you won't be able to assign individual class names.
s. Add Class To Body Using ERB In A View - Rails
In the layout page:
<% if content_for?(:body_class) %>
<body class="<%= content_for(:body_class) %>" >
<% else %>
<body>
<% end %>
In the content page:
<% content_for :body_class do 'my-body-class' end %>
I've used the accepted method in my app for a while, but never really loved how it worked, because if there is no class, you're gonna have that class=' ' on your body tag, littering your code. For my current use case, I just wanted a widescreen class (but you could easily get more advanced with different classes per your use case). I'm happy with this approach:
In your application helper:
def body_tag(&block)
content = capture(&block)
content_tag(:body, content, class: #widescreen ? "widescreen" : nil)
end
In application.html.erb
<%= body_tag do %>
<%# the rest of your content here %>
<% end %>
Then in your application controller:
private
def enable_widescreen
#widescreen = true
end
Then in any controller that you want it, just do:
before_action :enable_widescreen
Then feel free to make the class logic more advanced if you want to use it for different classes besides 'widescreen' - but the point is that this is an elegant way to allow for there NOT to be a class if you don't specify one, without
<body class>
showing up in your html.
I prefer to use the following method:
<body class="<%= content_for?(:body_class) ? yield(:body_class) : controller_name %>">
That method avoids the dreaded <body class>.
I frequently use the controller name to scope a number of styles so it's nice to not need to supply a content_for on every view if I only needed that one class.
TL; DR: How can I use the same layout multiple times on a page? All attempts to render partials with recurring layouts go into the elements where the first time the layout is used.
Quick Info:
Using
Rails 2.3.14
Ruby 1.8.7
My partials use content_for, which is pretty handy with layouts.
Basically, my layout looks like this:
<div class="header">
<%= yield :modal_header %>
</div>
<div class="body">
<%= yield :modal_body or yield %>
</div>
Generally, this is how I use my modal layout:
<% content_for :modal_header do %>
header text / elements
<% end %>
<% content_for :modal_body do %>
body info / settings for object or warning message, etc
<% end %>
This is how I render modals on my page:
<%= render :partial => "section_options", :layout => "modal" %>
and that works really well
BUT
when, on the same page, I try to render another modal:
<%= render :partial => "section_content_options", :layout => "modal" %>
This is what happens to the modal at the top of my page (occurs first in the HTML document)
<div class="header">
-- header from second partial --
-- header from first partial --
</div>
<div class="body">
-- body from second partial --
-- body from first partial --
</div>
and then later in the page, where the content from the second partial is supposed to be:
everything is rendered correctly ...
For the sake of this example:
<div class="header">
section content options
</div>
<div class="body">
section contents options ... options
lil confirm / cancel buttons
</div>
Is there a way to fix this? This behavior really messes with javascript bindings as well. so. yup.
Is there a bug with layouts in rails 2.3.14? do I have to upgrade to rails 3 to get rid of this problem?
Modals should be rendered using partials, not layouts. Layouts are designed as a way of defining the overall layout of the entire site, not specific parts. Something like a modal (which by design is specific) should be a partial that's rendered either through a condition or JavaScript.
Sorry for the slightly noobish question, as I am writing my first rails app.
I get the idea of the layout view, but if you are using them, is there any way to include a view specific js or css file? For example, I have layouts/products.html.erb, and for products/edit.html.erb I want products_edit.css, but I don't want that css for all product views, what is the best practice to accomplish that?
If you have a generic edit.css file, I would suggest an if in your layout
<%= stylesheet_link_tag 'edit' if params[:action] == 'edit' %>
Otherwise you can use content_for with a yield to add additional tags into the head.
layout.html.erb
<head>
...
<%= yield(:header) if #content_for_header %>
</head>
products/edit.html.erb
<% content_for :header do -%>
<%= stylesheet_link_tag 'edit_product' %>
<% end -%>
You can add a stylesheet tag inside the head tag of the layout by doing something like this:
layouts/products.html.erb:
<head>
...
<%= yield :css %>
...
</head>
products/edit.html.erb
<% content_for :css do
stylesheet_link_tag 'products_edit'
end %>
You can't if your </head> tag is in your layout.
You may want a different layout for that controller action. Like this on the render:
render :action => "index", :layout => "some_other_layout
Also you can set a different default layout for a whole controller with this line in the controller class:
layout "some_other_layout"
Check the API docs, there's some complex things you can do with conditionals on that layout method.