Ajax 核心API(即所謂的XMLHttpRequest)的唯一用途就是發送HTTP 請求,在Web 瀏覽器與伺服器之間進行資料交換。 Web 頁面中執行的JavaScript 程式碼,可以使用XMLHttpRequest 將該請求參數提交至伺服器端腳本,例如Servlet 或JSP 頁面。呼叫的Servlet/JSP 將發回一個回應,其中包含了一般用於不需刷新整個頁面即可更新使用者查看內容的資料。此種方法在效能和可用性方面均體現出了獨特的優勢,因為這將降低網路通訊量,而且Web UI 的使用幾乎與桌面GUI 一樣。
但是,開發這種使用者介面並不簡單,因為您必須在客戶端上使用JavaScript、在伺服器端上使用Java(或等效語言)實作資料交換、驗證以及處理。然而,在許多情況下,考慮到將會由此獲得的益處,付出額外精力建立一個基於Ajax 的介面是值得的。
在本文中,我將介紹一種用於在Ajax 用戶端和伺服器之間傳輸資料的主要方法,並比較傳統Web 應用程式模型與該Ajax 模型的不同點。此外,文中也將探討伺服器端與客戶端處理資料的技巧。
首先,您將了解如何在用戶端使用JavaScript 編碼請求物件的參數。您可以使用所謂的URL 編碼(Web 瀏覽器所使用的預設編碼),或可將請求參數包含在XML 文件中。伺服器將處理該請求,並傳回一個其資料也必須進行編碼的回應。本文將探討JavaScript Object Notation (JSON) 和XML,這些都是主要的回應資料格式選項。
本文的大部分內容將主要介紹Ajax 應用程式中通常使用的與XML 相關的API。在客戶端,XML API 的作用雖非常有限,但已夠用。在多數情況下,利用XMLHttpRequest 即可完成所有必要操作。此外,還可使用JavaScript 在Web 瀏覽器中分析XML 文件並串行化DOM 樹。在伺服器端,可用於處理XML 文件的API 和框架有很多種。本文將介紹如何使用針對XML 的標準Java API 來實作基本任務,該API 支援XML 模式、XPath、DOM 以及許多其他標準。
透過本文,您可以了解到在Ajax 應用程式中實現資料交換所使用的最佳技巧和最新的API。其中涉及的範例程式碼分別位於以下三個套件中:util、model 和feed。 util 套件中的類別提供了用於XML 分析、基於模式的驗證、基於XPath 的查詢、DOM 串行化以及JSON 編碼的方法。 model 套件包含的範例資料模型可用於從XML 文件進行初始化,然後再轉換至JSON 格式。 model 目錄中還有一個Schema 範例,可用於XML 驗證。 feed 套件中的類別可用於模擬資料饋送,其透過Ajax 每5 秒檢索一次來獲得訊息,以刷新Web 頁面。本文闡釋瞭如何透過終止未完成的Ajax 請求並在使用完XMLHttpRequest 物件後將其刪除,避免Web 瀏覽器的記憶體洩漏。
web 目錄中包含了JSP 和JavaScript 範例。 ajaxUtil.js 中包含了發送Ajax 請求、終止請求以及處理HTTP 錯誤的實用函數。該文件還提供了可用於XML 和URL 編碼、XML 分析以及DOM 串行化的JavaScript 實用程式。 ajaxCtrl.jsp 檔案充當Ajax 控制器,接收每一個Ajax 請求、轉發參數至資料模型,或供給處理,然後返回Ajax 回應。其餘的Web 檔案都是示範如何使用該實用方法的範例。
在用戶端建置請求
將資料傳送至Web 伺服器的最簡單方法是將請求編碼為查詢字串,該字串根據使用的HTTP 方法,既可附加至URL,也可包含在請求正文中。如果需要傳送複雜的資料結構,更好的解決方案是將資訊編碼在XML 文件中。我將在本部分中介紹這兩種方法。
編碼請求參數。開發傳統Web 應用程式時,無需擔心表單資料的編碼,因為Web 瀏覽器會在使用者提交資料時自動執行該操作。但是,在Ajax 應用程式中,您必須親自編碼請求參數。 JavaScript 提供了一個非常有用的函數escape(),該函數以%HH(其中HH 是十六進位程式碼)取代任何無法成為URL 一部分的字元。例如,任何空白字元都用%20 替換。
範例程式碼下載中提供了一個實用函數buildQueryString(),該函數可連接檢索自數組的參數,透過= 將每個參數的名稱和值相分離,並將& 字元置於每個名稱-值對之間:
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;
}
假設您要編碼以下參數:
var someParams = [
{ name:"name", 值:"John Smith" },
{ name:"email", 值:" [email protected] " },
{ name:"phone", 值: "(123) 456 7890" }
];
buildQueryString(someParams) 呼叫將產生包含以下內容的結果:
name=John%[email protected]&phone=%28123%29%20456%207890
如果希望使用GET 方法,則必須將查詢附加至URL 的? 字元之後。使用POST 時,應透過setRequestHeader() 將Content-Type 標題設為application/x-www-form-urlencoded,且必須將該查詢字串傳遞至XMLHttpRequest 的send() 方法,這會將該HTTP 請求傳送至伺服器.
建立XML 文件。利用字串透過其屬性和資料建構元素是用JavaScript 建立XML 文件最簡單的方法。如果採用這種解決方案,則需要一個實用方法來轉義&、< 、>、"、以及 字元:
function escapeXML(content) {
if (content == undefined)
return "";
if (!content.length || !content.charAt)
content = new String(content);
var result = "";
var length = content.length;
for (var i = 0; i < length; i++) {
var ch = content.charAt(i);
switch (ch) {
case &:
result += "&";
break;
case < :
result += "< ";
break;
case >:
result += ">";
break;
case ":
result += """;
break;
case \:
result += "'";
break;
default:
result += ch;
}
}
return result;
}
要讓任務更為簡單,還需要一些其他實用程式方法,例如:
function attribute(name, value) {
return " " + name + "="" + escapeXML(value) + """;
}
以下範例從一個具有以下三個屬性的物件的陣列建立一個XML 文件:symbol、shares 和paidPrice:
function buildPortfolioDoc(stocks) {
var xml = "< portfolio>";
for (var i = 0; i < stocks.length; i++) {
var stock = stocks[i];
xml += "< stock ";
xml += attribute("symbol", stock.symbol);
xml += attribute("shares", stock.shares);
xml += attribute("paidPrice", stock.paidPrice);
xml += "";
}
xml += "< /portfolio>";
return xml;
}
如果您喜好使用DOM,則可使用Web 瀏覽器的API 分析XML 和串列化DOM 樹。透過IE,您可以用新的ActiveXObject("Microsoft.XMLDOM") 建立一個空文檔。然後,可以使用loadXML() 或load() 方法分別從字串或URL 分析該XML。在使用IE 的情況下,每個節點都有一個稱為xml 的屬性,您可以利用它來獲得該節點及其所有子節點的XML 表示。因此,您可以分析XML 字串、修改DOM 樹,然後將該DOM 串列化回XML。
Firefox 和Netscape 瀏覽器可讓您使用document.implementation.createDocument(...) 建立空文檔。然後,可以使用createElement()、createTextNode()、createCDATASection() 等來建立DOM 節點。 Mozilla 瀏覽器也提供了兩個分別名為DOMParser 和XMLSerializer 的API。 DOMParser API 包含parseFromStream() 和parseFromString() 方法。 XMLSerializer 類別具有串列化DOM 樹的對應方法:serializeToStream() 和serializeToString()。
以下函數分析一個XML 字串並傳回DOM 文件:
function parse(xml) {
var dom;
try{
dom = new ActiveXObject("Microsoft.XMLDOM");
dom.async = false;
dom.loadXML(xml);
} catch (error) {
try{
var parser = new DOMParser();
dom = parser.parseFromString(xml, "text/xml");
delete parser;
} catch (error2) {
if (debug)
alert("XML parsing is not supported.");
}
}
return dom;
}
第二個函數串列化一個DOM 節點及其所有子節點,將XML 作為字串傳回:
function serialize(dom) {
var xml = dom.xml;
if (xml == undefined) {
try{
var serializer = new XMLSerializer();
xml = serializer.serializeToString(dom);
delete serializer;
} catch (error) {
if (debug)
alert("DOM serialization is not supported.");
}
}
return xml;
}
也可以使用XMLHttpRequest 作為分析程式或串列化程式。在從伺服器接收到對Ajax 請求的回應後,該回應會自動進行分析。可透過XMLHttpRequest 的responseText 和responseXML 屬性分別存取文字版本和DOM 樹。此外,在將DOM 樹傳遞至send() 方法時自動將其串列化。
發送請求。在先前的文章中,我介紹了XMLHttpRequest API 和一個實用函數sendHttpRequest(),您可以在提供下載的範例中的ajaxUtil.js 檔案中找到。此函數有四個參數(HTTP 方法、URL、一個參數數組和一個回調),可建立XMLHttpRequest 對象,設定其屬性並呼叫send() 方法。如果提供了回調參數,則非同步發送請求,並在收到回應後呼叫回調函數。否則,將同步發送請求,您可以在sendHttpRequest() 返回後即刻處理回應。
如您所見,在使用XMLHttpRequest 時必須進行一些重要選擇
將要使用的HTTP 方法(GET 或POST)
用於編碼請求參數的格式(本文前面已探討了XML 和URL 編碼)
是進行同步(等待回應)呼叫或非同步(使用回呼)呼叫回應的格式,如XML、XHTML、HTML 或JavaScript Object Notation (JSON)(本文稍後將對此進行探討)。假設您希望從數據饋送了解一些股價信息,且無需用戶幹預即可定期刷新信息。在本例中,應非同步發送HTTP 請求,這是為了在檢索資訊時不阻塞使用者介面。請求參數是一個符號數組,可在URL 中進行編碼。由於伺服器可能超載,因此您不希望在進行頻繁請求時傳送XML 文件。由於您只對最新的股價感興趣,因此應終止任何未完成的先前請求:
var ctrlURL = "ajaxCtrl.jsp";
var feedRequest = null;
function sendInfoRequest(symbols, callback) {
if (feedRequest)
abortRequest(feedRequest);
var params = new Array();
for (var i = 0; i < symbols.length; i++)
params[i] = {
name:"symbol",
value:symbols[i]
};
feedRequest = sendHttpRequest(
"GET", ctrlURL, params, callback);
}
在呼叫請求物件的abort() 方法之前,abortRequest() 函數(可在ajaxUtil.js 檔案中找到)會將onreadystatechange 屬性設為不執行任何操作的回呼。此外,刪除該請求物件以避免記憶體洩漏,這一點至關重要:
function abortRequest(request) {
function doNothing() {
}
request.onreadystatechange = doNothing;
request.abort();
delete feedRequest;
}
我們來考慮另一種情況:在傳輸要保存在資料庫中的整個用戶資料時,應同步發送請求,因為您可能不希望用戶在保存這些資料進行時對其進行修改。在這種情況下,首選XML 格式,這是因為在文件中進行物件模型編碼通常比使用許多字串參數更簡單。此外,保存資料的請求並不頻繁,伺服器可以毫無問題地處理負載。可將XML 文件編碼為參數,這樣您就可以使用EL 語法(${param.xml}) 在JSP 頁面中存取該文件了。以下就是發送在XML 文件中編碼的模型資料的函數:
function sendSaveRequest(xml) {
var params = [ { name:"xml", value:xml } ];
var saveRequest = sendHttpRequest("POST", ctrlURL, params);
if (saveRequest)
delete saveRequest;
}
如果需要復原物件模型,則也可同步傳送請求,從伺服器檢索資料。在這種情況下,伺服器應傳回JSON 回應,以便您可利用eval(loadRequest.responseText) 輕鬆將其轉換為JavaScript 物件樹:
function sendLoadRequest() {
var model = null;
var loadRequest = sendHttpRequest("GET", ctrlURL);
if (loadRequest) {
model = eval(loadRequest.responseText);
delete loadRequest;
}
return model;
}
以下兩部分介紹了通常在伺服器上對XML 文件執行的操作,以及如何回應Ajax 請求。
在伺服器端處理請求
Servlet/JSP 容器分析各個HTTP 請求並建立一個ServletRequest 實例,該實例可讓您透過getParameter() / getParameterValues() 取得請求參數,或透過getInputStream() 取得請求正文。在JSP 頁面中,也可以使用EL 語法(${param...} 和${paramValues...})來獲得這些參數。請注意,只有在Ajax 用戶端使用了類似buildQueryString() 之類的實用函數,透過application/x-www-form-urlencoded 格式來編碼資料(本文前一部分有述)的情況下,才可透過getParameter () 或${param...} 取得請求參數。如果在客戶端上將XML 文件或DOM 樹傳遞至XMLHttpRequest 的send() 方法,則必須在伺服器端使用ServletRequest 的getInputStream() 方法。
數據驗證。典型的Web 應用程式會進行許多資料驗證操作。多數可能的錯誤相當簡單,例如缺少請求參數、數字格式錯誤等等。這些錯誤通常是由於使用者忘記輸入表單元素的值或提供了無效值所引起的。 Web 框架(如JSF 和Oracle ADF Faces)非常善於處理這些使用者錯誤。在Ajax 應用程式中,這些錯誤可以在用戶端使用JavaScript 來擷取和處理。例如,您可使用isNaN(new Number(value)) 來驗證數字值是否無效。
出於安全和可靠性的考慮,應在伺服器端對資料進行重新驗證,而不應想當然地認為XML 請求格式設定正確。 XML 模式是在伺服器端驗證複雜請求的有用工具。範例程式碼下載中包含了一個名為XMLUtil 的類,它提供用於載入和使用模式文件的方法。以下程式碼段顯示如何初始化SchemaFactory:
import javax.xml.*;
import javax.xml.validation.*;
…
protected static SchemaFactory schemaFactory;
static {
schemaFactory = SchemaFactory.newInstance(
XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaFactory.setErrorHandler(newErrorHandler());
}
The newErrorHandler() method returns a SAX error handler:
import org.xml.sax.*;
…
public static ErrorHandler newErrorHandler() {
return new ErrorHandler() {
public void warning(SAXParseException e)
throws SAXException {
Logger.global.warning(e.getMessage());
}
public void error(SAXParseException e)
throws SAXException {
throw e;
}
public void fatalError(SAXParseException e)
throws SAXException {
throw e;
}
};
}
可以使用getResourceAsStream() 來尋找並載入某個目錄中的XSD 檔案或CLASSPATH 中指定的JAR:
public static InputStream getResourceAsStream(String name)
throws IOException {
InputStream in = XMLUtil.class.getResourceAsStream(name);
if (in == null)
throw new FileNotFoundException(name);
return in;
}
然後,使用SchemaFactory 實例透過newSchema() 方法來取得Schema 物件:
import javax.xml.validation.*;
…
public static Schema newSchema(String name)
throws IOException, SAXException {
Schema schema;
InputStream in = getResourceAsStream(name);
try{
schema = schemaFactory.newSchema(new StreamSource(in));
}finally{
in.close();
}
return schema;
}
您也可以使用以下方法建立Oracle XMLSchema 物件:
import oracle.xml.parser.schema.XMLSchema;
import oracle.xml.parser.schema.XSDBuilder;
…
public static XMLSchema newOracleSchema(String name)
throws IOException, SAXException {
XMLSchema schema;
InputStream in = getResourceAsStream(name);
try{
XSDBuilder builder = new XSDBuilder();
schema = builder.build(new InputSource(in));
} catch (Exception e){
throw new SAXException(e);
}finally{
in.close();
}
return schema;
}
接下來,您需要建立一個DocumentBuilderFactory。如果在CLASSPATH 中找到的是JAXP 1.1 實現,則由JAXP 1.2 定義的setSchema() 方法可能會拋出UnsupportedOperationException,此時需要將JAXP 1.1 實作替換為Java SE 5.0 的JAXP 1.2 實作。在這種情況下,您仍可使用newOracleSchema() 建立模式對象,並透過setAttribute()方法進行設定:
import javax.xml.parsers.*;
import oracle.xml.jaxp.JXDocumentBuilderFactory;
…
public static DocumentBuilderFactory newParserFactory(
String schemaName) throws IOException, SAXException {
DocumentBuilderFactory parserFactory
= DocumentBuilderFactory.newInstance();
try{
parserFactory.setSchema(newSchema(schemaName));
} catch (UnsupportedOperationException e) {
if (parserFactory instanceof JXDocumentBuilderFactory) {
parserFactory.setAttribute(
JXDocumentBuilderFactory.SCHEMA_OBJECT,
newOracleSchema(schemaName));
}
}
return parserFactory;
}
然後,建立一個DocumentBuilder 對象,並使用該物件驗證和分析XML 文件:
import javax.xml.parsers.*;
…
public static DocumentBuilder newParser(
DocumentBuilderFactory parserFactory)
throws ParserConfigurationException {
DocumentBuilder parser = parserFactory.newDocumentBuilder();
parser.setErrorHandler(newErrorHandler());
return parser;
};
假設您要根據portfolio.xsd 模式範例驗證XML 文件:
< xsd:schema xmlns:xsd=" http://www.w3.org/2001/XMLSchema ">
< xsd:element name="portfolio" type="portfolioType "
< xsd:complexType name="portfolioType">
< xsd:sequence>
< xsd:element name="stock"
minOccurs="0" maxOccurs="unbounded">
< xsd:complexType>
< xsd:attribute name="symbol"
type="xsd:string" use="required"/>
< xsd:attribute name="shares"
type="xsd:positiveInteger" use="required"/>
< xsd:attribute name="paidPrice"
type="xsd:decimal" use="required"/>
< /xsd:complexType>
< /xsd:element>
< /xsd:sequence>
< /xsd:complexType>
< /xsd:schema>
DataModel 類別的parsePortfolioDoc() 方法使用XMLUtil 驗證和分析xml 參數,並傳回一個DOM 文件:
private static final String SCHEMA_NAME
= "/ajaxapp/model/portfolio.xsd";
private static DocumentBuilderFactory parserFactory;
private static Document parsePortfolioDoc(String xml)
throws IOException, SAXException,
ParserConfigurationException {
synchronized (DataModel.class) {
if (parserFactory == null)
parserFactory = XMLUtil.newParserFactory(SCHEMA_NAME);
}
DocumentBuilder parser = XMLUtil.newParser(parserFactory);
InputSource in = new InputSource(new StringReader(xml));
return parser.parse(in);
}
現在,您擁有了一個DOM 樹,接下來要取得形成DOM 節點所需的資料。
提取所需資訊。您可以使用DOM API 或查詢語言(如XQuery 或XPath)來瀏覽DOM 樹。 Java 為XPath 提供了標準的API,後面會用到。 XMLUtil 類別建立一個具有newXPath() 方法的XPathFactory:
import javax.xml.xpath.*;
…
protected static XPathFactory xpathFactory;
static {
xpathFactory = XPathFactory.newInstance();
}
public static XPath newXPath() {
return xpathFactory.newXPath();
}
以下方法在給定的上下文中求解XPath 表達式,傳回結果值:
import javax.xml.xpath.*;
import org.w3c.dom.*;
…
public static String evalToString(String expression,
Object context) throws XPathExpressionException {
return (String) newXPath().evaluate(expression, context,
XPathConstants.STRING);
}
public static boolean evalToBoolean(String expression,
Object context) throws XPathExpressionException {
return ((Boolean) newXPath().evaluate(expression, context,
XPathConstants.BOOLEAN)).booleanValue();
}
public static double evalToNumber(String expression,
Object context) throws XPathExpressionException {
return ((Double) newXPath().evaluate(expression, context,
XPathConstants.NUMBER)).doubleValue();
}
public static Node evalToNode(String expression,
Object context) throws XPathExpressionException {
return (Node) newXPath().evaluate(expression, context,
XPathConstants.NODE);
}
public static NodeList evalToNodeList(String expression,
Object context) throws XPathExpressionException {
return (NodeList) newXPath().evaluate(expression, context,
XPathConstants.NODESET);
}
DataModel 的setData() 方法使用XPath 求解方法從組合XML 文件中提取資訊:
public synchronized void setData(String xml)
throws IOException, SAXException,
ParserConfigurationException,
XPathExpressionException {
try{
ArrayList stockList
= new ArrayList();
Document doc = parsePortfolioDoc(xml);
NodeList nodeList = XMLUtil.evalToNodeList(
"/portfolio/stock", doc);
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
StockBean stock = new StockBean();
stock.setSymbol(
XMLUtil.evalToString("@symbol", node));
stock.setShares(
(int) XMLUtil.evalToNumber("@shares", node));
stock.setPaidPrice(
XMLUtil.evalToNumber("@paidPrice", node));
stockList.add(stock);
}
this.stockList = stockList;
} catch (Exception e){
Logger.global.logp(Level.SEVERE, "DataModel", "setData",
e.getMessage(), e);
}
}
一旦伺服器端的資料模型中具備了數據,就可根據應用程式的要求進行處理了。然後,您必須回應Ajax 請求。
在伺服器端產生回應
將HTML 作為Ajax 請求的回應而傳回是一種最簡單的解決方案,這是因為您可以使用JSP 語法建立標記,而Ajax 用戶端只需使用< div> 或< span> 元素的innerHTML 屬性在頁面某處插入HTML。但是,向Ajax 用戶端傳回不帶任何表示標記的資料則更為有效。您可以使用XML 格式或JSON。
產生XML 回應。 Java EE 提供了許多建立XML 文件的選項:可透過JSP 產生、透過JAXB 從物件樹建立、或利用javax.xml.transform 產生。以下範例中的轉換程式將串列化一個DOM 樹:
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
…
public static TransformerFactory serializerFctory;
static {
serializerFctory = TransformerFactory.newInstance();
}
public static void serialize(Node node, OutputStream out)
throws TransformerException {
Transformer serializer = serializerFctory.newTransformer();
Properties serializerProps = new Properties();
serializerProps.put(OutputKeys.METHOD, "xml");
serializer.setOutputProperties(serializerProps);
Source source = new DOMSource(node);
Result result = new StreamResult(out);
serializer.transform(source, result);
}
有這麼多可在伺服器端產生XML 的標準選項和開發來源框架,您唯一要做的就是選擇一個適合您的選項。但是,在客戶端上,由於只能使用DOM 來分析XML,因此情況非常不同。某些瀏覽器也支援XPath 和XSLT。
在先前的Ajax 文章中,您學習如何透過JSP 產生XML,然後在客戶端上利用JavaScript 和DOM 進行分析。另一個解決方案是使用JSON 而非XML 作為回應Ajax 請求的資料格式。如前所述,JSON 字串可透過eval() 函數轉換為JavaScript 物件樹。較之利用JavaScript 從DOM 樹提取資訊而言,這更為簡單。您所需的就是一個在伺服器端產生JSON 的良好實用類別。
JSON 編碼。 JSONEncoder 類別提供了編碼文字、物件和陣列的方法。結果儲存在java.lang.StringBuilder 中:
package ajaxapp.util;
public class JSONEncoder {
private StringBuilder buf;
public JSONEncoder() {
buf = new StringBuilder();
}
...
}
character() 方法編碼單一字元:
public void character(char ch) {
switch (ch) {
case \:
case \":
case \ :
buf.append( \ );
buf.append(ch);
break;
case :
buf.append( \ );
buf.append( );
break;
case
:
buf.append( \ );
buf.append(
);
break;
case
:
buf.append( \ );
buf.append(
);
break;
default:
if (ch >= 32 && ch < 128)
buf.append(ch);
else{
buf.append( \ );
buf.append(u);
for (int j = 12; j >= 0; j-=4) {
int k = (((int) ch) >> j) & 0x0f;
int c = k < 10 ? + k :a + k - 10;
buf.append((char) c);
}
}
}
}
string() 方法編碼整個字串:
public void string(String str) {
int length = str.length();
for (int i = 0; i < length; i++)
character(str.charAt(i));
}
literal() 方法編碼JavaScript 文字:
public void literal(Object value) {
if (value instanceof String) {
buf.append(");
string((String) value);
buf.append(");
} else if (value instanceof Character) {
buf.append(\);
character(((Character) value).charValue());
buf.append(\);
} else
buf.append(value.toString());
}
comma() 方法附加一個逗號字元:
private void comma() {
buf.append(,);
}
deleteLastComma() 方法將移除緩衝區末端最後一個逗號字元(如果有的話):
private void deleteLastComma() {
if (buf.length() > 0)
if (buf.charAt(buf.length()-1) == ,)
buf.deleteCharAt(buf.length()-1);
}
startObject() 方法附加一個{ 字符,用來表示一個JavaScript 物件的開始:
public void startObject() {
buf.append({);
}
property() 方法編碼JavaScript 屬性:
public void property(String name, Object value) {
buf.append(name);
buf.append(:);
literal(value);
comma();
}
endObject() 方法附加一個} 字符,用來表示一個JavaScript 物件的結束:
public void endObject() {
deleteLastComma();
buf.append(});
comma();
}
startArray() 方法附加一個[ 字符,用來表示一個JavaScript 數組的開始:
public void startArray() {
buf.append([);
}
element() 方法編碼JavaScript 陣列的元素:
public void element(Object value) {
literal(value);
comma();
}
endArray() 方法附加一個] 字符,用來表示一個JavaScript 數組的結束:
public void endArray() {
deleteLastComma();
buf.append(]);
comma();
}
toString() 方法傳回JSON 字串:
public String toString() {
deleteLastComma();
return buf.toString();
}
clear() 方法清空緩衝區:
public void clear() {
buf.setLength(0);
}
DataModel 使用JSONEncoder 類別來編碼其維護的資料:
public synchronized String getData() {
JSONEncoder json = new JSONEncoder();
json.startArray();
for (int i = 0; i < stockList.size(); i++) {
StockBean stock = stockList.get(i);
json.startObject();
json.property("symbol", stock.getSymbol());
json.property("shares", stock.getShares());
json.property("paidPrice", stock.getPaidPrice());
json.endObject();
}
json.endArray();
return json.toString();
}
如果提供了xml 請求參數,則ajaxCtrl.jsp 頁面將設定模型的資料。否則,頁面會使用${dataModel.data} EL 表達式輸出getData() 傳回的JSON 字串:
< %@ taglib prefix="c" uri=" http://java.sun.com/jsp/jstl /core " %>
…
< jsp:useBean id="dataModel" scope="session"
class="ajaxapp.model.DataModel" />
< c:choose>
…
< c:when test="${!empty param.xml}">
< c:set target="${dataModel}"
property="data"
value="${param.xml}" />
< /c:when>
< c:otherwise>
${dataModel.data}
< /c:otherwise>
< /c:choose>
這個作業並未完成,因為Ajax 客戶端必須處理JSON 資料。
在用戶端處理回應
在典型的Web 應用程式中,您使用JSP、Web 框架和標記庫在伺服器端產生內容。 Ajax 應用程式非常適合這種情況,因為Web 框架(如JavaServer Faces 和Oracle ADF Faces)在建立Ajax 應用程式時非常有用。然而,Ajax 和非Ajax 應用程式之間仍然存在著明顯不同。使用Ajax 時,您必須在客戶端處理數據,並用JavaScript 動態產生內容來向使用者提供資料。
如果使用JSON 格式進行資料轉換,則使用JavaScript 提供的eval() 函數將文字轉換為物件樹非常容易。如果喜歡使用XML,則還需要執行許多其他操作,但這種格式也有其自身的優勢。例如,許多類型的客戶端可以使用XML,而JSON 只在JavaScript 環境中易於分析。此外,在使用XML 的情況下,可以更快地發現並修正錯誤,從而縮短了偵錯時間。
使用JavaScript 存取DOM 樹。 JavaScript 的DOM API 非常類似Java 的org.w3c.dom 程式包。主要的區別在於對屬性的存取。在JavaScript 中可直接存取屬性,而Java 將屬性視為私有,您需要透過get 和set 方法進行存取。例如,您可透過dom.documentElement 取得文件的根元素。
DOM 是一種低階的API,利用它可以存取已分析文件的結構。例如,在大多數情況下您都想忽略註釋,並可能不願意擁有相鄰的文字節點。來看下面這個簡單範例:
var xml = "< element>da< !--comment-->ta&"
+ "< ![CDATA[cdata< /element>";
您可以使用先前介紹的實用函數來分析上述XML 字串:
var dom = parse(xml);
您可以在ajaxUtil.js 中找到parse() 函數的程式碼;本例中,該函數傳回一個DOM 樹,其根元素包含一個文字節點,其後是一條註解、另一個文字節點和一個字元資料節點。如果希望包含的文字不帶註釋,則必須迭代元素的子元素,連接文字和字元資料節點的值(其類型分別為3 和4):
var element = dom.documentElement;
var childNodes = element.childNodes;
var text = "";
for (var i = 0; i < childNodes.length; i++)
if (childNodes[i].nodeValue) {
var type = childNodes[i].nodeType;
if (type == 3 || type == 4)
text += childNodes[i].nodeValue;
}
使用DOM 時,應建立一個小的實用函數集,以避免處理上述這些低階細節。
使用JavaScript 產生動態內容。 Web 瀏覽器可讓您透過文件物件存取Web 頁面的DOM 結構。例如,您可以利用document.getElementById(...) 非常輕鬆地找到一個元素。也可以建立可插入現有文件的新元素和文字節點。然而,如下所示,透過連接字串建構HTML 要更為簡單:
function updateInfo(request) {
var shares = eval(request.responseText);
var table = "< table border=1 cellpadding=5>";
table += "< tr>";
table += "< th>Symbol< /th>";
table += "< th>Trend< /th>";
table += "< th>Last Price< /th>";
table += "< /tr>";
for (var i = 0; i < shares.length; i++) {
var share = shares[i];
var symbol = escapeXML(share.symbol)
var trend = share.trend > 0 ? "+" : "-";
var lastPrice = new Number(share.lastPrice).toFixed(2);
table += "< tr>";
table += "< td>" + symbol + "< /td>";
table += "< td>" + trend + "< /td>";
table += "< td>" + lastPrice + "< /td>";
table += "< /tr>";
}
table += "< /table>";
document.getElementById("table").innerHTML = table;
}
透過設定由getElementById() 傳回的物件的innerHTML 屬性,可將產生的HTML 插入空
元素中,例如:
< div id="table">
< /div>
本文的範例將updateInfo() 函數用作回調來處理對Ajax 請求的回應,該請求是透過ajaxLogic.js 檔案中的sendInfoRequest 發送到伺服器的。如果希望每5 秒更新一次訊息,可使用JavaScript 的setInterval() 函數:
var symbols = [ ... ];
setInterval("sendInfoRequest(symbols, updateInfo)", 5000);
一個名為DataFeed 的類別模擬伺服器端的饋送。 ajaxCtrl.jsp 頁面呼叫該饋送的getData() 方法,將回應作為JSON 字串傳回。在客戶端,updateInfo() 函數利用eval(request.responseText) 對此JSON 字串進行分析,如上述程式碼範例所示。