Ajax isn’t a technology. It’s really several technologies, each flourishing in its own right, coming together in powerful new ways
—Jesse J. Garrett, who coined the name Ajax
Ajax is an acronym that stands for asynchronous JavaScript and XML. It encompasses techniques that allow us to liven up web pages with behaviors that happen outside the normal HTTP request life cycle (without a page refresh).
Some example use cases for Ajax techniques are the following:
• “Type ahead” input suggestion, as in Google search
• Asynchronous form data delivery
• Seamless navigation of web-presented maps, as in Google Maps
• Dynamically updated lists and tables, as in Gmail and other web-based email services
• Web-based spreadsheets
• Forms that allow in-place editing
• Live preview of formatted writing alongside a text input
Ajax is made possible by the XMLHttpRequestObject
(or XHR for short), an API that is available in all modern browsers. It allows JavaScript code on the browser to exchange data with the server and use it to change the user interface of your application on the fly without needing a page refresh. Working directly with XHR in a cross browser–compatible way is difficult, to say the least; however, we are lucky as the open-source ecosystem flourishes with Ajax JavaScript libraries.
Incidentally, Ajax, especially in Rails, has very little to do with XML, despite its presence there at the end of the acronym. In fact, by default Rails 4 does not include XML parsing (however, this can be reenabled). The payload of those asynchronous requests going back and forth to the server can be anything. Often it’s just a matter of form parameters posted to the server and receiving snippets of HTML back for dynamic insertion into the page’s DOM. Many times it even makes sense for the server to send back data encoded in a simple kind of JavaScript called JavaScript object notation (JSON).
It’s outside the scope of this book to teach you the fundamentals of JavaScript and/or Ajax. It’s also outside of our scope to dive into the design considerations of adding Ajax to your application, elements of which are lengthy and occasionally controversial. Proper coverage of those subjects would require a whole book and there are many such books to choose from in the marketplace. Therefore, the rest of the chapter will assume that you understand what Ajax is and why you would use it in your applications. It also assumes that you have a basic understanding of JavaScript programming.
Firebug1 is an extremely powerful extension for Firefox and a must-have tool for doing Ajax work. It lets you inspect Ajax requests and probe the DOM of the page extensively, even letting you change elements and CSS styles on the fly and see the results on your browser screen. It also has a very powerful JavaScript debugger that you can use to set watch expressions and breakpoints.
1. The first step to getting the Firebug plugin for Firefox is to visit http://www.getfirebug.com
Firebug also has an interactive console that allows you to experiment with JavaScript in the browser just as you would use irb
in Ruby. In some cases, the code samples in this chapter are copied from the Firebug console, which has a >>>
prompt.
As I’ve jokingly told many of my Ruby on Rails students when covering Ajax on Rails, “Even if you don’t listen to anything else I say, use Firebug! The productivity gains you experience, which will make up for my fee very quickly.”
Kevin Says ...
Alternatively, if you use Chrome or Safari, both browsers have similar built-in tools. My personal preference is the Chrome DevTools,2 which is continuously improved with each new release of Chrome.
2. http://developers.google.com/chrome-developer-tools/
The unobtrusive JavaScript (UJS) features in Rails provide a library-independent API for specifying Ajax actions. The Rails team has provided UJS implementations for both jQuery and Prototype, available under https://github.com/rails/jquery-ujs
and https://github.com/rails/prototype-rails
, respectively. By default, newly generated Rails applications use jQuery as its JavaScript library of choice.
To integrate jQuery into your Rails application, simply include the jquery-rails
gem in your Gemfile
and run bundle install
. Next, ensure that the right directives are present in your JavaScript manifest file (as seen in the following text).
1 # Gemfile
2 gem 'jquery-rails'
1 // app/assets/javascripts/application.js
2 //= require jquery
3 //= require jquery_ujs
By including those require statements in your JavaScript manifest file, both the jQuery and jquery_ujs
libraries will automatically be bundled up along with the rest of your assets and served to the browser efficiently. Use of manifest files is covered in detail in Chapter 20, “Asset Pipeline.”
Prior to version 3.0, Rails was not unobtrusive, resulting in generated markup being coupled to your JavaScript library of choice. For example, one of the most dramatic changes caused by the move to UJS was the way that delete links were generated.
1 = link_to 'Delete', user_path(1), method: :delete,
2 data: { confirm: "Are you sure?" }
Prior to the use of UJS techniques, the resulting HTML would look something like the following:
1 <a href="/users/1" onclick="if (confirm('Sure?')) { var f =
2 document.createElement('form'), f.style.display = 'none';
3 this.parentNode.appendChild(f); f.method = 'POST'; f.action =
4 this.href;var m = document.createElement('input'), m.setAttribute('type',
5 'hidden'), m.setAttribute('name', '_method'), m.setAttribute('value',
6 'delete'), f.appendChild(m);f.submit(); };return false;">Delete</a>
Now taking advantage of UJS, it will look like this:
1 <a data-confirm="Are you sure?" data-method="delete" href="/users/1"
2 rel="nofollow">Delete</a>
Note that Rails uses the standard HTML5 data-
attributes method as a means to attach custom events to DOM elements.
Also required for Rails UJS support is the csrf_meta_tag
, which must be placed in the head of the document and adds the csrf-param
and csrf-token
meta tags used in dynamic form generation.
1 %head
2 = csrf_meta_tag
CSRF stands for cross-site request forgery and the csrf_meta_tag
is one method of helping to prevent the attack from happening. CSRF is covered in detail in Chapter 15, “Security.”
As covered in Chapter 11, “All about Helpers,” Rails ships with view helper methods to generate markup for common HTML elements. The following is a listing of Action View helpers that have hooks to enable Ajax behavior via the unobtrusive JavaScript driver.
The button_to
helper generates a form containing a single button that submits to the URL created by the set of options. Setting the :remote
option to true
allows the unobtrusive JavaScript driver to make an Ajax request in the background to the URL.
To illustrate, the markup
= button_to("New User", new_user_path, remote: true)
generates
1 <form action="/users/new" class="button_to" data-remote="true"
2 method="post">
3 <div>
4 <input type="submit" value="New User">
5 <input name="authenticity_token" type="hidden"
6 value="HDVQ/5AHK+f5ChqN8qaah8Pd0gZzkoa21vqbvbayHBY=">
7 </div>
8 </form>
To display a JavaScript confirmation prompt with a question specified, supply data attribute :confirm
with a question. If accepted, the button will be submitted normally; otherwise, no action is taken.
= button_to("Deactivate", user, data: { confirm: 'Are you sure?' })
The unobtrusive JavaScript driver also allows for the disabling of the button when clicked via the :disable_with
data attribute. This prevents duplicate requests from hitting the server from subsequent button clicks by a user. If used in combination with remote: true
, once the request is complete, the unobtrusive JavaScript driver will re-enable the button and reset the text to its original value.
1 = button_to("Deactivate", user, data: { disable_with: 'Deactivating...' })
The form_for
helper is used to create forms with an Active Model instance. To enable the submission of a form via Ajax, set the :remote
option to true
. For instance, assuming we had a form to create a new user,
1 = form_for(user, remote: true) do |f|
2 ...
would generate
1 <form accept-charset="UTF-8" action="/users" class="new_user"
2 data-remote="true" id="new_user" method="post">
3 ...
4 </form>
Like form_for
, the form_tag
accepts the :remote
option to allow for Ajax form submission. For detailed information on form_tag
, see Chapter 11, “All about Helpers.”
The link_to
helper creates a link tag of the given name using a URL created by the set of options. Setting the option :remote
to true
allows the unobtrusive JavaScript driver to make an Ajax request to the URL instead of following the link.
= link_to "User", user, remote: true
By default, all links will always perform an HTTP GET
request. To specify an alternative HTTP verb, such as DELETE
, one can set the :method
option with the desired HTTP verb (:post
, :patch
, or :delete
).
= link_to "Delete User", user, method: :delete
If the user has JavaScript disabled, the request will always fall back to using GET, no matter what :method
you have specified.
The link_to
helper also accepts data attributes :confirm
and :disable_with
, covered earlier in the “button_to
” section in this chapter.
When a form, link, or button is marked with the data-remote
attribute, the jQuery UJS driver fires the custom events depicted in Table 19.1.
This allows you, for instance, to handle the success/failure of Ajax submissions. To illustrate, let’s bind to both the ajax:success
and ajax:error
events in the following CoffeeScript:
1 $(document).ready ->
2 $("#new_user")
3 .on "ajax:success", (event, data, status, xhr) ->
4 $(@).append xhr.responseText
5 .on "ajax:error", (event, xhr, status, error) ->
6 $(@).append "Something bad happened"
Rails 4 introduces a new, controversial feature called Turbolinks. Turbolinks is JavaScript library that, when enabled, attaches a click handler to all links of a HTML page. When a link is clicked, Turbolinks will execute an Ajax request and replace the contents of the current page with the response’s <body>
tag.
Using Turbolinks also changes the address of the current page, allowing users to bookmark a specific page and use the back button as they normally would. Turbolinks uses the HTML5 history API to achieve this.
The biggest advantage of Turbolinks is that it enables the user’s browser to only fetch the required stylesheets, JavaScripts, and even images once to render the page. Turbolinks effectively makes your site appear faster and more responsive.
To integrate Turbolinks into your existing Rails application, simply include the turbolinks
gem in your Gemfile
and run bundle install
. Next, add “require turbolinks
” in your JavaScript manifest file.
1 # Gemfile
2 gem 'turbolinks'
1 // app/assets/javascripts/application.js
2 //= require jquery
3 //= require jquery_ujs
4 //= require turbolinks
In Rails 4, Turbolinks is enabled by default but can be disabled if you prefer not to use it. To disable the use of Turbolinks for a specific link on a page, simply use the data-no-turbolink
tag like so:
= link_to 'User', user_path(1), 'data-no-turbolink' => true
It does not depend on any particular framework, such as jQuery or ZeptoJS, and is intended on being as unobtrusive as possible.
One caveat to Turbolinks is it only will work with GET
requests. You can, however, send POST
requests to a Turbolink-enabled link, as long as it sends a redirect instead of an immediate render. This is because the method must return the user’s browser to a location that can be rendered on a GET request. (pushState does not record the HTTP method, only the path per request.)
When using Turbolinks, the DOM’s ready
event will only be fired on the initial page request, as it overrides the normal page-loading process. This means you cannot rely on DOMContentLoaded
or jQuery.ready()
to trigger code evaluation. To trigger code that is dependent on the loading of a page in Turbolinks, one must attach to the custom Turbolinks page:change
event.
1 $(document).on "page:change", ->
2 alert "loaded!"
When Turbolinks requests a fresh version of a page from the server, the following events are fired on document
:
page:before-change A link that is Turbolinks-enabled has been clicked. Returning false
will cancel the Turbolinks process.
page:fetch Turbolinks has started fetching a new target page.
page:receive The new target page has been fetched from the server.
page:change The page has been parsed and changed to the new version.
page:update If jQuery is included, triggered on jQuery’s ajaxSuccess
event.
page:load End of page-loading process.
By default, Turbolinks caches 10-page loads to reduce requests to the server. In this case, the page:restore
event is fired at the end of the restore process.
jquery.turbolinks
If you have an existing Rails application that extensively binds to the jQuery.ready
event, you may want to look at using the jquery.turbolinks
library.3 When Turbolinks triggers the page:load
event on a document, jquery.turbolinks
will automatically jQuery.ready
events as well.
3. https://github.com/kossnocorp/jquery.turbolinks
Turbolinks undoubtedly speeds up many sites by avoiding the reprocessing of the <head>
tag. It was mature enough for the Rails core team to bundle it as an official part of Rails. Yet it has plenty of critics. Some raise objections about the headaches of making sure that all the Ajax functions of a large application actually work correctly with Turbolinks enabled. Others point out how it breaks apps in older browsers such as IE8. And others point out that it is inefficient, because most applications could get away with refreshing sections of the page smaller than the entire <body>
element.
We think it’s worth giving Turbolinks a try in your application, especially if you’re starting from scratch and can take its challenges into account from the beginning of a project. However, we also admit that we’ve disabled it in a lot of our projects. Your mileage may vary.
Here are some of the issues that you may need to address with your use of Turbolinks:4
4. http://net.tutsplus.com/tutorials/ruby/digging-into-rails-4
Memory Leaks Turbolinks does not clear or reload your JavaScript when the page changes. You could potentially see the effects of memory leaks in your applications, especially if you use a lot of JavaScript.
Event Bindings You have to take older browsers into consideration. Make sure you listen for page:* events, as well as DOMContentLoaded.
Client-Side Frameworks Turbolinks may not play nicely with other client-side frameworks like Backbone, Angular, Knockout, Ember, and so on.
JavaScript object notation (JSON) is a simple way to encode JavaScript objects. It is also considered a language-independent data format, making it a compact, human-readable, and versatile interchange format. This is the preferred method of interchanging data between the web application code running on the server and any code running in the browser, particularly for Ajax requests.
Rails provides a to_json
on every object, using a sensible mechanism to do so for every type. For example, BigDecimal
objects, although numbers, are serialized to JSON as strings, since that is the best way to represent a BigDecimal
in a language-independent manner. You can always customize the to_json
method of any of your classes if you wish, but it should not be necessary to do so.
To illustrate an Ajax request, let’s enable our Client controller to respond to JSON and provide a method to supply the number of draft timesheets outstanding for each client:
1 respond_to :html, :xml, :json
2 ...
3 # GET /clients/counts
4 # GET /clients/counts.json
5 def counts
6 respond_with(Client.all_with_counts) do |format|
7 format.html { redirect_to clients_path }
8 end
9 end
This uses the Client class method all_with_counts,
which returns an array of hashmaps:
1 def self.all_with_counts
2 all.map do |client|
3 { id: client.id, draft_timesheets_count: client.timesheets.
draft.count }
4 end
5 end
When GET /clients/counts
is requested and the content type is JSON, the response is the following:
1 [{"draft_timesheets_count":0, "id":20},
2 {"draft_timesheets_count":1, "id":21}]
You will note in the code example that HTML and XML are also supported content types for the response, so it’s up to the client to decide which format works best for them. We’ll look at formats other than JSON in the next few sections.
In this case, our Client index view requests a response in JSON format:
1 - content_for :head do
2 = javascript_include_tag 'clients.js'
3 ...
4 %table#clients_list
5 ...
6 - @clients.each do |client|
7 %tr[client]
8 %td= client.name
9 %td= client.code
10 %td.draft_timesheets_count= client.timesheets.draft.count
11 ...
12 = link_to 'Update draft timesheets count', counts_clients_path,
13 remote: true, data: { type: :json }, id: 'update_draft_timesheets'
To complete the asynchronous part of this Ajax-enabled feature, we also need to add an event handler to the UJS ajax:success
event, fired when the Ajax call on the update_draft_timesheets
element completes successfully. Here, jQuery is used to bind a JavaScript function to the event once the page has loaded. This is defined in clients.js
:
1 $(function() {
2 $("#update_draft_timesheets").on("ajax:success", function(event, data) {
3 $(data).each(function() {
4 var td = $('#client_' + this.id + ' .draft_timesheets_count')
5 td.html(this.draft_timesheets_count);
6 });
7 });
8 });
In each row of the clients
listing, the respective td
with a class of draft_timesheets_count
is updated in place with the values from the JSON response. There is no need for a page refresh, and user experience is improved.
As an architectural constraint, this does require this snippet of JavaScript to have intimate knowledge of the target page’s HTML structure and how to transform the JSON into changes on the DOM. This is a major reason JSON is the best format for decoupling the presentation layer of your application or, more important, when the page is requesting JSON from another application altogether.
Sometimes, however, it may be desirable for the server to respond with a snippet of HTML, used to replace a region of the target page.
The Ruby classes in your Rails application will normally contain the bulk of that application’s logic and state. Ajax-heavy applications can leverage that logic and state by transferring HTML—rather than JSON—to manipulate the DOM.
A web application may respond to an Ajax request with an HTML fragment, used to insert or replace an existing part of the page. This is usually done when the transformation relies on complex business rules and perhaps complex state that would be inefficient to duplicate in JavaScript.
Let’s say your application needs to display clients in some sort of priority order, and that order is highly variable and dependent on the current context. There could be a swag of rules dictating what order they are shown in. Perhaps it’s that whenever a client has more than a number of draft timesheets, we want to flag that in the page.
1 %td.draft_timesheets_count
2 - if client.timesheets.draft.count > 3
3 %span.drafts-overlimit WARNING!
4 %br
5 = client.timesheets.draft.count
Along with that, let’s say on a Friday or Saturday, we need to group clients by their hottest spending day so we can make ourselves an action plan for the beginning of the following week.
These are just two business rules that, when combined, are a bit of a handful to implement in both Rails and JavaScript. Applications tend to have many more than just two combined rules, and it quickly becomes prohibitive to implement those rules in JavaScript to transform JSON into DOM changes. That’s particularly true when the page making the Ajax call is external and not one we’ve written.
We can opt to transfer HTML in the Ajax call and, using JavaScript, to update a section of the page with that HTML. Under one context, the snippet of HTML returned could look like the following:
1 <tr id="client_22" class="client"></tr>
2 <tr>
3 <td></td><td>Aardworkers</td><td>AARD</td><td>$4321</td>
4 <td class="draft_timesheets_count">0</td>
5 </tr>
6 <tr id="client_23" class="client"></tr>
7 <tr>
8 <td></td><td>Zorganization</td><td>ZORG</td><td>$9999</td>
9 <td class="draft_timesheets_count">1</td>
10 </tr>
Whereas, in another context, it could look like this:
1 <tr>
2 <td>Friday</td>
3 </tr>
4 <tr>
5 <td>Saturday</td>
6 </tr>
7 <tr id="client_24" class="client"></tr>
8 <tr>
9 <td></td><td>Hashrocket</td><td>HR</td><td>$12000</td>
10 <td class="draft_timesheets_count">
11 <span class="drafts-overlimit">WARNING!</span>
12 5
13 </td>
14 </tr>
15 <tr id="client_22" class="client"></tr>
16 <tr>
17 <td></td><td>Aardworkers</td><td>AARD</td><td>$4321</td>
18 <td class="draft_timesheets_count">0</td>
19 </tr>
The JavaScript event handler for the Ajax response then just needs to update the innerHTML
of a particular HTML element to alter the page without having to know anything about the business rules used to determine what the resulting HTML should be.
The primary reason you want to work with a JavaScript response to an Ajax request is when it is for JSONP (JSON with padding). JSONP pads, or wraps, JSON data in a call to a JavaScript function that exists on your page. You specify the name of that function in a callback
query string parameter. Note that some public APIs may use something other than callback
, but it has become the convention in Rails and most JSONP applications.
Xavier Says ...
Although the Wikipedia entry5 for Ajax does not specifically mention JSONP, and the request is not XHR by Rails’ definition, we’d like to think of it as Ajax anyway—it is, after all, asynchronous JavaScript.
5. http://en.wikipedia.org/wiki/Ajax_(programming)
JSONP is one technique for obtaining cross domain data, avoiding the browser’s same-origin policy. This introduces a pile of safety and security issues that are beyond the scope of this book. However, if you need to use JSONP, the Rails stack provides an easy way to handle JSONP requests (with Rack::JSONP) or make JSONP requests (with UJS and jQuery).
To respond to JSONP requests, activate the Rack JSONP module from the rack-contrib
RubyGem in your environment.rb
file:
1 class Application < Rails::Application
2 require 'rack/contrib'
3 config.middleware.use 'Rack::JSONP'
4 ...
Then just use UJS to tell jQuery it’s a JSONP call by altering the data-type
to jsonp
:
1 = link_to 'Update draft timesheets count', counts_clients_path,
2 remote: true, data: { type: :jsonp }, id: 'update_draft_timesheets'
jQuery automatically adds the ?callback=
and random function name to the query string of the request URI. In addition to this, it also adds the necessary script
tags to our document to bypass the same-origin policy. Our existing event handler is bound to ajax:success
, so it is called with the data just like before. Now, though, it can receive that data from another web application.
jQuery also makes the request as if it is for JavaScript, so our Rails controller needs to respond_to :js
. Unfortunately, the Rails automatic rendering for JavaScript responses isn’t there yet, so we add a special handler for JavaScript in our controller:
1 respond_to :html, :js
2 ...
3
4 def counts
5 respond_with(Client.all_with_counts) do |format|
6 format.html { redirect_to clients_path }
7 format.js { render json: Client.all_with_counts.to_json }
8 end
9 end
We still convert our data to JSON. The Rack::JSONP
module then pads that JSON data in a call to the JavaScript function specified in the query string of the request. The response looks like this:
jsonp123456789([{"id":1,"draft_timesheets_count":0},
{"id":2,"draft_timesheets_count":1}])
When the Ajax response is complete, your Ajax event handler is called and the JSON data are passed to it as a parameter.
The success of Rails is often correlated to the rise of Web 2.0, and one of the factors linking Rails into that phenomenon is its baked-in support for Ajax. There are a ton of books about Ajax programming, as it’s a big subject, but it is an important enough part of Rails that we felt the need to include a quick introduction to it as part of this book.
3.147.77.4