6.7 Ajax - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith
Version: null
Table of Contents
6.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.
Ajax是更丰富WEB应用背后的驱动力,这些应该通常都是使用敏捷的,动态的语言来完成的,比如 Groovy 和 Ruby 。 Grails是通过其Ajax标签库来构建Ajax应用的,更完整的列表请参考标签库索引。
6.7.1 Ajax支持
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 You can replace
缺省情况下,Grails采用的是 jQuery 框架,但是通过其插件系统也提供了对其他框架的支持,比如Prototype、 Dojo:http://dojotoolkit.org/、Yahoo UI:http://developer.yahoo.com/yui/和Google Web Toolkit。本节将介绍Grails对Ajax的通用支持。在开始之前,请先在你页面的<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
<head>标签部分增加如下内容:<g:javascript library="jquery" />jQuery。这要感谢Grails的插件系统,有了它才能支持这么多不同的Ajax框架库,包括但不限于如下所提到的:
- jQuery
- Prototype
- Dojo
- YUI
- MooTools
6.7.1.1 异步超链接
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:The above link sends an asynchronous request to the
远程内容可以使用多种方法载入,最常使用的方法是通过remoteLink标签。此标签将创建HTML的锚标记用以执行一个异步请求,并在一个元素中设置响应内容。最简单的创建一个远程连接的方法如下:<g:remoteLink action="delete" id="1">Delete Book</g:remoteLink>
delete action of the current controller with an id of 1.
<g:remoteLink action="delete" id="1">Delete Book</g:remoteLink>
delete操作。
6.7.1.2 更新内容
This is great, but usually you provide feedback to the user about what happened:GSP code:The above example will call the action and set the contents of the Here the
目前都还不错,但一般来说你会提供一些信息反馈给用户,以告诉都发生过什么,比如: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.
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>
delete操作,并且将响应内容"Book 1 was deleted"设置到id为message的div中,这是通过标签中的update属性来完成的。此外还可以用Map参数来设定失败时要更新那些,比如:<div id="message"></div> <div id="error"></div> <g:remoteLink update="[success: 'message', failure: 'error']" action="delete" id="1"> Delete Book </g:remoteLink>
error将会被更新。
6.7.1.3 异步Form提交
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: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:
HTML的表单可以通过以下两种方式的一种进行异步提交。其一,使用formRemote标签,它的属性跟remoteLink标签类似,比如:<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>
<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>
6.7.1.4 Ajax事件
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:The above code will execute the "showProgress()" function which may show a progress bar or whatever is appropriate. Other events include:
当某个事件发生时,特定的JavaScript将会被调用到,所有这些事件都是以"on"为前缀,并且合适地反馈给用户或者其他处理,比如:<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 object you can use the implicit event parameter e to obtain it:<g:javascript> function fireMe(e) { alert("XmlHttpRequest = " + e) } } </g:javascript> <g:remoteLink action="example" update="success" onSuccess="fireMe(e)">Ajax Link</g:remoteLink>
<g:remoteLink action="show" id="1" update="success" onLoading="showProgress()" onComplete="hideProgress()">Show Book 1</g:remoteLink>
onSuccess- 成功时要调用的JavaScript函数onFailure- 失败时要调用的JavaScript函数on_ERROR_CODE- 处理特定的错误编码(比如on404="alert('not found!')")时要调用的JavaScript函数onUninitialized- Ajax引擎初始化失败时要调用的JavaScript函数onLoading- 远程调用正在加载响应时要调用的JavaScript函数onLoaded- 远程调用已经加载完响应时要调用的JavaScript函数onComplete- 远程调用完全结束(包括更新内容)时要调用的JavaScript函数
XmlHttpRequest对象,你可以使用隐式的事件参数e来获取它:<g:javascript> function fireMe(e) { alert("XmlHttpRequest = " + e) } } </g:javascript> <g:remoteLink action="example" update="success" onSuccess="fireMe(e)">Ajax Link</g:remoteLink>
6.7.2 用Prototype实现Ajax
Grails features an external plugin to add Prototype support to Grails. To install the plugin type the following command from the root of your project in a terminal window: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:If you require Scriptaculous too you can do the following instead:Now all of Grails tags such as remoteLink, formRemote and submitToRemote work with Prototype remoting.
Grails通过一个外部插件来提供对 Prototype 的支持。要安装此插件,在字符终端的窗口中,进入你工程的根目录,输入下面命令即可:grails install-plugin prototype
<g:javascript library="prototype" /><g:javascript library="scriptaculous" />grails install-plugin prototype
<g:javascript library="prototype" /><g:javascript library="scriptaculous" />6.7.3 用Dojo实现Ajax
Grails features an external plugin to add Dojo support to Grails. To install the plugin type the following command from the root of your project in a terminal window: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:Now all of Grails tags such as remoteLink, formRemote and submitToRemote work with Dojo remoting.
Grails通过一个外部插件来提供对 Dojo 的支持。要安装此插件,在字符终端的窗口中,进入你工程的根目录,输入下面命令即可:grails install-plugin dojo
<g:javascript library="dojo" />grails install-plugin dojo
<g:javascript library="dojo" />6.7.4 用GWT实现Ajax
Grails also features support for the Google Web Toolkit through a plugin. There is comprehensive documentation available on the Grails wiki.
Grails通过插件对 Google Web Toolkit 也提供了支持,其复杂的文档请参考官方网站。
6.7.5 服务端的Ajax
There are a number of different ways to implement Ajax which are typically broken down into:
实现Ajax有很多种不同的方式,但大体可分为如下几类:
- 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
- 内容为中心的Ajax - 使用远程调用返回的HTML结果更新页面
- 数据为中心的Ajax - 从服务器端发送接收XML或者JSON,并且以编程的方式更新页面
- 脚本为中心的Ajax - 接收从服务器端发出的JavaScript流,并且运行之
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>
内容为中心的Ajax
重申一下,内容为中心的Ajax主要跟从服务器端返回HTML内容相关,这些内容一般是通过使用render渲染模板的方式得到::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(e) { var book = eval("("+e.responseText+")") // evaluate the JSON $("book" + book.id + "_title").innerHTML = book.title } <g:javascript> <g:remoteLink action="test" update="foo" onSuccess="updateBook(e)"> Update Book </g:remoteLink> <g:set var="bookId">book${book.id}</g:set> <div id="${bookId}"> <div id="${bookId}_title">The Stand</div> </div>
JSON实现的数据为中心的Ajax
数据为中心的Ajax通常是在客户端以编程的方式处理返回结果和内容更新。在Grails中,一个JSON响应通常是使用JSON编组(marshalling)来处理的:import grails.converters.JSONdef showBook() {
def b = Book.get(params.id) render b as JSON
}<g:javascript> function updateBook(e) { var book = eval("("+e.responseText+")") // evaluate the JSON $("book" + book.id + "_title").innerHTML = book.title } <g:javascript> <g:remoteLink action="test" update="foo" onSuccess="updateBook(e)"> 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(e) { var xml = e.responseXML var id = xml.getElementsByTagName("book").getAttribute("id") $("book" + id + "_title") = xml.getElementsByTagName("title")[0].textContent } <g:javascript> <g:remoteLink action="test" update="foo" onSuccess="updateBook(e)"> Update Book </g:remoteLink> <g:set var="bookId">book${book.id}</g:set> <div id="${bookId}"> <div id="${bookId}_title">The Stand</div> </div>
XML实现的数据为中心的Ajax
在服务器端,处理XML是很容易的:import grails.converters.XMLdef showBook() {
def b = Book.get(params.id) render b as XML
}<g:javascript> function updateBook(e) { var xml = e.responseXML var id = xml.getElementsByTagName("book").getAttribute("id") $("book" + id + "_title") = xml.getElementsByTagName("title")[0].textContent } <g:javascript> <g:remoteLink action="test" update="foo" onSuccess="updateBook(e)"> 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')='${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.
JavaScript实现的脚本为中心的Ajax
脚本为中心的Ajax主要在客户端处理从后台返回的JavaScript,并且运行它们。比如如下示例:def showBook() {
def b = Book.get(params.id) response.contentType = "text/javascript"
String title = b.title.encodeAsJavascript()
render "$('book${b.id}_title')='${title}'"
}contentType为text/javascript。如果你在客户端使用的是Prototype,它会根据contentType的设置而自动执行。很明显,这种情况下,有一个很严重的前提,那就是你必须认可服务器端将依赖客户端的API,这也是Rails(Grails就是受其启发而来的--译者注)存在RJS的一个原因。尽管Grails并没有类似于RJS的功能,但是有一个动态JavaScript插件提供了类似的功能。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]
}
}响应Ajax和非Ajax请求
使用同一个控制器和操作来处理Ajax和非Ajax请求是非常直截了当的。Grails为HttpServletRequest增加了一个isXhr()用以标识是否为Ajax请求。比如你可以使用模板为Ajax请求渲染一个页面片段,否则就渲染一个完整的页面:def listBooks() {
def books = Book.list(params)
if (request.xhr) {
render template: "bookTable", model: [books: books]
} else {
render view: "list", model: [books: books]
}
}
