添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Oracle Account

Manage your account and access personalized content. Sign up for an Oracle Account

Sign in to Cloud

Access your cloud dashboard, manage orders, and more. Sign up for a free trial

2006 年 2 月发表

异步 JavaScript 和 XML (Ajax) 是一个新名词,它表示可以在所有主要的浏览器上使用的一组成熟的 API、技术和标准。除了 HTML/XHTML、CSS、JavaScript、XML 和 DOM 之外,所谓的 Ajax 应用程序使用了一个名为 XMLHttpRequest 的 API,它使您能够请求 URL,而无需刷新 Web 浏览器的当前页面。使这个 API 特别有趣的是它能够异步发送 HTTP 请求,这意味着用户无需等待响应。您将提供一个用 JavaScript 编写的回调函数,当浏览器获得响应时将调用该函数,而响应可能是一个 XML 文档,可以通过 DOM 来访问其内容。这些特性的组合使您能够构建高交互性的 Web 应用程序(例如 Google Maps 或 Google Suggest)。

即使 Ajax 代码在客户端上运行,您仍然需要一种服务器端技术来支持 Web 页面。JavaServer Faces (JSF) 和 ADF Faces(Oracle 将其作为开放源代码捐赠给了 Apache Foundation)是构建基于 Ajax 的用户界面的理想选择,因为这两个框架支持 JavaScript 并能够在服务器上处理表单数据。 在本文中,我将介绍如何将 Ajax 与 JSF 和 ADF Faces 框架的现有组件结合使用,以及如何借助 Oracle JDeveloper 10 g 来基于 Ajax、JSF 和 ADF Faces 创建 Web 应用程序。本文从简要讨论一个 Web 应用程序(用于演示 Ajax-ADF Faces 和 Ajax-JSF 集成技巧)开始。(该应用程序的支持 bean 没什么特别的,但您必须快速浏览一下它们,以便您能够理解本文的示例。)然后,您将了解如何构建一个简单的 Ajax 控制器和为 Ajax 请求生成响应 XML 的 JSP 页面。在此之后,您将看到 XMLHttpRequest API 的一个完整的概述(包含代码示例和使用方式)。本文的大量篇幅将着重介绍如何使用 JDeveloper 构建 Web 用户界面。然后您将看到与在客户端上运行的 JavaScript 代码相关的更多详细信息。您将了解如何将 JavaScript 与 JSF 和 ADF Faces 结合使用,如何调用 Ajax 控制器以及如何实现 Ajax 回调。

开发应用程序逻辑

假设您需要一个智能的 Web 邮件客户端,它能够记住您的通信人的电子邮件地址。您可能将以诸如 "Hi John" 或 "Hello John" 或简单地以 "John" 之类的消息开头。如果您已经发送了一封电子邮件给 John,那么应用程序将记住 John 的电子邮件地址,并将自动填写 TO、CC 和 BCC 域。您可能还想打开已发送的消息,以便能够重看或重发它。因此,用户界面应当有一个包含已发送消息的下拉列表。下面是该应用程序的外观:

支持 Bean 和 JSF 配置。 本文的示例应用程序需要一些 JavaBean 组件: MessageBean SessionBean 。前一个 bean 封装一条消息的数据。后一个 bean 位于 JSP 会话作用域中,并维护已发送消息的列表。每一个 MessageBean 实例保持对一个 SessionBean 对象的引用,该对象用在一个名为 sendAction() 的 JSF 动作方法中:

// MessageBean.java package jsfajax; import java.io.Serializable; public class MessageBean implements Serializable { private SessionBean ssnBean; private String msgSubject, msgContent, helloName; private String toAddr, ccAddr, bccAddr; public SessionBean getSsnBean() { return ssnBean; public void setSsnBean(SessionBean ssnBean) { this.ssnBean = ssnBean; public String getMsgSubject() { return msgSubject; public void setMsgSubject(String msgSubject) { this.msgSubject = msgSubject; public String sendAction() { ssnBean.sendMsg(this); return "sent"; SessionBean 类提供了 sendMsg() 方法,该方法将其 msgBean 参数加到已发送消息列表 ( msgList ) 的末尾。该方法还将创建一个 JSF SelectItem ,它被添加到另一个列表 ( itemList ) 的开头,以便用户界面能够以反序显示已发送的消息。 SessionBean 类有两个方法,它们按索引或 helloName 属性查找消息: // SessionBean.java package jsfajax; import javax.faces.model.SelectItem; import java.io.Serializable; import java.util.ArrayList; import java.util.List; public class SessionBean implements Serializable { private List msgList; private List itemList; public SessionBean() { msgList = new ArrayList(); itemList = new ArrayList(); public synchronized Object[] getMsgItems() { return itemList.toArray(); public synchronized void sendMsg(MessageBean msgBean) { msgList.add(msgBean); itemList.add(0, new SelectItem( msgBean.getMsgSubject())); public synchronized MessageBean findMsgByIndex(int msgIndex) { if (msgIndex >= 0 && msgIndex < msgList.size()) return (MessageBean) msgList.get(msgIndex); return null; public synchronized MessageBean findMsgByName(String helloName) { for (int i = msgList.size()-1; i >= 0; i--) { MessageBean msgBean = (MessageBean) msgList.get(i); if (msgBean.getHelloName().equals(helloName)) return msgBean; return null; 如果您想从一个不包含脚本的 JSP 页面中调用 findMsgByIndex() findMsgByName() ,那么您可以构建一个定制标记,该标记将作为一个属性接收该方法的参数,并将返回值存储在 JSP 请求作用域中。创建标记文件是实现定制标记的最容易方式: <%-- findMsg.tag --%> <%@ attribute name="msgIndex" required="false" %> <%@ attribute name="helloName" required="false" %> <jsp:useBean id="ssnBean" scope="session" class="jsfajax.SessionBean"<//> jsfajax.MessageBean msgBean = null; String msgIndex = (String) jspContext.getAttribute("msgIndex"); String helloName = (String) jspContext.getAttribute("helloName"); if (msgIndex != null && msgIndex.length() > 0) msgBean = ssnBean.findMsgByIndex( Integer.valueOf(msgIndex).intValue()); else if (helloName != null && helloName.length() > 0) msgBean = ssnBean.findMsgByName(helloName); if (msgBean != null) request.setAttribute("msgBean", msgBean); (在先前的一篇 OTN 文章(“ Web 应用程序中的可重用性 ”)中,我介绍了如何使用 JDeveloper 向导创建支持 bean 和标记文件。) 必须在 faces-config.xml 文件中配置示例应用程序的两个 bean、导航规则和其他 JSF 设置: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN" "http://java.sun.com/dtd/web-facesconfig_1_1.dtd"> <faces-config> <application> <default-render-kit-id>oracle.adf.core</default-render-kit-id> <locale-config> <default-locale>en</default-locale> </locale-config> </application> <managed-bean> <managed-bean-name>ssnBean</managed-bean-name> <managed-bean-class>jsfajax.SessionBean</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> <managed-bean> <managed-bean-name>msgBean</managed-bean-name> <managed-bean-class>jsfajax.MessageBean</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> <managed-property> <property-name>ssnBean</property-name> <value>#{sessionScope.ssnBean}</value> </managed-property> </managed-bean> <navigation-rule> <from-view-id>/ajaxForm.jsp</from-view-id> <navigation-case> <from-outcome>sent</from-outcome> <to-view-id>/ajaxForm.jsp</to-view-id> <redirect/> </navigation-case> </navigation-rule> </faces-config> 当您将一个 JSF 页面添加到现有工程中或者当您创建一个使用 JSF 的新 Web 工程时,JDeveloper 将自动创建 JSF 配置文件。JDeveloper 还为编辑 faces-config.xml 文件提供了一个易于使用的界面:

Ajax 控制器和 XML 响应。 所有 JSF 请求都由一个名为 FacesServlet 的控制器来处理,它将把请求转发给 JSF 页面。为了也处理 Ajax 请求,您可以用一个阶段状态监听器扩展 JSF 控制器。当您构建定制的 JSF-Ajax 组件时这个解决方案非常好,这是因为可以将这些组件和 JSF 监听器打包在同一个 JAR 文件中。当构建 Web 应用程序时,一种更简单的解决方案是创建一个 JSP 控制器和生成 XML 响应的小 JSP 页面。以下示例控制器 (ajaxCtrl.jsp) 使用了 JSTL 和几个标记文件:

<%-- ajaxCtrl.jsp --%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> <tags:response paramMap="${pageContext.request.parameterMap}"> <tags:findMsg msgIndex="${param.msgIndex}" helloName="${param.helloName}"/> <c:if test="${msgBean != null}"> <c:if test="${!empty param.msgIndex}"> <jsp:include page="msgIndexResp.jsp"/> </c:if> <c:if test="${!empty param.helloName}"> <jsp:include page="helloNameResp.jsp"/> </c:if> </c:if> </tags:response> response.tag 文件设置 Content-Type 头 ( text/xml ),设置 Cache-Control 头 ( no-cache ),并生成一个 <response> 元素,其属性将从一个 Map 获得。当 ajaxCtrl.jsp 调用 response.tag 时, paramMap 包含了请求参数。下面是标记文件的代码: <%-- response.tag --%> <%@ attribute name="paramMap" required="true" type="java.util.Map" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); <response <c:forEach var="attr" items="${paramMap}"> ${attr.key}="<c:out value='${attr.value[0]}'/>" </c:forEach> <jsp:doBody/> </response> property.tag 文件将生成一个包含 bean 属性值的 XML 元素。 <%-- property.tag --%> <%@ attribute name="name" required="true" %> <%@ attribute name="target" required="true" type="java.lang.Object" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <${name}><c:out value="${target[name]}"/></${name}> msgIndexResp.jsp 将生成包含一个 MessageBean 实例数据的一组 XML 元素: <%-- msgIndexResp.jsp --%> <%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> <tags:property name="msgSubject" target="${msgBean}"/> <tags:property name="msgContent" target="${msgBean}"/> <tags:property name="helloName" target="${msgBean}"/> <tags:property name="toAddr" target="${msgBean}"/> <tags:property name="ccAddr" target="${msgBean}"/> <tags:property name="bccAddr" target="${msgBean}"/> 响应 XML 如下所示: <response msgIndex="..."> <msgSubject>...</msgSubject> <msgContent>...</msgContent> <helloName>...</helloName> <toAddr>...</toAddr> <ccAddr>...</ccAddr> <bccAddr>...</bccAddr> </response> helloNameResp.jsp 将生成包含电子邮件地址的三个 XML 元素: <%-- helloNameResp.jsp --%> <%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> <tags:property name="toAddr" target="${msgBean}"/> <tags:property name="ccAddr" target="${msgBean}"/> <tags:property name="bccAddr" target="${msgBean}"/> 下一部分将介绍将 HTTP 请求发送至 Ajax 控制器的 API。

了解 XMLHttpRequest API

当构建实际应用程序时,您应当寻找一个包装 XMLHttpRequest API 的 Ajax 框架,以实现额外的特性(例如错误处理、form-XML 绑定等)。框架还将隐藏浏览器的差异,这是使用 JavaScript 时的一个常见问题。不过,本文中没有使用 Ajax 框架,这是因为本文的目标之一是帮助您了解 XMLHttpRequest 的工作原理。在 示例代码下载 的 ajaxUtil.js 文件中可以找到在这一部分中介绍的代码。该文件包含一个名为 sendHttpRequest() 的函数,它有四个参数:HTTP 方法( GET POST )、Ajax 控制器的 URL、一个包含请求参数的数组以及当从服务器检索到 XML 响应时将调用的一个回调函数。

创建请求对象。 在 IE 中(它是支持该 API 的第一个浏览器),利用 ActiveXObject() 创建一个请求对象。所有其他浏览器都提供一个 XMLHttpRequest() 构造器。因此,Ajax 代码必须检测浏览器,然后使用相应的方法创建请求对象。 sendHttpRequest() 函数将执行这一操作,并使用一个局部变量来保存对所创建对象的引用: // ajaxUtil.js function sendHttpRequest(method, url, params, callback) { var request; if (window.XMLHttpRequest) request = new XMLHttpRequest(); else if (window.ActiveXObject) request = new ActiveXObject("Microsoft.XMLHTTP"); return null; 初始化请求。 XMLHttpRequest API 提供了一个名为 open() 的函数,它有至少两个参数:HTTP 方法和 URL。还可以使用一个可选的 async 参数来指定是异步还是同步发送请求。默认值是 true。(本文稍后将提供更多详细信息。) open() 函数接受两个额外的参数(用户和口令),它们用于基于 HTTP 的认证。在调用 open() 前如果您使用 GET 方法,那么必须将请求参数加到 URL 后面。 buildQueryString() 函数(您可以在 ajaxUtil.js 文件中找到它)将对请求参数编码并返回 URL 的查询字符串。在确保正确初始化 HTTP 方法、URL 和 async 标记之后, sendHttpRequest() 函数将把它们传递给 open() : // ajaxUtil.js function sendHttpRequest(method, url, params, callback) { if (method) method = method.toUpperCase(); method = "GET"; var fullURL = url; if (params && method == "GET") fullURL += "?"+ buildQueryString(params); var async = false; if (callback) async = true; request.open(method, fullURL, async); buildQueryString() 函数接受一个数组作为参数。数组的每一个元素都必须有两个属性(name 和 value),它们在一个循环中被附加到查询字符串的后面: // ajaxUtil.js function buildQueryString(params) { var query = ""; for (var i = 0; i < params.length; i++) { query += (i > 0 ? "&" : "") + escape(params[i].name) + "=" + escape(params[i].value); return query; open() 函数不发送 HTTP 请求。当您调用另一个名为 send() 的函数时将发送 HTTP 请求。在 open() send() 之间,您可以设置回调函数,还可以用 setRequestHeader() 设置请求头。 设置回调。 请求对象有一个名为 onreadystatechange 的属性,它使您能够设置回调函数,浏览器将多次调用该回调函数来指示 HTTP 请求的不同阶段。您可以通过查看另一个名为 readyState 的属性了解当前阶段,该属性可能为以下值之一:

0 - 未初始化
1 - 正在加载
2 - 已加载 >
3 - 交互
4 - 完成

readyState 最初是 0;在调用 open() 后变为 1;在调用 send() 后变为 2;这时,可以用 getResponseHeader() getAllResponseHeaders() 来获取响应头;当浏览器正在下载 XML 时 readyState 是 3;可以从 responseText 属性中获取部分结果;一旦获得了整个 XML,浏览器将解析它,并且 readyState 变为 4;如果一切正常,就可以通过 responseXML 属性访问 DOM 树;status 属性指示 HTTP 状态码,并且可以通过 statusText 属性获取一条消息。在任何时候,您都可以用 abort() 函数退出请求。

浏览器不向回调函数传递任何参数,但在大多数情况下,您希望能够访问 Request 对象,以便可以查看 readyState status 属性。然后,您一般要处理能够通过 responseXML 属性访问的 XML。您可能试图把 request 变为全局变量,以便可以从回调函数中访问它,但在这种情况下,您将不能打开多个并发请求,除非您使用了一个 XMLHttpRequest 对象的数组。一种更好的解决方案是定义一个嵌套函数 ( calbackWrapper() ),它将验证请求和调用回调函数,并将 request 对象作为一个参数传递: // ajaxUtil.js function sendHttpRequest(method, url, params, callback) { function calbackWrapper() { if (async && request.readyState == 4) { if (request.status == 200 && hasResponse(request)) callback(request); reportError(request, url, params); if (async) request.onreadystatechange = calbackWrapper; 由于有了包装器,回调函数将如下所示: function myCallback(request) { var response = request.responseXML.documentElement; 因为 responseXML 是一个 DOM Document,因此它有一个 documentElement 属性,该属性使您能够访问 DOM 树的根元素。不过,该属性可能为空或者在出现解析错误时可能不包含您期望的值。ajaxUtil.js 的 hasResponse() 函数将验证 responseXML 、它的 documentElement 属性以及根元素的标记名: // ajaxUtil.js var responseTagName = "response"; function hasResponse(request) { var responseXML = request.responseXML; if (responseXML) { var docElem = responseXML.documentElement; if (docElem) { var tagName = docElem.tagName; if (tagName == responseTagName) return true; return false; 如果出现某些错误,ajaxUtil.js 的 reportError() 函数将指出问题。例如,如果 Ajax 控制器抛出异常,您将收到 "Internal Server Error",其 HTTP 状态是 500。正如之前所提到的,即使 HTTP 状态正常 (200),也可能出现 XML 解析错误。在这种情况下,一些浏览器(如 IE)将在访问 documentElement 属性时返回空值。其他浏览器(如 FireFox)将返回一个包含关于解析错误的更详细的 XML 文档。无论浏览器如何处理错误,如果看不到导致错误的实际 XML,那么关于解析错误的任何信息都几乎是无用的。因此, reportError() 将修改浏览器的位置属性以便您可以查看错误;错误可能是内部服务器错误、解析错误或其他错误。下面是报告错误的代码: // ajaxUtil.js var debug = true; function reportError(request, url, params) { if (debug) { if (request.status != 200) { if (request.statusText) alert(request.statusText); alert("HTTP Status:" + request.status); } else alert("Response Error"); if (params) url += "?"+ buildQueryString(params); document.location = url; 发送请求。 这时,request 对象已经初始化了,并设置了回调函数(如果有的话)。如果 HTTP 方法是 POST ,您仍必须设置 Content-Type 头,并用先前介绍的 buildQueryString() 函数构建请求的正文。然后将调用由 XMLHttpRequest API 定义的 send() 函数: // ajaxUtil.js function sendHttpRequest(method, url, params, callback) { var body = null; if (method == "POST") { request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); if (params) body = buildQueryString(params); request.send(body); if (!async && (request.readyState != 4 || request.status != 200 || !hasResponse(request))) { reportError(request, url, params); return null; return request; 在异步调用的情况下(即将回调函数的参数提供给 sendHttpRequest() ),回调函数包装器将处理任何可能的错误。如果一切正常,回调函数将在从服务器检索到 XML 响应时获得请求。下面是调用 sendHttpRequest() 的方式: sendHttpRequest(method, url, params, myCallback); sendHttpRequest() 函数还可以用于同步发送请求,这意味着浏览器将等待直到获得 XML 响应。在 HTTP 请求期间,用户界面将被阻塞,用户将不能做任何事情。为了执行异步调用,将不把回调参数提供给 sendHttpRequest() 。在这种情况下将不设置回调包装器, sendHttpRequest() 函数将在返回 request 对象前处理任何可能的错误。您可以使用同样的函数(在示例代码中名为 myCallback() )来异步处理请求: myCallback(sendHttpRequest(method, url, params));

构建用户界面

在前一部分中介绍的 Ajax 代码将在本文的示例应用程序中使用,该示例应用程序模拟了一个 Web 邮件界面。在这一部分中,您将看到如何使用 JDeveloper 创建 JSF 页面。然后,您将了解如何调用 Ajax 控制器以及如何处理 XML 响应。

创建 Web 表单。 在创建一个新项目后,选择 File New 打开 New Gallery 窗口。展开左侧面板中的 Web Tier 类别,选择 JSF 。然后转至右侧的面板,选择 JSF JSP ,单击 OK

跳过向导的欢迎页面,选择 Servlet 2.4\JSP 2.0 (J2EE 1.4) 选项:

为 JSF 页面提供名称 ajaxForm.jsp:

选择绑定样式:

选择错误页面选项:

选择您想使用的 JSP 标记库,将它们从左侧面板 (Available Libraries) 移到右侧面板 (Selected Libraries) 中:

提供页面标题:

单击 Finish 。JDeveloper 将生成一个包含一个空表单的 JSF 页面。确保在 Structure 导航器(左下角)中选择 h:form 。然后选择 JSF HTML ,并在 Component Palette(右上角)中单击 Panel Grid

跳过向导的欢迎页面,并在标记为 Number of Columns 的域中输入 1:

单击 Finish 。JDeveloper 将在页面的表单内部添加一个 JSF 网格面板。确保在 Structure 导航器中选择了 h:panelGrid 。然后在 Component Palette 中选择 ADF Faces Core ,单击 SelectOneChoice

这时将打开一个新窗口。在 Value 域中输入 #{ssnBean.msgItems} 。这是到由 SessionBean 维护的消息项列表的一个绑定。下面是您将在屏幕上看到的:

单击 Common Properties 选项卡,在 Label 域中输入 " Sent Messages: "::

单击 Advanced Properties 选项卡。输入 msgList 作为 id 属性的值。然后输入 msgListChange() 作为 onchange 属性的值。这是一个 JavaScript 函数,本文稍后将介绍。

单击 OK 。JDeveloper 将把列表组件插入到您先前添加的面板中。因为该列表是一个 ADF Faces 组件,JDeveloper 还将用相应的 ADF Faces 标记替换 <html> <head> <body> 标记: <afh:html> <afh:head> <afh:body> 。选择源代码编辑器查看生成的源代码:

在任何时候,您都可以在 Property Inspector(右下角)中修改 UI 组件的属性。例如,在 Structure 导航器中选择 h:form ,并将 id 属性修改为 ajaxForm 。如果您查看源代码,您将注意到在 <h:form> 标记中添加了一个 id 属性。您可能还想用 <af:form> 标记替换 <h:form> ,这将提供额外的特性(如对文件上载的支持)。您可以简单地在源代码编辑器中修改标记的前缀,或者可以使用一个 JDeveloper 向导。当处理一个大型页面而您又不想浪费时间来查找同一个表单的起始和结束标记时,这个向导特别有用。在 Structure 导航器中右键单击 h:form ,单击 Convert ,选择 ADF Faces Core ,选择 Form ,然后单击 OK

当然,上面使用的向导适用于 JDeveloper 支持的所有库的所有标记。当您用一个标记替换另一个标记时,向导将弹出窗口让您为在旧标记中不存在而新标记需要的属性提供值。如果由于新标记不支持旧标记的任何属性而删除这些属性时还会警告您。

我们来返回 JDeveloper 主窗口。要插入一个新组件,只需将脱字符光标移到您需要的位置,然后从 Component Palette 中选择该组件。您还可以在 Structure 导航器中右键单击页面的元素,然后使用 Insert before Insert inside Insert after

利用 JDeveloper 的导航器、编辑器和向导,您可以构建整个 Web 页面并做更多的事情。JDeveloper 为构建 EJB、Web 服务、数据库、Servlets、JSP、客户端 GUI、XML 模式、UML 图等提供了支持。

将 JavaScript 与 JSF 和 ADF Faces 结合使用。 为 JSF 页面开发的 JavaScript 代码与为普通 HTML 页面开发的代码没什么区别。许多 JSF 和 ADF Faces 组件都包含让您设置在客户端上运行的 JavaScript 事件处理器的属性。示例应用程序的 ajaxForm.jsp 页面使用了三个这样的属性: <afh:body> 中的 onload <af:selectOneChoice> 中的 onchange <af:inputText> 中的 onkeyup 。它们指定了三个 JavaScript 事件处理器:当浏览器完成了页面加载时将调用 disableAutoComplete() 函数,当表单列表的值改变时将调用 msgListChange() ,每当表单的文本域拥有焦点而按下某个键时将调用 msgContentKeyup() 。将通过 <script> 将包含应用程序的 JavaScript 代码的两个文件导入到位于页面标题中的 <f:verbatim> 内部: <%-- ajaxForm.jsp --%> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %> <%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %> <%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %> <%@ taglib prefix="afh" uri="http://xmlns.oracle.com/adf/faces/html" %> <f:view> <afh:html> <afh:head title="Email"> <f:verbatim> <script src="ajaxUtil.js"> </script> <script src="ajaxLogic.js"> </script> </f:verbatim> </afh:head> <afh:body onload="disableAutoComplete(formName)"> <af:panelPage title="Email"> <af:form id="ajaxForm"> <h:panelGrid columns="1" border="0" cellspacing="5"> <h:messages globalOnly="true"<//> <af:selectOneChoice id="msgList" label="Sent Messages: " onchange="msgListChange()"> <f:selectItems value="#{ssnBean.msgItems}"<//> </af:selectOneChoice> <af:objectLegend name="required"<//> <af:panelForm> <af:inputText id="msgSubject" value="#{msgBean.msgSubject}" label="Subject:" required="true" columns="40" maximumLength="80"> <f:validateLength minimum="1" maximum="80"<//> </af:inputText> <af:inputText id="msgContent" value="#{msgBean.msgContent}" label="Content:" required="true" rows="10" columns="40" onkeyup="msgContentKeyup()"> </af:inputText> <af:panelLabelAndMessage label=" "> <af:commandButton id="command" text="Send" action="#{msgBean.sendAction}"/> </af:panelLabelAndMessage> </af:panelForm> </h:panelGrid> <af:inputHidden id="helloName" value="#{msgBean.helloName}"/> </af:form> </af:panelPage> </afh:body> </afh:html> </f:view> Web 浏览器将尝试通过提供您在过去使用表单时已经输入的旧值来帮助您填充表单。当使用一个 Web 应用程序时(特别当您还有要更新表单的 Ajax 代码时),这个特性可能很烦人。ajaxUtil.js 文件包含了一个名为 disableAutoComplete() 的函数,它会关闭表单的 autocomplete 属性: // ajaxUtil.js function disableAutoComplete(formName) { document.forms[formName].setAttribute("autocomplete", "off"); getFormElem() 函数将返回一个代表表单元素的 JavaScript 对象: // ajaxUtil.js function getFormElem(formName, elemName) { return document.forms[formName].elements[elemName]; getFormValue() 函数将获取表单元素的值: // ajaxUtil.js function getFormValue(formName, elemName) { return getFormElem(formName, elemName).value; setFormValue() 函数将设置表单元素的值: // ajaxUtil.js function setFormValue(formName, elemName, value) { getFormElem(formName, elemName).value = value; 调用 Ajax 控制器。 ajaxLogic.js 文件包含了 updateAjaxForm() 回调函数和接下来将介绍的几个其他 JavaScript 函数。 invokeAjaxCtrl() 函数有一个参数列表和一个指示异步还是同步发送 HTTP 请求的标记。先前介绍过的 sendHttpRequest() 函数将完成大部分工作: // ajaxLogic.js var httpMethod = "POST"; var ctrlURL = "ajaxCtrl.jsp"; function invokeAjaxCtrl(params, async) { if (async) sendHttpRequest(httpMethod, ctrlURL, params, updateAjaxForm); updateAjaxForm(sendHttpRequest(httpMethod, ctrlURL, params)); msgListChange() 函数是一个事件处理器,当用户改变包含已发送消息列表的选择时将由浏览器调用它。当计算消息索引参数时, msgListChange() 将考虑到表单列表以反序显示消息的事实: // ajaxLogic.js var formName = "ajaxForm"; function msgListChange() { var msgList = getFormElem(formName, "msgList"); var msgIndex = msgList.options.length - msgList.selectedIndex - 1; var params = [ { name:"msgIndex", value:msgIndex } ]; invokeAjaxCtrl(params, false); 每当表单的文本域拥有焦点而发生键盘事件时都将调用 msgContentKeyup() 函数。 extractHelloName() 函数返回的值存储在一个隐藏的域中,并还将其作为一个请求参数发送出去: // ajaxLogic.js var formName = "ajaxForm"; function msgContentKeyup() { var msgContent = getFormValue(formName, "msgContent"); var helloName = extractHelloName(msgContent); if (helloName) { helloName = helloName.toUpperCase(); var oldName = getFormValue(formName, "helloName"); if (oldName != helloName) { setFormValue(formName, "helloName", helloName); var params = [ { name:"helloName", value:helloName } ]; invokeAjaxCtrl(params, true); extractHelloName() 函数获取消息的内容,并返回第一行包含字母的最后一个单词: // ajaxLogic.js function extractHelloName(content) { var name = null; var i = 0; while (i < content.length && !isLetter(content.charAt(i))) if (i < content.length) { var j = content.indexOf("\n", i); if (j != -1) { while (j > i && !isLetter(content.charAt(j-1))) i = j - 1; while (i > 0 && isLetter(content.charAt(i-1))) name = content.substring(i, j); return name; 如果参数是一个字母, isLetter() 函数将返回 true,否则将返回 false。 // ajaxLogic.js function isLetter(c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); 实现 Ajax 回调。 Ajax 回调必须处理响应 XML。例如,响应文档包含的数据可以用于更新 Web 表单。ajaxUtil.js 的 updateFormValue() 函数有三个参数:从文档的根元素开始的 DOM 树、表单名称以及包含数据的表单元素和 XML 元素的公共名称。以下 JavaScript 函数将获取 XML 元素包含的字符数据,并设置表单元素的值: // ajaxUtil.js function updateFormValue(xmlRoot, formName, elemName) { var xmlElem = xmlRoot.getElementsByTagName(elemName); if (xmlElem && xmlElem.length > 0) { var childNodes = xmlElem[0].childNodes; if (childNodes && childNodes.length > 0) { var value = ""; for (var i = 0; i < childNodes.length; i++) if (childNodes[i].nodeValue) value += childNodes[i].nodeValue; setFormValue(formName, elemName, value); updateAjaxForm() 函数是用从响应 XML 中提取的数据来更新示例应用程序表单的回调函数: // ajaxLogic.js var formName = "ajaxForm"; function updateAjaxForm(request) { if (request == null) return; var response = request.responseXML.documentElement; var helloNameParam = response.getAttribute("helloName"); var helloNameValue = getFormValue(formName, "helloName"); if (helloNameParam && helloNameParam != helloNameValue) return; updateFormValue(response, formName, "msgSubject"); updateFormValue(response, formName, "msgContent"); updateFormValue(response, formName, "helloName"); updateFormValue(response, formName, "toAddr"); updateFormValue(response, formName, "ccAddr"); updateFormValue(response, formName, "bccAddr"); 可以发送多个并发的 Ajax 请求,它们完成的顺序可能与将它们发送给服务器的顺序不同。在示例应用程序的情况下,这意味着用户可以在一个或多个请求完成之前修改 helloName — 发送这些请求是为了获取一些早就有的名字的电子邮件地址。因此, updateAjaxForm()callback 将把当前的 helloName 值(保存在一个隐藏的域中)与(作为 <response> 标记的一个属性返回的) helloName 请求参数的值进行比较。当构建您自己的 Ajax 应用程序时,必须考虑到您可能获得次序颠倒的 XML 响应的事实。一些响应甚至可能在网络通信中丢失。

这里介绍的 Ajax 技巧使您能够构建传统 Web 模式不可能实现的 Web 应用程序。然而,Ajax 不是一种完美的解决方案,这是因为异步数据交换与同步通信相比可能更不可靠、更不可预测。当其他因素(如网络通信量最小化和用户感知的性能)更重要时,您应当考虑 Ajax。在其他情况下,您需要将 Ajax 和其他 Web 框架(如 JSF 和 ADF Faces)结合使用,这将是一个非常好的组合。您应当记住的一点是, XMLHttpRequest / Microsoft.XMLHTTP 可以用来异步发送请求,这使您能够构建可以在无需刷新整个页面条件下进行更新的可靠 JSF 组件。实际上,一些 ADF Faces 组件的确在在客户端上运行的 JavaScript 代码中使用了这个浏览器 API。

Andrei Cioroianu [ devtools@devsphere.com ] 是 Devsphere 的创始人,该公司是一个开发、集成和咨询服务的提供商。Cioroianu 撰写了许多 Java 文章,分别由 Oracle 技术网、ONJava、JavaWorld 和 Java Developer's Journal 上发表。他还与别人合著了 Java XML Programmer's Reference Professional Java XML 两书(均由 Wrox Press 出版)。

将您的意见发送给我们