Alright, I'm trying to create an app with nested templates. I'm using Rails 3 Beta 2 and Haml. I've poked around and I've decided to take the clearest approach and have structured my templates like so:
# application.html.haml
!!!
%body
%h1 Outermost Template
= yield(:foobar)
# inner.html.haml
- content_for :foobar do
%h2 Inner Template
= yield
= render :file => 'layouts/application'
# foo_controller.rb
layout 'inner'
With all of this, I get a LocalJumpError with the message no block given. The stack traces are blank and pretty unhelpful. Any ideas? Are these known issues?
give:
def inside_layout layout = 'application', &block
render :inline => capture_haml(&block), :layout => "layouts/#{layout}"
end
a try. Use like http://m.onkey.org/2009/7/7/nested-layouts
content_for blocks shouldn't contain yield. They aren't passed a block themselves, which is where your error message is coming from.
Related
Consider a partial.haml like this:
- haml_tag :body, {:id => #instance_var}
What I'm trying to do is to nest haml content under a rendered partial. Something like:
= render 'partial_file' do
%h1 My test
%p Trying to nest under a partial output.
But this is causing view errors like:
'nil' is not an ActiveModel-compatible object that returns a valid partial path.
Is there any similar solution to accomplish this?
I solved this per the recommendation in Rails: How to render repetitive form block? by specifying the render as :layout:
= render :layout => 'partial_file' do
%h1 My test
I have an application with a global application layout file application.html.haml. I then have multiple "controller stacks": for our main site, our admin portal, and our business site. For each of these, controllers are within a module and all inherit from the same BaseController. Each stack has it's own layout file. Within the stack, some controllers have layout files as well.
I would like all views (unless otherwise specified) to render inside multiple levels of nested layouts : application, "stack", "controller".
For example, for the Site::BlogController#show action, I'd like rails to render:
/site/blog/show.html.haml inside /layouts/site/blog.html.haml inside /layouts/site.html.haml inside /layouts/application.html.haml
I am having difficulty understanding how to insert /layouts/site.html.haml into the stack. It appears as though automatically, rails will render the action inside the controller layout inside the application layout, however, I can't see how to "insert" layouts into the render stack.
Any help is greatly appreciated, however, I have read all the rails guides to no avail, so a link to http://guides.rubyonrails.org/layouts_and_rendering.html#using-nested-layouts will not really be helpful.
I reread the link i posted ( http://guides.rubyonrails.org/layouts_and_rendering.html#using-nested-layouts ) and realized I missed a key detail.
<%= render :file => 'layouts/application' %>
so, in Site::BaseController I have a call to layout 'site' and in /layouts/site.html.haml I have
= content_for :footer do
-#Content for footer
= render :file => 'layouts/application'
Then in Site::BlogController which extends Site::BaseController I have layout 'site/blog' and in /layouts/site/blog.html.haml I have
=content_for :header do
%h1 HELLO WORLD!
= render :file => 'layouts/site'
This then renders the layouts nested as described in the question. Sorry for missing this in my question. I should've read closer.
if you create a helper like this:
# renders a given haml block inside a layout
def inside_layout(layout = 'application', &block)
render :inline => capture_haml(&block), :layout => "layouts/#{layout}"
end
then you can define sublayout like this:
= inside_layout do
# nested layout html here
= yield
these layouts can be used like normal layouts.
more: http://www.requests.ch/blog/2013/10/30/combine-restful-rails-with-nested-layouts/
I've done similar, but only used 1 level of sublayouts. Can easily be tweaked to allow multiple levels.
In controllers/application_controller.rb:
def sub_layout
nil
end
In controller (for example blog_controller.rb):
def sub_layout
"blog"
end
In layouts/application.html.erb rather than <%=yield%>:
<%= controller.sub_layout ? (render :partial => "/layouts/#{controller.sub_layout}") : yield %>
Make a partial layouts/_blog.html.erb:
...code
<%=yield%>
...code
Repeat for other controller & sub layouts.
EDIT:
If you need to do this on a per-action basis:
def sub_layout
{
'index' => 'blog',
'new' => 'other_sub_layout',
'edit' => 'asdf'
}[action_name]
end
I guess the simplest way to do it is by adding this line of code in the parent of a nested layout:
((render "layouts/#{controller_name}" rescue nil)|| yield )
you could add as many nested layouts as you want by only changing the path directory of the next layout to be rendered.
note: make sure your nested layout is named _layoutname.whatever and that your nested layout has a yield inside
You can create a partial with a yield in it.
_my_sub_layout.html.erb:
<h3>I'm a sub layout and here's my content:</h3>
<%= yield %>
In some other view or even in your main layout application.html.erb render the partial as a layout:
<%= render layout: 'my_sub_layout' do %>
<p>I'm the sub layout's content</p>
<% end %>
It seems the render method has changed.
In the view I used to be able to do the following:
= render :layout => 'some_layout' do
some stuff to be rendered
It seems the best fix is to move the content into a partial and call the layout
= render :partial => 'some stuff to be rendered', :layout => 'some_layout'
I was just wondering if anyone had come across this and if it is a bug or an intended change?
EDIT
Rendering a block inline with a layout works. Check out the part about applying a layout to a block within any template at http://api.rubyonrails.org/classes/ActionView/Partials.html
The issue I am having is with the latest version of HAML not rendering nested render calls properly.
https://github.com/nex3/haml/issues/412
From your post, it appears you are trying to do this in a view.
Is it possible that you are confusing ActionController's render and ActionView's render? Looking # the API documentation for 2.3.8 & 3.x, it doesn't seem there was ever a :layout option within ActionView's render.
UPDATE
Actually, I may have been off-base. It does seem that there is an :inline option as described here.
render(options = {}, locals = {}, &block)
Returns the result of a
render that’s dictated by the options hash. The primary options are:
:partial - See ActionView::Partials.
:update - Calls update_page with the block given.
:file - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
:inline - Renders an inline template similar to how it’s done in the controller.
:text - Renders the text passed in out.
This is fixed in the most recent version of HAML v3.1.3
In my application.html.erb layout for my app, I want to have a partial that renders if it exists for the given view. for example.
If the visitor is at http://example.com/users/show, I'd want the partial /users/_sidebar.html.erb to render.
But if the visitor were at say, http://example.com/user/locations/san_francisco, I'd want the partial /users/locations/_sidebar.html.erb to render.
So the thing here is that if there were no partial for that controller/action it would render some generic partial in my shared directory, and I'd rather not litter every single view with content_for blocks ya know?
Any ideas guys?
My solution is a bit different. Throw this in your application helper:
def render_partial_if_exists(base_name, options={})
file_name = ::Rails.root.to_s+"/app/views/layouts/_#{base_name}.html.erb"
partial_name = "layouts/#{base_name}"
else_file_name = ::Rails.root.to_s+"/app/views/layouts/_#{options[:else]}.html.erb"
else_partial_name = "layouts/#{options[:else]}"
if File.exists?(file_name)
render :partial => partial_name
elsif (options.key?(:else) and !options[:else].nil? and File.exists?(else_file_name))
render :partial => else_partial_name
end
end
Then in your view:
<%= render_partial_if_exists "page_#{controller.action_name}_sidebar", :else => "page_sidebar" %>
In an edit action, if "layouts/page_edit_sidebar" exists it renders it, otherwise it will render a standby "layouts/page_sidebar"
Sean Behan has a great post on exactly this:
http://seanbehan.com/programming/render-partial-if-file-exists/
I might move it to a helper and tweak it a bit to:
<%= render_sidebar %>
# This method could use either the rescue or the if file exists technique.
def render_sidebar
render(:partial => "/#{controller.name}/sidebar"
rescue
#default side bar
end
I am trying to create a bunch of dynamic helper methods like these:
show_admin_sidebar
show_posts_sidebar
show_users_sidebar
So far I have this in my helper.rb file:
#spits out a partial
def show_sidebar(name, show_sidebar = true)
#content_for_sidebar = render :partial => "partials/#{name}"
#show_sidebar = show_sidebar
end
def show_sidebar?
#show_sidebar
end
In my application layout file I have this: (NB - I'm using HAML):
- if show_sidebar?
= yield(:sidebar)
This allows me to say the following in my views:
- show_sidebar(:foo)
- show_sidebar(:bar)
And this renders the desired partial.
The problem with this is that I can only add one sidebar per page. So, I figure I need to have dynamic methods like: show_admin_sidebar, show_foo_sidebar.
So I have tried to do this:
def show_#{name}_sidebar(show_sidebar = true)
#name = name
#content_for_#{#name}_sidebar = render :partial => "partials/#{#name}"
#show_sidebar = show_sidebar
end
and then in my layout:
- if show_sidebar?
= yield("{#name}_sidebar")
But rails does not like this at all.
I have tried almost everything I can think of in my helper file and nothing works.
The reason I am using helper methods for this is because I want my content div to be 100% page width unless there is a sidebar present in which case the main content goes into a smaller div and the sidebar content goes into it's own..
If I can't get this working, then I can easily fix the problem by just adding the partials manually but I'd like to get my head round this....
Anyone got any experience with this kind of thing?
The entire approach to this was bizarrely overcomplicated, didn't follow Rails conventions at all, nor make the slightest bit of sense, and shame on prior respondents for enabling this approach instead of helping him to simplify. My apologies for being 13 months late with the answer.
Your controller should be deciding if a sidebar is to be shown or not, and setting an instance variable #side_bar_name to either nil or a sidebar name string. Then somewhere in shared view code, probably views/layouts/application.html.erb, you would have something as simple as this:
<% if #side_bar_name %>
<%= render :partial => "partials/#{#side_bar_name}" %>
<% end %>
Or better yet:
<%= render(:partial => "partials/#{#side_bar_name}") if #side_bar_name %>
If you want to use a helper (which is not a bad idea for keeping your code DRY and readable) it would basically be the same code, just moved into the helper.
<%= side_bar_helper %>
def side_bar_helper
render(:partial => "partials/#{#side_bar_name}") if #side_bar_name
end
What the controller does is up to you. It would probably do something like this:
if session[:show_side_bar]
# maybe use cookies instead of session, or store user preference in a database
#side_bar_name = session[:side_bar_name]
end
Here is a solution for you, however I wouldn't suggest too much metaprogramming:
#Add the following snippet to the proper helper module:
['admin','user','whatever'].each do |name|
class_eval{
"def show_#{name}_sidebar(show_sidebar = true)
#name = #{name}
#content_for_#{#name}_sidebar = render :partial => 'partials/#{#name}'
#show_sidebar = show_sidebar
end"
}
end
def show_#{name}_sidebar(show_sidebar = true)
That doesn't look like valid Ruby to me. Are you parsing and evaling this yourself or just throwing that right in the file and expecting it to work?