views:

27

answers:

1

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?

+1  A: 

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).

numbers1311407
+1 Thanks for the `render_to_string` method. This will be very helpful :)
macek
Glad to help. Please let me know if you have an issue with it, or if you come up with a nicer way to do the same thing :)
numbers1311407