2011-05-11 57 views
23

我正在尝试使Backbone.js收藏与服务器上发生的事情保持同步。使用Backbone.js轮询收藏集

我的代码是类似以下内容:

var Comment = Backbone.Model.extend({}); 
var CommentCollection = Backbone.Collection.extend({ 
    model: Comment 
}); 

var CommentView = Backbone.View.extend({ /* ... */ }); 
var CommentListView = Backbone.View.extend({ 
    initialize: function() { 
     _.bindAll(this, 'addOne', 'addAll'); 

     this.collection.bind('add', this.addOne); 
     this.collection.bind('refresh', this.addAll); 
    }, 
    addOne: function (item) { 
     var view = new CommentView({model: item}); 
     $(this.el).append(view.render().el); 
    }, 
    addAll: function() { 
     this.collection.each(this.addOne); 
    } 
}); 

var comments = new CommentCollection; 
setInterval(function() { 
    comments.fetch(); 
}, 5000); 

什么情况是,当评论有牵强,refresh叫,到了CommentListView的底部同样的话 - 其我期待什么从上面的代码。

我想知道什么是“刷新”视图的最佳方式,而不会丢失任何“本地状态”。

回答

16

你想要做的是每隔几秒刷新一次收集并追加新的评论。我的建议是在你的后端处理这个问题。只发送最后一条评论中的最后一个时间戳,并仅从该日期向服务器询问增量。

要做到这一点,您的收藏:

CommentCollection = Backbone.Collection.extend({ 
    url: function(){ 
    return "/comments?from_time=" + this.last().get("created_at"); 
    }, 
    comparator: function(comment){ 
    return comment.get("created_at"); 
    } 
}); 

在你的后台,查询数据库基础上,FROM_TIME parameter.Your客户端代码不会更改以刷新视图。

如果你不想改变任何原因,你的后端代码添加此行则addAll功能:

addAll: function(){ 
    $(this.el).empty(); 
    this.collection.each(this.addOne); 
} 
3

做一个重复的集合。取()它。比较这两个找到增量。应用它们。

 /* 
     * Update a collection using the changes from previous fetch, 
     * but without actually performing a fetch on the target 
     * collection. 
     */ 
     updateUsingDeltas: function(collection) { 
     // Make a new collection of the type of the parameter 
     // collection. 
     var newCollection = new collection.constructor(); 

     // Assign it the model and url of collection. 
     newCollection.url = collection.url; 
     newCollection.model = collection.model; 

     // Call fetch on the new collection. 
     var that = this; 
     newCollection.fetch({ 
      success: function() { 
      // Calc the deltas between the new and original collections. 
      var modelIds = that.getIdsOfModels(collection.models); 
      var newModelIds = that.getIdsOfModels(newCollection.models); 

      // If an activity is found in the new collection that isn't in 
      // the existing one, then add it to the existing collection. 
      _(newCollection.models).each(function(activity) { 
       if (modelIds.indexOf(activity.id) == -1) { 
       collection.add(activity); 
       } 
      }, that); 

      // If an activity in the existing colleciton isn't found in the 
      // new one, remove it from the existing collection. 
      _(collection.models).each(function(activity) { 
       if (newModelIds.indexOf(activity.id) == -1) { 
       collection.remove(activity); 
       } 
      }, that); 

      // TODO compare the models that are found in both collections, 
      // but have changed. Maybe just jsonify them and string or md5 
      // compare. 
      } 
     }); 
     }, 

     getIdsOfModels: function(models) { 
     return _(models).map(function(model) { return model.id; }); 
     }, 
+0

的最佳解决方案。我正试图在collection.parse方法中完成此操作,并且无处可去。我真的希望这种行为被添加到骨干API。无论如何,这是我的TODO项目更新现有模型的实现。 '_(collection.models)。每个(功能(活性){ \t \t \t \t \t \t如果(newModelIds.indexOf(activity.id)!= - 1){ \t \t \t \t \t \t \t活性。集(newCollection.get(activity.id)); \t \t \t \t \t \t} \t \t \t \t那么);' – 2012-07-24 02:48:08

8

Backbone.Collection.merge

大厦@杰布的响应上面,我已经封装这种行为为骨干的扩展,你可以复制并粘贴到.js([选项])文件并包含在您的页面中(在包含Backbone库之后)。

它为Backbone.Collection对象提供了一种名为merge的方法。它不是完全重置现有集合(如fetch那样),而是将服务器响应与现有集合进行比较并合并它们的差异。

  1. 增加模型在响应中,但不在现有的集合中。
  2. 删除在现有集合中但未在响应中的模型。
  3. 最后,它更新在响应中的现有集合AND中找到的模型的属性。

为添加,删除和更新模型而触发所有预期事件。

选项散列接受successerror回调将被传递(collection, response)作为参数,它提供了一个名为即无论成功或错误(大多为轮询场景有用)执行complete第三回调选项。

它会触发称为“合并:成功”和“合并:错误”的事件。

这里是扩展:

// Backbone Collection Extensions 
// --------------- 

// Extend the Collection type with a "merge" method to update a collection 
// of models without doing a full reset. 

Backbone.Collection.prototype.merge = function(callbacks) { 
    // Make a new collection of the type of the parameter 
    // collection. 
    var me = this; 
    var newCollection = new me.constructor(me.models, me.options); 
    this.success = function() { }; 
    this.error = function() { }; 
    this.complete = function() { }; 

    // Set up any callbacks that were provided 
    if(callbacks != undefined) { 
     if(callbacks.success != undefined) { 
      me.success = callbacks.success; 
     } 

     if(callbacks.error != undefined) { 
      me.error = callbacks.error; 
     } 

     if(callbacks.complete != undefined) { 
      me.complete = callbacks.complete; 
     } 
    } 

    // Assign it the model and url of collection. 
    newCollection.url = me.url; 
    newCollection.model = me.model; 

    // Call fetch on the new collection. 
    return newCollection.fetch({ 
     success: function(model, response) { 
      // Calc the deltas between the new and original collections. 
      var modelIds = me.getIdsOfModels(me.models); 
      var newModelIds = me.getIdsOfModels(newCollection.models); 

      // If an activity is found in the new collection that isn't in 
      // the existing one, then add it to the existing collection. 
      _(newCollection.models).each(function(activity) { 
       if (_.indexOf(modelIds, activity.id) == -1) { 
        me.add(activity); 
       } 
      }, me); 

      // If an activity in the existing collection isn't found in the 
      // new one, remove it from the existing collection. 
      var modelsToBeRemoved = new Array(); 
      _(me.models).each(function(activity) { 
       if (_.indexOf(newModelIds, activity.id) == -1) { 
        modelsToBeRemoved.push(activity); 
       } 
      }, me); 
      if(modelsToBeRemoved.length > 0) { 
       for(var i in modelsToBeRemoved) { 
        me.remove(modelsToBeRemoved[i]); 
       } 
      } 

      // If an activity in the existing collection is found in the 
      // new one, update the existing collection. 
      _(me.models).each(function(activity) { 
       if (_.indexOf(newModelIds, activity.id) != -1) { 
        activity.set(newCollection.get(activity.id)); 
       } 
      }, me); 

      me.trigger("merge:success"); 

      me.success(model, response); 
      me.complete(); 
     }, 
     error: function(model, response) { 
      me.trigger("merge:error"); 

      me.error(model, response); 
      me.complete(); 
     } 
    }); 
}; 

Backbone.Collection.prototype.getIdsOfModels = function(models) { 
     return _(models).map(function(model) { return model.id; }); 
}; 

简单的使用场景:

var MyCollection = Backbone.Collection.extend({ 
    ... 
}); 
var collection = new MyCollection(); 
collection.merge(); 

错误处理应用场景:

var MyCollection = Backbone.Collection.extend({ 
    ... 
}); 

var collection = new MyCollection(); 

var jqXHR = collection.merge({ 
    success: function(model, response) { 
     console.log("Merge succeeded..."); 
    }, 
    error: function(model, response) { 
     console.log("Merge failed..."); 
     handleError(response); 
    }, 
    complete: function() { 
     console.log("Merge attempt complete..."); 
    } 
}); 

function handleError(jqXHR) { 
    console.log(jqXHR.statusText); 

    // Direct the user to the login page if the session expires 
    if(jqXHR.statusText == 'Unauthorized') { 
     window.location.href = "/login";       
    } 
}; 
+0

通过设置fetch()方法的update选项参数,现在有一种更简单的方法来处理这个问题。看到我的其他答案。 – 2012-12-29 16:38:31

30

或者只是使用最简单的除骨干的获取方法:

this.fetch({ update: true }); 

当从服务器模型数据的回报,收集将是(有效)复位,除非你通过{更新:真正},在这种情况下,它将使用更新(智能)合并提取的模型。 - Backbone Documentation

:-)

+0

这应该绝对是upvoted! +1 – 2013-01-11 21:23:06