Rails: How do you render with a layout inside a JS template? - ruby-on-rails

I'm familiar with using Ajax templates to update particular parts of a page, but how do you render with layout when doing so? For example, given a layout:
#foo
= yield :foo
a simple view "show.html.haml":
= render #bar
and a partial:
- content_for :foo
= bar.to_html
... the HTML result would render within the layout and I'd see my bar content, but say I want to use Ajax to update only the #foo div. I create "show.js.erb":
$("#foo").html("<% escape_javascript(render(#bar)) %>");
But the result is nothing, as my _bar partial is rendered but outside of the layout, thus my :foo content is never yielded to. How do I get the JS template to render inside that layout?

I've found two answers, but I wonder if there's a better way still. These answers are for Rails 3, by the way.
1. Use a separate JS layout.
This is probably the "more correct" way, but unfortunately didn't work for my situation as I'll explain shortly.
What I wasn't fully aware of, is that in a JS request, the lookup formats are set to JS and HTML. Meaning that the controller will render the HTML template if the JS template does not exist.
But it will not look to the HTML layout in the same fashion, meaning the HTML template will be rendered, but the content_for block is never yielded to, leading to an empty response.
So to make the simple example above work out-of-the-box. You'd delete "show.js.erb" and add a JS layout, (e.g. "bars.js.erb") in the lookup path, which would look like this:
$("#foo").html("<% escape_javascript(yield(:foo)) %>");
In this way, the HTML template is rendered, but in the JS layout, and the HTML of #foo is swapped out for the new content of the response.
2. Render the HTML content in the JS response block.
However, #1 this was not an ideal answer for me. My app uses many nested layouts, most of which are very similar. To make the above example work I'd have to create a lot of JS layouts, all of which more or less copies of the original HTML layouts. A waste of time, and not at all DRY. So I came up with this solution.
It feels less ideal than #1, and please tell me if there's a more appropriate way. But this is what I came up with:
# in bars_controller.rb
def show
# ...
respond_to do |format|
format.js do
lookup_context.update_details(:formats => [:html]) do
#content = render_to_string
end
render
end
end
end
In this way I temporarily set the mimetype for the template lookup to be HTML, render the content to a variable, then render the JS template:
// show.js.erb
$("#foo").html("<%= escape_javascript(#content) %>");
There is one further complication to this. In my nested layout setup, in the HTML response, the layout calls the rendering of its parent to continue to build the body, leading to the complete page. In my case, I want it to simply return the body content. So while I don't need JS layouts for this solution, I do need to slightly change my layout, like this:
-# my_layout.html.haml
-# (given a parent layout that yields to :body)
- content_for :body do
= yield(:foo)
- if request.xhr?
= yield(:body)
- else
= render :file => "layouts/my_parent_layout"
In this way the parent is not called on a JS request, simply resulting in the body (up to this point in the nested layout stack).

Related

Render :layout is searching for partial instead of layout

I understand this is a bad idea, but from what I've seen in ApplicationControllers, using:
render :layout => "something" ...
Should render using a layout located at views/layouts/something.html.erb
However, when I am making this call from inside of a view, it errors out with:
Missing partial my_controller_name/something with ...
Searched in:
* "{path here}/app/views"
Which seems to me its looking for a partial, instead of a layout as I specified. Does anyone know what is going on with that?
A sufficient example small enough to reproduce it:
<%= render :layout => 'something' do %>
<div>Hello</div>
<% end %>
This is all under Rails vs 4.0.2
render works differently in controllers than it does in views. In controllers, it's primarily for rendering action templates, while in views, it's primarily for rendering partial templates. When you want to render a specific layout for an action, you have a few options, but all of them are in the controller.
If you want every action in a particular controller to use that layout, you can either specify layout 'something' in that controller (usually near the top) or for a ApplesController, you can create a new layout in app/views/layouts/apples.html.erb and this will automatically be used as the default layout for the ApplesController.
If you want just a single action in a controller to use that layout, you can use your render layout: 'something' inside of a controller action, where the action to render is implied to be the current action.
Links from the Rails docs:
Action Rendering
Partial Rendering
Nested Layouts

Rails/Ajax/Error Processing

So there are tons of articles about how to do this, but certainly there's a best practice...and I don't know enough to filter out silly solutions, good ones, and best ones.
I simply want to submit my forms via ajax (in a dialog) and get the errors back just like I would without using ajax...meaning I like the rails standard error handeling/flash messages/label classes.
Is the best way to reload the entire partial?
Is the best way to use .js.erb (or coffee) for partial stuff? (If so, can you explain how to use these partials?
Is the best way to parse JSON back into the form somehow?
What else am I missing in my [limited] knowledge base?
The way I'd do it is to render a create.js.erb view like:
$("#my_dialog").replaceWith("<%= j(render 'dialog') %>");
where _dialog.html.erb contains the HTML for the contents of your dialog.
<div id="my_dialog">
<!-- flash stuff etc -->
<%= form_for ... %>
<!-- ... -->
<% end %>
</div>
Your controller, for example, will look something like:
class EntriesController < ApplicationController
def create
#entry = Entry.new(params[:entry])
respond_to do |format|
if #entry.save
format.html { redirect_to #entry }
format.js {} # this will render create.js.erb for js requests
else
format.html { render action: "new" }
format.js {} # this will render create.js.erb for js requests
end
end
end
end
summit like 'dat. If you don't want to reload the whole form you can update or do whatever you want in .js.erb
Using js.erb is the way to go. Here's the rationale:
Reloading part of your page basically defeats the purpose of Ajax - which is to modify the page without having to reload or refresh anything. And parsing JSON would be quite tedious.
Using js.erb lets you easily leverage validations that Rails provides. In your js.erb, you can access anything that you normally would from your controller, including the validation errors, you and you can update DOM elements based on those errors. Since you're modifying individual DOM elements, you don't need to concern yourself over the fact that your form may be inside a partial.

How can I load a embedded ruby javascript file on pageload?

So, as I am sure you are all familiar with, you can have actions in Rails that call html.erb files. You can also set up actions to render remotely that call embedded ruby files (for example submitting a "post" form to the "posts" controller, handling it remotely, and calling a js.erb file to update elements in the page).
What I want to know is how to run a js.erb file when I'm running an action that loads a template (html.erb file). To explain, consider if I want to run a User Show page:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
end
Linking to user_path(User.find(1)) will load show.html.erb, and all seems great.
But what if I want to click on a link to user_path(User.find(1)) and load show.html.erb while also loading show.js.erb? If (and I hope it is) this is possible, how could I adapt the show definition to also load show.html.erb and custom_js_file_name.js.erb?
FYI: I'm using Rails 3.0.9 and 3.1.3 on two different applications, and assume that I would put show.js.erb or any others in the Users folder (views/users/...)
By default,
def show
#user = User.find(params[:id])
end
will only render the show view based on if it was requested via HTML, JSON, JS, etc.
I think what you are describing is better suited for the render method in Rails. Typically, controllers use the different format.(:format) methods in the respond blocks to respond to a controller call based on what the request type was (JSON, HTML, JS, etc).
In your show.html.erb file:
<%= render "users/show.js" %>
This allows you to render any arbitrary file you want in another one of your views. This also allows you to split up your large view files into smaller (reusable) pieces called partials (note: all partials are named with a _ character at the beginning and are called via <%= render :partial => "users/my_partial" %> which would render the _my_partial.some_format.erb file)
Try having the show action render the show.js.erb file when requested with a format of js. That should get Rails to render the dynamic template; now link to it from the original show.html.erb with a javascript link tag.
In show.html.erb:
<script type="text/javascript" src="<%= show_users_path(#user.id) -%>.js"></script>
I haven't tried this and the rendering of show.js.erb may put additional formatting that would be a problem.

Rails: What does it actually mean to "render a template"

I've become a bit confused about the idea of "rendering" a "template" due to the way an author speaks about it in a book I'm reading.
My original understanding of "rendering a template" was that it meant that Rails is providing the content that is viewed on the screen/presented to the viewer (in the way that a partial is rendered) but the book I'm reading seems to be using the concept of "rendering a template" to also mean something else. Let me explain in context
This book (rails 3 in action) sets up a page layout using the conventional layouts/application.html.erb file, and then it "yields" to different view pages, such as views/tickets/show.html.erb which adds more content to the screen. that's all straightforward..
Within this view views/tickets/show.html.erb, there is a rendering of a partial (which is also a straightforward concept).
<div id='tags'><%= render #ticket.tags %></div>
Now within this partial there is, using ajax, a call to a "remove" method in the "tags_controller.rb" which is designed to allow authorized users to remove a "tag" from a "ticket" in our mock project management application.
<% if can?(:tag, #ticket.project) || current_user.admin? %>
<%= link_to "x", remove_ticket_tag_path(#ticket, tag),
:remote => true,
:method => :delete,
:html => { :id => "delete-#{tag.name.parameterize}" } %>
<% end %>
Now here is the "remove" action in the tags controller (which disassociates the tag from the ticket in the database)...
def remove
#ticket = Ticket.find(params[:ticket_id])
if can?(:tag, #ticket.project) || current_user.admin?
#tag = Tag.find(params[:id])
#ticket.tags -= [#tag]
#ticket.save
end
end
end
At the end of this remove action, the author originally included render :nothing => true , but then he revised the action because, as he says, "you’re going to get it to render a template." Here's where I get confused
The template that he gets this action to render is "remove.js.erb", which only has one line of jquery inside it, whose purpose is to remove the "tag" from the page (i.e. the tag that the user sees on the screen) now that it has been disassociated from the ticket in the database.
$('#tag-<%= #tag.name.parameterize %>').remove();
When I read "rendering a template" I expect the application to be inserting content into the page, but the template rendered by the "remove" action in the controller only calls a jquery function that removes one element from the page.
If a "template" is "rendered", I'm expecting another template to be removed (in order to make room for the new template), or I'm expecting content to be "rendered" in the way that a partial is rendered. Can you clarify what is actually happening when a "template" is "rendered" in the situation with the jquery in this question? Is it actually putting a new page in front of the user (I expected some sort of physical page to be rendered)
You're nearly there! Rendering a template is indeed always about producing content, but for a slightly wider description of content. It could be a chunk of html, for example an ajax call to get new items might produce some html describing the new items, but it doesn't have to be.
A template might produce javascript as it does in your second example. Personally I am trying to avoid this and instead pass JSON back to the client and let the client side js perform the required work.
Another type of rendering you might perform is to produce some JSON. APIs will often do this, but you might also do this on a normal page. For example rather than rendering some javascript to delete tag x you might render the json
{ to_delete: "tag-123"}
and then have your jQuery success callback use that payload to know which element to remove from the DOM, by having this in your application.js file
$('a.delete_tag').live('ajax:success', function(data){
var selector = '#' + data.to_delete;
$(selector).remove()
}
(Assuming that your delete links had the class 'delete_tag')
Rendering JSON like this isn't really a template at all, since you'd usually do this via
render :json => {:to_delete => "tag-#{#tag.name.parameterize}"}
although I suppose you could use an erb template for this (I can't imagine why though).
My understanding is that js.erb is "rendered" by executing the javascript functions within it. Very often something like the below is done:
jQuery(document).ready(function() {
jQuery('#element').html('<%= escape_javascript(render pages/content) %>');
});
There's a really succinct overview of rendering at http://guides.rubyonrails.org/layouts_and_rendering.html that may help as it also goes into the details of the ActionController::Base#render method and what happens behind the scenes when you use render :nothing (for example). Render but can be used for files or inline code as well -- not just 'templates' in the traditional sense.

Help me understand dynamic layouts in Sinatra

Help me understand this; I'm learning Sinatra (and Rails for that matter, er, and Ruby).
Say I'm doing a search app. The search form is laid out in one div, and the results will be laid out in another. The search form is rendered into the div by a previous view (maybe from a login form).
I want to process the form params, perform the search, and render the results into the results div.
If I have a single "yield" in the layout and render the divs from different views, the results div erases the search div when it renders.
If I define the divs in the default layout, then just render the content, obviously the layout will be messed up: there would have to be two "yields" and I don't think Sinatra supports passing blocks in to yields.
I tried foca's sinatra-content-for plugin, and that seems closer to what I need. But I can't figure out where to place the "yield_content" statements.
If I have this haml in my layout:
#search
-# search form
= yield_content :search
#results
-# search results
= yield_content :results
... this in my search view:
- content_for :search do
%form{:method => "post"... etc.
... and this in the results view:
- content_for :results do
%table{:class => 'results'... etc.
This sort of works but when I render the results view, the search div is emptied out. I would like to have it remain. Am I doing something wrong? How should I set this up?
I think you mean you ALWAYS want to show 2 divs, but in a new search they should be empty and on a results page they should be populated. You can probably get away w/ one haml template, and just populate it diff'tly on the request method:
get "/search" do
# render haml
end
post "/search" do
# set instance variables: #search & #results
# run search
# render haml
end
(Sorry this is very pseudo... not at a real computer.)

Resources