top
Loading...
開發保留標準瀏覽器功能的AJAX應用程序
Ajax應用程序由于其豐富的功能、交互性以及快速的響應能力而得到人們的普遍贊許。它可以使用XMLHttpRequest對象動態地加載數據,而不是加載新的頁面。在它大肆進行宣傳以及許多人興奮的同時,有評論指出,Ajax應用程序丟失了瀏覽器的一些重要功能,包括對后退前進按鈕的支持。

本文將首先闡明為什么在Ajax應用程序中除非顯式地構建后退/前進按鈕以及其它瀏覽器功能,否則它們將無法運行的原因。然后,我們將簡要介紹開發人員如何解決這些問題。最后,我們將看到有關Backbase Ajax引擎如何支持后退/前進按鈕以及其它標準瀏覽器功能的詳細情況。

Ajax應用程序是否需要后退按鈕?

Ajax承諾,可以讓開發人員完全基于標準的Web瀏覽器技術(通常是指DHTML)創建在視覺上吸引人的、高度交互式的Web應用程序。

以前開發人員不得不在功能豐富(具有高度交互性的、吸引人的用戶界面)和易于到達(不需要進行客戶端安裝就可以工作在所有Web瀏覽器下的前端)二者之中作出選擇。而Ajax應用程序應該能夠產生既“功能豐富”又“易于到達”的前端。

但是一個界面怎樣才算是“功能豐富”的,而一個應用程序又怎樣才是“易于到達”的呢?

很難精確地定義“功能豐富”的含義,但是卻很容易直覺地認識到:當您看到一個界面時,您就會知道它是不是功能豐富的。象Microsoft Office之類的桌面應用程序就是功能豐富的。功能豐富的界面使用諸如選項卡和上下文菜單這樣的高級UI控件。這樣的界面提供一些高級交互方法。例如,拖放、對關注的UI元素進行高亮顯示等。傳統的瀏覽器應用程序是功能不豐富的。它們僅限于諸如表單之類的簡單控件,交互主要是由到新頁面的單擊鏈接組成。我們只要看看微軟的電子郵件客戶端就可以看出功能豐富和功能不豐富的區別:Outlook是功能豐富的,而Hotmail就是功能不豐富的。

Ajax應用程序已經由于功能豐富而得到人們的普遍贊許。Google的Gmail就是其中最具代表性的例子。Google所開發的其它Ajax應用程序(Google Suggest、 Google Map)、微軟即將推出的名為“Kahuna”的Web郵件客戶端以及Backbase RSS Reader都包含了一些高級控件和交互模塊。

通過前面的討論,可以說Ajax應用程序很明顯滿足“功能豐富”的標準。那么它是不是“易于到達”的呢?

首先,最基本的是,只有界面在Web瀏覽器中運行的應用程序才是“易于到達”的。Ajax應用程序是基于瀏覽器標準的,因此可以通過Web瀏覽器來訪問。

但是,僅僅可以通過Web瀏覽器訪問還不夠。終端用戶希望在使用Web應用程序時所面對的是特定的交互方式。應用程序需要遵從傳統的Web交互方式,并提供以下的可用功能:
  • 后退和前進按鈕可以正常工作,以便終端用戶可以導航到歷史記錄頁面。
  • 用戶應該可以創建書簽。
  • 支持深鏈接,以保證終端用戶可以將這個頁面通過電子郵件發送給朋友和同事。
  • 刷新按鈕可以正常工作,以便刷新當前的狀態而不是重新初始化應用程序。
  • 開發人員可以使用“查看源文件”看到源代碼。
  • 終端用戶可以使用“查找”對頁面進行搜索。
  • 搜索引擎可以為頁面做索引并創建到搜索項的深鏈接。

以前討論的大多數Ajax應用程序的確打破了標準的Web交互方式。在下一節中,我們將討論為什么許多Ajax應用程序會這么做。

為什么Ajax應用程序常常會使后退按鈕無法正常工作?


我們所說的Web基于以下三個原則:
  • 使用 (D)HTML來定義界面
  • 使用HTTP實現客戶端與服務器間的通訊
  • 使用URI進行尋址

Ajax編程突破了由以上原則所帶來的種種限制,使得界面功能更加豐富。Ajax廣泛使用了JavaScript(“J”)以創建功能豐富的UI組件和交互性。Ajax還引入了異步的XML通信(“A”和“X”),也就是使用XMLHttpRequest對象導入新的數據和表示邏輯而不必刷新頁面。然而,目前的Ajax模型并沒有解決如何處理URI的問題。

Ajax應用程序對(D)HTML和HTTP的使用方式做了改變,而這種改變帶來的直接結果就是后退按鈕和Web的基本交互方式的其它元素無法正常工作了。在本節的其余部分,我將說明如何通過以Ajax的方式處理URI來解決上述問題。首先我們來看看在傳統的Web應用程序中URI是如何與用戶交互相關聯的。

從技術方面來說,用戶交互是指用戶界面狀態的一次更改。狀態改變由終端用戶發起。瀏覽器客戶端通過向服務器發出頁面請求來處理狀態更改(REST法則)。服務器將發送新的頁面和新的URI到客戶端以生成新的界面狀態。
簡單地說,每個用戶交互都是通過會導致如下結果的服務器往返來處理的:
  • 生成新的頁面
  • 生成新的URI

這些Web功能之所以能夠被使用,是因為瀏覽器在它的歷史記錄堆棧中記錄了連續的URI,并在地址欄中向終端用戶顯示當前URI,用戶可以通過地址欄復制URI,并將其發送給朋友。當用戶單擊后退按鈕或者向瀏覽器的地址欄中粘貼一個來自于電子郵件的URI時,就會觸發一次到服務器的往返。因為服務器負責狀態管理,所以它就可以生成相應的頁面。

Ajax應用程序與傳統的Web應用程序之間的主要區別在,Ajax應用程序可以處理用戶的交互而無需頁面重新加載。例如,通過XMLHttpRequest對象從服務器載入數據,或者使用JavaScript來處理拖放客戶端。

在上面的兩個例子中,狀態改變了,但是卻沒有生成新的URI。因此,單擊后退按鈕或刷新按鈕會產生意外的結果,在地址欄中也不會有深鏈接的URI。

為了提供傳統的Web可用功能,Ajax應用程序需要以類似于服務器處理傳統的Web應用程序的方式來處理URI客戶端。Ajax應用程序需要實現以下功能:
  • 當客戶端狀態發生改變時,生成一個URI,并將其發送到瀏覽器
  • 當瀏覽器請求新的URI時可以重新創建狀態。

實現以上功能后,瀏覽器的歷史記錄就可以正常工作,瀏覽器的地址欄就可以顯示URI,當然,您也就可以將它發送給朋友了。

這里還有另外一個難點,那就是如何確定Ajax引擎什么時候需要實現以上功能(例如,哪一次狀態改變需要創建新的URI)。在傳統的模式中,每一次頁面刷新都對應著一次URI更新。而在Ajax模式中,每一個客戶端事件都將新的URI添加到瀏覽器堆棧中。交互設計者和開發人員將不得不做出決定:哪一次狀態改變是有意義的。只對有意義的狀態改變才需要生成URI。

下面我們對提供Web可用功能的Ajax應用程序在客戶端需要實現的功能做一下總結:
  • 創建歷史記錄
    • 保存有意義的狀態
    • 生成相應的URI
    • 將這個URI添加到瀏覽器的堆棧中
  • 恢復歷史記錄
    • 檢測URI的改變
    • 通過URI重新創建狀態
在Ajax中支持后退按鈕的基本設計思想

在這一節中,我們將討論在Ajax應用程序中支持后退按鈕所需的基本步驟,并給出說明所需步驟的簡單示例代碼。

簡單示例程序如圖1所示,在界面中將有一個選擇框,它有兩個值:“Year 1”和“Year 2”。對于這個程序,我們將在選擇框值發生改變時跟蹤歷史記錄。這意味著用戶可以首先選擇“Year 2”然后單擊后退按鈕后退到先前的選擇。

帶有選擇框的簡單示例程序
圖1.帶有選擇框的簡單示例程序

示例程序最初是一個帶有JavaScript getter和setter(用于選擇框值)的簡單HTML表單:
<html> <head> <script language="JavaScript" type="text/JavaScript"> function reportOptionValue() {   var myForm = document.make_history;   var mySelect = myForm.change_year;   return mySelect.options[mySelect.selectedIndex].value; } function setOptionValue(value) {   var myForm = document.make_history;   var mySelect = myForm.change_year;   mySelect.options[value-1].selected = true; } </script> </head> <body> <form name=make_history>   <select name=change_year>     <option value="year_1">Year 1</option>     <option value="year_2">Year 2</option>   </select> </form> </body> </html> 

我們將首先實現第一個要求:創建狀態的歷史記錄。正如我們前面所提到的,這個要求包含以下三個步驟:
  • 創建歷史記錄
    • 保存有意義的狀態
    • 生成相應的URI
    • 將這個URI添加到瀏覽器的堆棧中
我們希望能夠保存選擇框的每一次更改。因此我們將創建新的包含選擇框狀態信息的URI。

為了遵循Internet標準,我們將使用URI的碎片標識符部分。按照IETF RFC 3986的規定,“……作為客戶端間接引用的主要形式,碎片標識符在信息檢索系統中起著特殊的作用,〈……〉碎片標識符在解除引用之前與URI的其余部分是分離的,因此,碎片本身中的標識信息只被用戶代理所廢棄,而不考慮URI方案……”。

使用碎片標識符,我們可以創建一個“Ajax-URI”,其中的客戶端部分和服務器端部分使用“#”隔開。

JavaScript提供了window.location()函數,以便通過URI更新瀏覽器的歷史記錄和地址。此外,我們可以使用window.location.hash()直接訪問碎片標識符。

在下面的代碼片斷中,您可以看到如何通過對選擇框使用onchange事件處理程序來擴展我們的代碼,該處理程序使用一個“Ajax-URI”來更新瀏覽器歷史記錄及地址欄。
<html> <head> <script language="JavaScript" type="text/JavaScript"> function makeHistory(newHash) {   window.location.hash = newHash; } function reportOptionValue() {   var myForm = document.make_history;   var mySelect = myForm.change_year;   return mySelect.options[mySelect.selectedIndex].value; } function setOptionValue(value) {   var myForm = document.make_history;   var mySelect = myForm.change_year;   mySelect.options[value-1].selected = true; } </script> </head> <body> <form name=make_history>   <select name=change_year      onchange=       "return makeHistory(reportOptionValue())">     <option value="year_1">Year 1</option>     <option value="year_2">Year 2</option>   </select> </form> </body> </html> 
正如我們在圖2中所看到的,選擇框的每一次變動都將導致瀏覽器地址的更新。請注意,在需要使用隱藏幀以獲取正確的行為的Internet Explorer (IE)中會存在一些問題,詳細情況還是請參見Mike Stenhouse和Brad Neuberg的文章。

狀態變化時歷史記錄堆棧被更新
圖2.狀態變化時歷史記錄堆棧被更新

我們現在有了一個在選擇框的值發生變化時創建新URI的事件處理程序。新URI使用碎片標識符存儲重新創建先前狀態所需的信息。現在我們可以著手實現下一個功能了。
  • 恢復歷史記錄
    • 檢測URI的更改
    • 通過URI重新創建狀態

在第一步中,我們通過window.location.hash()函數更新了客戶端的URI。這個調用并不會產生服務器的往返,也不會導致頁面刷新。因此,我們需要使用Ajax的方法(在客戶端)處理URI的改變。

首先需要增加一個輪詢函數,以定時檢查瀏覽器歷史記錄中的URI。我將在頁面的onload事件中使用pollHash()函數,每隔1000毫秒它將重新執行一次。

這個輪詢函數將調用handleHistory()函數,后者檢查在上一次檢查之后URI是否改變了。我們將借助一個名為expectedHash的全局變量來實現。

最后一部分是確定URI是否發生了改變,這種改變由選擇框中的事件處理程序引起,或者是因為終端用戶單擊了后退按鈕而造成。我們通過在選擇框的事件處理程序中設置expectedHash來達到此目的。
<html> <head> <script language="JavaScript" type="text/JavaScript"> var expectedHash = ""; function makeHistory(newHash) {   window.location.hash = newHash;   expectedHash = window.location.hash;   return true; } function reportOptionValue() {   var myForm = document.make_history;   var mySelect = myForm.change_year;   return mySelect.options[mySelect.selectedIndex].value; } function setOptionValue(value) {   var myForm = document.make_history;   var mySelect = myForm.change_year;   mySelect.options[value-1].selected = true;   return true; } function handleHistory() {   if ( window.location.hash != expectedHash )   {     expectedHash = window.location.hash;     var newoption = expectedHash.substring(6);     setOptionValue( newoption );   }   return true; } function pollHash() {   handleHistory();   window.setInterval("handleHistory()", 1000);   return true; } </script> </head> <body language="JavaScript"       onload="return pollHash()"> <form name=make_history>   <select name=change_year      onchange="return makeHistory(reportOptionValue())">     <option value="year_1">Year 1</option>     <option value="year_2">Year 2</option>   </select> </form> </body> </html> 
到此,我們的示例程序就完成了。在這個程序中,我們演示了如何在URI中記錄狀態,如何將URI添加到瀏覽器的歷史記錄堆棧中,如何從后退按鈕檢測地址變動,以及最終如何重新創建所需的狀態。

這個示例程序還缺少以下功能:
  • 對使用隱藏幀的IE的支持
  • 更多的固定URI(這個示例程序只用于選擇框選項少于10的情況)
  • 在構造時注冊初始狀態

以一種兼容所有瀏覽器的健壯方式實現對所有傳統的Web可用功能的處理不是一件容易的事。一種替代方法是使用對這些功能提供了內置支持的Ajax工具包。

案例分析:包含后退按鈕和深鏈接的Ajax論壇

Backbase Ajax引擎是一個成熟的、功能豐富的Ajax軟件包。對所有傳統Web可用功能的支持是Backbase的優點之一。

Backbase DevNet包含了為開發人員提供的、與Backbase和Ajax有關的信息。而開發人員論壇是DevNet的一部分。

Backbase Web應用程序(包括DevNet及其討論論壇)是使用Backbase構建的。為了演示該論壇功能豐富和易于到達的特點,我們將逐步遍歷論壇的典型用例:
  • 開發人員瀏覽論壇,閱讀不同的主題。
  • 開發人員復制這個主題的URI,將其粘貼到電子郵件中并發送給朋友。這個朋友從電子郵件中復制這個URI到一個瀏覽器中并打開同一論壇主題。
  • 開發人員單擊后退按鈕以閱讀以前的主題。
進行幾次用戶交互后的論壇界面狀態

我們來看看開發人員來到“BXML”論壇并選中名為“Issue with vertical and horizontal menus”的貼子之后,論壇界面的狀態以及地址欄中的對應URI是什么樣的情況。

論壇和貼子被選中,并被高亮顯示。討論的主題被顯示出來以供閱讀。在URI的碎片標識符中包含了所有的相關信息。在#后面,我們看到了為書簽和深鏈接而記錄的完整狀態:“forum”表示開發人員在瀏覽這個Web站點的論壇部分;“forum=2”表示當前選中的是BXML論壇,“thread=211”記錄了當前所選擇的主題。最后,方括號中的“[5]”表示與書簽結合的對多個后退和前進步驟的處理。

具有Ajax URI的論壇初始狀態
圖3.具有Ajax URI的論壇初始狀態 (單擊圖片查看大圖)

訪問Backbase論壇,您就可以看到URI如何隨著每次狀態改變而更新,即使更新是在客戶端進行處理的,或者牽涉到通過XMLHttpRequest對象進行部分頁面更新。

在新的瀏覽器窗口內重新創建論壇界面的狀態

現在讓我們看看當開發人員將當前URI發送給朋友時會發生什么情況。這個朋友在瀏覽器窗口中打開了這個URI,期望能看到相同的界面狀態。需要在新的瀏覽器中重新創建該狀態。對于本文,我是從一個Firefox窗口中復制URI到一個新打開的IE窗口中。

在地址欄中輸入URI首先會產生一個服務器端的請求。使用“#”前的部分,會加載Backbase.com,在這一過程中,Backbase Ajax引擎也就實現了初始化。活動的Backbase引擎會閱讀URI中“#”后的部分。通過這些信息,Backbase引擎會轉到“論壇(forum)”部分,并選定BXML論壇(id=2)中的第211個主題,從而創建相應的狀態。不需要頁面的刷新,只需從服務器中加載附加的內容并在客戶端部分地更新界面,就可以實現了。

在后續的瀏覽器功能的處理中,新的URI被添加到瀏覽器歷史記錄中,這個新的URI既可以在地址欄中使用,也可以用來做深鏈接。“[0]”表示沒有可返回(使用后退按鈕)的先前狀態。

在新的瀏覽器窗口中重新創建論壇狀態
圖4.在新的瀏覽器窗口中重新創建論壇狀態(單擊圖片查看大圖)

用戶單擊后退按鈕后的論壇界面狀態


第一步我們研究了URI如何隨著由用戶交互所觸發的界面狀態更改而更新。下面我們將看到相反的情況:用戶請求新的URI,相應的狀態被重新創建。

通過單擊后退按鈕,用戶要求返回先前閱讀的頁面。瀏覽器通過從歷史記錄堆棧中找回先前的URI來響應后退按鈕。Backbase Ajax引擎將監測這一變化,從歷史記錄中讀取新的URI,并來到“論壇”部分選定BXML論壇(id=2)中的第192個主題,從而重新構建相應的狀態。新的URI將按照上述語義顯示在地址欄中。

到這里,我們的案例分析也就結束了。

單擊后退按鈕后的論壇狀態
圖5.單擊后退按鈕后的論壇狀態(單擊圖片查看大圖)

Ajax程序確實需要后退按鈕!


在過去的幾年中,Web開發人員因為市場要求“易于到達”并愿意接受“功能豐富”方面的犧牲,所以選擇構建Web界面。然而,當前Ajax受到的普遍關注清楚地顯示出這種情況實際上只是暫時的。市場現在強烈要求Web程序也能像桌面應用程序那樣具有豐富的功能、交互性以及敏捷的響應能力。

但是,終端用戶已經習慣了Web交互方式。使用常見模式與任何Web界面進行交互可以提高生產力。終端用戶期望后退/前進按鈕和刷新按鈕能正常工作,可以創建書簽和深鏈接,可以查看源文件,使用“查找”對頁面進行搜索,而且搜索引擎可以對Ajax應用程序建立索引。

Ajax社區必須知道:正如本文所述,在Ajax應用程序中提供對后退/前進按鈕以及其它傳統瀏覽器功能的支持的技術是存在的。雖然實現起來并不容易,而且會增加成本,但是Ajax社區的成功需要將傳統的瀏覽器功能構建到Ajax應用程序中。因此,我強烈呼吁Ajax開發人員構建支持這些功能的Ajax應用程序!

結束語


在本文中,我著重闡明了Ajax應用程序為什么需要遵從傳統的Web交互方式并提供傳統的Web可用功能。我確定可以通過創建在碎片標識符中包含客戶端狀態信息的“Ajax URI” ,從而將這些功能編程到Ajax應用程序中。

閱讀相關代碼,您會發現,由于狀態處理代碼通常非常重要,再加上不同瀏覽器之間常常不兼容,實現完整的通用解決方案是相當困難的。而Backbase Ajax引擎通過開箱即用地提供所需功能,為該問題提供了一種解決方案。
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗