AjaxAnywhere使用一个名为aa.js的Javascript文件来处理客户端的全部Ajax操作,包括初始化XMLHttpRequest、获取表单内容、发送Ajax请求、执行回调函数等。aa.js也是使用AjaxAnywhere之前必须了解的,至少应该知道其经常用到的API。Ajax Anywhere的官方网站提供了相应的Javascript Document,方便快速查找和了解这些API。
aa.js中定义了一个AjaxAnywhere对象,针对Ajax的各种操作被抽象成AjaxAnywhere对象的方法,通过这些对象方法完成所需的操作。必要的时候,可以重载这些方法,以便满足个性化的需求。在aa.js文件的末端,AjaxAnywhere对象使用默认的构造方法完成对象实例化。
ajaxAnywhere = new AjaxAnywhere();
ajaxAnywhere.bindById();
所以,所有引用aa.js的页面都可以在Javascript代码段中使用AjaxAnywhere对象的实例ajaxAnywhere。
当AjaxAnywhere初始化的时候,它在默认的构造函数中完成XMLHttpRequest对象的创建,并保存在AjaxAnywhere对象属性req中。AjaxAnywhere对象默认的构造方法如例程11-23所示。
例程11-23 AjaxAnywhere对象的默认构造方法
function AjaxAnywhere() {
this.id = AjaxAnywhere.defaultInstanceName;//id,用于生成更新区域的编号等用途
this.formName = null;//页面表单名称
this.notSupported = false;//是否支持Ajax
this.delayBeforeContentUpdate = true;//在更新页面内容之前是否延迟
this.delayInMillis = 100;//延迟时间
//初始化XMLHttpRequest对象--req
if (window.XMLHttpRequest) {
this.req = new XMLHttpRequest();
} else if (window.ActiveXObject) {
try {
this.req = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try {
this.req = new ActiveXObject("Microsoft.XMLHTTP");
} catch(e1) {
this.notSupported = true;
/* XMLHTTPRequest not supported */
}
}
}
//确定浏览器是否支持Ajax
if (this.req == null || typeof this.req == "undefined")
this.notSupported = true;
}
AjaxAnywhere提供两个公共方法处理Ajax请求的发送:submitAJAX(additionalPost Data, submitButton)和getAJAX(url, zonesToRefresh)。前者用于发送POST类型的Ajax请求,后者则用于发送GET类型的请求,可以直接在Web页面的表单中或者Javascript代码段直接使用ajaxAnywhere.submitAJAX(additionalPostData, submitButton)或者ajaxAny where. getAJAX (url, zonesToRefresh)向服务器发送Ajax请求。
ajaxAnywhere对象的属性formName保存Ajax所指向的表单名称,只要为其指定表单名称(如果未指定,则默认是Web页面中的第一个表单),submitAJAX(additionalPost Data,submitButton)就能够自动获取指定表单的全部表单域及其值,组成parameterName1 =value1 ¶meterName2=value2字符串,这个过程由私有(private)方法preparePostData (submitButton)完成;preparePostData(submitButton)方法遍历表单中的全部元素,将下拉列表、文本框、复选框、单选框等的值自动加入字符串中;submitAJAX方法的参数additionalPostData代表除了表单域值外还要发送给服务器的内容,submitButton则是代表发送操作是否由提交按钮触发的。SubmitAJAX()方法的代码如例程11-24所示。
例程11-24 submitAJAX() 方法发送POST类型请求
AjaxAnywhere.prototype.submitAJAX = function(additionalPostData, submitButton) {
//如果浏览器不支持Ajax
if (this.notSupported)
return this.onSubmitAjaxNotSupported(additionalPostData);
//附加参数为空
if (additionalPostData == null || typeof additionalPostData == "undefined")
additionalPostData = "";
//id绑定
this.bindById();
//获取当前表单对象
var form = this.findForm();
//获取表单的action,确定表单提交目标的url
var actionAttrNode = form.attributes.getNamedItem("action");
var url = actionAttrNode == null?null:actionAttrNode.nodeValue;
//如果表单action未设置,则url为当前页面
if ((url == null) || (url == ""))
url = location.href;
//确定请求成功后要重载刷新的页面区域
var zones = this.getZonesToReload(url, submitButton);
//如果未设置重载刷新区域,则刷新整个页面
if (zones == null) {
if (typeof form.submit_old == "undefined")
form.submit();
else
form.submit_old();
return;
}
//放弃上一次未完成的请求
this.dropPreviousRequest();
//设置请求参数,发送类型为POST,请求为异步方式
this.req.open("POST", url, true);
this.req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
this.req.setRequestHeader("Accept", "text/xml");
//确定要发送给服务器的内容
var postData = this.preparePostData(submitButton);
//已设置要重载刷新的区域,将区域名称附加在发送内容中
if (zones != "")
postData = '&aazones=' + encodeURIComponent(zones) + "&" + postData + "&" + additionalPostData;
else
postData += "&" + additionalPostData;
//发送Ajax请求
this.sendPreparedRequest(postData);
}
显然,如果使用AjaxAnywhere自定义标签为Web页面划分指定了刷新区域,则submitAJAX()方法也会将其包含在参数中发送到服务器端。
相对的,getAJAX(url,zoneToRefresh)方法则比较简单。它获取Ajax请求的目标URL,将需要发送的参数附着在这个URL后面,然后调用XMLHttpRequest对象的open()和send()方法将其发送除去。getAJAX(url,zoneToRefresh)方法的代码如例程11-25所示。
例程11-25 getAJAX()方法发送GET类型请求
AjaxAnywhere.prototype.getAJAX = function(url, zonesToRefresh) {
//如果浏览器不支持Ajax,则刷新整个页面
if (this.notSupported)
return this.onGetAjaxNotSupported(url);
//id绑定
this.bindById();
//如果为设置刷新区域,则刷新整个页面
if (zonesToRefresh == null || typeof zonesToRefresh == "undefined")
zonesToRefresh = "";
var urlDependentZones = this.getZonesToReload(url);
if (urlDependentZones == null) {
location.href = url;
return;
}
//已经设置多个刷新区域
if (urlDependentZones.length != 0)
zonesToRefresh += "," + urlDependentZones;
//放弃上一次未完成的额请求
this.dropPreviousRequest();
//确定请求附加的参数
url += (url.indexOf("?") != -1) ? "&" : "?";
url += "aa_rand=" + Math.random();
// avoid caching
if (zonesToRefresh != null && zonesToRefresh != "")
url += '&aazones=' + encodeURIComponent(zonesToRefresh);
//设置请求参数,发送类型为GET,响应结果为XML文档
this.req.open("GET", url, true);
this.req.setRequestHeader("Accept", "text/xml");
//发送Ajax请求
this.sendPreparedRequest("");
}
与一般的Ajax应用程序一样,AjaxAnywhere更新页面的操作仍然由回调函数完成,这也是Ajax的机制所定义的。应该注意到,submitAJAX()方法和getAJAX()方法的后面都调用了一个sendPreparedRequest()的方法,只是两者传入的参数内容有所不同而已。AjaxAnywhere对象在该方法中设置必要的请求头信息,为XMLHttpRequest对象指定回调函数,然后才将Ajax请求发送出去。setPreparedRequest()方法如例程11-26所示。
例程11-26 setPreparedRequest()方法
AjaxAnywhere.prototype.sendPreparedRequest = function (postData) {
//确定Ajax请求回调函数
var callbackKey = this.id + "_callbackFunction";
if (typeof window[callbackKey] == "undefined")
window[callbackKey] = new Function("AjaxAnywhere.findInstance (\"" + this.id + "\").callback(); ");
this.req.onreadystatechange = window[callbackKey];
//显示请求正在处理的提示信息
this.showLoadingMessage();
//设置请求头参数,当前请求为aaxml请求
this.req.setRequestHeader("aaxmlrequest", "true");
//发送请求
this.req.send(postData);
}
从这个方法可以看出,如果未在Web页面中定义相应的回调函数,则AjaxAnywhere将使用默认的回调函数。
AjaxAnywhere统一使用XML文档来组织服务器返回的响应数据。这个文档采用UTF-8的编码方式,例程11-22所配置的AAFilter也采用UTF-8的方式从请求中获取请求数据。AjaxAnywhere支持页面区域内容刷新、URL跳转、脚本、图像等处理功能,这些处理功能的信息都包含在这个XML文档中。当客户端发送的Ajax请求被正常执行后,AjaxAnywhere即将返回的响应数据组织为符合例程11-27所示文档类型定义文件DTD所规定范式的XML文档。
例程11-27 AjaxAnywhere返回的XML文档的文档类型定义DTD
<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT zones ( document*, zone*, script*, exception*, redirect*, image* ) >
<!ELEMENT document (#PCDATA)>
<!ELEMENT zone ( #PCDATA ) >
<!ATTLIST zone name NMTOKEN #REQUIRED >
<!ELEMENT script ( #PCDATA ) >
<!ELEMENT exception ( #PCDATA ) >
<!ATTLIST exception type NMTOKEN #REQUIRED >
<!ELEMENT redirect ( #PCDATA ) >
<!ELEMENT image (#PCDATA) >
例程11-28是一个仅包含zone,script,exception和redirect节点的示范XML文档。这个文档由AjaxAnywhere的业务类自动组织,与Web应用程序的其他实体类、业务类无关。Web应用程序只需要按照传统的通信机制一样,将响应结果输出即可。
例程11-28 AjaxAnywhere返回的包含响应数据的XML文档
<?xml version="1.0" encoding="UTF-8"?>
<zones>
<zone name="stateSavingScript"/>
<zone name="countriesList">
<![CDATA[
<select size="10" name="country">
<option value=IN>India</options>
</select>
]]>
</zone>
<script>
<![CDATA[
var states = document.getElementsByName("jsf_state_64")
var trees = document.getElementsByName("jsf_tree_64")
if(states!=null)
for(var i=0;i<states.length;i++)
if(states[i].tagName.toLowerCase()=="input") {
states[i].values="null";
}
if(trees!=null)
for(var i=0;i<trees.length;i++)
if(trees[i].tagName.toLowerCase()=="input")
trees[i].values="null";
]]>
</script>
<script/>
<script/>
<exception type="org.apache.jasper.JasperException"><![CDATA[
org.apache.jasper.JasperException:AjaxAnywhere demo exception
at org.apache.jasper.servlet.JspServletWraper.service (Jsp Servlet Wrapper.java:372)
............
]]>
</exception>
<redirect><![CDATA[reditected.jsp]]></redirect>
</zones>
AjaxAnywhere默认的回调函数使用XMLHttpRequest对象的responseXML()方法来获取服务器返回的这个XML文档,使用DOM解析。如果XML文档包含document和zone元素,则按照之前设定的更新区域来更新页面中指定区域的内容。如果XML文档中包含script元素,则从元素中提取脚本,使用eval()方法执行。如果服务器响应请求的时候发生系统异常或者HTTP请求异常,则XML文档中会包含exception元素,回调函数即可从元素中获取异常信息,交给this.handleException (type, details)方法做异常处理。如果XML文档中包含redirect元素,则回调函数将其转化为“locarion.href=newURL”的方法,将页面跳转到指定的URL。如果XML文档中包含image元素,则AjaxAnywhere即从服务器获取image对象所指定的图像,将其缓存在浏览器。AjaxAnywhere默认的回调函数如例程11-29所示。
例程11-29 AjaxAnywhere对象默认的构造函数
AjaxAnywhere.prototype.callback = function() {
if (this.req.readyState == 4) {
this.onBeforeResponseProcessing();
this.hideLoadingMessage();
if (this.req.status == 200) {
if (this.req.getResponseHeader('content-type').toLowerCase(). substring(0, 8) != 'text/xml')
alert("AjaxAnywhere error : content-type in not text/xml : [" + this.req.getResponseHeader('content-type') + "]");
var docs = this.req.responseXML.getElementsByTagName("documen t");
var redirects = this.req.responseXML.getElementsByTagNa me ("redirect");
var zones = this.req.responseXML.getElementsByTagName("zon e");
var exceptions = this.req.responseXML.getElementsByTagName ("exception");
var scripts = this.req.responseXML.getElementsByTagName("scr ipt");
var images = this.req.responseXML.getElementsByTagName("imag e");
if (redirects.length != 0) {
var newURL = redirects[0].firstChild.data;
location.href = newURL;
}
if (docs.length != 0) {
var newContent = docs[0].firstChild.data;
//cleanup ressources
delete this.req;
document.close();
document.write(newContent);
document.close();
}
if (images.length != 0) {
var preLoad = new Array(images.length);
for (var i = 0; i < images.length; i++) {
var img = images[i].firstChild;
if (img != null) {
preLoad[i] = new Image();
preLoad[i].src = img.data;
}
}
if (this.delayBeforeContentUpdate) {
delay(this.delayInMillis);
}
}
if (zones.length != 0) {
for (var i = 0; i < zones.length; i++) {
var zoneNode = zones[i];
var name = zoneNode.getAttribute("name");
var fc = zoneNode.firstChild;
var html = (fc == null)?"":fc.data;
var zoneHolder = document.getElementById("aazone." + name);
if (zoneHolder != null && typeof(zoneHolder) != "undefined") {
zoneHolder.innerHTML = html;
}
}
}
if (exceptions.length != 0) {
var e = exceptions[0];
var type = e.getAttribute("type");
var stackTrace = e.firstChild.data;
this.handleException(type, stackTrace);
}
if (scripts.length != 0) {
for (var $$$$i = 0; $$$$i < scripts.length; $$$$i++) {
// use $$$$i variable to avoid collision with "i" inside user script
var script = scripts[$$$$i].firstChild;
if (script != null) {
script = script.data;
if (script.indexOf("document.write") != -1) {
this.handleException("document.write", "This script contains document.write(), which is not compatible with AjaxAnywhere : \n\n" + script);
} else {
eval(script);
}
}
}
var globals = this.getGlobalScriptsDeclarationsList(scr ipt);
if (globals != null)
for (var i in globals) {
var objName = globals[i];
try {
window[objName] = eval(objName);
} catch(e) {
}
}
}
} else {
if (this.req.status != 0)
this.handleHttpErrorCode(this.req.status);
}
this.restoreSubstitutedSubmitButtons();
this.onAfterResponseProcessing();
}
}
值得注意的是,在例程11-29所示的默认回调函数中,除了解析XML文档外,在回调函数执行业务逻辑之前和之后,还分别调用了两个可供重载的方法:this.onBeforeResp onse Processing() 和 this.onAfterResponseProcessing()。如果希望在执行回调函数更新页面内容之前处理额外的业务,则可以在适当的位置重载this.onBeforeResponseProcessing()方法。如果希望执行完回调函数更新页面内容之后,还希望继续执行其他逻辑操作,则可以在适当的位置重载this.onAfterResponseProcessing()方法。从aa.js的源码中,可以看到目前这两个方法都未执行任何操作,如例程11-30所示。
例程11-30 onBeforeResponseProcessing()和onAfterResponseProcessing()方法
/**
* Override this method to implement a custom action
*/
AjaxAnywhere.prototype.onBeforeResponseProcessing = function () {
};
/**
* Override this method to implement a custom action
*/
AjaxAnywhere.prototype.onAfterResponseProcessing = function () {
};