文章目錄
- Read
- Write
- Notification
- Identify
本文已經首發於InfoQ中文站,著作權,原文為《Dojo Data Store —— 統一資料提供者》,如需轉載,請務必附帶本聲明,謝謝。
InfoQ中文站是一個面向中高端技術人員的線上獨立社區,為Java、.NET、Ruby、SOA、敏捷、架構等領域提供及時而有深度的資訊、高端技術大會如QCon 、線下技術交流活動QClub、免費迷你書下載如《架構師》等。
無論在傳統的案頭應用還是在主流的互連網應用中,資料始終佔據著軟體應用中的核心地位。當下,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: function(/* Object */ keywordArgs)
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資料:
data = { 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:
var jsonStore = new dojo.data.ItemFileReadStore({data: data});
ItemFileReadStore比較適合於處理資料量較小的資料來源,資料來源可以是一個JSON檔案或者象本例一樣直接指定到用戶端記憶體中的一組資料。當你使用更加大型的JSON資料集時,可以使用JsonRestStore,採用Rest服務來進行資料提供。
接下來,我們來聲明一個DataGrid。在這裡DataStore是通過”store”屬性進行設定的。
<table jsid="grid" store="jsonStore" query="{name:’*'}" 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>
產生的DataGrid如所示:
由於Dojo中對資料展現層與資料中介層的松耦合,同樣一份資料來源可以在不進行任何處理的情況下為多個Widget提供資料,而且由於資料的過濾、排序、分頁都是根據資料擷取請求按需返回的,使用相同 DataStore的多個Widget間也不會產生衝突。下面我們就以同樣的DataStore,為一個dijit.form.ComboBox提供資料:
<input dojoType="dijit.form.ComboBox" store="jsonStore" searchAttr="name"></input>
在很多實際應用中,可能會使用不同的資料來源,下面,我們採用不同的資料格式,以XmlStore來替換ItemFileReadStore。首先將JSON資料轉換為XML資料格式:
<continents> <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中:
var xmlStore = new dojox.data.XmlStore({ 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設計充分達成了資料展現層與資料管理層之間的松耦合,同時統一的資料提供者使得對多種資料格式的應用以及程式移植都帶來了相當大的便利性。