7.7 Ajax - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 2.3.8
Table of Contents
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" />
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>
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"
}
<div id="message"></div> <g:remoteLink action="delete" id="1" update="message"> Delete Book </g:remoteLink>
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>
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 >
<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>
onSuccess
- The JavaScript function to call if successfulonFailure
- The JavaScript function to call if the call failedon_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 initialiseonLoading
- The JavaScript function to call when the remote function is loading the responseonLoaded
- The JavaScript function to call when the remote function is completed loading the responseonComplete
- The JavaScript function to call when the remote function is complete, including any updates
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"
<g:javascript library="prototype" />
<g:javascript library="scriptaculous" />
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"
<g:javascript library="dojo" />
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
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])
}
<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.JSONdef showBook() {
def b = Book.get(params.id) render b as JSON
}
<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.XMLdef showBook() {
def b = Book.get(params.id) render b as XML
}
<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}');" }
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 theisXhr()
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] } }