Chapter 12. Ajax on Rails

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.

12.0.1 Changes in Rails 3

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.


Josh says ...

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).

12.0.2 Firebug

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.

12.1 Unobtrusive JavaScript

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.


Xavier says ...

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.

12.1.1 UJS Usage

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

image

Now, taking advantage of UJS techniques, it will look like

image

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

12.2 Writing JavaScript in Ruby with RJS

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

image

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.

image

For that template to work, we’ll need to add a collection route for searching area codes in routes.rb.

image

Now we’ll use RJS in our AreaCodesController to update the page automatically as a result of searching.

image

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

image

The JavaScript generated uses the Prototype framework.

12.2.1 RJS Templates

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

image

The controller action shrinks to just the logic that belongs there.

image

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.

12.2.2 <<(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:

image

12.2.3 [](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.

image

12.2.4 alert(message)

This will display a JavaScript alert with the given message:

image

12.2.5 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.

image

12.2.6 delay(seconds = 1) ...

This will execute the given block after the given number of seconds have passed.

image

12.2.7 draggable(id, options = {})

This creates a draggable element.

12.2.8 drop_receiving(id, options = {})

Specifies an element that can act as a drop receiver for other elements that have been made draggable.

12.2.9 hide(*ids)

Hides the elements identified by the given DOM ids.

image

12.2.10 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

image

The options_for_render can be either a string of HTML to insert or options passed to render.

image

12.2.11 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.

12.2.12 redirect_to(location)

Causes the browser to redirect to the given location.

image

12.2.13 remove(*ids)

Removes the given elements identified by the DOM ids.

12.2.14 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.

image

12.2.15 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.

12.2.16 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.

image

12.2.17 show(*ids)

Show the given hidden elements identified by the DOM ids.

12.2.18 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.

12.2.19 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.

12.2.20 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

12.3 Ajax and JSON

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.

12.3.1 Ajax 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:

image

This uses the Client class method all_with_counts which returns an array of hashmaps:

image

When GET /clients/counts is requested and the content type is JSON the response is:

image

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:

image


Note

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:

image

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.

12.4 Ajax and HTML

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.

image

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

image

Whereas, in another context, it could look like

image

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.

12.5 Ajax and JavaScript

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.


Xavier says ...

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:

image

then, just use UJS to tell jQuery it’s a JSONP call by altering the data-type to jsonp:

image

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:

image

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.

12.6 Conclusion

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.126.56