We're not actually going to talk about remote=> true today. Rails offers a lot of automagic features that elegantly abstract your code. But, for beginners, this isn't necessarily a good thing. So, before we rely too much on the automagic of the 'remote: true' option for ajax form submission, let's take a look at what's going on under the hood.
We're working on a picture sharing app that allows users to "like" pictures. These "likes" are persisted into our database and then each picture's description will update to reflect the new number of likes. We don't want to refresh the entire page to reflect this change, and this is where ajax comes in.
Ajax (asynchronous javascript and xml) is a method of building interactive web apps that process user requests immediately. With ajax, we can reload certain sections of our page and leave the rest of it alone.
Let's take a look at our index.html.erb page below. Here, you'll see the div that contains our "like" button.
<div class="like">
<%= link_to like_path(id: picture.id), method: post do %>
<div class="like-btn text-center" picture_id="<%= picture.id %>">
<span class="<%= picture.heart_class(current_user) %>"></span>
</div>
<% end %>
<div class="likes-message">
<%=picture.likes_message(current_user)%>
</div>
The link_to method provided by rails is setting us up to send a post request to the like action of our Pictures controller:
def like
@picture = Picture.find(params[:id])
@picture.update_likes(current_user)
redirect_to root_path
end
The code above will send the picture's id to the Pictures Controller, create and save a new 'like' for that picture and then redirect the user to the index page. This means the entire page will reload every time we click "like." We don't like that. Let's ajaxify.
1. Alter your link_to tag on the index.html.erb page to remove the method: post. We are going to write an ajax post request and we are going to disrupt to usual action of this link (i.e. stop it from reloading the page). To do both of those things successfully, we need to remove that method.
<%= link_to like_path(id: picture.id), do %>
2.Add an event listener to our pictures.js file so that we will execute the ajax request when the user clicks the like button.
$(function(){
likeListener();
});
function likeListener(){
$(".like-btn").click(likeHandler);
};
Now that we've told our program to execute the likeHandler function when a user clickes the button with the class of like-btn, we need to actually define that likeHandler function. First, what does that function need to do?
It needs to submit the post request to the Pictures Controller and it needs to asynchronously replace just the lik div with the new content--the content that reflects the new 'like' created and persisted by the post request.
3.Define the likeHandler function to (a) prevent the default action of reloading the entire page, and (b) reload only the relevent content:
function likeHandler(e){
e.preventDefault();
var url = $(this).parents("a").attr("href");
var picture_id = $(this).attr("picture_id");
var self = $(this)
$.post(url, function(response){
likeCallBack(response, self);
})
};
function likeCallBack(response, self){
self.parents(".like").replaceWith(response);
}
We've prevented the default action with e.preventDefault();, then we used the css elements on the page to grab the info we need to construct our post request. The url we built with var url is the destination of the post request.
Our ajax post request is defined with $.post(url..
The ajax .post method takes in two arguments: the url of the post request, and a callback function that will be immediately invoked on completion of the post request.
Let's talk a bit about the callback function. What do we want to occur on completion of our post request? What is the 'completion of our post request'? If you recall from our fist code snippet, the post request sends a picture's id to the 'like' action of the pictures controller, saves a new 'like' for that picture, and redirects to the index page, which is then reloaded. So, the outcome of our post request is the creation of a new 'like' and subsequent changes to the description of each picture on the index page.
Therefore, our callback function is responsible for taking the result of that post request---altered html that reflects the creation of the new 'like'---and rendering just that newly altered section of html on the index page.
4. Write the callback function
The callback funtion takes in an argument, which we've called response. This is the response body returned by the completion of the ajax post request. In other words, its a giant string of html that represents the entire updated index.html.erb page.
In order to replace only the relevent 'like' section, we need to be able to grab the element that we're listening on from the current page. Inside our function $(this) refers to the div with a class of like-btn. That is because we first defined our listener to listen for a click on that element. In order to pass this element into our callback function, so that the callback function can replace it with the new content (the response from the post request), we need to bind it to a variable.
var self = $(this)
Now, our callback function can invoke a function that will operate of self, find the entire 'like' section that we need to replace and replace it with the new content without reloading the whole page
function likeCallBack(response, self){
self.parents(".like").replaceWith(response);
}
We're almost done! As it stands, our like action in our pictures controller is redirecting to the root_path, i.e., the index page. This means that each time we "like" a picture, we will be replacing the like div with the entirety of the newly updated index page. Instead, we want the like action in our pictures controller to render only the like section of the page. So...
5. Make a 'like' partial: In the views directory, create a _like.html.erb file.
<div class="like">
<%= link_to like_path(id: picture.id) do %>
<div class="like-btn text-center" picture_id="<%= picture.id %>">
<span class="<%= picture.heart_class(current_user) %>"></span>
</div>
<% end %>
<div class="likes-message"><%= picture.likes_message(current_user) %></div>
We've extracted our 'like' div from our index.html.erb into our partial.
6.Tell your 'like' action in your pictures controller to render the partial
def like
@picture = Picture.find(params[:id])
@picture.update_likes(current_user)
render '/pictures/like', layout: false
end
This has the effect of altering our "response" that we get back from our ajax post request. Before, when the 'like' action redirected to the root_path, the response consisted of the entire index page. Now, the response of the ajax post request is just the updated `like' div.