Html5 是目前正在討論的新一代 HTML 標准,它代表了現在 Web 領域的最新發展方向。在 Html5 標准中,加入了新的多樣的內容描述標簽,直接支持表單驗證、視頻音頻標簽、網頁元素的拖拽、離線存儲和工作線程等功能。其中一個新特性就是對離線應用開發的支持。
在開發支持離線的 Web 應用程序時,開發者通常需要使用以下三個方面的功能:
盡管 HTML5 還處於草稿狀態,但是各大主流浏覽器都已經實現了其中的很多功能。Chrome、Firefox、Safari 和 Opera 的最新版本都對 HTML5 離線功能提供了完整的支持。IE8 也支持了其中的在線狀態檢測和 DOM Storage 功能。下面將具體介紹 Html5 離線功能中的離線資源緩存、在線狀態檢測、DOM Storage 和 Web SQL Database,最後通過一個簡單的 Web 程序說明使用 Html5 開發離線應用的方法。
離線資源緩存
為了能夠讓用戶在離線狀態下繼續訪問 Web 應用,開發者需要提供一個 cache manifest 文件。這個文件中列出了所有需要在離線狀態下使用的資源,浏覽器會把這些資源緩存到本地。本節先通過一個例子展示 cache manifest 文件的用途,然後詳細描述其書寫方法,最後說明緩存的更新方式。
cache manifest 示例
我們通過 W3C 提供的示例來說明。Clock Web 應用由三個文件“clock.Html”、“clock.CSS”和“clock.JS”組成。
清單 1. Clock 應用代碼
<!-- clock.html --> <!DOCTYPE HTML> <html> <head> <title>Clock</title> <script src="clock.JS"></script> <link rel="stylesheet" href="clock.CSS"> </head> <body> <p>The time is: <output id="clock"></output></p> </body> </Html> /* clock.CSS */ output { font: 2em sans-serif; } /* clock.JS */ setTimeout(function () { document.getElementById('clock').value = new Date(); }, 1000);
當用戶在離線狀態下訪問“clock.html”時,頁面將無法展現。為了支持離線訪問,開發者必須添加 cache manifest 文件,指明需要緩存的資源。這個例子中的 cache manifest 文件為“clock.manifest”,它聲明了 3 個需要緩存的資源文件“clock.Html”、“clock.CSS”和“clock.JS”。
清單 2. clock.manifest 代碼
CACHE MANIFEST clock.Html clock.CSS clock.JS
添加了 cache manifest 文件後,還需要修改“clock.html”,把 <html> 標簽的 manifest 屬性設置為“clock.manifest”。修改後的“clock.Html”代碼如下。
清單 3. 設置 manifest 後的 clock.Html 代碼
<!-- clock.html --> <!DOCTYPE HTML> <html manifest="clock.manifest"> <head> <title>Clock</title> <script src="clock.JS"></script> <link rel="stylesheet" href="clock.CSS"> </head> <body> <p>The time is: <output id="clock"></output></p> </body> </Html>
修改後,當用戶在線訪問“clock.html”時,浏覽器會緩存“clock.Html”、“clock.CSS”和“clock.JS”文件;而當用戶離線訪問時,這個 Web 應用也可以正常使用了。
cache manifest 格式
下面說明書寫 cache manifest 文件需要遵循的格式。
清單 4 的代碼中給出了 cache manifest 中各類標識符的使用示例。
清單 4. cache manifest 示例代碼
CACHE MANIFEST # 上一行是必須書寫的。 images/sound-icon.png images/background.png NETWORK: comm.CGI
# 下面是另一些需要緩存的資源,在這個示例中只有一個 CSS 文件。
CACHE: style/default.CSS FALLBACK: /files/projects /projects
更新緩存
應用程序可以等待浏覽器自動更新緩存,也可以使用 Javascript 接口手動觸發更新。
浏覽器除了在第一次訪問 Web 應用時緩存資源外,只會在 cache manifest 文件本身發生變化時更新緩存。而 cache manifest 中的資源文件發生變化並不會觸發更新。
開發者也可以使用 window.applicationCache 的接口更新緩存。方法是檢測 window.applicationCache.status 的值,如果是 UPDATEREADY,那麼可以調用 window.applicationCache.update() 更新緩存。示范代碼如下。
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { window.applicationCache.update(); }
在線狀態檢測
如果 Web 應用程序僅僅是一些靜態頁面的組合,那麼通過 cache manifest 緩存資源文件以後,就可以支持離線訪問了。但是隨著互聯網的發展,特別是 Web2.0 概念流行以來,用戶的提交的數據漸漸成為互聯網的主流。那麼在開發支持離線的 Web 應用時,就不能僅僅滿足於靜態頁面的展現,還必需考慮如何讓用戶在離線狀態下也可以操作數據。離線狀態時,把數據存儲在本地;在線以後,再把數據同步到服務器上。為了做到這一點,開發者首先必須知道浏覽器是否在線。Html5 提供了兩種檢測是否在線的方式:navigator.online 和 online/offline 事件。
navigator.onLine 屬性表示當前是否在線。如果為 true, 表示在線;如果為 false, 表示離線。當網絡狀態發生變化時,navigator.onLine 的值也隨之變化。開發者可以通過讀取它的值獲取網絡狀態。
當開發離線應用時,通過 navigator.onLine 獲取網絡狀態通常是不夠的。開發者還需要在網絡狀態發生變化時立刻得到通知,因此 Html5 還提供了 online/offline 事件。當在線 / 離線狀態切換時,online/offline 事件將觸發在 body 元素上,並且沿著 document.body、document 和 window 的順序冒泡。因此,開發者可以通過監聽它們的 online/offline 事件來獲悉網絡狀態。
DOM Storage
在開發支持離線功能的 Web 應用時,開發者需要在本地存儲數據。當前浏覽器支持的 cookIE 雖然也可以用來存儲數據,但是 cookIE 長度非常小(通常幾 k),而且功能有限。因此,Html5 中新引入了 DOM Storage 機制,用於存儲 key/value 對,它的設計目標是提供大規模、安全且易用的存儲功能。
DOM Storage 分類
DOM Storage 分為兩類:sessionStorage 和 localStorage。除了以下區別外,這兩類存儲對象的功能是完全一致的。
DOM Storage 接口
每一個 Storage 對象都可以存儲一系列 key/value 對,Storage 接口定義為:
interface Storage { readonly attribute unsigned long length; getter DOMString key(in unsigned long index); getter any getItem(in DOMString key); setter creator void setItem(in DOMString key, in any data); deleter void removeItem(in DOMString key); void clear(); };
其中最常用的接口是 getItem 和 setItem。getItem 用於獲取指定 key 的 value,而 setItem 用於設置指定 key 的 value。
DOM Storage 示例
這裡給出一個使用了 sessionStorage 的例子,localStorage 的用法與它相同。首先使用 SetItem 添加了一個名為“userName”的項,它的值是“developerworks”。然後,調用 getItem 得到“userName”的值,並且彈出提示框顯示它。最後,調用 removeItem 刪除“userName”。
清單 6 DOM Storage 示例代碼
<!DOCTYPE HTML> <html> <body> <script> // 在 sessionStorage 中定義'userName'變量 sessionStorage.setItem('userName', 'developerworks'); // 訪問'userName'變量 alert("Your user is: " + sessionStorage.getItem('userName')); // 最後刪除'userName' sessionStorage.removeItem('userName'); </script> </body> </Html>
Web SQL Database
除了 DOM Storage 以外,Html5 中還有另外一種數據存儲方式 Web SQL Database。它提供了基本的關系數據庫功能,支持頁面上的復雜的、交互式的數據存儲。它既可以用來存儲用戶產生的數據,也可以作為從服務器獲取數據的本地高速緩存。例如可以把電子郵件、日程等數據存儲到數據庫中。Web SQL Database 支持數據庫事務的概念,從而保證了即使多個浏覽器窗口操作同一數據,也不會產生沖突。
Web SQL Database 基本用法
使用數據庫的第一步是創建並打開數據庫,API 是 openDatabase。當數據庫已經存在時,openDatabase 僅僅打開數據庫;如果這個數據庫不存在,那麼就創建一個空數據庫並且打開它。openDatabase 的定義是:
Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize, in optional DatabaseCallback creationCallback);
name:數據庫名。
version:數據庫版本。
displayName:顯示名稱。
estimatedSize:數據庫預估長度(以字節為單位)。
creationCallback:回調函數。
在打開數據庫以後,就可以使用事務 API transaction。每一個事務作為操作數據庫的原子操作,不會被打斷,從而避免了數據沖突。transaction 的定義是:
void transaction(in SQLTransactionCallback callback, in optional SQLTransactionErrorCallback errorCallback, in optional SQLVoidCallback successCallback);
callback:事務回調函數,其中可以執行 SQL 語句。
errorCallback:出錯回調函數。
successCallback:執行成功回調函數。
在事務的回調函數 callback 中,可以執行 SQL 語句,API 是 executeSQL。executeSQL 的定義是:
void executeSql(in DOMString sqlStatement, in optional ObjectArray arguments, in optional SQLStatementCallback callback, in optional SQLStatementErrorCallback errorCallback);
sqlStatement:SQL 語句。
arguments:SQL 語句需要的參數。
callback:回調函數。
errorCallback:出錯回調函數。
Web SQL Database 示例
下面通過一個例子說明 Web SQL Database 的基本用法。它首先調用 openDatabase 創建了名為“fooDB”的數據庫。然後使用 transaction 執行兩條 SQL 語句。第一條 SQL 語句創建了名為“foo”的表,第二條 SQL 語句向表中插入一條記錄。
清單 7 Web SQL Database 示例代碼
var db = openDatabase('fooDB', '1.0', 'fooDB', 2 * 1024); db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS foo (id unique, text)'); tx.executeSql('INSERT INTO foo (id, text) VALUES (1, "foobar")'); });
離線應用示例
最後,通過一個例子來說明使用 Html5 開發離線應用的基本方法。這個例子會用到前面提到的離線資源緩存、在線狀態檢測和 DOM Storage 等功能。假設我們開發一個便簽管理的 Web 應用程序,用戶可以在其中添加和刪除便簽。它支持離線功能,允許用戶在離線狀態下添加、刪除便簽,並且當在線以後能夠同步到服務器上。
這個程序的界面很簡單,如圖 1 所示。用戶點擊“New Note”按鈕可以在彈出框中創建新的便簽,雙擊某便簽就表示刪除它。
這個頁面的源文件是 index.Html,它的代碼如清單 8 所示。
<Html manifest="notes.manifest"> <head> <script type="text/javascript" src="server.JS"></script> <script type="text/Javascript" src="data.JS"></script> <script type="text/Javascript" src="UI.JS"></script> <title>Note List</title> </head> <body onload = "SyncWithServer()"> <input type="button" value="New Note" onclick="newNote()"> <ul id="list"></ul> </body> </Html>
在 body 中聲明了一個按鈕和一個無序列表。當按下“New Note”按鈕時,newNote 函數將被調用,它用來添加一條新的便簽。而無序列表初始為空,它是用來顯示便簽的列表。
定義 cache manifest 文件,聲明需要緩存的資源。在這個例子中,需要緩存“index.html”、“server.js”、“data.js”和“UI.JS”等 4 個文件。除了前面列出的“index.Html”外,“server.js”、“data.js”和“UI.JS”分別包含服務器相關、數據存儲和用戶界面代碼。cache manifest 文件定義如下。
CACHE MANIFEST index.Html server.js data.js UI.JS
用戶界面代碼定義在 UI.JS 中。
function newNote() { var title = window.prompt("New Note:"); if (title) { add(title); } } function add(title) { // 在界面中添加 addUIItem(title); // 在數據中添加 addDataItem(title); } function remove(title) { // 從界面中刪除 removeUIItem(title); // 從數據中刪除 removeDataItem(title); } function addUIItem(title) { var item = document.createElement("li"); item.setAttribute("ondblclick", "remove('"+title+"')"); item.innerHTML=title; var list = document.getElementById("list"); list.appendChild(item); } function removeUIItem(title) { var list = document.getElementById("list"); for (var i = 0; i < list.children.length; i++) { if(list.children[i].innerHtml == title) { list.removeChild(list.children[i]); } } }
UI.JS 中的代碼包含添加便簽和刪除便簽的界面操作。
數據存儲代碼定義在 data.JS 中。
var storage = window['localStorage']; function addDataItem(title) { if (navigator.onLine) // 在線狀態 { addServerItem(title); } else // 離線狀態 { var str = storage.getItem("toAdd"); if(str == null) { str = title; } else { str = str + "," + title; } storage.setItem("toAdd", str); } } function removeDataItem(title) { if (navigator.onLine) // 在線狀態 { removeServerItem(title); } else // 離線狀態 { var str = storage.getItem("toRemove"); if(str == null) { str = title; } else { str = str + "," + title; } storage.setItem("toRemove", str); } } function SyncWithServer() { // 如果當前是離線狀態,不需要做任何處理 if (navigator.onLine == false)return; var i = 0; // 和服務器同步添加操作 var str = storage.getItem("toAdd"); if(str != null) { var addItems = str.split(","); for(i = 0; i<addItems.length; i++) { addDataItem(addItems[i]); } storage.removeItem("toAdd"); } // 和服務器同步刪除操作 str = storage.getItem("toRemove"); if(str != null) { var removeItems = str.split(","); for(i = 0; i<removeItems.length; i++) { removeDataItem(removeItems[i]); } storage.removeItem("toRemove"); } // 刪除界面中的所有便簽 var list = document.getElementById("list"); while(list.lastChild != list.firstElementChild) list.removeChild(list.lastChild); if(list.firstElementChild) list.removeChild(list.firstElementChild); // 從服務器獲取全部便簽,並顯示在界面中 var allItems = getServerItems(); if(allItems != "") { var items = allItems.split(","); for(i = 0; i<items.length; i++) { addUIItem(items[i]); } } }
window.addEventListener("online", SyncWithServer,false);
data.JS 中的代碼包含添加便簽、刪除便簽和與服務器同步等數據操作。其中用到了 navigator.onLine 屬性、online 事件、DOM Storage 等 Html5 新功能。
在 data.JS 的最後一行,注冊了 window 的 online 事件處理函數 SyncWithServer。當 online 事件發生時,SyncWithServer 將被調用。其功能如下。
服務器相關代碼定義在 server.JS 中。
function addServerItem(title) { // 在服務器中添加一項 } function removeServerItem(title) { // 在服務器中刪除一項 } function getServerItems() { // 返回服務器中存儲的便簽列表 }
由於這部分代碼與服務器有關,這裡只說明各個函數的功能,具體實現可以根據不同服務器編寫代碼。