本文翻譯自: Dojo Object
Stores
原作者: Kris Zyp
翻譯: Siqi
Dojo 1.6推出了一套新的名為Dojo Object Store的data store API。 這套基於HTML5 IndexedDB object
store API
的新store API旨在大大簡化Dojo store的互動和構建。
這套新的API遵循HTTP/REST命名規範,並與dojox.storage
providers
(local storage, cookie storage, 和 WebSQL
storage適用)以及其他所有符合這些開放標準的庫相容。
以下是這套新store API的幾條核心理念:
Dojo Object Store
API是一套介於不同的資料提供者和資料消費者之間的介面。使用者可以使用這套介面實現任意store,而Dojo核心庫本身配置了兩個常用的核心store——dojo/store/Memory和dojo/store/JsonRest。
簡單的: dojo/store/Memory
這是一個非常簡單的常駐記憶體(in-memory)store。它對於快速建立一個store是非常有用的,
特別是對於較小的資料集。只要簡單地提供一個數組作為資料來源,便可以建立出一個Memory
store,之後你便可以開始對該store進行查詢和互動。(更多使用細節請參見Memory object store
documentation
)
Memory store是一個同步模式的store,也就是說它直接傳回值,這令它使用起來非常簡單。例如:通過id來獲得一個對象:
var product = productStore.get("slinky");
需要再次強調的是,這個新的object store返回的是簡單的對象,所以我們可以很容易的擷取其中的屬性:
var name = product.name;
使用簡單對象也使得更新store非常簡單:
product.name = "New name";<br />productStore.put(product);
改進過的查詢功能是Dojo object
stores的新功能之一。查詢通過使用query()方法來實現,而返回的結果集提供一系列方便的迭代方法(interative
methods)——與dojo.query非常相似——同步模式和非同步模式的sotre都提供此功能。因此我們可以使用forEach,map,或是filter方法來操作結果集。Memory
Store支援多種形式的查詢。首先,我們可以通過索引值匹配(name-value
matches)來進行查詢(和Dojo.data中的ItemFileReadStore一樣)。以下我們根據category進行查詢:
store.query({category:"shoe"}).forEach(function(shoe){<br />// 每次匹配成功時被調用<br />});
索引值匹配提供了一種簡單的查詢機制,但是有時需要更複雜的查詢。Memory
store也接受函數來進行過濾,因此允許任意複雜查詢。例如,查詢所有價格低於10的產品:
store.query(function(product){<br />return product.price < 10;<br />}).forEach(function(shoe){<br />// 每次匹配成功時被調用<br />});
我們也可以通過函數名來引用函數,其本質是引用store中同名的方法。
store.lessThanTen = function(product){<br />return product.price < 10;<br />});<br />store.query("lessThanTen").forEach(function(shoe){<br />// 每次匹配成功時被調用<br />});
JSON 和REST: dojo/store/JsonRest
JsonRest
假定有一套伺服器端的API存在,且該套API旨在與store進行互動。它實現了一個健壯的、符合標準的HTTP/REST用戶端介面。dojo/store/JsonRest遵循REST高擴充性的原則,非常適合大資料集。JsonRest是一個非同步模式的store,其所有非同步方法呼叫返回promise(有一個列外:getIdentity方法永遠是同步的)。
Json Rest object
store與HTTP相容伺服器互動的方式與dojox.data.JsonRestStore十分相似。然而,JsonRest在store
API重構的過程中已經得到了極大的簡化。簡單地提供一個連結到伺服器的URL便可以建立出一個JsonRest store(更多使用細節請參見JsonRest object store
documentation
):
store = new dojo.store.JsonRest({target:"/Data/"});
store的方法十分直觀,與HTTP方法相對應。store.get(“some-id”)會發送一個GET請求到/Data/some-id並返回一個promise/Deferred作為結果。例如:
store.get("some-id").then(function(someObject){<br />// 使用someObject<br />});
store.remove(id)則會對應地發送一個DELETE請求。add(object)和put(object)也會同樣觸發相應的請求。如果傳給put(object)(或add(object))的object帶有identity屬性,則將會發送一個PUT請求。如果object不含有id或是第二個參數(options)包含一個值為true的incremental屬性,則將會發送一個POST請求。
如果options.overwrite為true的話,該請求會包含一個If-Match: *
header,若options.overwrite為false或是使用add(object)的話,則會包含一個If-None-Match:
*
header。這樣的伺服器之間的通訊一般在建立或是更改對象時發生。
同步與非同步標準化
如果你在寫一個同步非同步皆有的store的話,我們推薦使用dojo.when()。由於then()方法僅當store方法是非同步時才有效,因此這種情況下它並不值得完全依賴,我們可以使用dojo.when(),任何返回的值都將被恰當的處理。
dojo.when(store.get(id), function(object){<br />// 該函數將在get()方法完成時被調用,不論返回一個promise還是<br />// 直接返回一個值<br />});
dojo.when()方法可以被應用到所有store方法上。
用戶端資料緩衝: dojo/store/Cache
除了核心store的實現,Dojo還配備了兩個store封裝器(wrappers)。第一個是dojo/store/Cache。這個封裝器需要與兩個store一起使用:一個caching
store和一個master store。一個典型的Cache封裝器使用情境是將JsonRest作為master store, 將Memory
store作為用戶端的caching store。這使得你可以利用JsonRest store來與伺服器進行通訊,使用Memory
store來進行緩衝來避免不必要的HTTP請求。下面有一個例子來說明我們如何將它搭建起來:
memoryStore = new dojo.store.Memory({});<br />restStore = new dojo.store.JsonRest({target:"/Data/"});<br />store = new dojo.store.Cache(restStore, memoryStore);
現在我們可以使用我們整合了的store來執行一個查詢。下面我們將查詢所有的objects(我們可以省略查詢條件來查詢所對象):
var results = store.query();
這將使得返回結果被緩衝在memory store中。之後我們可以通過get()方法來擷取一個object而不需要額外發送一個HTTP請求:
object = store.get("some-id");
通過put(),add()和remove()方法對資料進行的改變都將反應到被緩衝的資料中。查詢通常要求細粒度的應用程式來控制哪些資料需要被緩衝,哪些不需要。因此,Cache
store不會嘗試自動查詢快取。但是,如果你選擇使用緩衝進行查詢的話,也沒有問題。可以簡單地查詢caching
store,也就是我們例子中的memoryStore:
memoryStore.query({category:"shoe"}).forEach(…);
監控資料變化: dojo/store/Observable
Dojo還配備了一個store封裝器來增加對資料變化通知的支援。Dojo object store API和遺留的Dojo Data
API的通知機制十分不同。舊的API存在一個問題,通知是store層面的,因此要決定一個事件如何真正地影響一個渲染好的資料集是不可能的。
Dojo object
store通過將通知事件的監控綁定到查詢的結果集上而不是store解決了這個問題。通過dojo/store/Observable模組,你可以封裝一個store,而封裝後的store得到的查詢結果集都是“可監控的(observable)”。也就是說,query()方法返回的對象/數組/promise都有一個可以被用來監視結果集變化的observe()方法。參見Observable store wrapper
documentation for the exact signature of the observe() method and
callback
。
Observable
模組使得渲染一個結果集並即時根據底層資料的變化對介面進行更新變得非常容易。讓我們來看一個例子。我們將根據儲存的對象建立一個無序列表(<ul>)。首先,建立列表,然後我們將根據資料的變化做出反饋:
// 首先向我們的store添加監控功能<br />store = dojo.store.Observable(store);<br />var listNode = dojo.byId("list");<br />var itemNodes = [];<br />// 現在我們查詢資料<br />var shoes = store.query({category:"shoe"});<br />// 然後渲染返回的資料<br />shoes.forEach(function(shoe){<br />// 渲染每一個節點<br /> insertRow(shoe, itemNodes.length);<br />});<br />// 現在我們監控結果集的變化<br />shoes.observe(function(object, removedFrom, insertedInto){<br />if(removedFrom > -1){ // 刪除了的資料<br /> dojo.destroy(itemNodes[removedFrom]);<br /> itemNodes.splice(removedFrom, 1);<br />}<br />if(insertedInto > -1){ // 新資料或是更新了的資料<br /> insertRow(object, insertedInto);<br />}<br />});<br />function insertRow(product, index){<br />return itemNodes.splice(index, 0, dojo.create("li",<br />{innerHTML: product.name + ": " + product.price}, listNode));<br />}<br />
通過搭建起一個可以刪除行和添加行的監控函數,我們基本上可以應對任何資料變化,包括添加、刪除和更新。Observable模組甚至監控索引的更新,這使得如果結果集的排序順尋發生變化了的話,更新的對象也可以被正確地移動到一個新索引指向的地方。同時請注意,在本例中,通過監聽shoes結果集,我們只會獲得合格結果集中的相關更新。如果一個對象被更新、刪除或是添加,並且它的category屬性不是"shoe”,則沒有通知時間會被發送給相應的監聽器上。如果一個對象本來不是
“shoe”,之後被更新為一個"shoe”,這將觸發一個向結果集添加資料的通知。如果一個對象本來是"shoe”在更新之後不再是”shoe”,這將觸發一個從資料集中刪除資料的通知。
Observable模組還會向store添加一個notify()方法。這對於Comet-driven的即時應用程式是非常有用的,因為它們會非同步地從伺服器接受更新並將其告知store(和所有store結果集上的監聽器)。更多Dojo
1.6中Comet 和即時應用程式的相關細節請參見Dojo
Socket
。
與現存的Widget和Store協同工作
大多數Dijit widgets仍然基於遺留的Dojo Data API。但是,dojo配置了一個適配器(adapter)讓使用者可以在基於Dojo
Data的widget上使用新的object store。dojo/data/ObjectStore
模組作為一個是配置器接受一個object
store並返回一個data store。Dojo還配置了一個可以讓使用object store的widgets相容遺留的data store的適配器。dojo/store/DataStore
模組接受一個data store並返回一個object store。
層級結構
object store API定義了一個用來實現層級結構的方法——getChildren(object, options)。getChildren
一般被一個父object調用,並返回其子項目集。getChildren的實現一般根據應用程式的需求來定製,但是也有很多常用的實現方式:
與其他Store相容
因為store API是一個常用的模式,所以很多庫的介面可以很容易的與store API相容。dojox.storage providers
的介面和store API就很類似,除了put()寫法有一點細微區別。可以很容易的將該方法進行轉換:
var storage = dojo.delegate(dojox.storage);<br />var storage.put = function(object, options){<br />var deferred = dojo.Deferred();<br /> dojox.storage.put(options.id || object.id, object, function(status){<br />if(status == dojox.storage.FAILED){<br /> deferred.reject(status);<br />}else if(status == dojox.storage.SUCCESS){<br /> deferred.resolve(status);<br />}<br />});<br />return deferred;<br />};
或者我們可以將Jens Arps StorageJS
library
進行轉換,其原本使用set()方法而不是put()方法:
var store = dojo.delegate(storage);<br />var store.put = function(object, options){<br />return storage.set(options.id || object.id, object);<br />};
StorageJS API還有一個allKeys()方法可以被轉換成query()方法。
Object Stores
為了吸取dojo.data的精華並符合HTML5 IndexedDB標準,同時簡化使用並使功能層次化,新的Dojo object
store構架被進行了徹底的重構。令人振奮的是現在我們終於可以使用這一新的手段來搭建我們的應用程式。同時我們很期待Dojo社區對於該設計的寶貴意見。