BackboneJs - 在模型更改时保留模型内集合的事件

BackboneJs - Retain events on a collection inside a model when model changes

我在模型中有一个集合,如下图所示:

var itemModel = Backbone.Model.extend({

   defaults:{
      name:"",
      brand:"",
      priceCollection:[]
    }
})

有附加到 itemModel 的更改侦听器以及附加到集合的更改侦听器作为 this.listenTo(itemModel.get('priceCollection'),'change',this.dosomething) 在视图中。

问题是集合上的更改侦听器可以正常工作只要父模型没有更改,如果模型通过 itemModel.set(newattributes) itemModel.get('priceCollection') 绑定的事件丢失。

如何保留此活动?还是每次模型更改时我都应该重新绑定此事件?或者我应该将集合上的侦听器从视图移动到模型并触发自定义 Backbone 事件吗?

需要注意的是这个模型是单例的

请记住,Backbone 假定集合和模型应映射 1:1 到服务器端资源。它对 API 布局和数据结构做出了明确的假设 - 请参阅 Model.url, Model.urlRoot and Collection.url.

提案

你说模型是单例。在这种情况下,我建议单独维护模型和集合。

由于 SomeModel 没有伴随着具有紧密关系的特定集合 SomeCollection,因此没有必要在属性级别上将它们关联起来。建立事件侦听器和同步数据所需的工作仅在一处。

// some controller (app main)

var model = new SomeSingletonModel();
var collection = new SomeSingletonCollection();

var view = new SomeView({
  model: model,
  collection: collection
});

可能映射到SomeSingletonModel的资源会传送一个数组。

使用集合作为模型属性(model.get("name") 是什么)比使用普通数组有什么好处?同步和更改事件。两者可能仅在视图更新集合的模型时才需要。当 View 仅呈现时,Collection 在许多情况下不会提供任何好处。

如果需要更新该数组的数据,使用 Collection 可能是正确的选择,因为 Backbone 的同步机制。

但是如何使集合与模型保持同步(你问)?

您的控制器需要监听模型并在 syncreset 上更新集合:

model.on("sync reset", function() {
  // "priceCollection" is a model attribute 
  collection.reset(model.get("priceCollection"));

  // optionally unset "priceCollection" on the model
  this.unset("priceCollection", { silent: true });
});

这将初始化集合。

对集合模型的任何更改将仅作为集合或模型同步机制的一部分。

前言

另见 my other answer 当模型是单例时,这可能是更好的选择。

请注意关于 Backbone 对 API 设计的假设的第一个陈述。

使用 Model 和 Collection

之间的耦合的提案

注意:如有必要,在所有这些实现中 Collection 的 url 或模型的(Collection 的模型)url/rootUrl 可能会在 sync 上得到(重新)定义以控制同步。

更新 change/sync/reset

的内部参考

此实现删除模型属性并使用其数据更新 object 属性。

object 属性是一个 Collection 实例,在模型更改时只会重置,不会重新创建。

var CustomModel = Backbone.Model.extend({
  defaults: {
    // defaults go here - "children" may be contained here
  },

  // implement constructor to act before the parent constructor is able to
  // call set() (L402 in v1.3.0) with the initial values
  // See https://github.com/jashkenas/backbone/blob/1.3.0/backbone.js#L402
  constructor: function() {
    // create children collection as object attribute - replaces model attr.
    this.children = new Backbone.Collection();
    // listen to changing events to catch away that attribute an update the
    // object attribute
    this.listenTo(this, "change:children sync reset", this.onChangeColl);

    // apply original constructor
    Backbone.Model.apply(this, arguments);
  },

  onChangeColl: function() {
    // check for presence since syncing will trigger "set" and then "sync",
    // the latter would then empty the collection again after it has been updated
    if (this.has("children")) {
      // update "children" on syncing/resetting - this will trigger "reset"
      this.children.reset(this.get("children"));

      // remove implicitly created model attribute
      // use silent to prevent endless loop due to change upon change event
      this.unset("children", { silent: true });
    }
  }
});

在 Fiddle 或控制台中测试时的用法示例:

var c = new CustomModel({ a: 1, children: [{ x: 1 }, { x: 5 }] });
c.set({a: 8, children: [{ x: 50 }, { x: 89 }]});
c.url = "/dummy"
// replace sync() only for fetch() demo - the implementation does what sync() would do on success
c.sync = function(method, coll, opts){  if (method == "read") { opts.success({ a: 100, children: [{ x: 42 }, { x: 47 }] }); } }
c.fetch();
  • 监听 collection 事件更容易实现,因为模型生命周期只有一个实例
魂斗罗
  • 代码更复杂
  • collection 如果没有进一步实施,数据不会部分同步

替换为 change/sync/reset

此实现拦截模型属性更改并将其数据替换为已使用原始数据初始化(重置)的 Collection 实例。

var CustomModel = Backbone.Model.extend({
  defaults: {
    // this is optional
    children: new Backbone.Collection()
  },

  initialize: function() {
    // listen to model attribute changing events to swap the raw data with a
    // collection instance
    this.listenTo(this, "change:children sync reset", this.onChangeColl);
  },

  onChangeColl: function() {
    if (this.has("children")) {
      // use silent to prevent endless loop due to change upon change event
      this.set("children", new Backbone.Collection(this.get("children")), { silent: true });
    }
  }
});

在 Fiddle 或控制台中测试时的用法示例:

var c = new CustomModel({ a: 1, children: [{ x: 1 }, { x: 5 }] });
c.set({ a: 8, children: [{ x: 50 }, { x: 89 }] });
c.url = "/dummy";
// replace sync() only for fetch() demo - the implementation does what sync() would do on success
c.sync = function(method, coll, opts){  if (method == "read") { opts.success({ a: 100, children: [{ x: 42 }, { x: 47 }] }); } }
c.fetch();
  • 直接实施
魂斗罗
  • 同步中包含的数据,排除需要更多的努力
  • 听 Collection 不切实际:因为所有消费者都需要 unbind/bind

注意:根据您的要求和 API 设计,您可能不希望 children 自动同步到服务器。在这种情况下,该解决方案是有限的。您可以覆盖模型的 toJSON(),但这可能会限制它在应用程序其他部分的使用(例如将数据输入视图)。


反向关系:Collection 有一个模型

也许您的主要数据实际上是Collection。所以用额外的数据装饰 Collection 是另一种方法。此实现提供了一个沿 Collection 的模型,该模型将在 collection 同步时更新。

此实现仅最适合获取 collection 数据和属性(例如,使用目录本身的属性获取目录内容)。

var CustomCollection = Backbone.Collection.extend({
   initialize: function() {
      // maintain decorative attributes of this collection
      this.attrs = new Backbone.Model();
   },

   parse: function(data, opts) {
      // remove "children" before setting the remainder to the Model
      this.attrs.set(_.omit(data, "children"));
      // return the collection content only
      return data.children;
   }
});

在 Fiddle 或控制台中测试时的用法示例:

var c = new CustomCollection({ a: 1, b: 2, children: [{ x: 2 }, { x: 3 }] }, { parse: true });
c.reset({ a: 9, b: 11, children: [{ x: 5 }, { x: 10 }] } , { parse: true });
// replace sync() only for fetch() demo - the implementation does what sync() would do on success
c.sync = function(method, coll, opts){ if (method == "read") { opts.success({ a: 100, b: 124, children: [{ x: 42 }, { x: 47 }] }) } }
c.fetch();
  • 直接实施
  • 委托模型事件更容易
魂斗罗
  • collection 如果没有进一步实施,数据不会部分同步
  • 需要 parse() 来实现 -- 依次要求 parse: true 始终传递给 reset()set() 以及 -- 要求 parse() 以 collection 作为范围调用 (this)(这可以通过在绑定到 [=27 的 initialize 中定义 parse 来规避=] 使用 `bind()´)