Wilcox Development Solutions Blog

Returning HTML content from AJAX requests - a pattern for Rails 3

May 12, 2011

The problem: semantic formats for returned HTML for AJAX

In a previous Rails 2 project, we decided that Rails apps return 3+ kinds of content:

  • A complete HTML page, for user viewing
  • JSON (for JS/web API viewing)
  • A partial HTML page, for jQuery DOM swapping/

However, it was hard to know when to return a full HTML page, and when to :layout => false

So we invented a semantic format

Huh?

Rails actions typically go something like this

`

from todo.rb

def show @object = Todo.find(params[:id]) respondto do |format| format.html { render “show” } # being explicit here, render not really required format.json {@object.tojson} end end

`

So, if the format.html gets called, how do you know if you should show the whole page, or just some partial page update (for example, to update the Todo item on the screen for some AJAX effect or another?

The solution

The solution was - for the Rails 2 project - to use a custom MIME type to identify when we wanted a snippet of HTML. Our AJAX requests looked like:

$.ajax({ url: "/todos/" + id, beforeSend: function(xhr){ xhr.setRequestHeader("Accept", "text/html-partial") }, success: function(){....}

That Accept parameter set up the MIME type, and we added it to our config/initializers/mime_types.rb file and we were ready to rock

It’s not so simple in Rails 3

Setting up the MIME type

Rails, until about October 2010, didn’t respect MIME types that well. I believe that it would take this MIME type and return text/html

Rails 3 takes the MIME type, and returns it. So even if you got the rest of the example up, your browser would complain because it doesn’t know what to do with a text/html-partial MIME type.

So, instead of a MIME type, it’s best to use a format parameter, for Rails 3

Set up your config/initializers/mime_types.rb file to contain:

Mime::Type.register_alias "text/html", :partial

This is essentially just an alias for another MIME type (text/html).

So, we’re going to use a format

Because we don’t want the user to see “Unknown MIME type, save or open?” in their browsers, we are going to cheat a little bit and specify our format via an extension.

Our Javascript code should now look like

$.ajax({ url: "/todos/" + id + ".partial", success: function(){....}

Your actions, knowing about full page HTML, and AJAX HTML

Now you need to set up your actions

`

from todo.rb

def show @object = Todo.find(params[:id]) respondto do |format| format.html { render “show” } # being explicit here, render not really required format.partial { render “show”, :layout => false } format.json {@object.tojson} end end

`

Except we have a problem. If you actually try this out, you will get a Rails Missing Template error

Rails 3 takes the MIME type into consideration when constructing the template path/name to render.

format.html { render "show" }

-- Rails looks for todo/show.html

format.partial { render "show", :layout => false }

-- Rails looks for todo/show.partial

Now, ideally we want to share as much HTML as possible, so we have a problem

In my use case today, I was actually rendering a Rails partial - a partial that was also used in non Ajax situations. So renaming my file to be _something.partial.erb just wasn’t going to cut it

Actually, this behavior does introduce an interesting side effect: being able to further isolate snippets returned for AJAX vs full page reload requests, if the situation demands it.

But my situation demanded sharing. I’m sure separating things out will work for some requests, and it’s a handy thing to have… but most of the time I want Rails to read from the blah.html.erb file.

Setting up a Rails 3 ActionView::FileSystemResolver

So I decided to go monkey patching Rails, to provide the support I wanted.

Before I banged my head on my desk too hard, I found the following article on using FileSystemResolver for some other things:

Implementing A Rails 3 View Resolver

My resolver is very similar to that one:

Conclusion

  • We have a way to separate full page HTML requests from AJAX requests that only want a specific section of a page
  • We use the less magical extensions to provide formatting, instead of MIME types
  • We can provide AJAX specific templates if we want (blah.**partial**.erb
  • We fall back to .html if nothing specific exists, because we really want to share HTML code between a full page redraw and an AJAX redraw