我不’知道你,但Backbone.js总是让我渴望更多。它’这是它的简单性,让我想要了解别人如何使用它来解决现实世界问题。当我’ve started work on CLEVERIM CRM.,大量使用Backbone.js,我想阅读一些现实世界问题及其解决方案,以便尽可能重复使用,而不是花时间重新发明轮子。到底,我不得不提出自己的解决方案,因为很多问题,就像 唠叨和过滤骨干网.JS视图/集合 并被咬了 微妙的虫子。虽然这是一个良好的学习经历,有时候,聪明人从其他人中学习’经验和那个经验 ’总是首选。本文是我分享一些经验教训的一部分,并在我提出的解决方案上获得反馈,毫无疑问,可以得到改善。

在这篇文章中,我’m将涵盖我必须解决的实时更新问题。它’在实现协作解决方案时,这是一个问题。拥有许多用户更新相同信息,您需要确保实时地将更新无缝地看到更新。将其转化为技术要求,即:

  1. 检测服务器端的集合和模型更改,并能够更新所有客户端
  2. 处理收集添加–将一个新模型添加到集合中
  3. 处理收集更新–现有型号已更新
  4. 处理集合删除–现有模型已删除,因此从集合中删除

作为第一步,我首先通过查看backbone.js提供的基础架构来更新集合,希望我能重用它。集合具有一个获取方法(重新)从服务器加载集合。它有支持重新加载整个集合(a“reset fetch”)或将新模型附加到您的收藏(一个“add fetch”)。使用易于使用:


    coll.fetch();
    coll.fetch( { add : true } );

这former is doing a “reset fetch”,而后者是一个“add fetch”.
这“add”版本,同时执行递增更新,因为我想要,它无法应对更新或删除。特别是,如果您从服务器发送现有模型,则“add fetch”会将其检测为重复并忽略它。它不会更新现有模型。

这“reset fetch”可以完成工作,但它可以完成’s slow and it’没有打算赢得任何美容竞赛。在我的情况下,实施CRM系统,我必须处理大量数据,潜在的每个集合的模型,所以做了一个“reset fetch”每次任何改变都是不可能的。我想“reset fetch”更适合少的收藏品’t change very often.

在这一点上,我是我自己的。我不得不实现我自己的渐进式更新和更新Backbone.js模型的方式,并且解决方案必须足够通用,因此它涵盖了我所拥有的所有模型和集合。要为您提供一个想法,我有收集联系人,公司,销售机会,案例,文件,任务,注释,评论,持久过滤器,自定义字段,用户,最新活动流和其他一些。

这server side and the detection problem

这server side is not a backbone.js problem per se, but it’■解决客户端的实时更新的解决方案的关键部分,它无法忽略。在服务器端,您必须回答几个问题:

  1. 我们如何检测模型更改时(添加,更新,删除操作),以便将这些客户端传输到其他客户端
  2. 这些更改将如何流回客户端
  3. 使用什么类型的数据结构来发送数据;一些东西’通常足以覆盖所有收藏品,并使新系列添加尽可能无痛

检测服务器侧更改

这requirement is to detect whether a model has changed since the last client update. It’不仅仅是布尔值,无论是什么东西是否需要满足多个客户端(用户),每次更新都会略有不同。
我的想法在这里为每个模型的每个记录存储最后一个更新的时间戳,然后每个客户端存储最后一个更新的时间戳“sync”使用服务器操作。
同步成为一个问题“给我所有具有时间戳的记录>= client timestamp”.

因此,对于对应于我的Backbone.js模型的每个DB数据模型,我添加了3个时间戳:


added_on         - timestamp when was the model added
deleted_on       - timestamp when the model was deleted (marked deleted)
last_modified_on - timestamp when any modifications (add, update, delete)
                   were made to this model

为了清楚起见,我’还增加了一个布尔“is_deleted”标记已删除的模型。它’值得注意的是,在这个模型中,我们可以’只需删除DB的模型,因为我们无法轻易检测到这些更改,因为模型将不再在DB中。

依赖于Last_Modified_On为所有操作进行更新(添加,更新,删除),检测可以通过对DB数据模型运行查询来完成更改的内容,如下所示:


select * from contacts where last_modified_on >= client_timestamp
select * from companies where last_modified_on >= client_timestamp
select * from sales where last_modified_on >= client_timestamp

等等…

我们可以轻松地检测到模型上的一些条件更改的内容,我们可以在我们迭代上面查询的结果时处理。


if model.added_on >= client_timestamp => added
if model.is_deleted and model.deleted_on >= client_timestamp => deleted
else => updated (relies on the implicit condition already in the sql:
                    last_modified_on >= client_timestamp)

客户/服务器数据结构

现在我们已经检测到发生了改变的东西’■将信息包装回到客户端可以处理的数据结构中以便更新Backbone.js模型。对于每个模型,我们可以发送json数据结构中已更改的内容:


	{
	  "添加" : [ json_model1, json_model2, ... ],
	  "upd" : [ json_modelA, json_modelB, ... ],
	  "del" : [ json_modelX, json_modelY, ...]
	}

这包含一个添加的模型列表,其中包含更新模型的列表和列表,其中包含从最后一个客户端时间戳删除的模型的列表。这只是在JSON结构中格式化,我们之前检测到的更改。

我们需要为已更改的每个集合执行此操作。

我们还需要通过代表一个代表一个的新时间戳“as of”对于我们的增量数据’重发。一旦客户端使用数据更新Backbone.js模型,我们’re sending, it’LL还将自己的Client_Timestamp更新为此新时间戳。这实际上确保了我们’重新无法再次获得相同的变化,我们’只要总是会得到我们不的新变化’t yet have.
这final data structure can look something like this:


{
    data : {
        contacts : {
                      "添加" : [ json_model1, json_model2, ... ],
                      "upd" : [ json_modelA, json_modelB, ... ],
                      "del" : [ json_modelX, json_modelY, ...]
                    },
        companies : {
                      "添加" : [ json_model1, json_model2, ... ],
                      "upd" : [ json_modelA, json_modelB, ... ],
                      "del" : [ json_modelX, json_modelY, ...]
                    }
        ...
    },
    timestamp: the_new_timestamp
}

上面的JSON_MODELS应该是您模型的JSON表示。这很重要,因为那么我们可以通过整个阵列在相关集合上添加或删除呼叫,而不是必须在我们的代码中迭代模型。

客户/服务器通信

客户端需要不断与服务器同步保持同步,因此暗示Web套接字,COMET“forever connection”或更简单的Ajax轮询方案。后者的优势是更容易实现和理解,所以我们’ll去那个。由于该机制封装良好,因此可以稍后更改此通信的实现详细信息,而不会影响其余的实现。

在Ajax轮询解决方案中,客户端将使用setInterval设置常规回调,并将Ajax调用到服务器到/ sync /端点。它将在最新的Client_Timestamp中传递,服务器将运行查询来检测已更改的内容和回复JSON结构以将该信息传递回客户端。

对于数据下的每个集合,客户端可以轻松地立即更新备份关联集合。它’■仅为新型号,Clift.Remove为已删除的型号进行编制,并迭代每个更新的模型和调用集合。这里’有一些代码,例如一个例子:


updateCollection : function(data, collectionTag, collection, addToFront){
    var colInfo = data[collectionTag],
        add, upd, del;
    if(colInfo) {
        //do incremental update of the collection
        add = colInfo.add;
        if(add && _.isArray(add) && add.length > 0) {
            if(addToFront){
                collection.add(add, { at : 0 });
            } else {
                collection.add(add);
            }
        }
        upd = colInfo.upd;
        if(upd && _.isArray(upd) && upd.length > 0) {
            _.each( upd, function(elem) {
                if(elem.id) {
                    var model = collection.get(elem.id);
                    if(model) {
                        model.set(elem);
                    } else {
                        collection.add(elem);
                    }
                }
            } );
        }
        del = colInfo.del;
        if(del && _.isArray(del) && del.length > 0) {
            collection.remove(del);
        }
    }
}

这nice thing about this function is that we can call it in turn for all our collections:


    updateCollection(data, 'contacts', contactsColl);
    updateCollection(data, 'companies', companiesColl);
    updateCollection(data, 'sales', salesColl);
    updateCollection(data, 'whatsNew', whatsNewColl, true);

对于Whatsnew,我们添加到集合的前面,以便我们可以更轻松地显示我们视图顶部的新项目。
更新集合并没有任何内容失败后,您可以安全地更新与您刚刚收到的增量数据相关联的时间戳。

app.timestamp = data.timestamp;

我们将将此新时间戳传递到下一个同步Ajax呼叫中。
如果在更新模型期间失败的东西,我们就不会’T更新时间戳,所以下次我们’LL再次获得相同的数据,只是为了确保我们’由于瞬态错误,重新丢失更新。

结论

那’在实时更新方面几乎是它。

视图将自动更新从添加,更改或删除集合或在各个模型上的触发器上更新。

出于性能原因,您’LL希望确保您只更新您的视图一次。如果您通过此同步机制收到许多更新,请不要’要为每个元素更新视图。如果你不这样做,请尝试’t believe me it’s将杀死您的应用程序的性能。

骨干.js将为您添加的每个元素触发添加/删除/更改事件,又可以触发重新呈现您的视图。要绕过那个,请将您的触发器处理程序删除:

    collection.on("添加 render change", _.debounce( reRenderView, 0 ));

与更新的视图相关的另一个重要问题是你不’如果用户在键入某些东西的中间,则要做到这一点,附加某些东西,使用视图’在他下面渲染。你不’t想要任何键入的文本丢失。这将为您的观点添加一些复杂性,特别是重新渲染部分。实现可以在您的视图中使用简单状态机进行,并在某些状态下推迟重新渲染。

像我刚才描述的那样的计划目前为我们的 一个页面crm应用程序。如果您,我们确实有一个快速的演示’遗嘱有兴趣看看。如果你决定看起来,不要’犹豫给我发送任何反馈。一世’M总是在寻找有关如何改进它的反馈。上面的代码也是如此。如果你认为它可以更好,请告诉我。毕竟,这是我在这里发布本文时的目标之一。