390: Turbolinks
(view original Railscast)
Turbolinks is a gem that will be included by default in new Rails 4 applications. It’s also compatible with Rails 3 so we can use it in our current apps too. This gem can make our applications appear faster to the user by using JavaScript to replace the HTML body of new pages instead of relying on a full page load. In this episode we’ll try this gem out in a Rails 3 app. The application we’ll be using is a Todo-list application that handles multiple projects, each of which can have a set of tasks. New tasks start off being marked as incomplete but we can mark them as completed by checking the checkbox next to each item.
Adding Turbolinks
We’ll add Turbolinks to this application to see how it works. First we’ll add it to the list of gems in the gemfile then run bundle to install it.
/Gemfile
gem 'turbolinks'
Next we’ll go into our application’s Javascript manifest file and add a line to require turbolinks
. Turbolinks doesn’t depend on jQuery so we can use it even if we don’t use jQuery in our application.
/app/assets/javascripts/application.js
//= require jquery //= require jquery_ujs //= require turbolinks //= require_tree .
If we restart our Rails application now we can browse around it like before although we won’t notice much difference when we do. We can check to see if Turbolinks is active by opening the network inspector and then clicking around the site. When we do this it seems that a full page reload is happening each time we click a links and so Turbolinks isn’t active. This could be because the browser we’re using isn’t supported by Turbolinks and so we could upgrade it or use a different one. Turbolinks expects a recent browser but if it isn’t supported then it will degrade gracefully and our app will still work like we expect it to. Instead let’s try viewing our page again in a recent version of Chrome. When we do we’ll see that when we click a link now the page isn’t fully reloaded. Instead an AJAX request is made for the next page by the turbolinks.js
file.
This can make the site feel faster to the user as the browser isn’t reinterpreting the JavaScript and CSS each time the page loads but how does it work? What Turbolinks does is listen to click
events for each link on the page. When one of these fires the request is made by JavaScript and Turbolinks will look at the response’s body. It then uses JavaScript to update the current page with this new content, replacing the title
and body
elements so that it looks like the new page. It also uses the Push State API to modify the URL so that of the new page. This technique is very similar to PJAX, which was covered in episode 294.
Issues With Existing JavaScript Functionality
Our application appears to be working well with Turbolinks but we might find that some of our existing JavaScript stops working. For example, if we try to update a task to mark it as complete now nothing happens. If we reload the current page and check the checkbox again, however, it will work. The CoffeeScript code for this page looks like this:
/app/assets/javascripts/projects.js.coffee
jQuery -> $('.edit_task input[type=checkbox]').click -> $(this).parent('form').submit()
This code listens for the click
event for each of the tasks’ checkboxes. When one is clicked, the form that contains the checkbox is submitted and the task is marked as complete. The important line in this code is the first one, which calls the jQuery
function. This code listens for the ready
event on the document which is triggered when the page’s DOM has finished loading. If we didn’t wrap the rest of the code in this function then jQuery would try attaching click
events to the matching checkboxes before they had loaded in.
When we use Turbolinks this callback is only triggered on the initial page load. This means that if we start on another page then move to the page for a task the DOM’s ready
event isn’t fired as we’re still technically on the same HTML page. There are several events that Turbolinks will trigger, one of which is called page:load
. We can use this to simulate the DOM ready behaviour in our CoffeeScript file.
/app/assets/javascripts/projects.js.coffee
ready = -> $('.edit_task input[type=checkbox]').click -> $(this).parent('form').submit() $(document).ready(ready) $(document).on('page:load', ready)
Now we set the function that checks the checkboxes to a variable called ready
and pass it to both the document.ready
and the page:load
events. This way the events for the checkboxes will be attached whether we’ve loaded the page via Turbolinks or not. With this change in place the checkboxes now work as we expect them to again and checking an incomplete task will move it to the completed list.
If we want this behaviour automatically we can use the JQuery Turbolinks gem. There is another way we can work around this problem. Instead of selecting elements and listening to their click
events we can listen to the document’s click
event and check to see when this event fires if the element that caused the event was the checkbox for a task.
/app/assets/javascripts/projects.js.coffee
$('document').on 'click', '.edit_task input[type=checkbox]', -> $(this).parent('form').submit()
This way we don’t need to fire this code on a DOM-ready event as it will fire on any checkbox that’s added to the HTML, event after this JavaScript code is executed. This approach has the additional advantage that if we add more tasks through AJAX they will automatically have this event applied to them.
It’s a good idea to keep an eye on the Turbolinks issue tracker as there are some issues that we should be aware of. There are several third-party libraries that are incompatible with Turbolinks such as Twitter Bootstrap and jQuery UI Calendar although work is in progress to make them compatible. Also there are some odd situations where reloading the page or clicking the back button in certain browsers can cause some unexpected behaviour such as submitting a POST request instead of a GET in certain circumstances. Workarounds for these issues exist that will disable Turbolinks in these situations, although they shouldn’t be needed in future versions of Turbolinks.
With all these issues you might be wondering whether using Turbolinks is worth it. One thing that may persuade you to use it is Turbolinks Test. This runs benchmarks to see if Turbolinks gives a speed boost in certain situations and most of the time it does, reducing response time by up to a half. Of course all applications are different but it’s easy to use this with your own applications to see if it’s responsiveness is improved.
If we create a new Rails 4 application and decide not to use Turbolinks we can easily remove it by commenting-out the gem in the gemfile and the require
statement in the application’s JavaScript manifest file.