Ajax 和 XML: 五種常見 Ajax 模式

Asynchronous JavaScript + XML(Ajax)無疑是 2006 年最熱門的技術術語,且有望在 2007 得到進一步發展。但是對您的應用程序來說它究竟有什麼意義呢?Ajax 應用程序中哪一種常見架構模式應用最廣泛呢?本文將介紹五種常見 Ajax 設計模式,可以使用它們作爲工作的基礎。

的確,Ajax 是 Web 2.0 熱門術語,所有人都希望將其應用於自己的站點。但是它對我們究竟有什麼意義?工程師該如何在架構的層面上將其集成到自己的站點中?在這篇文章中,我將介紹 Ajax 的基本知識,並展示一些已經成爲 Web 2.0 開發最佳實踐的 Ajax 設計模式。

請訪問 Ajax 技術資源中心,這是有關 Ajax 編程模型信息的一站式中心,包括很多文檔、教程、論壇、blog、wiki 和新聞。任何 Ajax 的新信息都能在這裏找到。

首先,Ajax 僅僅是一個涉及一組技術的術語,包括 Dynamic HTML(DHTML)和 XMLHTTPRequest 對象。DHTML 由三個元素組合而成,它們分別是超文本標記語言(Hypertext Markup Language,HTML)、JavaScript 代碼和級聯樣式表(Cascading Style Sheet,CSS)。在 Web 頁面使用 JavaScript 代碼,可以動態地改變頁面,包括添加、刪除或更改頁面內容。這就是 DHTML 的動態 部分。JavaScript 代碼使用 XMLHTTPRequest 對象在加載頁面後向服務器請求數據。

這兩種元素的組合 —— 從服務器動態請求數據然後使用這些數據更改頁面 —— 就是 Ajax 的本質,也是 Web 2.0 站點的動態特性。

但這並沒有真正告訴您如何實際應用這些特性以及如何在站點中使用它們。因此,需要一組簡單的設計模式。 如果您對這個術語感到陌生,本文推介了一本非常優秀的同名書籍(參見 參考資料)。這本書針對工程師經常面對的任務提供了一組實現模式。它不僅提供了設計系統的最佳實踐,還介紹了工程師談論代碼時用到的術語。

本文介紹了五種常見 Ajax 設計模式。它們在使用 HTML、XML 和 JavaScript 代碼從服務器獲取數據方面有所不同。我先介紹最簡單的模式,它將使用來自服務器的新 HTML 頁面來更新頁面。

模式 1. 替換 HTML 片段

最常見的 Ajax 任務也許就是向服務器請求更新的 HTML 並使用它更新部分頁面。可能需要週期性地完成這一任務 —— 比如,更新股市報價。也可能要按需更新 —— 比如,對搜索請求進行響應。

清單 1 中的代碼從服務器請求一個頁面然後將內容放入頁面主體的 <div> 標記中。


清單 1. Pat1_replace_div.html
<html>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var dobj = document.getElementById( 'htmlDiv' );
    dobj.innerHTML = req.responseText;
  }
}

function loadUrl( url ) {
  if(window.XMLHttpRequest) {
    try { req = new XMLHttpRequest();
    } catch(e) { req = false; }
  } else if(window.ActiveXObject) {
    try { req = new ActiveXObject('Msxml2.XMLHTTP');
    } catch(e) {
    try { req = new ActiveXObject('Microsoft.XMLHTTP');
    } catch(e) { req = false; }
  } }
  if(req) {
    req.onreadystatechange = processReqChange;
    req.open('GET', url, true);
    req.send('');
  }
}

var url = window.location.toString();
url = url.replace( /pat1_replace_div.html/, 'pat1_content.html' );
loadUrl( url );
</script>
<body>
Dynamic content is shown between here:<br/>
<div id="htmlDiv" style="border:1px solid black;padding:10px;">
</div>
And here.<br/>
</body>
</html>

清單 2 展示了代碼請求的內容。


清單 2. Pat1_content.html
	
HTML encoded content goes here.

要查看真實環境的演示,請查看這個在線版本 pat1_replace_div.html 在新窗口中打開鏈接以查看 pat1_replace_div.html.

在 Firefox 中加載頁面後,可以看到 圖 1 所示的結果。


圖 1. 替換了 <div> 標記的頁面
替換了標記的頁面

現在回到 清單 1 中的代碼,來觀察一些內容。第一個要注意的是 loadUrl() 函數,它從服務器請求一個 URL。該函數使用 XMLHTTPRequest 對象向服務器請求新內容。它還指定了一個回調函數 —— 本例中,是 processReqChange —— 當瀏覽器收到內容時將調用它。

processReqChange 函數將查看對象以確定請求是否完成。如果是的話,該函數將頁面 <div> 標記的 innerHTML 設置爲響應的文本。

<div> 標記作爲一個動態內容的佔位符,這是 Ajax 代碼的主要組成部分。這些標記沒有可見的表示形式(除非添加邊框,像我這樣做),但它們很好地標記了內容的放置位置。工程師還使用 <span> 標記用於可代替的片段,稍後我將對其進行演示。<div><span> 標記的不同之處是前者加入了一個斷行符(如一個段落),而後者使用邊線勾畫出一節內聯文本。

暫時回到 processReqChange 函數,該函數對 statusreadyState 的值進行檢查非常重要。有些瀏覽器可能只在請求完成時才調用這個函數,而也有些瀏覽器會不斷回調該函數從而告訴代碼請求依然在運行。

選項卡式顯示變體

該模式的另一種變體就是創建一個選項卡樣式的顯示。 清單 3 展示了一個簡單的選項卡式 Ajax 界面。


清單 3. Pat1_tabs.html
<html>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var dobj = document.getElementById( 'tabDiv' );
    dobj.innerHTML = req.responseText;
  }
}

function loadUrl( tab ) {
  var url = window.location.toString();
  url = url.replace( /pat1_tabs.html/, tab );
  ...
}

function tab1() { loadUrl( 'pat1_tab1_content.html' ); }
function tab2() { loadUrl( 'pat1_tab2_content.html' ); }
tab1();
</script>
<body>
<a href="javascript: void tab1();">Tab 1<a> 
<a href="javascript: void tab2();">Tab 2<a>
<div id="tabDiv" style="border:1px solid black;padding:10px;">
</div>
</body>
</html>

清單 4 顯示了第一個選項卡的內容。


清單 4. Pat1_tab1_content.html
Tab 1 content

清單 5 顯示了第二個選項卡的內容。


清單 5. Pat1_tab2_content.html
Tab 2 content

要查看真實環境的演示,請查看在線版本 pat1_tabs.html 在新窗口打開鏈接以查看 pat1_tabs.html.

當我在自己的瀏覽器上顯示該頁面時,我看到了第一個選項卡,如 圖 2 所示。


圖 2. 第一個選項卡的內容
第一個選項卡的內容

然後單擊第二個選項卡的鏈接。瀏覽器檢索第二個選項卡的內容然後將它顯示在選項卡區域,如 圖 3 所示。


圖 3. 第二個選項卡的內容
第二個選項卡的內容

這是該設計模式的最典型用法 —— 從用戶那裏獲得請求並使用新的內容更新部分顯示,本例演示了創建選項卡顯示的技巧。應用程序端的價值就是您可以爲用戶下載非常輕量級的頁面,用戶可以根據自己的需求訪問這些內容。

在 Ajax 出現之前,最常見的技術是將所有的選項卡都放在頁面上,然後根據需要顯示或隱藏它們。這就是說即使從來不查看第二個選項卡,也會爲其創建 HTML,既浪費服務器時間又浪費帶寬。使用這種新的 Ajax 方法,只有當用戶請求第二個選項卡時纔會爲其創建 HTML。

read more 變體

要查看真實環境的演示,請查看在線版本 pat1_readmore.html 在新窗口打開鏈接以查看 pat1_readmore.html.

該模式的另一個變化就是 Read more 鏈接,如 圖 4 所示。


圖 4. 我的博客登錄處的 Read more 鏈接
Read more 鏈接

假如想希望閱讀更多關於我遛狗的經歷,可以單擊 Read more 鏈接,使該鏈接替換爲完整的故事,如 圖 5 所示。


圖 5. 單擊 Read more 鏈接後顯示的頁面
單擊 Read more 後

這樣做的好處是顧客可以在無需刷新頁面的情況下獲得更多內容。

清單 6 顯示了該頁的代碼。


清單 6. Pat1_readmore.html
<html>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var dobj = document.getElementById( "moreSpan" );
    dobj.innerHTML = req.responseText;
  }
}

function loadUrl( url ) { ... }

function getMore()
{
  var url = window.location.toString();
  url = url.replace( /pat1_readmore.html/, 'pat1_readmore_content.html' );
  loadUrl( url );
}
</script>
<body>
<h1>Walking the dog</h1>
I took my dog for a walk today. 
<span id="moreSpan">
<a href="javascript: void getMore()">Read more...</a>
</span>
</body>
</html>

清單 7 顯示了 “read more” 部分的內容。


清單 7. Pat1_readmore_content.html
It was a nice day out. Warm and sunny. My dog liked getting out for a stretch.

這些代碼演示的是 <span> 標記的用法,而非 <div> 標記。所使用的方法取決於用戶界面(UI)的需求。但是正如您所看到的那樣,無論哪種方法使用起來都很簡單。

爲頁面獲取新的 HTML 只是其中一件事情,如果您希望 JavaScript 代碼在頁面中使用數據執行一些更智能化的任務該怎麼辦呢?如何使用結構化的方式將數據發送到瀏覽器呢?毫無疑問,這正是使用 XML 的原因。





回頁首


模式 2. 讀取 XML 數據

出於某些原因,Ajax 已成爲 XML 的同義詞,儘管 XML 不是絕對必要的。從上面幾個例子可以看出,您完全可以返回簡單的文本甚至是 HTML 片段 —— 或者 Extensible HTML(XHTML)—— 代碼。但是發送 XML 自有其優勢所在。

清單 8 顯示的 Ajax 代碼首先向服務器請求圖書記錄,然後將數據顯示在頁面內的表格中。


清單 8. Pat2_xml.html
<html>
<head>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 && req.responseXML ) {
    var dtable = document.getElementById( 'dataBody' );
    var nl = req.responseXML.getElementsByTagName( 'book' );
    for( var i = 0; i < nl.length; i++ ) {
      var nli = nl.item( i );
      var elAuthor = nli.getElementsByTagName( 'author' );
      var author = elAuthor.item(0).firstChild.nodeValue;
      var elTitle = nli.getElementsByTagName( 'title' );
      var title = elTitle.item(0).firstChild.nodeValue;

      var elTr = dtable.insertRow( -1 );

      var elAuthorTd = elTr.insertCell( -1 );
      elAuthorTd.innerHTML = author;

      var elTitleTd = elTr.insertCell( -1 );
      elTitleTd.innerHTML = title;
} } }

function loadXMLDoc( url ) {
  if(window.XMLHttpRequest) {
    try { req = new XMLHttpRequest();
    } catch(e) { req = false; }
  } else if(window.ActiveXObject) {
    try { req = new ActiveXObject('Msxml2.XMLHTTP');
    } catch(e) {
    try { req = new ActiveXObject('Microsoft.XMLHTTP');
    } catch(e) { req = false; }
  } }
  if(req) {
    req.onreadystatechange = processReqChange;
    req.open('GET', url, true);
    req.send('');
  }
}

var url = window.location.toString();
url = url.replace( /pat2_xml.html/, 'pat2_xml_data.xml' );
loadXMLDoc( url );
</script>
</head>
<body>
<table cellspacing="0" cellpadding="3" width="100%">
<tbody id="dataBody">
<tr>
  <th width="20%">Author</th>
  <th width="80%">Title</th>
</tr>
</tbody>
</table>
</body>
</html>

清單 9 顯示了該頁面的數據。


清單 9. Pat2_xml_data.xml
<books>
  <book>
    <author>Jack Herrington</author>
    <title>Code Generation in Action</title>
  </book>
  <book>
    <author>Jack Herrington</author>
    <title>Podcasting Hacks</title>
  </book>
  <book>
    <author>Jack Herrington</author>
    <title>PHP Hacks</title>
  </book>
</books>

要查看真實環境的演示,請查看在線版本 pat2_xml.html 在新窗口打開鏈接以查看 pat2_xml.html.

在瀏覽器中加載頁面時,我看到了如 圖 6 所示的結果。


圖 6. XML 顯示頁面
XML 顯示頁面

此頁面和上一個模式中顯示的頁面之間最大的區別就是 processReqChange 函數,這裏沒有使用 responseText,而是 responseXML,這是一個 XML 文檔對象模型(Document Object Model,DOM),該模型只有在來自服務器的響應是正確編碼的 XML 時纔是可用的。

通過使用 responseXML,我請求了 XML 文檔的 <book> 標記的列表。然後分別從中獲取 <title><author> 元素。接下來,爲每本書向表中添加一行,再爲每行添加包含作者和題目數據的單元格。

這是 XML 數據的最基本應用。更復雜的 JavaScript 代碼可以執行客戶端排序或根據返回的數據進行搜索。

遺憾的是,傳遞 XML 數據的缺點是需要瀏覽器多花費一些時間來解析整個 XML 文檔。同樣,JavaScript 代碼在 XML 中查找數據也很複雜(參見 清單 8)。一個替代辦法是從服務器請求 JavaScript 代碼。





回頁首


模式 3. 讀取 JavaScript 數據

從服務器請求 JavaScript 數據這種技術通常用於 JavaScript Object Notation(JSON)這種良好的代碼。返回 JavaScript 數據的優點就是能夠使瀏覽器高效地解析並創建使用起來更加簡單的 JavaScript 數據結構。

讓我們將 清單 8 中從服務器讀取 XML 的代碼修改爲從服務器讀取 JavaScript 數據的代碼。新代碼如 清單 10 所示。


清單 10. Pat3_js.html
<html><head><script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var dtable = document.getElementById( 'dataBody' );
    var books = eval( req.responseText );
    for( var b in books ) {
      var elTr = dtable.insertRow( -1 );

      var elAuthorTd = elTr.insertCell( -1 );
      elAuthorTd.innerHTML = books[b].author;

      var elTitleTd = elTr.insertCell( -1 );
      elTitleTd.innerHTML = books[b].title;
} } }

...

所有的 HTML 代碼保持不變。processReqChange 函數僅僅更改爲讀取 eval 從而從服務器返回 JavaScript 數據。該函數隨後使用 eval 的 JavaScript 對象作爲數據源,後者又將其添加到表中。

清單 11 顯示了來自服務器的 JavaScript 數據。


清單 11. Pat3_js_data.js
[ { author: 'Jack Herrington', title: 'Code Generation in Action' },
{ author: 'Jack Herrington', title: 'Podcasting Hacks' },
{ author: 'Jack Herrington', title: 'PHP Hacks' }
]

要查看真實環境的演示,請查看在線版本 pat3_js.html 在新窗口中打開鏈接以查看 pat3_js.html.

爲什麼衆多 Ajax 應用程序工程師更喜歡使用 JavaScript 代碼而不是 XML 來對數據編碼?答案很明顯。JavaScript 代碼更容易讀取和管理,並且也更容易被瀏覽器處理。

收集和顯示所有的數據之後,即可看到 Ajax 的重點就是顯示當前數據 —— 當前 最重要的部分。那麼,如何保證總是能夠從服務器獲得最新的數據呢?





回頁首


模式 4. 避免瀏覽器緩存

瀏覽器會嘗試化 Web 流量,所以如果您對同一個 URL 請求兩次,很可能還不如重新請求一次頁面,您的瀏覽器將僅僅使用瀏覽器緩存中存儲的頁面。所以,Ajax 應用程序中另一個常見模式是使用 URL 中的隨機元素來保證瀏覽器不會返回一個緩存的結果。

我最喜歡的技巧就是向 URL 添加當前時間的數字值。 清單 12 展示了這一技巧。


清單 12. Pat4_cache.html
<html>
<script>
...

function loadUrl( url ) {
  url = url + "?t="+((new Date()).valueOf());
  ...
}

...

要查看真實環境的演示,請查看在線版本 pat4_cache.html 在新窗口中打開鏈接以查看 pat4_cache.html.

代碼取自 清單 1,對 URL 字符串執行了一些額外的 JavaScript 文本操作。我將 URL 連接到一個新的參數 t,它具有一個時間值。服務器是否能識別這個值實際上並不重要。這只不過是用來確保瀏覽器忽視其基於 URL 的頁面緩存。





回頁首


模式 5. 替換多個 HTML 片段

最後要演示的這個模式是第一個模式的高級版本:包含來自服務器內容的 <div> 標記的替代物。Web 應用程序中的一個常見問題是在響應用戶輸入時,必須更新顯示頁面中的一些區域。舉例來說,在一個股票報價應用程序中,顯示頁面的一部分可能會展示最近的報價,而另一部分則顯示最近報價的列表。

爲更新顯示頁面中的多個區域,我使用了服務器的 XML 響應,它包含每個部分的數據。然後,使用一個正則表達式將響應分解爲單個部分。清單 13 顯示了這種技巧。


清單 13. Pat5_multi_segment.html
<html>
<head>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var one = req.responseText.match( //<one/>(.*?)/<//one/>/ );
    document.getElementById( 'divOne' ).innerHTML = one[1];
    var two = req.responseText.match( //<two/>(.*?)/<//two/>/ );
    document.getElementById( 'divTwo' ).innerHTML = two[1];
} }

function loadXMLDoc( url ) { ... }

var url = window.location.toString();
url = url.replace( /pat5_multi_segment.html/, 'pat5_data.xml' );
loadXMLDoc( url );
</script>
</head>
<body>

This is the content for segment one:<br/>
<div id="divOne" style="border:1px solid black;padding:10px;">
</div>
And segment two:<br/>
<div id="divTwo" style="border:1px solid black;padding:10px;">
</div>

</body>
</html>

清單 14 展示了來自服務器的數據。


清單 14. Pat5_data.xml
<segments>
  <one>Content for segment one</one>
  <two>Content for segment <b>two</b></two>
</segments>

要查看真實環境的演示,請查看在線版本 pat5_multi_segment.html 在新窗口中打開鏈接以查看 pat5_multi_segment.html.

在瀏覽器中加載這段代碼時,將看到 圖 7 所示的結果。


圖 7. 使用服務器的數據更新顯示頁面中的兩部分
顯示其中的兩部分

在頁面代碼中,我還可以使用 XML 響應,因爲服務器返回的是有效的 XML。但是使用正則表達式比從 XML 代碼中分解單獨部分更加簡單。





回頁首


結束語

分享這篇文章……

digg 提交到 Digg
del.icio.us 發佈到 del.icio.us
Slashdot 提交到 Slashdot!

Ajax 的功能之強大與之被誤解和誤用的程度相當。本文中演示的模式爲在 Web 應用程序中使用 Ajax 提供了一個不錯的起點。除了使用這裏提供的代碼,我還建議您關注以下 Web 2.0 革命帶來的某些出色的 Ajax 和 Web UI 庫。其中最主要的是 Prototype.js 庫,它提供了向瀏覽器發送以及從瀏覽器獲取數據的簡便方法,以及瀏覽器間兼容的方法更新 Web 頁面內容。使用這些庫的價值在於工程師可以在廣泛的瀏覽器和平臺上專注地維護和測試它們,這樣可省去大量的工作和麻煩。

無論從哪方面說,就像本文中的模式演示的那樣,Ajax 可以爲您的應用程序添加動態行爲。

 
發佈了34 篇原創文章 · 獲贊 8 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章