1. Ajax的起源和发展:花开两朵,各表一枝。话说当年微软为了让自己的web版Outlook看起来和桌面应用差不多,就把Ajax作成了IE浏览器的一个ActivX插件,然后通过JavaScript调用它进行异步的浏览器request。大家一看觉得不错,于是把它进一步标准化,产生了一个叫XMLHttpRequest的本地JavaScript对象,用来支持其他的浏览器,从而激发了Web 2.0的革命!微软在IE 7.0开始支持XMLHttpRequest作为本地对象,但是6.0里还是ActivX插件,所以在编码时还得先检测浏览器,再确定其加载方式。例如:
var req=null;
if(window.XMLHttpRequest) {
req = new XMLHttpRequest();
} else if (window.ActiveXObject) {
req = new ActiveXObject("Microsoft.XMLHTTP");
}
2. 继续上面的代码,利用回调事件onreadystatechange来触发一个函数(例子中的processRequest)对response对象进行异步处理:
req.onreadystatechange = processRequest;
req.open("GET"," http://localhost:8080/a/remote/location ", true);
req.send();
function processRequest(obj) {
alert(obj.responseXML)
}
整个过程如下:
* 浏览器调用JavaScript
* JavaScript产生XMLHttpRequest对象req
* 通过XMLHttpRequest对象req发送一个request到服务器
* 服务器返回XML Response到XMLHttpRequest对象
* XMLHttpRequest对象触发JavaScript中的回调函数processRequest
* processRequest执行产生一个alert。
3. 上述的代码显然比较繁琐,于是出现了很多Ajax框架来对一些逻辑进行封装,从简单的Prototype到全面的Dojo。grails则提供了一个灵活的配置,允许开发者自定义使用哪个Ajax库(缺省是Prototype)。
使用Prototype需要在index.gsp的定义一个g:javascript标签:
这样Prototype就会自动加载所有dependencies使之可用。
4. 在此之后,可以在.gsp中使用g:remoteLink标签在页面HTML生成一个tag,点击的时候就执行一个异步请求给对应的controller / action,然后把服务器返回的response内容加载到标签中指定的update属性对应的DOM ID中。例如:
5. 变更Ajax库:很简单,只需要在index.gsp的修改g:javascript标签即可,如:
6. 异步表单提交:使用g:formRemote标签,定义类似于g:remoteLink的参数,把需要刷新的页面部分单独做成一个gsp template,然后在gsp页面的div和controller的action中指定render该template即可。
7. before和after属性:每个Ajax标签都支持这两个属性,对应在远程调用之前和之后插入任意的JavaScript代码进行执行。例如:
update="loginBox"
name="loginForm">
...
注意:before和after不是事件的钩子,特别是after的执行是紧接着Ajax call之后,并不需要等待其返回,甚至Ajax call没有成功它也照样会执行。
8. 处理事件:在grails的Ajax标签中可以登记多个不同的事件处理项,例如:
onComplete="hideProgress();"
update="loginBox"
name="loginForm">
...
事件包括:onSuccess, onFailure, onLoaded, onComplete, onERROR_CODE。在Prototype里有总体性响应机制,可以把Ajax的事件逻辑集中化,从而可以让所有的Ajax调用都具有统一的表现特性。
9. 动画效果:可以用Scriptaculous库,然后利用Ajax事件如onComplete,配合
Effect.Appear($('album${album.id}'))
这样就会在template中增加Scriptaculous库中的Appear效果。推荐通过这种方式来实现淡入淡出等动画效果。
10. 支持Ajax的表单域:
Instant Search:
paramName="q"
url="[controller:'store', action:'search']"/>
配合grails的Searchable插件(在Compass和Lucene的基础上开发的),安装好之后在需要进行动态查询的Domain类中增加一行:
static searchable = true,即可对整个Domain类的数据进行模糊查询。如果要对Domain类中的一个属性子集甚至一个属性进行更加精准的查询,Searchable也提供了一套DSL用于映射Domain类和查询索引,例如:
class User{
static searchable = [only:['name','title']]
...
}
然后就是在controller里实现search action。如同GORM一样,Searchable提供了一整套新的方法来支持查询,包括:
search:返回一个查询结果对象,其中包含一个查询到的对象子集
searchTop:返回符合查询条件的第一个对象
searchEvery:返回符合查询条件的所有对象
countHits:返回查询到的记录数
termFreqs:返回每个索引项出现的频率
使用它们进行查询action的例子:
def search ={
def q=params.q?:null
def searchResults
if(q){
searchResults = [
albumResults:trySearch{ album.search(q,[max:10])},
q:q.encodeAsHTML()
]
}
render(template:"searchResults",model:searchResults)
}
def trySearch(Closure callable) {
try{
return callable.call()
}
catch(Exception e) {
log.debug "search error: ${e.message}",e
return []
}
}
11. 关于Ajax的性能问题:由于涉及到前后端的交互,Ajax应用需要付出性能成本。需要render的代码块越多,Ajax应用就需要处理越多的request。可以采用EJB里的一些特性如DTO(Data Transfer Object)来优化远程方法调用,可以把批量操作整合为单个的调用,经验证明,一次传输大量的数据比把它分拆为很多小的数据在性能上往往有明显的改进;此外,通过缓存技术把一部分处理转移到Ajax客户端也是行之有效的方法。