3、面向 Java 開(kāi)發(fā)人員的 Ajax: 構(gòu)建動(dòng)態(tài)的 Java 應(yīng)用程序
Ajax?為更好的?Web?應(yīng)用程序鋪平了道路級(jí)別:?中級(jí)
Philip?McCarthy?,?軟件開(kāi)發(fā)顧問(wèn),?獨(dú)立咨詢(xún)顧問(wèn)
2005?年?10?月?20?日
在?Web?應(yīng)用程序開(kāi)發(fā)中,頁(yè)面重載循環(huán)是最大的一個(gè)使用障礙,對(duì)于?Java™?開(kāi)發(fā)人員來(lái)說(shuō)也是一個(gè)嚴(yán)峻的挑戰(zhàn)。在這個(gè)系列中,作者?Philip?McCarthy?介紹了一種創(chuàng)建動(dòng)態(tài)應(yīng)用程序體驗(yàn)的開(kāi)創(chuàng)性方式。Ajax(異步?javascript?和?XML)是一種編程技術(shù),它允許為基于?Java?的?Web?應(yīng)用程序把?Java?技術(shù)、XML?和?javascript?組合起來(lái),從而打破頁(yè)面重載的范式。
Ajax(即異步?javascript?
和?XML)是一種?Web?應(yīng)用程序開(kāi)發(fā)的手段,它采用客戶(hù)端腳本與?Web?服務(wù)器交換數(shù)據(jù)。所以,不必采用會(huì)中斷交互的完整頁(yè)面刷新,就可以動(dòng)態(tài)地
更新?Web?頁(yè)面。使用?Ajax,可以創(chuàng)建更加豐富、更加動(dòng)態(tài)的?Web?應(yīng)用程序用戶(hù)界面,其即時(shí)性與可用性甚至能夠接近本機(jī)桌面應(yīng)用程序。
Ajax?不是一項(xiàng)技術(shù),而更像是一個(gè)?模式?——?一種識(shí)別和描述有用的設(shè)計(jì)技術(shù)的方式。Ajax?是新穎的,因?yàn)樵S多開(kāi)發(fā)人員才剛剛開(kāi)始知道 它,但是所有實(shí)現(xiàn)?Ajax?應(yīng)用程序的組件都已經(jīng)存在若干年了。它目前受到重視是因?yàn)樵?2004?和?2005?年出現(xiàn)了一些基于?Ajax?技術(shù)的 非常棒的動(dòng)態(tài)?Web?UI,最著名的就是?Google?的?GMail?和?Maps?應(yīng)用程序,以及照片共享站點(diǎn)?Flickr。這些用戶(hù)界面具有 足夠的開(kāi)創(chuàng)性,有些開(kāi)發(fā)人員稱(chēng)之為“Web?2.0”,因此對(duì)?Ajax?應(yīng)用程序的興趣飛速上升。
在這個(gè)系列中,我將提供使用?Ajax?開(kāi)發(fā)應(yīng)用程序需要的全部工具?。在第一篇文章中,我將解釋?Ajax?背后的概念,演示為基于?Java? 的?Web?應(yīng)用程序創(chuàng)建?Ajax?界面的基本步驟。我將使用代碼示例演示讓?Ajax?應(yīng)用程序如此動(dòng)態(tài)的服務(wù)器端?Java?代碼和客戶(hù)端?javascript。最后,我將指出?Ajax?方式的一些不足,以及在創(chuàng)建?Ajax?應(yīng)用程序時(shí)應(yīng)當(dāng)考慮的一些更廣的可用性和訪(fǎng)問(wèn)性問(wèn)題。
更好的購(gòu)物車(chē)
可以用?Ajax?增強(qiáng)傳統(tǒng)的?Web?應(yīng)用程序,通過(guò)消除頁(yè)面裝入從而簡(jiǎn)化交互。為了演示這一點(diǎn),我采用一個(gè)簡(jiǎn)單的購(gòu)物車(chē)示例,在向里面添加項(xiàng)目 時(shí),它會(huì)動(dòng)態(tài)更新。這項(xiàng)技術(shù)如果整合到在線(xiàn)商店,那么用戶(hù)可以持續(xù)地瀏覽和向購(gòu)物車(chē)中添加項(xiàng)目,而不必在每次點(diǎn)擊之后都等候完整的頁(yè)面更新。雖然這篇文章 中的有些代碼特定于購(gòu)物車(chē)示例,但是演示的技術(shù)可以應(yīng)用于任何?Ajax?應(yīng)用程序。清單?1?顯示了購(gòu)物車(chē)示例使用的有關(guān)?HTML?代碼,整篇文章中 都會(huì)使用這個(gè)?HTML。
清單1.?購(gòu)物車(chē)示例的有關(guān)片斷
<!--?Table?of?products?from?store's?catalog,?one?row?per?item?-->
<th>Name</th>?<th>Description</th>?<th>Price</th>?<th></th>
...
<tr>
??<!--?Item?details?-->
??<td>Hat</td>?<td>Stylish?bowler?hat</td>?<td>$19.99</td>
??<td>
????<!--?Click?button?to?add?item?to?cart?via?Ajax?request?-->
????<button?onclick="addToCart('hat001')">Add?to?Cart</button>
??</td>
</tr>
...
<!--?Representation?of?shopping?cart,?updated?asynchronously?-->
<ul?id="cart-contents">
??<!--?List-items?will?be?added?here?for?each?item?in?the?cart?-->
??
</ul>
<!--?Total?cost?of?items?in?cart?displayed?inside?span?element?-->
Total?cost:?<span?id="total">$0.00</span>
Ajax?往返過(guò)程
Ajax?交互開(kāi)始于叫作?XMLHttpRequest?的?javascript?對(duì)象。顧名思義,它允許客戶(hù)端腳本執(zhí)行 ?HTTP?請(qǐng)求,并解析?XML?服務(wù)器響應(yīng)。Ajax?往返過(guò)程的第一步是創(chuàng)建?XMLHttpRequest?的實(shí)例。在 ?XMLHttpRequest?對(duì)象上設(shè)置請(qǐng)求使用的?HTTP?方法(GET?或?POST)以及目標(biāo)?URL。
現(xiàn)在,您還記得?Ajax?的第一個(gè)?a?是代表?異步(asynchronous)?嗎?在發(fā)送?HTTP?請(qǐng)求時(shí),不想讓瀏覽器掛著等候服務(wù)器 響應(yīng)。相反,您想讓瀏覽器繼續(xù)對(duì)用戶(hù)與頁(yè)面的交互進(jìn)行響應(yīng),并在服務(wù)器響應(yīng)到達(dá)時(shí)再進(jìn)行處理。為了實(shí)現(xiàn)這個(gè)要求,可以在?XMLHttpRequest? 上注冊(cè)一個(gè)回調(diào)函數(shù),然后異步地分派?XMLHttpRequest。然后控制就會(huì)返回瀏覽器,當(dāng)服務(wù)器響應(yīng)到達(dá)時(shí),會(huì)調(diào)用回調(diào)函數(shù)。
在?Java?Web?服務(wù)器上,請(qǐng)求同其他?HttpServletRequest?一樣到達(dá)。在解析了請(qǐng)求參數(shù)之后,servlet?調(diào)用必要的應(yīng)用程序邏輯,把響應(yīng)序列化成?XML,并把?XML?寫(xiě)入?HttpServletResponse。
回到客戶(hù)端時(shí),現(xiàn)在調(diào)用注冊(cè)在?XMLHttpRequest?上的回調(diào)函數(shù),處理服務(wù)器返回的?XML?文檔。最后,根據(jù)服務(wù)器返回的數(shù)據(jù),用?javascript?操縱頁(yè)面的?HTML?DOM,把用戶(hù)界面更新。圖?1?是?Ajax?往返過(guò)程的順序圖。
現(xiàn)在您對(duì)?Ajax?往返過(guò)程有了一個(gè)高層面的認(rèn)識(shí)。下面我將放大其中的每一步驟,進(jìn)行更詳細(xì)的觀(guān)察。如果過(guò)程中迷了路,請(qǐng)回頭看圖?1?——?由于?Ajax?方式的異步性質(zhì),所以順序并非十分簡(jiǎn)單。
分派?XMLHttpRequest
我將從?Ajax?序列的起點(diǎn)開(kāi)始:創(chuàng)建和分派來(lái)自瀏覽器的?XMLHttpRequest。不幸的是,不同的瀏覽器創(chuàng)建?XMLHttpRequest?的方法各不相同。清單?2?的?javascript?函數(shù)消除了這些依賴(lài)于瀏覽器的技巧,它可以檢測(cè)當(dāng)前瀏覽器要使用的正確方式,并返回一個(gè)可以使用的?XMLHttpRequest。最好是把它當(dāng)作輔助代碼:只要把它拷貝到?javascript?庫(kù),并在需要?XMLHttpRequest?的時(shí)候使用它就可以了。
清單?2.?創(chuàng)建跨瀏覽器的?XMLHttpRequest
/*
?*?Returns?a?new?XMLHttpRequest?object,?or?false?if?this?browser
?*?doesn't?support?it
?*/
function?newXMLHttpRequest()?{
??var?xmlreq?=?false;
??if?(window.XMLHttpRequest)?{
????//?Create?XMLHttpRequest?object?in?non-Microsoft?browsers
????xmlreq?=?new?XMLHttpRequest();
??}?else?if?(window.ActiveXObject)?{
????//?Create?XMLHttpRequest?via?MS?ActiveX
????try?{
??????//?Try?to?create?XMLHttpRequest?in?later?versions
??????//?of?Internet?Explorer
??????xmlreq?=?new?ActiveXObject("Msxml2.XMLHTTP");
????}?catch?(e1)?{
??????//?Failed?to?create?required?ActiveXObject
??????try?{
????????//?Try?version?supported?by?older?versions
????????//?of?Internet?Explorer
????????xmlreq?=?new?ActiveXObject("Microsoft.XMLHTTP");
??????}?catch?(e2)?{
????????//?Unable?to?create?an?XMLHttpRequest?with?ActiveX
??????}
????}
??}
??return?xmlreq;
}
??
?
稍后我將討論處理那些不支持?XMLHttpRequest?的瀏覽器的技術(shù)。目前,示例假設(shè)清單?2?的?newXMLHttpRequest?函數(shù)總能返回?XMLHttpRequest?實(shí)例。
返回示例的購(gòu)物車(chē)場(chǎng)景,我想要當(dāng)用戶(hù)在目錄項(xiàng)目上點(diǎn)擊?Add?to?Cart?時(shí)啟動(dòng)?Ajax?交互。名為?addToCart()?的?onclick?處理函數(shù)負(fù)責(zé)通過(guò)?Ajax?調(diào)用來(lái)更新購(gòu)物車(chē)的狀態(tài)(請(qǐng)參閱?清單?1)。正如清單?3?所示,addToCart()?需要做的第一件事是通過(guò)調(diào)用清單?2?的?newXMLHttpRequest()?函數(shù)得到?XMLHttpRequest?對(duì)象。接下來(lái),它注冊(cè)一個(gè)回調(diào)函數(shù),用來(lái)接收服務(wù)器響應(yīng)(我稍后再詳細(xì)解釋這一步;請(qǐng)參閱?清單?6)。
因?yàn)檎?qǐng)求會(huì)修改服務(wù)器上的狀態(tài),所以我將用?HTTP?POST?做這個(gè)工作。通過(guò)?POST?發(fā)送數(shù)據(jù)要求三個(gè)步驟。第一,需要打開(kāi)與要通信的服 務(wù)器資源的?POST?連接?——?在這個(gè)示例中,服務(wù)器資源是一個(gè)映射到?URL?cart.do?的?servlet。然后,我在 ?XMLHttpRequest?上設(shè)置一個(gè)頭,指明請(qǐng)求的內(nèi)容是表單?編碼的數(shù)據(jù)。最后,我用表單編碼的數(shù)據(jù)作為請(qǐng)求體發(fā)送請(qǐng)求。
清單?3?把這些步驟放在了一起。
清單?3.?分派?Add?to?Cart?XMLHttpRequest
/*
?*?Adds?an?item,?identified?by?its?product?code,?to?the?shopping?cart
?*?itemCode?-?product?code?of?the?item?to?add.
?*/
function?addToCart(itemCode)?{
??//?Obtain?an?XMLHttpRequest?instance
??var?req?=?newXMLHttpRequest();
??//?Set?the?handler?function?to?receive?callback?notifications
??//?from?the?request?object
??var?handlerFunction?=?getReadyStateHandler(req,?updateCart);
??req.onreadystatechange?=?handlerFunction;
??
??//?Open?an?HTTP?POST?connection?to?the?shopping?cart?servlet.
??//?Third?parameter?specifies?request?is?asynchronous.
??req.open("POST",?"cart.do",?true);
??//?Specify?that?the?body?of?the?request?contains?form?data
??req.setRequestHeader("Content-Type",?
???????????????????????"application/x-www-form-urlencoded");
??//?Send?form?encoded?data?stating?that?I?want?to?add?the?
??//?specified?item?to?the?cart.
??req.send("action=add&item="+itemCode);
}
這就是建立?Ajax?往返過(guò)程的第一部分,即創(chuàng)建和分派來(lái)自客戶(hù)機(jī)的?HTTP?請(qǐng)求。接下來(lái)是用來(lái)處理請(qǐng)求的?Java?servlet?代碼。
servlet?請(qǐng)求處理
用?servlet?處理?XMLHttpRequest,與處理普通的瀏覽器?HTTP?請(qǐng)求一樣。可以用 ?HttpServletRequest.getParameter()?得到在?POST?請(qǐng)求體中發(fā)送的表單編碼數(shù)據(jù)。Ajax?請(qǐng)求被放進(jìn)與來(lái)自應(yīng) 用程序的常規(guī)?Web?請(qǐng)求一樣的?HttpSession?中。對(duì)于示例購(gòu)物車(chē)場(chǎng)景來(lái)說(shuō),這很有用,因?yàn)檫@讓我可以把購(gòu)物車(chē)狀態(tài)封裝在 ?JavaBean?中,并在請(qǐng)求之間在會(huì)話(huà)中維持這個(gè)狀態(tài)。
清單?4?是處理?Ajax?請(qǐng)求、更新購(gòu)物車(chē)的簡(jiǎn)單?servlet?的一部分。Cart?bean?是從用戶(hù)會(huì)話(huà)中獲得的,并根據(jù)請(qǐng)求參數(shù)更新 它的狀態(tài)。然后?Cart?被序列化成?XML,XML?又被寫(xiě)入?ServletResponse。重要的是把響應(yīng)的內(nèi)容類(lèi)型設(shè)置為 ?application/xml,否則?XMLHttpRequest?不會(huì)把響應(yīng)內(nèi)容解析成?XML?DOM。
清單?4.?處理?Ajax?請(qǐng)求的?servlet?代碼
public?void?doPost(HttpServletRequest?req,?HttpServletResponse?res)
????????????????????????throws?java.io.IOException?{
??Cart?cart?=?getCartFromSession(req);
??String?action?=?req.getParameter("action");
??String?item?=?req.getParameter("item");
??
??if?((action?!=?null)&&(item?!=?null))?{
????//?Add?or?remove?items?from?the?Cart
????if?("add".equals(action))?{
??????cart.addItem(item);
????}?else?if?("remove".equals(action))?{
??????cart.removeItems(item);
????}
??}
??//?Serialize?the?Cart's?state?to?XML
??String?cartXml?=?cart.toXml();
??//?Write?XML?to?response.
??res.setContentType("application/xml");
??res.getWriter().write(cartXml);
}
?
清單?5?顯示了?Cart.toXml()?方法生成的示例?XML。它很簡(jiǎn)單。請(qǐng)注意?cart?元素的?generated?屬性,它是?System.currentTimeMillis()?生成的一個(gè)時(shí)間戳。
清單?5.?Cart?對(duì)象的XML?序列化示例?
<?xml?version="1.0"?>
<cart?generated="1123969988414"?total="$171.95">
??<item?code="hat001">
????<name>Hat</name>
????<quantity>2</quantity>
??</item>
??<item?code="cha001">
????<name>Chair</name>
????<quantity>1</quantity>
??</item>
??<item?code="dog001">
????<name>Dog</name>
????<quantity>1</quantity>
??</item>
</cart>
?
如果查看應(yīng)用程序源代碼(可以從?下載?一節(jié)得到)中的?Cart.java,可以看到生成?XML?的方式只是把字符串添加在一起。雖然對(duì)這個(gè)示例來(lái)說(shuō)足夠了,但是對(duì)于從?Java?代碼生成?XML?來(lái)說(shuō)則是最差的方式。我將在這個(gè)系列的下一期中介紹一些更好的方式。
現(xiàn)在您已經(jīng)知道了?CartServlet?響應(yīng)?XMLHttpRequest?的方式。下一件事就是返回客戶(hù)端,查看如何用?XML?響應(yīng)更新頁(yè)面狀態(tài)。
用?javascript?進(jìn)行響應(yīng)處理
XMLHttpRequest?的?readyState?屬性是一個(gè)數(shù)值,它指出請(qǐng)求生命周期的狀態(tài)。它從?0(代表“未初始化”)變化到?4 (代表“完成”)。每次?readyState?變化時(shí),readystatechange?事件就觸發(fā),由?onreadystatechange?屬 性指定的事件處理函數(shù)就被調(diào)用。
在?清單?3?中已經(jīng)看到了如何調(diào)用?getReadyStateHandler()?函數(shù)創(chuàng)建事件處理函數(shù)。然后把這個(gè)事件處理函數(shù)分配給?onreadystatechange?屬性。getReadyStateHandler()?利用了這樣一個(gè)事實(shí):函數(shù)是?javascript? 中的一級(jí)對(duì)象。這意味著函數(shù)可以是其他函數(shù)的參數(shù),也可以創(chuàng)建和返回其他函數(shù)。getReadyStateHandler()?的工作是返回一個(gè)函數(shù),檢 查?XMLHttpRequest?是否已經(jīng)完成,并把?XML?響應(yīng)傳遞給調(diào)用者指定的事件處理函數(shù)。清單?6?是 ?getReadyStateHandler()?的代碼。
清單?6.?getReadyStateHandler()?函數(shù)
/*
?*?Returns?a?function?that?waits?for?the?specified?XMLHttpRequest
?*?to?complete,?then?passes?its?XML?response?to?the?given?handler?function.
?*?req?-?The?XMLHttpRequest?whose?state?is?changing
?*?responseXmlHandler?-?Function?to?pass?the?XML?response?to
?*/
function?getReadyStateHandler(req,?responseXmlHandler)?{
??//?Return?an?anonymous?function?that?listens?to?the?
??//?XMLHttpRequest?instance
??return?function?()?{
????//?If?the?request's?status?is?"complete"
????if?(req.readyState?==?4)?{
??????
??????//?Check?that?a?successful?server?response?was?received
??????if?(req.status?==?200)?{
????????//?Pass?the?XML?payload?of?the?response?to?the?
????????//?handler?function
????????responseXmlHandler(req.responseXML);
??????}?else?{
????????//?An?HTTP?problem?has?occurred
????????alert("HTTP?error:?"+req.status);
??????}
????}
??}
}
?
在清單?6?中,檢查?XMLHttpRequest?的?status?屬性以查看請(qǐng)求是否成功完成。status?包含服務(wù)器響應(yīng)的
?HTTP?狀態(tài)碼。在執(zhí)行簡(jiǎn)單的?GET?和?POST?請(qǐng)求時(shí),可以假設(shè)任何大于?200?(OK)的碼都是錯(cuò)誤。如果服務(wù)器發(fā)送重定向響應(yīng)(例如
?301?或?302),瀏覽器會(huì)透明地進(jìn)行重定向并從新的位置獲取資源;XMLHttpRequest?看不到重定向狀態(tài)碼。而且,瀏覽器會(huì)自動(dòng)添加
?Cache-Control:?no-cache?頭到所有?XMLHttpRequest,這樣客戶(hù)代碼永遠(yuǎn)也不用處理?304(未經(jīng)修改)服務(wù)器響
應(yīng)。
?
?
關(guān)于?getReadyStateHandler()
getReadyStateHandler()?是段相對(duì)復(fù)雜的代碼,特別是如果您不習(xí)慣閱讀?javascript?的話(huà)。但是通過(guò)把這個(gè)函數(shù)放在?javascript?庫(kù)中,就可以處理?Ajax?服務(wù)器響應(yīng),而不必處理?XMLHttpRequest?的內(nèi)部細(xì)節(jié)。重要的是要理解如何在自己的代碼中使用?getReadyStateHandler()。
在?清單?3? 中看到了?getReadyStateHandler()?像這樣被調(diào)用:handlerFunction?= ?getReadyStateHandler(req,?updateCart)。在這個(gè)示例中,getReadyStateHandler()?返回的 函數(shù)將檢查在?req?變量中的?XMLHttpRequest?是否已經(jīng)完成,然后用響應(yīng)的?XML?調(diào)用名為?updateCart?的函數(shù)。
提取購(gòu)物車(chē)數(shù)據(jù)
清單?7?是?updateCart()?本身的代碼。函數(shù)用?DOM?調(diào)用檢查購(gòu)物車(chē)的?XML?文檔,然后更新?Web?頁(yè)面(請(qǐng)參閱?清單?1), 反映新的購(gòu)物車(chē)內(nèi)容。這里的重點(diǎn)是用來(lái)從?XML?DOM?提取數(shù)據(jù)的調(diào)用。cart?元素的?generated?屬性是在?Cart?序列化為 ?XML?時(shí)生成的一個(gè)時(shí)間戳,檢查它可以保證新的購(gòu)物車(chē)數(shù)據(jù)不會(huì)被舊的數(shù)據(jù)覆蓋。Ajax?請(qǐng)求天生是異步的,所以這個(gè)檢查可以處理服務(wù)器響應(yīng)未按次序 到達(dá)的情況。
清單?7.?更新頁(yè)面,反映購(gòu)物車(chē)的?XML?文檔
function?updateCart(cartXML)?{
?//?Get?the?root?"cart"?element?from?the?document
?var?cart?=?cartXML.getElementsByTagName("cart")[0];
?//?Check?that?a?more?recent?cart?document?hasn't?been?processed
?//?already
?var?generated?=?cart.getAttribute("generated");
?if?(generated?>?lastCartUpdate)?{
???lastCartUpdate?=?generated;
???//?Clear?the?HTML?list?used?to?display?the?cart?contents
???var?contents?=?document.getElementById("cart-contents");
???contents.innerHTML?=?"";
???//?Loop?over?the?items?in?the?cart
???var?items?=?cart.getElementsByTagName("item");
???for?(var?I?=?0?;?I?<?items.length?;?I++)?{
?????var?item?=?items[I];
?????//?Extract?the?text?nodes?from?the?name?and?quantity?elements
?????var?name?=?item.getElementsByTagName("name")[0]
???????????????????????????????????????????????.firstChild.nodevalue;
???????????????????????????????????????????????
?????var?quantity?=?item.getElementsByTagName("quantity")[0]
???????????????????????????????????????????????.firstChild.nodevalue;
?????//?Create?and?add?a?list?item?HTML?element?for?this?cart?item
?????var?li?=?document.createElement("li");
?????li.appendChild(document.createTextNode(name+"?x?"+quantity));
?????contents.appendChild(li);
???}
?}
?//?Update?the?cart's?total?using?the?value?from?the?cart?document
?document.getElementById("total").innerHTML?=?
??????????????????????????????????????????cart.getAttribute("total");
}
?
到此,整個(gè)?Ajax?往返過(guò)程完成了,但是您可能想讓?Web?應(yīng)用程序運(yùn)行一下查看實(shí)際效果(請(qǐng)參閱?下載?一節(jié))。這個(gè)示例非常簡(jiǎn)單,有很多需要改進(jìn)之處。例如,我包含了從購(gòu)物車(chē)中清除項(xiàng)目的服務(wù)器端代碼,但是無(wú)法從?UI?訪(fǎng)問(wèn)它。作為一個(gè)好的練習(xí),請(qǐng)?jiān)囍趹?yīng)用程序現(xiàn)有的?javascript?代碼之上構(gòu)建出能夠?qū)崿F(xiàn)這個(gè)功能的代碼。
使用?Ajax?的挑戰(zhàn)
就像任何技術(shù)一樣,使用?Ajax?也有許多出錯(cuò)的可能性。我目前在這里討論的問(wèn)題還缺乏容易的解決方案,但是會(huì)隨著?Ajax?的成熟而改進(jìn)。隨著開(kāi)發(fā)人員社區(qū)增加開(kāi)發(fā)?Ajax?應(yīng)用程序的經(jīng)驗(yàn),將會(huì)記錄下最佳實(shí)踐和指南。
XMLHttpRequest?的可用性
Ajax?開(kāi)發(fā)人員面臨的一個(gè)最大問(wèn)題是:在沒(méi)有?XMLHttpRequest?可用時(shí)該如何響應(yīng)?雖然主要的現(xiàn)代瀏覽器都支持 ?XMLHttpRequest,但仍然有少數(shù)用戶(hù)的瀏覽器不支持,或者瀏覽器的安全設(shè)置阻止使用?XMLHttpRequest。如果開(kāi)發(fā)的?Web? 應(yīng)用程序要部署在企業(yè)內(nèi)部網(wǎng),那么可能擁有指定支持哪種瀏覽器的權(quán)力,從而可以認(rèn)為?XMLHttpRequest?總能使用。但是,如果要部署在公共 ?Web?上,那么就必須當(dāng)心,如果假設(shè)?XMLHttpRequest?可用,那么就可能會(huì)阻止那些使用舊的瀏覽器、殘疾人專(zhuān)用瀏覽器和手持設(shè)備上的輕 量級(jí)瀏覽器的用戶(hù)使用您的應(yīng)用程序。
所以,您應(yīng)當(dāng)努力讓?xiě)?yīng)用程序“平穩(wěn)降級(jí)”,在沒(méi)有?XMLHttpRequest?支持的瀏覽器中也能夠工作。在購(gòu)物車(chē)的示例中,把應(yīng)用程序降級(jí)的 最好方式可能是讓?Add?to?Cart?按鈕執(zhí)行一個(gè)常規(guī)的表單提交,刷新頁(yè)面來(lái)反映購(gòu)物車(chē)更新后的狀態(tài)。Ajax?的行為應(yīng)當(dāng)在頁(yè)面裝入的時(shí)候就通 過(guò)?javascript?添加到頁(yè)面,只有在?XMLHttpRequest?可用時(shí)才把?javascript?事件處理函數(shù)附加到每個(gè)?Add?to?Cart?按鈕。另一種方式是在用戶(hù)登錄時(shí)檢測(cè)?XMLHttpRequest?是否可用,然后相應(yīng)地提供應(yīng)用程序的?Ajax?版本或基于表單的普通版本。
可用性考慮
關(guān)于?Ajax?應(yīng)用程序的某些可用性問(wèn)題比較普遍。例如,讓用戶(hù)知道他們的輸入已經(jīng)注冊(cè)了可能是重要的,因?yàn)樯陈┕鈽?biāo)和?spinning?瀏覽 器的常用反饋機(jī)制“throbber”對(duì)?XMLHttpRequest?不適用。一種技術(shù)是用“Now?updating...”類(lèi)型的信息替換 ?Submit?按鈕,這樣用戶(hù)在等候響應(yīng)期間就不會(huì)反復(fù)單擊按鈕了。
另一個(gè)問(wèn)題是,用戶(hù)可能沒(méi)有注意到他們正在查看的頁(yè)面的某一部分已經(jīng)更新了??梢允褂貌煌目梢暭夹g(shù),把用戶(hù)的眼球帶到頁(yè)面的更新區(qū)域,從而緩解這 個(gè)問(wèn)題。由?Ajax?更新頁(yè)面造成的其他問(wèn)題還包括:“破壞了”瀏覽器的后退按鈕,地址欄中的?URL?也無(wú)法反映頁(yè)面的整個(gè)狀態(tài),妨礙了設(shè)置書(shū)簽。請(qǐng) 參閱?參考資料?一節(jié),獲得專(zhuān)門(mén)解決?Ajax?應(yīng)用程序可用性問(wèn)題的文章。
服務(wù)器負(fù)載
用?Ajax?實(shí)現(xiàn)代替普通的基于表單的?UI,會(huì)大大提高對(duì)服務(wù)器發(fā)出的請(qǐng)求數(shù)量。例如,一個(gè)普通的?Google?Web?搜索對(duì)服務(wù)器只有一 個(gè)請(qǐng)求,是在用戶(hù)提交搜索表單時(shí)出現(xiàn)的。而?Google?Suggest?試圖自動(dòng)完成搜索術(shù)語(yǔ),它要在用戶(hù)輸入時(shí)向服務(wù)器發(fā)送多個(gè)請(qǐng)求。在開(kāi)發(fā) ?Ajax?應(yīng)用程序時(shí),要注意將要發(fā)送給服務(wù)器的請(qǐng)求數(shù)量以及由此造成的服務(wù)器負(fù)荷。降低服務(wù)器負(fù)載的辦法是,在客戶(hù)機(jī)上對(duì)請(qǐng)求進(jìn)行緩沖并且緩存服務(wù)器 響應(yīng)(如果可能的話(huà))。還應(yīng)該嘗試將?Ajax?Web?應(yīng)用程序設(shè)計(jì)為在客戶(hù)機(jī)上執(zhí)行盡可能多的邏輯,而不必聯(lián)絡(luò)服務(wù)器。
處理異步
非常重要的是,要理解無(wú)法保證?XMLHttpRequest?會(huì)按照分派它們的順序完成。實(shí)際上,應(yīng)當(dāng)假設(shè)它們不會(huì)按順序完成,并且在設(shè)計(jì)應(yīng)用程序時(shí)把這一點(diǎn)記在心上。在購(gòu)物車(chē)的示例中,使用最后更新的時(shí)間戳來(lái)確保新的購(gòu)物車(chē)數(shù)據(jù)不會(huì)被舊的數(shù)據(jù)覆蓋(請(qǐng)參閱?清單?7)。這個(gè)非常基本的方式可以用于購(gòu)物車(chē)場(chǎng)景,但是可能不適合其他場(chǎng)景。所以在設(shè)計(jì)時(shí)請(qǐng)考慮如何處理異步的服務(wù)器響應(yīng)。
結(jié)束語(yǔ)
現(xiàn)在您對(duì)?Ajax?的基本原則應(yīng)當(dāng)有了很好的理解,對(duì)參與?Ajax?交互的客戶(hù)端和服務(wù)器端組件也應(yīng)當(dāng)有了初步的知識(shí)。這些是基于?Java? 的?Ajax?Web?應(yīng)用程序的構(gòu)造塊。另外,您應(yīng)當(dāng)理解了伴隨?Ajax?方式的一些高級(jí)設(shè)計(jì)問(wèn)題。創(chuàng)建成功的?Ajax?應(yīng)用程序要求整體考慮,從 ?UI?設(shè)計(jì)到?javascript?設(shè)計(jì),再到服務(wù)器端架構(gòu);但是您現(xiàn)在應(yīng)當(dāng)已經(jīng)武裝了考慮其他這些方面所需要的核心?Ajax?知識(shí)。
如果使用這里演示的技術(shù)編寫(xiě)大型?Ajax?應(yīng)用程序的復(fù)雜性讓您覺(jué)得恐慌,那么有好消息給您。由于?Struts、Spring?和 ?Hibernate?這類(lèi)框架的發(fā)展把?Web?應(yīng)用程序開(kāi)發(fā)從底層?Servlet?API?和?JDBC?的細(xì)節(jié)中抽象出來(lái),所以正在出現(xiàn)簡(jiǎn)化 ?Ajax?開(kāi)發(fā)的工具包。其中有些只側(cè)重于客戶(hù)端,提供了向頁(yè)面添加可視效果的簡(jiǎn)便方式,或者簡(jiǎn)化了對(duì)?XMLHttpRequest?的使用。有些則 走得更遠(yuǎn),提供了從服務(wù)器端代碼自動(dòng)生成?Ajax?接口的方式。這些框架替您完成了繁重的任務(wù),所以您可以采用更高級(jí)的方式進(jìn)行?Ajax?開(kāi)發(fā)。我在 這個(gè)系列中將研究其中的一些。
Ajax?社區(qū)正在快速前進(jìn),所以會(huì)有大量有價(jià)值的信息涌現(xiàn)。在閱讀這個(gè)系列的下一期之前,我建議您參考?參考資料?一節(jié)中列出的文章,特別是如果您是剛接觸?Ajax?或客戶(hù)端開(kāi)發(fā)的話(huà)。您還應(yīng)當(dāng)花些時(shí)間研究示例源代碼并考慮一些增強(qiáng)它的方式。
在這個(gè)系列的下一篇文章中,我將深入討論?XMLHttpRequest?API,并推薦一些從?JavaBean?方便地創(chuàng)建?XML?的方式。我還將介紹替代?XML?進(jìn)行?Ajax?數(shù)據(jù)傳遞的方式,例如?JSON(javascript?Object?Notation)輕量級(jí)數(shù)據(jù)交換格式。
下載
描述?名字?大小??下載方法?
Sample?code?j-ajax1.zip?8?KB??FTP??
?關(guān)于下載方法的信息?
?
?獲取?Adobe??Reader??
posted on 2006-03-18 18:58 Vincent.Chen 閱讀(194) 評(píng)論(0) 編輯 收藏 所屬分類(lèi): AJAX