Skip to main content

47 jQuery UJS and AJAX

Episode 17 · July 27, 2014

A quick introduction to jQuery UJS and how we can use it to make an AJAX request to render a javascript template from the Rails server

Javascript Frontend


Notes

These examples all show the raw HTML that is needed. You can generate these data attributes using Rails form, form_tag and link_to helpers by adding the data: {} option.


"data-confirm": Confirmation dialogs for links and forms
This displays a confirmation dialog from the browser asking the user to confirm they want to execute an action.

<form data-confirm="Are you sure you want to submit?">...</form>

"data-disable-with": Automatic disabling of links and submit buttons in forms
This option disables and changes the value of the button or link it's added to when submitting the form or clicking on the link. You can learn more about this option in Button Loading Animations with jQuery UJS

<input type="submit" value="Save" data-disable-with="Saving...">

"data-method": Links that result in POST, PUT, PATCH or DELETE requests
This option allows you to change the request type from the default. With links, you can change them to submit other requests than the default GET.

<a href="..." data-method="delete" rel="nofollow">Delete this entry</a>

"data-remote": Make links and forms submit asynchronously with Ajax
This option submits the form or link asynchronously and, by default, expects the server to render a Javascript response which then gets executed in the browser.

<form data-remote="true" action="...">
  ...
</form>

"data-type": Set Ajax request type for "data-remote" requests
This option allows you to set the remote dataType option when using remote: true

<form data-remote="true" data-type="json">...</form>

Resources

jQuery UJS on Github
List of supported options

Transcripts

In this episode, we're going to take a look at AJAX, and how we can do that with the jQuery UJS that rails provides, so let's dive in. I have crated a rails app with a scaffold for books, and books have names, descriptions and published at dates, what we're going to do is go into the show action and add a "Publish" button here that will talk to the application, it will write the published at date, and then we'll add it to the page with JavaScript. We're going to first start with the jQuery UJS version, which should be the simpler of the two, but it's definitely more rails specific, and you'll probably not want to use this for your bigger applications, so let's dive in. This page, the show action for the books is where we want to add the publish link to the page. If we just go in here and we add <%= link_to "Publish", publish_book_path(@book) %> and then we'll dive into the routes to make the publish_book_path actually exist. We want to create:

routes.rb

Rails.application.routes.draw do 
    resources :books do 
        member do 
            path :publish
        end 
    end 
end 

Let's add a placeholder

app/views/books/show.html.erb

<strong>Published At:</strong>
<%= @book.published_at %>

Now that we have the links, when we click on this, we'll get taken as a get request to the pusblished path, so that's incorrect, we made a patch and we want it to actually submit and change the record. When we click on a link by default, those are going to be handled as GET request. Now the next setp is to add the ability to make this link a PATCH request. Rails provides unintrusive JavaScript that gets triggered by the method attribute here and we can set it to PATCH. When we open up our application.js file, you'll see that there's a require jquery_ujs, which stands for "jQuery unintrusive JavaScript". That JavaScript file basically provides the ability to look for things like this method attribute, and then change the way, the behavior of this link to actually submit a patch request rather than just a GET request. When we refresh our page here, if we inspect this attribute, on the link you can see that there's a data method that is equal to PATCH, and of course we defined that right here. Now this JavaScript will intercept this link, and when we click on it, we no longer get the GET request in route problem, but we're missing the action. Rails mapped this correctly to the route, we sent the PATCH request and now we just have to fill out the rest of this action. If we open up our books_controller.rb now

before_action :set_book, only: [:show, :edit, :update, :destroy, :publish]

#Rest of controller code 

def publish 
    @book = Book.find(params[:id])
end 

Now if we refresh and submit this again, we'll get a template missing. This is submitting a PATCH request, but it's still expecting to render some html, and we're actually loading a new page. This isn't actual AJAX, because we are submitting the request, but we're moving, and not actually taking the result, we're going to another page. In that sense we are still submitting data over, but we're not actually taking the result and modifying the current page with that. The way we can do that is to go back to our show action, and also add a remote: true option. Here, now when we submit this, let's go back to the show action, nothing happens. That's kind of interesting. What is going on here? If we go into our rails logs, we can see a big stack trace, and we can see that it published it, but this time it was expecting a JavaScript response, and last time that we did it, it expected an html response. In your rails logs, you can see when it says "Processing by", you can see the controller, the action and the response it expects to receive back. Now that we've added remote :true option. Rather than looking for an html response, we're expecting a JavaScript response, and when this crashed, you didn't see anything go wrong on the front end side because the JavaScript here on the front end was expecting the JavaScript response to come back, and then it would execute that JavaScript here on the browser, and the browser was not being taken to a different page to load. Everything that will crash in this remote :true links or forms, those crashes will happen here in the console in your browser, so you'll be able to see that when we submit this link, you will get another error and another and another, and you will see that there's a 500 error, which means that there's an internal server error. Something on the server happened that crashed when we submitted a PATCH request to our url.

The way to fix that 500 error is to actually put in the template into our views. If we go into our app/views/books folder, we can see that the formats that we have are a few html ones, and a couple JSON responses, but we don't have any js or JavaScript responses, and that's what we actually need to build. When you look in the terminal, and you see that it says: "Missing Template", it is saying that we're missing the template in app/views/books/publish and publish needs to be English, it could be a js format, ECS format or a bunch of these other formats. Then, we also have handlers. These handlers are the extensions that you see when you have html.erb, you are using the html format here, and then the erb is the handler used to generate the html. You can mix and match these, and then in our case, we want to edit the app/views/books/publish.js.erb file, and here we can add in an alert just to say hello and see if it's working or not.

Now, if we refresh this page and click "Publish", we can see it says: "hello". That means that what happens is when you click this, JavaScript intercepts the click, it submits an AJAX PATCH request over, the application server, our rails app, returns this file, and that gets evaluated on the client's side. We're returning a string of JavaScript to the browser, and it evaluates the result. For convenience and learning this stuff, it's extremely simple, so you don't even have to write the AJAX request on the client side because jQuery UJS does that for you. With this, now, we can go into our

app/views/books/show.html.erb

<strong>Published At:</strong>
<span id="published-at"><%= @book.published_at %></span>

app/views/books/publish.js.erb

$("#published-at").text("<%= @book.published_at %>")

If we refresh this page once more, you'll see that if we click "Publish", it updates the time almost immediately, and we can keep doing that and see that it keeps getting published again. It's not loading a new page, it's loading this in the background through an AJAX request and updating the page with the JavaScript that we have. There's some gotchas with this, if you write incorrect or invalid JavaScript here, it's really hard to debug. When you click "Publish" and you have broken JavaScript there, nothing happens, you're not getting an error either. That means that the rails application is doing just fine, it's loading and rendering that publish.js.erb, but when it comes back to the browser to be evaluated, and you have bad JavaScript, it doesn't do anything wrong, it just doesn't work.

You also want to be careful here that the attributes on the model are being escaped properly, and that these don't actually insert JavaScript into your response, you don't want someone to add a malicious book in there and have a weird published_at date or description or name that actually has JavaScript in it, that gets rendered and executed in people's browser.

One last thing is a little fun bonus, if you add the data {disable_with: "Publishing..."} attribute that I covered in the last episode, if you add this to your link and you also make this publish take a little longer, if you refresh this page, now when you click "Publish", it changes to "Publishing" and when it's finished, it updates and the link goes back to normal. You're able to use this disable_with option the same way because UJS provides this functionality, and that's sort of the beauty of it, it allows you to do these sort of complicated things with only having to define what it should do, and it will automatically detect that and add that functionality into your rails app, which is really neat.

Discussion