概述
與傳統模式相比,面向服務的網際網路應用提供一種更加快速,友善的方式來釋出、處理資訊。其分布式及松耦合的特性也受到越來越多企業的青睐。随着企業的發展,應用程式的資料量往往呈幾何級規模快速膨脹,用戶端和伺服器端之間交換的資料格式也是多種多樣,常見的比如有 JSON,XML 等。伴随 Web2.0、RIA 的發展,在用戶端處理資料逐漸成為一種趨勢,但是基于 XMLHTTPRequest 的一般 Ajax 用戶端程式必須由 Web 開發人員自己編寫處理各種資料格式的代碼。這樣,不僅加重了用戶端邏輯的複雜性,而且降低了程式的可維護性和可擴充性。而 Dojo Data 庫旨在為不同的資料格式提供一種統一的資料通路模型,使得資料的讀寫都采用統一的接口,進而有利于程式的移植和維護。
本文将介紹 Dojo Data 庫的工作原理,及常用的 API,并結合一個具體的應用場景,介紹如何使用 Dojo Data 庫提供的 dojo.data.ItemFileReadStore 和 dojox.data.XmlStore 存儲庫來實作 Web 用戶端資料的擷取、分頁、排序、過濾查找等功能,最後簡要介紹了 Dojo Data 庫的 Write API。
回頁首
Dojo Data 工作原理
在富用戶端應用程式中,用戶端和伺服器端交換的資料格式多種多樣(比如 JSON,XML 等),是以,一個基于用戶端的統一資料通路層成為富用戶端應用程式的基礎。Dojo 提供了許多存儲庫來通路不同格式的資料,如 dojo.data.ItemFileReadStore(用來讀取 JSON 檔案),dojox.data.XmlStore,dojox.data.AtomReadStore,dojox.data.CsvStore,和 dojox.data.OpmlStore 等。Dojo Data 的目标是為不同的用戶端應用或 widget 等提供一個統一的資料通路接口,以達到互操作性的目的,而各種用戶端應用不需要了解各種諸如 JSON、XML、CSV 等資料的具體格式,通過統一的通路接口就能輕松的通路和操作資料。簡單的說,我們可以把 Dojo.Data 了解為位于 dojo.xhrGet() 之上的資料解析層。與之不同的是,Dojo.xhrGet() 隻負責異步的讀取資料,而 Dojo Data 還要将具體的資料解析成通用的資料通路模型(資料項及其屬性)。如下圖所示:
圖 1.Dojo Data 結構圖
Dojo Data 術語
與 JDBC 或者是 ODBC(它們屏蔽了底層資料庫的差異)不同,Dojo Data 是基于用戶端的資料通路層。盡管如此,我們可以列舉出很多 Dojo Data 和關系資料庫之間概念上的相似之處。如下表所示:
表 1.Dojo Data 與關系資料庫概念比較
Dojo Data 概念 | 對應的關系資料庫概念 | 描述 |
---|---|---|
DataStore | 遊标 | 從資料源讀取資料的 javaScript 對象,使得我們利用 Dojo Data API 存取資料時就像存取資料庫中的每一條記錄一樣。 |
Data source | 資料庫 | 資料源。一般說來,資料源可以是檔案,資料庫,web 服務等。 |
item | 行 | 一條具有屬性名值對的資料項 |
attribute | 列 | 一個資料項的屬性 |
value | -- | 一個資料項的屬性的值 |
reference | -- | 在一個資料項中指向另外一個資料項的值 |
identity | 主鍵 | 資料項的一個或多個屬性,用于唯一辨別該資料項 |
query | SQL Select 語句的 where 查詢子句 | 向資料源請求一個符合條件的資料集。該查詢條件建議用名值對的形式表示。 |
Dojo Data APIs | JDBC 或者是 ODBC | Datastore 的标準實作,它包含一系列的 API,例如讀和寫,一個 datastore 可以實作其中一個或者是多個 API |
internal data representation | --- | 一個私有的資料結構,datastore 用來在本地記憶體中緩存資料(例如 XML DOM 結點,匿名的 JSON 對象,或者是一些資料。) |
request | SQL Select 查詢 | 一系列用來修飾或者是排序資料的參數,包括查詢,排序,大小寫限制,或者是回調。 |
Dojo Data API
Dojo Data API 是一套設計良好的 API,它遵循着一定的設計原則:
- 資料通路被分解成獨立的 API,包括 read,write,identify,notifaction 等,因而不同的存儲庫可以選擇實作不同的 API,如下表所示:
表 2.Dojo Data API 清單
Dojo Data API | 描述 |
---|---|
Dojo.data.api.read | 提供讀取資料項或者其屬性值的功能,同時也用來搜尋,排序,和過濾資料。 |
Dojo.data.api.write | 提供建立,删除,更新資料項或者其屬性值的功能。然而并不是所有的應用和服務都提供更新資料的功能,比如說 Flickr,Google Map 等都是隻提供隻讀功能的應用。 |
Dojo.data.api.identify | 提供基于唯一的标示符來定位和查詢資料項的功能,然而并不是所有的資料格式都提供唯一的标示符。 |
Dojo.data.notification | 提供當 datastore 的資料項改變等事件發生時通知偵聽器的功能。最基本的事件包括資料的建立,修改和删除等。這對于那些需要不斷的輪詢伺服器來更新資料的應用來說,顯得非常重要。 |
在本文中将重點介紹 read API。
- 一般來說,資料項和其屬性的通路、修改、建立和删除都用 Dojo Data API 來實作,并不直接通路資料項對象裡的屬性。
回頁首
應用場景及示例程式
有了對 Dojo Data 的基本了解之後,本文将介紹如何利用 Dojo Data API 建立統一的資料通路模型。具體可以分為以下幾個方面:
- 建立針對于具體資料格式的存儲庫。例如,Dojo 提供了 ItemFileReadStore、XMLStore、CSVStore 等來分别處理 JSON、XML、CSV 等格式的資料。
- 利用 Dojo Data Read API 完成對資料模型的讀操作,常用的 API 包括:
表 3 常用 Read API
API | 描述 |
---|---|
fetch | 定義擷取資料後的處理邏輯,可以通過指定參數來實作資料的分頁、排序、查找過濾等 |
getAttributes | 獲得某資料項的所有屬性名 |
getValues | 獲得某資料項的所有屬性值 |
getValue | 獲得某資料項的某個屬性值 |
hasAttribute | 判斷該資料項是否包含某個屬性 |
- 利用 Dojo Data Write API 完成對資料模型的寫操作,常用的 API 包括:
表 4 常用 Write API
API | 描述 |
---|---|
newItem | 建立新的資料項 |
setValue | 更新資料項資訊 |
deleteItem | 删除資料項 |
下面,本文将結合一個具體的應用場景,介紹如何使用 Dojo Data 中已有的 dojo.data.ItemFileReadStore 存儲庫和 dojox.data.XmlStore 存儲庫來提供統一的資料通路模型,實作資料的擷取,分頁,排序,以及過濾查找。
在我們的應用場景中,某公司有兩個分公司:分公司 A 和分公司 B。由于曆史原因,分公司 A、B 都保留着各自獨立的員工管理系統,而他們又以 Web 服務的方式分别以 JSON 和 XML 的格式來提供資料。現在,我們需要提供一個統一的通路入口來分别展現兩個分公司的員工資訊。由于這兩個分公司系統以不同的格式(JSON 和 XML)來提供資料,是以我們可以利用 Dojo Data 所提供的統一資料通路 API,而不用關心後端分公司的内部資料格式。
圖 2. 應用場景圖示
回頁首
資料格式
一個員工記錄所包含的屬性如表 3 所示:
表 5. 員工記錄屬性
員工号 | 姓名 | 性别 | 出生日期 | 所屬部門 | 入職時間 | 上級主管人員 |
---|
分公司 A 以 JSON 的格式提供員工資料資訊,以下是從分公司 A 提供的 Web 服務中擷取的資料資訊片段:
清單 1 JSON 格式的員工資料資訊
|
分公司 B 以 XML 的格式提供員工資料資訊,以下是從分公司 B 提供的 Web 服務中擷取的資料資訊片段:
清單 2 XML 格式的員工資料資訊
|
回頁首
建立存儲庫
Dojo Data 提供了現成的存儲庫 dojo.data.ItemFileReadStore 用來讀取和解析 JSON 格式的資料。我們通過如下代碼建立一個存儲庫,用來處理分公司 A 的員工管理系統所提供的資料。
清單 3 建立存儲庫 dojo.data.ItemFileReadStore
|
其中,url 屬性指定了需要通路的資料資源的位址。因為浏覽器的跨域限制,我們需要通過伺服器端的代理來通路其他伺服器域的資料資源。http://host/ajaxproxy 是伺服器端代理的位址,通過參數 serviceurl 設定真實的外部伺服器域的位址。
Dojo Data 同時也提供了存儲庫 dojox.data.XmlStore 來處理 XML 格式的資料。我們可以通過如下代碼建立一個存儲庫,來處理分公司 B 的員工管理系統提供的資料。
清單 4 建立存儲庫 dojox.data.XmlStore
|
由于有了不同資料格式的存儲庫,而在不同存儲庫上的處理操作又都是按照 Dojo Data 定義的标準 Read API 來進行的。是以,接下來我們介紹的處理過程對于分公司 A 和分公司 B 都是一樣的。
回頁首
擷取資料
資料的擷取,對于一個資訊系統來說,是一個最基本也是最重要的一個功能。Dojo Data Read API 為異步擷取異構資料提供了很大的便利性和靈活性。通過 Dojo.Data 我們可以擷取資料源的所有資料。在本節中,我們将通過上述的應用場景來展示如何利用 Dojo Data 來讀取資料。
- API
簡單來講,異步的擷取資料是通過 fetch 方法來實作的。其中的異步操作是通過 onComplete 屬性所指定的回調方法來實作的,它定義了資料加載完畢用戶端的處理邏輯。
-
- fetch: function( keywordArgs)
異步擷取一個資料項集合。參數 keywordArgs 可以是 dojo.data.api.Request 的一個執行個體,也可以是包含一些特定參數的 JavaScript 對象,這些參數可以實作特定的行為,比如分頁,排序,查找等。
其中,onComplete 屬性指定了所有資料項擷取完畢後對應的回調函數。
-
- getAttributes: function( item)
傳回該資料項的所有屬性名。參數 item 指定了目前正在通路的資料項。
-
- getValue: function( item, attribute, defaultValue)
傳回該資料項目前屬性的屬性值。參數 item 指定了目前正在通路的資料項。attribute 指定了目前通路的屬性,而 defaultValue(可選)指定了目前屬性的預設值,若目前屬性沒有值,則将傳回該預設值。
- 示例
在這個示例中,我們将擷取某分公司的所有員工的基本資訊,并以表格的形式展現出來。
清單 5 擷取員工的基本資訊
|
以上代碼片段通過 fetch 方法擷取所有員工的基本資訊,然後回調 onComplete 對應的方法,用來渲染表格及加載資料。在加載資料時用到了 Dojo Data Read API 中的 getAttributes 方法和 getValue 方法,前者獲得目前資料項的所有屬性名,後者傳回目前資料項指定屬性的屬性值。
- 提高使用者體驗度
作為補充,這裡我們再介紹另外一種替代方案,即不用 onComplete 指定的回調方法,改用 onItem 指定回調方法,兩者的差別在于,onComplete 對應資料全部加載完畢之後用戶端的處理邏輯,而 onItem 對應每一個資料項加載完畢之後用戶端的處理邏輯。顯然,後者更加展現了異步的優勢,尤其是在網絡條件不好的情況下,用戶端不用等待所有資料加載完畢一起顯示,而是讀到一條顯示一條,最大程度的提高了使用者體驗度。當然,我們也可以兩者結合起來使用,此時,onComplete 對應的回調方法中的 items 參數會為空,但是可以在此方法中完成一些資料加載完畢後的額外資料處理邏輯。
回頁首
分頁
前面介紹了如何通過 Read API 的 fetch 方法查詢并獲得一個資料集合。但通常的情況是,因為應用程式界面設計的考慮,不希望一次展現所有傳回的資料集合。這時,我們往往需要通過分頁來提供更好的使用者體驗。分頁通常可在伺服器端實作,通過伺服器端腳本來控制分頁邏輯。而 Dojo Data 同樣提供了分頁機制,不同的是其分頁的處理是在用戶端。通過設定 Dojo Data Read API 中的 fetch 方法的相關分頁選項便能友善的實作分頁。
- API
簡單來講,分頁機制是通過在 fetch 方法中指定一個 start 參數和一個 count 參數來起作用的。Start 參數決定了 fetch 方法從那裡開始傳回資料項。資料集裡的第一個資料索引為 0。第二個參數 count 指定了從 start 參數指定的位置開始,要傳回的資料項的數量。預設情況下,如果 start 和 count 不指定,從第一個資料開始,一直到資料集合結束,傳回所有的資料項。
在如下的示例中,我們将用到如下 Read API:
fetch: function( keywordArgs)
異步地擷取一個資料項集合。
通過在 keywordArgs 中指定 start 和 count 屬性來控制分頁。
- 示例
在我們的示例中要求每頁顯示 10 個員工資訊,同時頁面上會顯示所有的跳轉到分頁面的連結,供使用者在各個頁面中擷取資料。
清單 6 分頁擷取員工資訊
|
在我們的示例代碼中用到了 onBegin 回調函數,它帶有 size 和 request 兩個參數,其中 size 傳回的是目前存儲庫中所有員工記錄的數量。在得知員工記錄總數量和每頁需要顯示的員工數量後,我們便可以計算出分頁的數量。最終帶有分頁功能的員工記錄展現界面如下:
圖 3. 帶分頁功能的界面
- 性能考慮
因為 Dojo Data 的分頁是一次性把 server 端傳回的資料格式作為一個存儲庫,然後再在用戶端進行具體的分頁操作,是以它并不适合處理超大量的資料。因為所有的資料都必須下載下傳到用戶端,這樣會降低用戶端的性能。如果是超大量的資料,并希望獲得分頁功能,最好的辦法還是在伺服器端來實作分頁邏輯。
回頁首
排序
上節中獲得的資料集合是無序的,然而,在我們的人事管理系統中,排序是一個不可或缺的功能,例如我們需要按照員工的名字進行排序,然後展示給終端使用者。
- API
一般說來,資料的排序是 fetch 方法中指定 sort 參數來完成的。Sort 參數不僅指定了要排序的字段,而且還必須指定排序的順序即升序還是降序。
fetch: function( keywordArgs)
異步地擷取一個資料項集合。
通過在 keywordArgs 中指定 sort 對象來控制排序。其中 sort 是一個包含 attribute 屬性和 descending 屬性的 JavaScript 對象數組。對于每一個對象,attribute 屬性用來指定要被排序的資料項屬性,descending 屬性指定按升序還是降序來排序。true 為降序,false 為升序。當指定了多個排序的對象時,排在前面的擁有較高的優先級。
- 示例
在本節的示例中,我們将以上節獲得的資料為基礎,并在此基礎上,實作能按照表格中的名字進行排序的功能。
清單 7 對資料進行排序
|
由上述代碼可知,當我們點選資料項中的名字列頭時,目前頁面會按照名字進行升序排序。當然,這裡的代碼隻是起一個示範作用,更複雜更全面的代碼,可參考 dojo.grid 的源碼實作。注意,如果某一個傳回的資料項在指定排序的 attribute 列上沒有值 (undefined),同時是按升序排序時,則該資料項将出現在所有被排序資料項的最底部。
回頁首
查找過濾
除了展現公司中所有的員工資訊,有時候我們還需要查找一部分特定的員工資訊。比如,所有研發部門的員工資訊,或所有 2005 年入職的員工資訊。為了滿足這個需求,我們可以在用戶端取出所有的員工資料,之後在一個循環中進行篩選過濾,最終得到我們所需要的資料。這個方法雖然可行,但顯得繁瑣。幸運的是,Dojo Data 提供了查找過濾的機制,利用這套機制,我們可以用最少的代碼來完成對所需資料的過濾。
- 設定查詢條件(query)
若要實作對資料的查詢過濾,我們首先要設定相應的查詢條件。Dojo Data 推薦使用 Javascript 對象來表述具體的查詢條件,其功能類似 SQL Select 語句中的 where 查詢子句,而且也同樣分為精确比對查詢和模糊比對查詢。
-
- 精确比對查詢
這種情況下,隻需将所要查詢的值賦給相應的屬性即可。以我們的應用場景為例,當查詢公司中所有研發部門的員工資訊時,需設定查詢條件:{dept : ”研發”}。
-
- 模糊比對查詢
這種情況下,需要使用通配符來描述所要查詢的值。Dojo Data 推薦使用兩種通配符:*(任意個數字元)和?(單個字元)
當查詢公司中所有 2005 年入職的員工資訊時,需設定查詢條件:{onboard : ”2005*”}。
對于多條件查詢的情況,這時各個查詢條件之間是“與”的關系。比如查詢公司中所有 2005 年入職的研發部門員工資訊時,需設定查詢條件:{dept: ”研發”,onboard :”2005*”}。
- 設定查詢配置(queryOptions)
在對資料進行查詢過濾時,可以使用 queryOptions 來設定一些針對于查詢的配置參數。目前,Dojo Data 支援兩種配置參數:
-
- ignoreCase
該參數定義了對資料進行比對查詢時,是否忽略大小寫。true 為忽略,false 為不忽略。
-
- deep
有時我們所查詢的資料是分級資料,這時 deep 參數定義了在查詢分級資料時,是否要需要查詢子結點。true 代表需要,false 代表不需要(這時僅對第一級結點進行查詢)。
- 示例
配合 Dojo Data API 的 fetch 方法,我們就可以實作對資料的查詢過濾,以下是查詢公司中所有 2005 年入職的研發部門員工資訊的示例代碼:
清單 8 對員工資料進行過濾查詢
|
回頁首
更新
為了使使用者能夠對資料進行更新操作,Dojo Data 提供了一套 Write API,用來建立、更改、删除資料。同 Read API 類似,Write API 的設計目标也是屏蔽底層資料存儲格式的差異,為使用者提供統一的資料通路 API。借助這些 API,使用者可以專注于業務層面的邏輯實作,而無需花費太多精力去關注底層資料的存儲格式。
那麼,Dojo Data 是如何實作資料的更新呢?首先,使用者利用 Dojo Data 提供的 Write API 對資料進行更新操作,這時,Dojo Data 并沒有将這種更新變化傳遞到伺服器端,而是将其儲存在本地記憶體中;其次,Dojo Data 利用 Dojo XMLHttpRequest 技術與伺服器進行異步通信,将使用者所做的操作傳遞給伺服器端,進而最終實作對伺服器端存儲檔案的更新。遺憾的是,目前很多 service 并不支援資料的更新操作。是以,Dojo Data 的 Write API 的應用不如 Read API 廣泛。基于這個原因,我們對具體的 Write API 隻作簡單的介紹。
- 建立新的資料項
newItem: function( keywordArgs, parentInfo)
-
- 描述
--- 傳回一條新的資料項
--- 參數 keywordArgs:javascript 對象,用來描述新建立資料項的屬性
--- 參數 parentInfo:可選。javascript 對象,定義目前建立資料項的父節點
-
- 示例
清單 9 建立新的資料項
|
- 更改資料項資訊
setValue: function( item, attribute, value)
-
- 描述
--- 更改資料項中指定屬性的值
--- 參數 item:被更改的資料項
--- 參數 attribute:被更改的屬性
--- 參數 value:更改後的值
-
- 示例
清單 10 更新資料項資訊
|
- 删除資料項
deleteItem: function( item)
-
- 描述
--- 删除某條資料項
--- 參數 item:将被删除的資料項
-
- 示例
清單 11 删除資料項
|
- 儲存資料
save: function( keywordArgs)
-
- 描述
--- 将本地的更新操作同步到伺服器上去
--- 運作結果将會被傳入到參數 keywordArgs 所定義的回調函數中:onComplete: function(),onError: function(errorData)--- 參數 attribute:被更改的屬性
-
- 示例
清單 12 儲存資料
|
- 資料復原
revert: function()
-
- 描述
--- 丢棄沒有儲存的更新,復原到上一次儲存時的狀态
-
- 示例
清單 13 資料復原
|
回頁首
總結
本文通過一個具體的應用場景,介紹了 Dojo Data 的使用方法。通過閱讀本文讀者能夠了解 Dojo Data 庫的工作原理以及使用 Dojo 提供的存儲庫來進行統一的資料模型應用開發。