【问题标题】:Polling a Collection with Backbone.js使用 Backbone.js 轮询集合
【发布时间】:2011-08-23 05:29:20
【问题描述】:

我正在尝试使 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);

当获取 cmets 时,refresh 被调用,CommentListView 底部的相同 cmets - 这是我对上面代码的期望。

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

【问题讨论】:

    标签: javascript ajax polling backbone.js


    【解决方案1】:

    或者只是使用简单得多的附加到主干的 fetch 方法:

    this.fetch({ update: true });
    

    当模型数据从服务器返回时,集合将(有效地)重置,除非您传递 {update: true},在这种情况下,它将使用 update 来(智能地)合并获取的模型。 - Backbone Documentation

    :-)

    【讨论】:

      【解决方案2】:

      Backbone.Collection.merge([options])

      基于@Jeb 的上述回复,我已将此行为封装到 Backbone 扩展中,您可以将其复制并粘贴到 .js 文件中并包含在您的页面中(在包含 Backbone 库本身之后)。

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

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

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

      options 哈希将successerror 回调作为参数传递给(collection, response),它提供了第三个回调选项complete,无论成功或失败都会执行错误(主要用于轮询场景)。

      它触发名为“merge:success”和“merge:error”的事件。

      这是扩展名:

      // 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";                        
          }
      };
      

      【讨论】:

      • 现在有一种更简单的方法来处理这个问题,方法是设置 fetch() 方法的更新选项参数。请参阅我的其他答案。
      【解决方案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; });
            },
      

      【讨论】:

      • 优秀的解决方案。我试图在 collection.parse 方法中完成此操作,但一无所获。我真的希望将这种行为添加到 Backbone api 中。无论如何,这是我对更新现有模型的 TODO 项的实现。 _(collection.models).each(function(activity) { if (newModelIds.indexOf(activity.id) != -1) { activity.set(newCollection.get(activity.id)); } }, that);
      【解决方案4】:

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

      为此,请在您的收藏中:

      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 参数查询您的数据库。您的客户端代码不会更改以刷新视图。

      如果您出于任何原因不想更改后端代码,请在 addAll 函数中添加此行:

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

      【讨论】:

        猜你喜欢
        • 2013-04-08
        • 2012-05-10
        • 1970-01-01
        • 2012-10-30
        • 2011-11-08
        • 2011-12-19
        • 1970-01-01
        • 2011-12-19
        • 2014-03-12
        相关资源
        最近更新 更多