45

In creating a ruby on rails / jquery app, there's a part of a page that is time-consuming to generate.

I want to change how the page is loaded so that most of the page loads right away, and a placeholder is reserved for the time-consuming part to load asynchronously, and be injected into the page with ajax / jquery when it is finished.

What I have now (simplified):

app/views/sample/show.html.erb:

<div id="theResult">
    <%= render :partial => 'calculate', :object => @org) %>
</div>

and the partial will use some parts @org to generate some content (hitting another external REST service).

app/views/sample/_calculate.html.erb

<%
    # code to take org and turn it into content
%>
<!--...html to display results here -->

I realize this is probably breaking proper MVC architecture rules since my partial seems to have too much logic, and would like to clean that up as well...

So I guess I have two questions in one: (1) how do I get this to work, and (2) how should I clean this up to follow good ruby/rails/mvc practices?

Krease
  • 15,805
  • 8
  • 54
  • 86
  • [Watch here, maybe it helps you](http://stackoverflow.com/questions/3661967/rails-3-equivalent-for-periodically-call-remote) – timaschew Jul 15 '11 at 01:07

2 Answers2

72

First put an empty, placeholder div in the main response

<div id="pink-dancing-elephants"></div>

and then add a little jQuery to the page

$.ajax({
    url: "/elephants/dancing",
    cache: false,
    success: function(html){
      $("#pink-dancing-elephants").append(html);
    }
});

and have the action that responses to /elephants/dancing/pink return the blob of HTML that you want to have fill up the div. In the action that is invoked by the AJAX request, you'll want to render with :layout => false to keep the returned blob of HTML from including the entire frame. E.g.

# elephants_controller.rb
def dancing
  @elephants = #whatever
  render :layout => false
end

This would render the template in views/elephants/dancing.html.erb.

cailinanne
  • 8,332
  • 5
  • 41
  • 49
  • 1
    Thanks - this works and definitely gets me the results I'm looking for. Do you know if there's a way to do this without having to put "/elephants/dancing" in the routes (allowing someone to view it independently of the main page) – Krease Jul 15 '11 at 17:02
  • 1
    It sounds like you can almost do it using route constraints (See [Section 3.9](http://guides.rubyonrails.org/routing.html#http-verb-constraints), but according to the docs you can only use the methods of Request that return a String. In your case, you would want to use the request.xhr? method, that returns a Boolean. – cailinanne Jul 18 '11 at 17:19
  • I'd like to point out [this](http://stackoverflow.com/a/4462955/1159949) answer for those like me who are trying to load in a collection using what is shown above. thanks for the help cail! – Cooper Maruyama Nov 12 '12 at 07:09
10

There's a simpler way to do it compared to @cailinanne.

Using her same example, but I tend to use this technique to update a div, so my div would first have that partial:

<div id="pink-dancing-elephants">
   <%= render "elephants/dancing", elephant: some_thing  %>
</div>

But then use the jQuery load method:

$("#pink-dancing-elephants").load("/elephants/dancing");

This will return the whole partial. Be sure to use layout: false, and optionally set params. I tend to use a partial like this

# elephants_controller.rb
def dancing
  render "elephants/_dancing", 
         locals: { elephant: some_thing },
         layout: false
end

This would render the template in views/elephants/_dancing.html.erb.

NOTE the underscore in the partial name for the controller method and how you don't use it when calling render in the view.

justingordon
  • 12,553
  • 12
  • 72
  • 116
  • 2
    would you explain why it's necessary to call `render ...` in both template and controller – Jake Berger Apr 27 '15 at 15:42
  • 2
    @jberger: he is explaining how to use the accepted answer's technique to refresh an already-loaded partial. The template's `render` is for the first load; the controller's is for subsequent async updates. – Benjamin Carlsson Jun 28 '15 at 20:11