Rails 4: How to partials & AJAX, dead easy
Consider the basic following scenario...
A view with two columns:
one with links of Category model instances
the other empty but eager to show all the Item instances belonging to each Category.
And you want to show all of that without reloading the page. I had never played much with partials in Rails, but they are really really convenient.
So I have my index method
#items_controller.rb
def index
@items = Item.all
@categories = Category.all
end
For the sake of brevity, I will simplify the views. This way you can also understand the idea and apply it to your own views.
The index view contains two render calls
<!-- items/index.html.erb -->
<div class="grid">
<%= render 'sidebar_menu' %>
<%= render partial: 'item_grid', locals: { items: @items} %>
</div>
The category links in the sidebar_menu
partial are something like the following:
<%= link_to cat.name, fetch_items_path(:cat_id => cat.id), :remote => true %>
fetch_items_path
is the route that leads to our custom javascript method, which will be described next.
#config/routes.rb
...
get "/fetch_items" => 'items#from_category', as: 'fetch_items'
For more info on how to build custom routes, check Rails amazing documentation.
The :remote => true
is the most important part here, it allows the whole Ajax business in the first place.
The item_grid
partial looks like:
<div>
<div id="items_grid" >
<%= render partial: 'items_list', locals: {items: items} %>
</div>
</div>
The subpartial items_list
just renders a list of div boxes to show our Item instances.
<% items.each do |item| %>
<div class="item_box">
...
</div>
<% end %>
Now we need the method that will do the AJAX magic. For simplicity you could have something like this:
#items_controller.rb
def from_category
@selected = Item.where(:category_id => params[:cat_id])
respond_to do |format|
format.js
end
end
Notice the type of format, there's no html view because we don't need it. We're going through JS.
Therefore, we need to create a javascript file, which will repopulate the div in the second column.
//views/items/from_category.js.erb
$("#items_grid").html("<%= escape_javascript(render partial: 'items_list', locals: { items: @selected } ) %>");
Let's look at this line with care.
I'm rendering the same partial I was rendering in items#index, and the local variable for the partial is now the array of Item instances that match a given category. The difference is that I'm doing this through AJAX, so there's no need to reload the entire page.
Written by Sebastián González
Related protips
27 Responses
Hi, thanks for the article.
I don't understand the connection between 'fetchitemspath' and the 'fromcategory' action in your items controller.
In other words can you explain "fetchitems_path is the route that leads to our custom javascript method". I do not understand how that works.
Thank you!
In Rails, you control the routes in the routes.rb file. I will add it to the pro tip.
Thank you for writing this, it was super helpful. Just one question, what is the purpose of the :remote => true statement at the end of the link tag?
As it's pointed out just beneath the routes file, the remote: true
hash tell Rails that the anchor element must respond in Javascript format (triggerin the Ajax logic), instead of the usual HTML.
Note that the controller method has to respond in this format also, otherwise is of no use.
This is the best tutorial / documentation I've seen for this. Making the content minimal and concentrating on the mechanism was a good idea. Well done!
Thanks nruth! I wrote this down because I couldn't find anything like it.
Hopefully, people won't have to do the same long research I had to do!
How would you do this if the target element wasn't a unique element with an id, but instead if there's multiple of them with the same class name, and you want to target just that one.
Somehow you have to get the clicked element. How do you do this when you use render js? It's possible if you do a full ajax.on click event, and call $(this) within that function, but otherwise, how do you do it using render js? Cause you may want to do something on the back end in the controller or model before executing the front end part.
This is where you have to get creative with your javascript. Instead of using the remote: true
option, I'd use an ajax call, with
dataType: 'script'` so I can capture the Javascript behaviour returned by the controller, and/or complementing it with the logic in the success function.
With the ajax call you have greater freedom for sending parameters with the call (maybe a data-something field with the id?). If you somehow decide to send an identifier, you'd have to capture it in the controller and then use it in the response to successfully identified the correct element.
i created a search page the problem is the search page has a partial which is not rendered in the start but when parameters filled and submitted then a table is rendered showing the result i am sending the parameters through AJAX but when the response comes back i am sending the response to the same controller for rendering adding an extra parameter so that it satisfies the if-else condition i placed in that controller so that the code which hit the back-end do not run ,in else block it renders the whole page search page again with form and table .i don't want to refresh the whole page,i want to render search table partial without refreshing. i tried to use respond_to |format| format.js and js.file for rendering in that div in search page but its not working ..so i am rendering whole page now. please help
This is nice, thank you.
Hi manikantasai, please write your question in StackOverflow, where you can add the code in context (which make the analysis easier) and reach a higher audience.
Thank you! I've seen a lot of tutorials, but this one is really dead simple!
Thanks for this useful tutorial.
Thanks! This was very helpful.
Beatiful post . It helped me so much. Thanks a lot !!
HI, and for the URL changing, nothing?
I have problem also with Turbolinks 3. How to fix?
History.pushstate? Like in the 246 Railscast?
Thanks man!
just finished implementing a dynamic form with partials, based on selecting from a set of links, this guide, first time, took me 15 minutes to follow and adapt. You've got my sincere thanks, it's amazingly clear.
Ruby is great but I hate using rails on the frontend because of this mess. Use angular and see how easy it is
You can use render partial: 'foo', collection: @items
and then remove the manual iteration you do inside the item partial.
This was super helpful, thanks so much!
This is a great article. There is another method that I used with prototype helper that allows you to render the js response in the controller method without having to create a js.erb like so:
render update to |page|
page.replace_html div, partial...
Is there a possible way of doing something like this without prototype helper?
Really nice tutorial...............Good job
I am facing problem with is code: not really a problem but something that baffles me, something that i must have missed out, this code works fine in development mode ie. rails s but breaks when i on production environment ie, rails s -e production
Need to add !!!!!!
format.js { render layout: false}
Sebastian, nicely done article. I'm encountering an "ActionController::UnknownFormat" error, however, on the respond_do |format| line. I've researched this and can't find a fix. Any ideas what might cause this?
Thanks for this!