(Quick Reference)

7.7 Ajax - Reference Documentation

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari

Version: 2.3.8

7.7 Ajax

Ajax is the driving force behind the shift to richer web applications. These types of applications in general are better suited to agile, dynamic frameworks written in languages like Groovy and Ruby Grails provides support for building Ajax applications through its Ajax tag library. For a full list of these see the Tag Library Reference.

Note: JavaScript examples use the jQuery library.

7.7.1 Ajax Support

By default Grails ships with the jQuery library, but through the Plugin system provides support for other frameworks such as Prototype, Dojo:http://dojotoolkit.org/, Yahoo UI:http://developer.yahoo.com/yui/ and the Google Web Toolkit.

This section covers Grails' support for Ajax in general. To get started, add this line to the <head> tag of your page:

<g:javascript library="jquery" />

You can replace jQuery with any other library supplied by a plugin you have installed. This works because of Grails' support for adaptive tag libraries. Thanks to Grails' plugin system there is support for a number of different Ajax libraries including (but not limited to):

  • jQuery
  • Prototype
  • Dojo
  • YUI
  • MooTools

7.7.1.1 Remoting Linking

Remote content can be loaded in a number of ways, the most commons way is through the remoteLink tag. This tag allows the creation of HTML anchor tags that perform an asynchronous request and optionally set the response in an element. The simplest way to create a remote link is as follows:

<g:remoteLink action="delete" id="1">Delete Book</g:remoteLink>

The above link sends an asynchronous request to the delete action of the current controller with an id of 1.

7.7.1.2 Updating Content

This is great, but usually you provide feedback to the user about what happened:

def delete() {
    def b = Book.get(params.id)
    b.delete()
    render "Book ${b.id} was deleted"
}

GSP code:

<div id="message"></div>
<g:remoteLink action="delete" id="1" update="message">
Delete Book
</g:remoteLink>

The above example will call the action and set the contents of the message div to the response in this case "Book 1 was deleted". This is done by the update attribute on the tag, which can also take a Map to indicate what should be updated on failure:

<div id="message"></div>
<div id="error"></div>
<g:remoteLink update="[success: 'message', failure: 'error']"
              action="delete" id="1">
Delete Book
</g:remoteLink>

Here the error div will be updated if the request failed.

7.7.1.3 Remote Form Submission

An HTML form can also be submitted asynchronously in one of two ways. Firstly using the formRemote tag which expects similar attributes to those for the remoteLink tag:

<g:formRemote url="[controller: 'book', action: 'delete']"
              update="[success: 'message', failure: 'error']">
    <input type="hidden" name="id" value="1" />
    <input type="submit" value="Delete Book!" />
</g:formRemote >

Or alternatively you can use the submitToRemote tag to create a submit button. This allows some buttons to submit remotely and some not depending on the action:

<form action="delete">
    <input type="hidden" name="id" value="1" />
    <g:submitToRemote action="delete"
                      update="[success: 'message', failure: 'error']" />
</form>

7.7.1.4 Ajax Events

Specific JavaScript can be called if certain events occur, all the events start with the "on" prefix and let you give feedback to the user where appropriate, or take other action:

<g:remoteLink action="show"
              id="1"
              update="success"
              onLoading="showProgress()"
              onComplete="hideProgress()">Show Book 1</g:remoteLink>

The above code will execute the "showProgress()" function which may show a progress bar or whatever is appropriate. Other events include:

  • onSuccess - The JavaScript function to call if successful
  • onFailure - The JavaScript function to call if the call failed
  • on_ERROR_CODE - The JavaScript function to call to handle specified error codes (eg on404="alert('not found!')")
  • onUninitialized - The JavaScript function to call the a Ajax engine failed to initialise
  • onLoading - The JavaScript function to call when the remote function is loading the response
  • onLoaded - The JavaScript function to call when the remote function is completed loading the response
  • onComplete - The JavaScript function to call when the remote function is complete, including any updates

You can simply refer to the XMLHttpRequest variable to obtain the request:

<g:javascript>
    function fireMe(event) {
        alert("XmlHttpRequest = " + event)
    }
}
</g:javascript>
<g:remoteLink action="example"
              update="success"
              onFailure="fireMe(XMLHttpRequest)">Ajax Link</g:remoteLink>

7.7.2 Ajax with Prototype

Grails features an external plugin to add Prototype support to Grails. To install the plugin, list it in BuildConfig.groovy:

runtime ":prototype:latest.release"

This will download the current supported version of the Prototype plugin and install it into your Grails project. With that done you can add the following reference to the top of your page:

<g:javascript library="prototype" />

If you require Scriptaculous too you can do the following instead:

<g:javascript library="scriptaculous" />

Now all of Grails tags such as remoteLink, formRemote and submitToRemote work with Prototype remoting.

7.7.3 Ajax with Dojo

Grails features an external plugin to add Dojo support to Grails. To install the plugin, list it in BuildConfig.groovy:

compile ":dojo:latest.release"

This will download the current supported version of Dojo and install it into your Grails project. With that done you can add the following reference to the top of your page:

<g:javascript library="dojo" />

Now all of Grails tags such as remoteLink, formRemote and submitToRemote work with Dojo remoting.

7.7.4 Ajax with GWT

Grails also features support for the Google Web Toolkit through a plugin. There is comprehensive documentation available on the Grails wiki.

7.7.5 Ajax on the Server

There are a number of different ways to implement Ajax which are typically broken down into:
  • Content Centric Ajax - Where you just use the HTML result of a remote call to update the page
  • Data Centric Ajax - Where you actually send an XML or JSON response from the server and programmatically update the page
  • Script Centric Ajax - Where the server sends down a stream of JavaScript to be evaluated on the fly

Most of the examples in the Ajax section cover Content Centric Ajax where you are updating the page, but you may also want to use Data Centric or Script Centric. This guide covers the different styles of Ajax.

Content Centric Ajax

Just to re-cap, content centric Ajax involves sending some HTML back from the server and is typically done by rendering a template with the render method:

def showBook() {
    def b = Book.get(params.id)

render(template: "bookTemplate", model: [book: b]) }

Calling this on the client involves using the remoteLink tag:

<g:remoteLink action="showBook" id="${book.id}"
              update="book${book.id}">Update Book</g:remoteLink>

<div id="book${book.id}"> <!--existing book mark-up --> </div>

Data Centric Ajax with JSON

Data Centric Ajax typically involves evaluating the response on the client and updating programmatically. For a JSON response with Grails you would typically use Grails' JSON marshalling capability:

import grails.converters.JSON

def showBook() { def b = Book.get(params.id)

render b as JSON }

And then on the client parse the incoming JSON request using an Ajax event handler:

<g:javascript>
function updateBook(data) {
    $("#book" + data.id + "_title").html( data.title );
}
</g:javascript>
<g:remoteLink action="showBook" id="${book.id}" onSuccess="updateBook(data)">
    Update Book
</g:remoteLink>
<g:set var="bookId">book${book.id}</g:set>
<div id="${bookId}">
    <div id="${bookId}_title">The Stand</div>
</div>

Data Centric Ajax with XML

On the server side using XML is equally simple:

import grails.converters.XML

def showBook() { def b = Book.get(params.id)

render b as XML }

However, since DOM is involved the client gets more complicated:

<g:javascript>
function updateBook(data) {
    var id = $(data).find("book").attr("id");
    $("#book" + id + "_title").html( $(data).find("title").text() );
}
</g:javascript>
<g:remoteLink action="showBook" id="${book.id}" onSuccess="updateBook(data)">
    Update Book
</g:remoteLink>
<g:set var="bookId">book${book.id}</g:set>
<div id="${bookId}">
    <div id="${bookId}_title">The Stand</div>
</div>

Script Centric Ajax with JavaScript

Script centric Ajax involves actually sending JavaScript back that gets evaluated on the client. An example of this can be seen below:

def showBook() {
    def b = Book.get(params.id)

response.contentType = "text/javascript" String title = b.title.encodeAsJavaScript() render "$('#book${b.id}_title').html('${title}');" }

The important thing to remember is to set the contentType to text/javascript. If you use Prototype on the client the returned JavaScript will automatically be evaluated due to this contentType setting.

Obviously in this case it is critical that you have an agreed client-side API as you don't want changes on the client breaking the server. This is one of the reasons Rails has something like RJS. Although Grails does not currently have a feature such as RJS there is a Dynamic JavaScript Plugin that offers similar capabilities.

Responding to both Ajax and non-Ajax requests

It's straightforward to have the same Grails controller action handle both Ajax and non-Ajax requests. Grails adds the isXhr() method to HttpServletRequest which can be used to identify Ajax requests. For example you could render a page fragment using a template for Ajax requests or the full page for regular HTTP requests:

def listBooks() {
    def books = Book.list(params)
    if (request.xhr) {
        render template: "bookTable", model: [books: books]
    } else {
        render view: "list", model: [books: books]
    }
}