本文翻译自: 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。
这是一个非常简单的常驻内存(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"; 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){ // 每次匹配成功时被调用 });
键值匹配提供了一种简单的查询机制,但是有时需要更复杂的查询。Memory store也接受函数来进行过滤,因此允许任意复杂查询。例如,查询所有价格低于10的产品:
store.query(function(product){ return product.price < 10; }).forEach(function(shoe){ // 每次匹配成功时被调用 });
我们也可以通过函数名来引用函数,其本质是引用store中同名的方法。
store.lessThanTen = function(product){ return product.price < 10; }); store.query("lessThanTen").forEach(function(shoe){ // 每次匹配成功时被调用 });
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){ // 使用someObject });
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){ // 该函数将在get()方法完成时被调用,不论返回一个promise还是 // 直接返回一个值 });
dojo.when()方法可以被应用到所有store方法上。
除了核心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({}); restStore = new dojo.store.JsonRest({target:"/Data/"}); 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封装器来增加对数据变化通知的支持。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添加监控功能 store = dojo.store.Observable(store); var listNode = dojo.byId("list"); var itemNodes = []; // 现在我们查询数据 var shoes = store.query({category:"shoe"}); // 然后渲染返回的数据 shoes.forEach(function(shoe){ // 渲染每一个节点 insertRow(shoe, itemNodes.length); }); // 现在我们监控结果集的变化 shoes.observe(function(object, removedFrom, insertedInto){ if(removedFrom > -1){ // 删除了的数据 dojo.destroy(itemNodes[removedFrom]); itemNodes.splice(removedFrom, 1); } if(insertedInto > -1){ // 新数据或是更新了的数据 insertRow(object, insertedInto); } }); function insertRow(product, index){ return itemNodes.splice(index, 0, dojo.create("li", {innerHTML: product.name + ": " + product.price}, listNode)); }
通过搭建起一个可以删除行和添加行的监控函数,我们基本上可以应对任何数据变化,包括添加、删除和更新。Observable模块甚至监控索引的更新,这使得如果结果集的排序顺寻发生变化了的话,更新的对象也可以被正确地移动到一个新索引指向的地方。同时请注意,在本例中,通过监听shoes结果集,我们只会获得符合条件的结果集中的相关更新。如果一个对象被更新、删除或是添加,并且它的category属性不是"shoe”,则没有通知时间会被发送给相应的监听器上。如果一个对象本来不是 “shoe”,之后被更新为一个"shoe”,这将触发一个向结果集添加数据的通知。如果一个对象本来是"shoe”在更新之后不再是”shoe”,这将触发一个从数据集中删除数据的通知。
Observable模块还会向store添加一个notify()方法。这对于Comet-driven的实时应用程序是非常有用的,因为它们会异步地从服务器接受更新并将其告知store(和所有store结果集上的监听器)。更多Dojo 1.6中Comet 和实时应用程序的相关细节请参见Dojo Socket 。
大多数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 API是一个常用的模式,所以很多库的接口可以很容易的与store API兼容。dojox.storage providers 的接口和store API就很类似,除了put()写法有一点细微区别。可以很容易的将该方法进行转换:
var storage = dojo.delegate(dojox.storage); var storage.put = function(object, options){ var deferred = dojo.Deferred(); dojox.storage.put(options.id || object.id, object, function(status){ if(status == dojox.storage.FAILED){ deferred.reject(status); }else if(status == dojox.storage.SUCCESS){ deferred.resolve(status); } }); return deferred; };
或者我们可以将Jens Arps StorageJS library 进行转换,其原本使用set()方法而不是put()方法:
var store = dojo.delegate(storage); var store.put = function(object, options){ return storage.set(options.id || object.id, object); };
StorageJS API还有一个allKeys()方法可以被转换成query()方法。
为了吸取dojo.data的精华并符合HTML5 IndexedDB标准,同时简化使用并使功能层次化,新的Dojo object store构架被进行了彻底的重构。令人振奋的是现在我们终于可以使用这一新的手段来搭建我们的应用程序。同时我们很期待Dojo社区对于该设计的宝贵意见。