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 term
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
• “Type ahead” input suggestion, as in Google search
• Sending form data asynchronously
• 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, which is why 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. 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 and that you have a basic understanding of JavaScript programming.
Since the First Edition of The Rails Way, the landscape has changed. jQuery (located at http://jquery.com) is the dominant JavaScript framework, due in part to its clean, unobtrusive API and its use of CSS selectors to obtain elements in the page. Prototype and Scriptaculous have their adherents but for day-to-day Ajax and Rails work, jQuery is the workhorse.
Experience has shown us that if you want JavaScript code in your application, learn JavaScript and write it!
There is a declarative mechanism (where you write what you want, rather than how to do it) in Rails that ultimately generates JavaScript, the Unobtrusive JavaScript (UJS) API.
In Rails 3, the choice of JavaScript library to use in conjunction with Rails’ Ajax helpers is yours, and you can choose either Prototype or jQuery (or any other library that has driver support for Rails).
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.
Firebug also has an interactive console, which 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 will make up for my fee very quickly.”
If you’re developing using Safari or Chrome, those fine browser have built-in development tools that mimic Firebug, but I still think the original is the best.
The new 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 http://github.com/rails/jqueryujs and http://github.com/rails/prototype-ujs, respectively.
Prototype is the default JavaScript library used in Rails 3 and newly-generated applications use it to drive their Ajax UJS features. You can prevent the application generator from doing that by passing it -J
or --skip-prototype
. In that case rails.js
is not generated, only application.js
.
To use jQuery, just download the jQuery rails.js
file into public/javascripts
. Then add the following code to your layout’s head section:
= javascript_include_tag
"http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"
= javascript_include_tag 'rails'
Note that for our example we’ve hotlinked directly to the jQuery library provided free-of-charge by Google.
One of the most dramatic changes caused by the move to UJS is the way that delete links are generated.
= link_to 'Delete', user_path(1), :method => :delete, :confirm => "Sure?"
Prior to Rails 3 the resulting HTML would look something like
Now, taking advantage of UJS techniques, it will look like
What a difference! 2 Remote forms and link helpers also change due to UJS. Before Rails 3 you would write
remote_form_for(@user)
but now that changes to
form_for(@user, :remote => true)
Ajax links are now written as
link_to "More", more_user_details_path(@user), :remote => true
The above examples will append data-remote="true"
attributes to the HTML output.
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.
%head
= csrf_meta_tag
Rails includes a feature called RJS, which generates blocks of JavaScript code based on Ruby code. It allows you to manipulate a view from server side code and is used in conjunction with Ajax requests.
The example code in this section adds instant searching of US telephone area codes to the index view of an area codes resource. For your reference, the AreaCode
model has number
and location
attributes and looks like
The observe_field
method used in the following example is no longer a native part of Rails 3 and is not covered in this book. I don’t think it’s too difficult to figure out what it does. To use it you must install the official Prototype Legacy Helper plugin like this:
rails plugin install git://github.com/rails/prototype_legacy_helper
Our view features a simple table of area codes and a text field that is observed for changes.
For that template to work, we’ll need to add a collection route for searching area codes in routes.rb
.
Now we’ll use RJS in our AreaCodesController
to update the page automatically as a result of searching.
The replace_html
method of RJS replaces the inner HTML of the element identified in the first argument with the value of the second argument. We can use FireBug to see the JavaScript sent back to the browser in the response body.3
The JavaScript generated uses the Prototype framework.
It’s a poor practice to combine controller and view logic in one place, which is exactly what we did when we used render(:update)
. We can fix that by moving the RJS code out of the controller and into its own template named search.js.rjs
with the following contents
The controller action shrinks to just the logic that belongs there.
The respond_to
construct is gone, and we instead rely on Rails’ default behavior of picking a view that matches the request. In other words, Rails will choose to serve JavaScript view to Ajax requests automatically.
Rails comes with a comprehensive selection of RJS methods described in the following sections.
<<(javascript)
This method will write raw JavaScript to the page. This is useful if we have a custom method in application.js
that we want to call. For example:
[](id)
This returns a reference of the element identified by id in the DOM. Further calls can then be made on this element reference like hide
, show
, and so on. This behaves just like the $(id)
construct in jQuery.
alert(message)
This will display a JavaScript alert with the given message:
call(function, *arguments, & block)
Calls the JavaScript function with the given arguments if any. If a block is given, a new JavaScript generator will be created and all generated JavaScript will be wrapped in a function() { ... }
and passed as the class final argument.
delay(seconds = 1) ...
This will execute the given block after the given number of seconds have passed.
draggable(id, options = {})
This creates a draggable element.
drop_receiving(id, options = {})
Specifies an element that can act as a drop receiver for other elements that have been made draggable.
hide(*ids)
Hides the elements identified by the given DOM ids.
insert_html(position, id, *options_for_render)
Inserts HTML at the given position relative to the given element identified by the DOM id. Position can be any one of the values shown in Table 12.1.
Table 12.1. Options for insert_html
Method
The options_for_render
can be either a string of HTML to insert or options passed to render
.
literal(code)
This is used to pass a literal JavaScript expression as an argument to another JavaScript generator method. The returned object will have a to_json
method that will evaluate to code.
redirect_to(location)
Causes the browser to redirect to the given location.
remove(*ids)
Removes the given elements identified by the DOM ids.
replace(id, *options_for_render)
Replaces the entire element (not just its internal HTML) identified by the DOM id with either a string or render options set in options_for_render
.
replace_html(id, *options_for_render)
Replaces the internal HTML identified by the DOM id with either a string or render options set in options_for_render
.
select(pattern)
Obtains a collection of element references by finding it through a CSS pattern. You can use standard jQuery enumerations with the returned collection.
show(*ids)
Show the given hidden elements identified by the DOM ids.
sortable(id, options = {})
Creates a sortable list of elements. See http://webtempest.com/sortable-listin-ruby-on-rails-3-almost-unobtrusive-jquery for a quick tutorial.
toggle(*ids)
Toggles the visibility of the elements identified by the ids. In other words, visible elements will become hidden and hidden elements will become visible.
visual_effect(name, id = nil, options = {})
This will start the named effect on the element identified by the DOM id. From RJS you can call appear
, fade
, slidedown
, slideup
, blinddown
, and blindup
. Each of these effects results in an element showing or hiding on the page. You can also call toggle_appear
, toggle_slide
, and toggle_blind
to toggle the effect. For a complete list of visual effects, not just the displaying of elements, and options they take, consult the Scriptaculous documentation. To fade an element, we would do the following:
render :update do |page|
page.visual_effect :fade, 'my_div'
end
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.
link_to
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:
This uses the Client class method all_with_counts
which returns an array of hashmaps:
When GET /clients/counts
is requested and the content type is JSON the response is:
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:
UJS probably should take the option :data_type
and convert it to the HTML 5 attribute data-type
when using jQuery, or explicitly specify the format in the URL when using Prototype. We’ll be keeping a lookout for that behavior in future versions of Rails.
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
:
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 why JSON is the best format for decoupling the presentation layer of your application or, more importantly, 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 which is 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 most 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.
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 both in Rails and in JavaScript. Applications tend to have many more than just two rules combining 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
Whereas, in another context, it could look like
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 3 and most JSONP applications.
Although the Wikipedia entry4 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 anyways - it is after all asynchronous JavaScript.
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 3 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:
then, just use UJS to tell jQuery it’s a JSONP call by altering the data-type
to jsonp
:
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 3 automatic rendering for JavaScript responses isn’t there yet so we add a special handler for JavaScript in our controller:
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 is 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, including some that are specific to using Ajax and Rails together. It’s a big subject, but an important enough part of Rails that we felt the need to include a quick introduction to it as part of this book.
3.135.247.68