翻译 - 【Dojo Tutorials】Real-time Stores
基于实时存储的web应用程序给用户一定程度的及时性而不是传统应用的体验,数据一旦变化用户立即就能看到。Dojo的对象存储接口,是Dojo应用中数据模型的基石,被设计为支持数据的实时更新。本教材中将会讲到如何利用通知系统与实时组件的相互作用。
开始
本教程将会用到我们曾经在介绍Dojo对象存储与数据模型指南中学习的知识。在数据模型指南中我们学习了如何为查询的结果构建一个视图渲染器及利用observe()监视数据变化。我们可以调用存储的query()方法获取返回的查询结果集合;从这,我们可以在返回结果上调用forEach来迭代结果集合。我们也可以在返回结果上调用observe()来监听变化。下面是一个实现的例子:
1 require([ 2 "dojo/dom", 3 "dojo/dojo/dom-construct" 4 ], function(dom, domConstruct) { 5 function viewResults(results) { 6 var container = dom.byId("container"); 7 var rows = []; 8 9 // functions called within observe callback below 10 function addRow(market, i) { 11 // insert row into DOM, and also into our internal array 12 rows.splice(i, 0, domConstruct.create("div", { 13 innerHTML: market.name + "index: " + market.index.toFixed(2) + " at: " + market.date.toLocaleTimeString() 14 }, container, i)); 15 } 16 17 function removeRow(i) { 18 // remove row from DOM and array (splice returns the removed items) 19 domConstruct.destroy(rows.splice(i, 1)[0]); 20 } 21 22 // add initial items, and handle future changes 23 results.forEach(addRow); 24 results.observe(function(market, removeFrom, insertedInto) { 25 // this will be called any time a market is added, removed, or updated 26 if(removedFrom > -1) { 27 removeRow(removedFrom); 28 } 29 if(insertedInto > -1) { 30 addRow(market, insertedInto); 31 } 32 }, true); // we can indicate to be notified of object updates as well 33 } 34 35 //... 36 37 var results = marketStore.query({}); 38 viewResults(results); 39 });
从一个存储监视数据至关重要的是observe()方法,这是查询结果的一个方法。observe方法的回调函数有三个参数:
- object:被修改的数据对象
- removedFrom:对象在删除或修改之前在结果集合中的索引。removedFrom为-1表示是像结果集合中加入了数据。
- insertedInto:对象在添加到结果集合中或被修改了之后在集合应有的索引值,-1则表示有对象被从结果集合中删除了
observe方法属于结果集合因为通知的意义是将结果结合作为上下文的。一个通知并非一定是向结果集合中添加了数据,也可以是更新动作。删除也一样,更新或删除都会有removedFrom传递。
数据变化通知的功能对于任意存储都是可用的,为结果集合提供了一个observe方法。早些时候是通过为存储包一层Observable方法来实现的。下面的例子,我们将构建一个数据集,实例化一个内存存储,并使用dojo/store/Observable包裹它:
1 require([ 2 "dojo/store/Memory", 3 "dojo/store/Observable" 4 ], function(Memory, Observable) { 5 var data = [ 6 {"name": "Dow Jones", "index": 12197.88, "date": new Date()}, 7 {"name": "Nasdap", "index": 2730.68, "date": new Date()}, 8 {"name": "S&P 500", "index": 1310.19, "date": new Date()} 9 ]; 10 11 // create the store with the data 12 marketStore = new Memory({data: data, idProperty: "name"}); 13 // wrap the store with Observable to make it possible to monitor: 14 marketStore = Observable(marketStore); 15 });
现在不管何时通过put, add, remove调用来修改数据,通知都会传递给视图渲染器,就可以自动的更新视图了。
远程发起通知
通过使用Observable包裹存储是可以在数据更新的时候实现自动通知。然而,如果你有个Comet型的实时应用,通知有可能是来自其他用户通过服务器传递过来的。这种情况下,就不再是使用put,add与remove的场景了,因为这些操作都是本地用户来调用的,需要发送到服务器。使用服务器发起的调用,我们不想让更新操作在返回到服务器上去,因为服务器已经知道了数据的变化,实际上需要在服务端实现来抑制回调到服务器。正因如此Observable存储包裹器提供了一个notify方法,它被设计为来自其他源引起的数据变化通知存储。put与notify的不同在与,put是发起一个变化请求来修改数据,notify是数据已经修改了然后再通知变化。(译者注:很绕吧,我也搞不懂了,大概就那意思)
使用notify方法,我们有个单独的调用对象用于数据变化通知。notify方法有两个参数:第一个参数是要添加或更新的对象,第二个表示要更新或删除对象的标识。如果只提供了第一个参数,表示一个新的数据被添加。如果第一个参数是undefined或null(且有第二个参数),这表示引用要被删除。如果两个参数都有,则标识一个更新动作发生。为了通知一个更新的对象,我们可以这样调用notify方法:
1 marketStore.notify({"date": "2008-02-29", "name": "Dow Jones", "index": 12197.88}, "Dow Jones");
为了演示,我们使用setInterval模拟一个远程调用:
1 setInterval(function() { 2 // choose a market randomly 3 var market = data[Math.floor(Math.random() * 3)]; 4 // change it randomly 5 market.index += Math.random() - 0.5; 6 // update date 7 market.date = new Date(); 8 // notify of the change 9 marketStore.notify(market, market.name); 10 }, 1000); // every second
notify方法常与Comet驱动消息一起使用,让我们看看如何使用dojox/socket,用于Comnet样式的通信,和长轮询。简单的说,就是dojox/socket允许与带有WebSocket或长轮询的服务通讯。接下来我们创建一个socket连接,并使用服务器消息通知存储数据的变化:
1 require(["dojox/socket"], function(socket) { 2 var socket = Socket("/comet"); 3 socket.on("message", function(event) { 4 var data = event.data; 5 switch(data.action) { 6 case "create": store.notify(data.object);break; 7 case "update": store.notify(data.object, data.object.id);break; 8 case "delete": store.notify(undefined, data.object.id);break; 9 default: return; // some other action 10 } 11 }); 12 });
实现你自己的observe()
可能有这样的情况,使用自己实现的observe()方法会更高效。当你需要做专有的缓存或通知方案,或自己实现了query()方法时,这是很重要的。我们直接在query()方法中实现observe,一般实现方法如下:
1 require([ 2 "dojox/socket", 3 "dojo/store/Memory", 4 "dojo/store/util/QueryResults", 5 "dojo/_base/array" 6 ], function(Socket, Memory, QueryResults, arrayUtil) { 7 new Memory({ 8 idProperty: "name", 9 data: data, 10 query: function(query, options) { 11 // execute the query and get the results as an array 12 var resultsArray = this.queryEngine(query, options)(this.data); 13 14 // create a results object with standard interative methods 15 var results = QueryResults(resultsArray); 16 17 // keep track of listeners 18 var listeners = []; 19 20 // add the observe method 21 result.observe = function(listeners) { 22 listeners.push(); 23 }; 24 25 socket.on("message", function(event) { 26 // ... process event 27 arrayUtil.forEach(listeners, function(listener) { 28 listener(object, insertedInto, removedFrom); 29 }); 30 }); 31 } 32 }); 33 });
总结
Dojo对象存储的观察者模式接口提供了一个强大的功能,结合数据模型传递实时更新事件。数据视图可以连接查询结果而不需要关心是如何引起的数据更新。带有统一的接口,可以让视图在由其他源引起的服务器数据更新时及时做出响应。