Dojo Data Store——統一數據訪問接口
無論在傳統的桌面應用還是在主流的互聯網應用中,數據始終占據著軟件應用中的核心地位。當下,web2.0已經是一個讓人們耳熟能詳的詞匯,而由此帶來的數據的開放與共享,引領我們走入了海量數據時代。在今天的互聯網上,數據的交互幾乎成為了我們的終極訴求,可隨之而來的數據多樣性,信息的分布式存儲及松耦合,以及數據量的幾何級規模的膨脹也帶來了數據組織上的難度的增大,與此同時,伴隨著Ajax, RIA及面向服務的網絡應用的發展,其所要求的客戶端數據處理邏輯的復雜性不斷增加,使得開發難度不斷加大。
出于簡化數據處理邏輯,增加應用的可維護及可擴展性的需求,目前流行的JavaScript框架也基本都會具有各自的數據處理模塊或接口。本文的目的就是為了介紹Dojo的數據處理模塊:Dojo.data。作為Dojo的數據處理中間層,其主要的職責就是解析及管理由數據源傳入的各種類型的數據,通過統一的數據訪問與處理接口與數據展現層(Dojo Widget)進行通訊,便于各個Widget的管理與程序的移植。
Dojo Data中的數據管理
在面向服務應用大行其道的今天,協調數據的多樣性是開發互聯網應用中不可避免的首要問題。我們常見的數據格式包括Json, XML, Csv等,作為數據處理的中間層,能夠讓用戶以統一的接口連接不同的數據源是一個基本需求。在Dojo.data模塊中,預定義了不同的DataStore用于訪問管理不同數據格式的數據源,而所有的DataStore都會實現相同的數據訪問接口,這樣就可以成功實現數據提供層與數據展現層之間的松耦合。表1中列出了Dojo中部分已實現的各種不同的DataStore。
表1. Dojo中部分已實現的DataStore
DataStore | 描述 |
dojo.data.ItemFileReadStore | 用于JSON數據的只讀的DataStore |
dojo.data.ItemFileWriteStore | 用于JSON數據的可讀寫的DataStore |
dojox.data.CsvStore | 用于CVS數據的只讀的DataStore |
dojox.data.OpmlStore | 用于OPML(Outline Processor Markup Language)數據的只讀的DataStore |
dojox.data.HtmlTableStore | 用于HTML table數據的只讀的DataStore |
dojox.data.XmlStore | 用于XML數據的可讀寫的DataStore |
dojox.data.FlickrStore | 用于讀取flickr.com提供的數據的只讀的DataStore。是一個很好的web service相關的DataStore的示例 |
dojox.data.QueryReadStore | 用于讀取由服務器端提供的JSON數據的只讀的DataStore |
盡管讀取的數據源多種多樣,但在DataStore中,通過統一數據訪問接口,對數據的組織管理是一致的。每條數據項都被作為一個item對象,其中包含了一定的鍵(attribute)值(value)對用以對應數據條目中的各個屬性值。下面以一段簡單的JSON數據片段為例,來介紹這種對應關系:
identifier: 'id',
label: 'name',
items: [
{ "id": "AF", "name":"Africa", "type":"continent",
"population":"900 million", "area": "30,221,532 sq km" },
{ "id": "AS", "name":"Asia", "type":"continent",
"population":"1 billion", "area": "25,428,192 sq km" }
]
}
在這段JSON數據中共有兩條數據項(item),分別都包含有"id", "name", "type", "population"與"area"五個屬性字段。
Dojo.data 組織架構
為了符合各種應用中對數據中間層的不同需求,Dojo.data包對數據訪問處理接口進行了一定程度的劃分,包括 read,write,identify,notifaction 等。各種DataStore可以根據其應用需求實現特定的接口。
表2. Dojo.data.api主要接口
Dojo.data主要接口 | 描述 |
Dojo.data.api.read | 提供讀取數據項或者其屬性值的功能,同時也支持對數據集的搜索,排序,和過濾。 |
Dojo.data.api.write | 提供創建,刪除,更新數據項功能。 |
Dojo.data.api.identify | 提供基于唯一的標示符來定位和查詢數據項的功能。 |
Dojo.data.api.notification | 提供當 datastore的數據項改變等事件發生時通知偵聽器的功能。最基本的事件包括數據的創建,修改和刪除等。這也是Dojo.data的一項很重要的功能,通過此接口可以將數據展現層與數據中間層更好的分離開來。 |
Dojo.data API簡介
Read
數據的獲取是數據中間層的核心,Dojo.data.Read接口為異步獲取異構數據提供了很大的便利性和靈活性。在Read接口中,主要是通過異步方式進行數據的獲取,同時也提供了數據的排序、分頁、簡單查詢等基本功能的支持。
fetch方法可以說是Dojo.data包的核心方法,它主要采用異步方法來獲取數據。該方法接收一個鍵值對對象參數,用戶可以通過對此參數中各個屬性進行指定以獲取特定的數據集合,如分頁,簡單查詢過濾,排序等。以下是部分主要的參數屬性介紹:
- onBegin與onComplete: fetch方法是采用異步的方式來進行數據的獲取,用戶可以通過onBegin與onComplete這兩個參數指定fetch方法的數據獲取回調函數,onBegin在數據返回前會被調用一次,傳入兩個參數,分別為應返回數據集的條目數及此次fetch的request對象;而onComplete方法是作為數據返回的回調函數,數據集作為第一個參數傳入給該回調函數。
- start與count: 通常來說幾乎所有的實際應用都會要求分頁返回數據以提供更好的用戶體驗,start和count這兩個屬性就是為支持分頁功能而實現的。start用于指定返回數據的起始索引(由0開始),而count則用于設置返回的數據條目數。
- query: 除了分頁以外,按需返回特定的數據集也是一項重要功能,在Dojo.data中,這一功能則是通過query屬性提供支持的。query的值一般可設置為一個鍵值對對象,“鍵”應被設置為數據條目中的某項屬性,而“值”則為條件指定。Dojo.data提供了精確匹配與模糊匹配(通配符:*為任意字符,?為單個字符)兩種方式對數據進行過濾,可以根據具體情況選擇使用。
- sort:由于可能出現多個Widget使用同一個DataStore,數據集并不會以特定的序列進行存儲,當需要進行排序時,可以通過sort屬性進行指定,DataStore則會相應的返回符合條件的數據集。sort 參數不僅指定了要排序的字段,而且還必須指定排序的順序即升序還是降序。
dataStore.fetch({
// 設置獲取數據的起始位置
start: 0,
// 設置獲取數據的條目數
count: 25,
// 設置模糊過濾條件
query: {'name': *},
// 數據排序設定
sort: [{ attribute: 'name', descending: false }],
// 設置開始數據獲取的回調函數
onBegin: function(size, requestObj){...},
// 設置數據獲取完成后的回調函數
onComplete: function(items, requestObj){...},
// 設置數據獲取失敗后的回調函數
onError: function(error, requestObj){...}
}); - getValue: function(/*item*/item, /*attribute-name-string*/attribute, /*value?*/ defaultValue)
用于獲取某個給定的數據項的某個屬性值,如果該條數據不含有指定的屬性,則返回一個指定的默認值。item參數為給定的數據項,attribute參數為指定的屬性字段,defaultValue為可選參數。
var value = dataStore.getValue(item, 'name', 'no name');
- getAttributes: function(/* item */ item)
獲取給定數據項的所有屬性字段,返回值為一個數組。
Write
Dojo.data.Wirte接口主要提供了數據的更新功能API,包括創建、刪除、更新數據。同 Read 接口類似,Write API 的設計目標也是屏蔽底層數據存儲格式的差異,為用戶提供統一的數據訪問 API。借助這些 API,用戶可以專注于業務層面的邏輯實現,而無需花費太多精力去關注底層數據的存儲格式。
- newItem: function(/*Object?*/ keywordArgs, /*Object?*/ parentInfo)
在DataStore中新創建一個數據項。第一個參數為一個鍵值對對象,用于設定新創建的數據項,第二個參數為可選參數,當用戶想將新創建的數據項作為某個已存在的數據項的子,則可以通過這個參數進行設定。具體應用請參照下面的小示例:
var euItem = {"id": "EU", "name":"Europe", "type":"continent", "children": [] }
// 新建數據項
dataStore.newItem(euItem);
// 新建子數據項
dataStore.newItem({"id": "GM", "name":"Germany", "type":"country"}, {parent: euItem, attribute: "children"}); - deleteItem: function(/*item*/ item)
在DataStore中刪除指定的數據項。
- setValue: function(/*item*/ item, /*string*/ attribute, /*almost anything*/value)
更新某條給定數據項的某個屬性值。
Notification
當DataStore中有數據更新時,相應的Notification中定義的監聽函數就會被調用。使用過Dojo的讀者可 能都會注意到,在Widget中一般不會有new、delete等其他JavaScript庫控件中常見的API。這是因為Dojo data的設計是力求將數據層與表現層進行分割,對數據的操作都集中在數據層進行控制,而數據集的改變也能夠自動的在應用控件上進行反映,這一功能就是當DataStore在進行數據更新操作時,通過Notification接口的通知作用實現的。
- onNew: function(/*item*/ newItem, /*object?*/ parentInfo)
當DataStore中創建新數據項操作成功后被自動調用。newItem參數就是新創建的數據項對象,parentInfo是可選參數,用于描述新創建數據項的父數據項。
- onDelete: function(/*item*/ deletedItem)
當DataStore中刪除某項數據項后被自動調用。deletedItem參數就是被刪除的數據項對象。
- onSet: function(/*item*/ item, /*attribute-name-string*/ attribute, /*object | array*/ oldValue, /*object | array*/ newValue)
在DataStore的某項數據項被更新后進行調用。四個參數分別為數據項對象,被更新數據項屬性,該數據的原有值以及更新后的值。
Identify
很多數據源都會為數據提供唯一的標識符,Dojo.data.Identify接口則提供了基于唯一標識符進行數據獲取定位的API支持。
- fetchItemByIdentity: function(/*object*/ keywordArgs)
同Read接口中的fetch方法類似,此方法也是一個異步方法,用戶需要在參數對象中指定數據項獲取后的回調處理函數。keywordArgs參數是一個鍵值對對象,主要需要包括兩個屬性,一個是要進行指定獲取的數據項標識符identify,另一個則是回調處理函數onItem。在指定identify的數據項獲取成功后,onItem回調函數則會被自動調用,以處理后續操作。
dataStore.fetchItemByIdentity({
// 指定要進行獲取的數據項的id
identity: "AS",
// 設定數據返回后的回調函數
onItem: function(item){…},
// 設定錯誤回調函數
onError: function(error){…}
}); - getIdentity: function(/*item */ item)
此方法用于獲取給定數據項的標識符。
DataStore應用
一般來說,Dijit中的各個小部件都提供了對DataStore的支持,當我們在使用某個Widget來進行數據展現時,通常我們只需要根據數據源的格式類型來選擇好DataStore,然后在Widget聲明中對DataStore進行指定就可以了。下面我們就通過DataGrid及ComboBox作為數據展現UI,基于不同的數據格式為它們設置不同的DataStore。
以下是一份JSON數據:
identifier: 'id',
label: 'name',
items: [
{ "id": "AF", "name":"Africa", "type":"continent",
"population":"900 million", "area": "30,221,532 sq km" },
{ "id": "AS", "name":"Asia", "type":"continent",
"population":"1 billion", "area": "25,428,192 sq km" },
{ "id": "OC", "name":"Oceania", "type":"continent",
"population":"21 million", "area": "15,928,294 sq km" },
{ "id": "EU", "name":"Europe", "type":"continent",
"population":"56 million", "area": "25,928,294 sq km" },
{ "id": "NA", "name":"North America", "type":"continent",
"population":"100 million", "area": "90,928,294 sq km" },
{ "id": "SA", "name":"South America", "type":"continent",
"population":"102 million", "area": "78,928,294 sq km" },
{ "id": "AN", "name":"Antarctica", "type":"continent",
"population":"998", "area": "102,928,294 sq km" }
]};
在這里,我們采用比較簡單的dojo.data.ItemFileReadStore:
ItemFileReadStore比較適合于處理數據量較小的數據源,數據源可以是一個JSON文件或者象本例一樣直接指定到客戶端內存中的一組數據。當你使用更加大型的JSON數據集時,可以使用JsonRestStore,采用Rest服務來進行數據提供。
接下來,我們來聲明一個DataGrid。在這里DataStore是通過”store”屬性進行設置的。
<thead>
<tr>
<th field="name" width="auto">Name</th>
<th field="population" width="auto">Population</th>
<th field="area" width="auto">Area</th>
</tr>
</thead>
</table>
生成的DataGrid如下圖所示:
由于Dojo中對數據展現層與數據中間層的松耦合,同樣一份數據源可以在不進行任何處理的情況下為多個Widget提供數據,而且由于數據的過濾、排序、分頁都是根據數據獲取請求按需返回的,使用相同 DataStore的多個Widget間也不會產生沖突。下面我們就以同樣的DataStore,為一個dijit.form.ComboBox提供數據:
在很多實際應用中,可能會使用不同的數據源,下面,我們采用不同的數據格式,以XmlStore來替換ItemFileReadStore。首先將JSON數據轉換為XML數據格式:
<continent>
<name>Africa</name>
<population>900 million</population>
<area>30,221,532 sq km</area>
</continent>
<continent>
<name>Asia</name>
<population>1 billion</population>
<area>25,428,192 sq km</area>
</continent>
<continent>
<name>Oceania</name>
<population>21 million</population>
<area>15,928,294 sq km</area>
</continent>
<continent>
<name>Europe</name>
<population>56 million</population>
<area>25,928,294 sq km</area>
</continent>
<continent>
<name>North America</name>
<population>100 million</population>
<area>90,928,294 sq km</area>
</continent>
<continent>
<name>South America</name>
<population>102 million</population>
<area>78,928,294 sq km</area>
</continent>
<continent>
<name>Antarctica</name>
<population>998</population>
<area>102,928,294 sq km</area>
</continent>
</continents>
XmlStore是一個客戶端的數據存儲器,用于讀取XML數據源。它由Dojo官方提供并包含在DojoX子項目中。XmlStore為基本的XML數據(一種常用的數據交換格式)提供讀/寫接口。XmlStore可以用于一般的XML文檔,因此非常有用。存儲器的設計是你可以通過覆蓋其部分方法來自定義讀/寫數據的行為。下面的示例給出了如何創建XmlStore并將其應用到Grid及ComboBox中:
url: ‘continents.xml’,
label: ‘name’
});
<table jsid="grid" store="xmlStore" dojoType="dojox.grid.DataGrid"class="grid">
<thead>
<tr>
<th field="name" width="auto">Name</th>
<th field="population" width="auto">Population</th>
<th field="area" width="auto">Area</th>
</tr>
</thead>
</table>
<input dojoType="dijit.form.ComboBox" store="xmlStore" searchAttr="name">
我們幾乎不需要修改關于Grid和ComboBox的任何代碼,就能讓它們繼續工作。唯一需要做的改動,就是聲明一個數據源,并將它設置為grid的輸入。我們不需要操心任何關于數據獲取、解析、以及管理的事情,數據存儲器的API做了所有的工作。
可以看出,作為數據中間層,Dojo.data通過優秀的API設計充分達成了數據展現層與數據管理層之間的松耦合,同時統一的數據訪問接口使得對多種數據格式的應用以及程序移植都帶來了相當大的便利性。