骨干视图的正确示例:更改属性,CRUD,不使用Zombie Views

时间:2014-07-18 03:16:42

标签: backbone.js

尝试制作合理的Backbone教学模式,展示利用骨干功能的正确方法,以及祖父母,父母和孩子的观点,模型和集合...

我正在尝试更改模型上的布尔属性,可以跨多个父视图进行实例化。如何调整列表以实现此目的?
当前的问题是,当您单击任何非最后一个子视图时,它会将该子视图移动到结尾并重新实例化它。

Plnkr
点击“添加代表”
点击“添加节拍”(您可以多次点击此按钮)
单击除最后一个之外的任何节拍视图将实例化相同节拍的更多视图

孩子:

// our beat, which contains everything Backbone relating to the 'beat'
define("beat", ["jquery", "underscore", "backbone"], function($, _, Backbone) {
  var beat = {};

  //The model for our beat
  beat.Model = Backbone.Model.extend({
    defaults: {
      selected: true
    },
    initialize: function(boolean){
      if(boolean) {
        this.selected = boolean;
      }
    }
  });

  //The collection of beats for our measure
  beat.Collection = Backbone.Collection.extend({
    model: beat.Model,
    initialize: function(){
      this.add([{selected: true}])
    }
  });

  //A view for our representation
  beat.View = Backbone.View.extend({
    events: {
      'click .beat' : 'toggleBeatModel'
    },
    initialize: function(options) {
      if(options.model){
        this.model=options.model;
        this.container = options.container;
        this.idAttr = options.idAttr;
      }
      this.model.on('change', this.render, this);
      this.render();
    },
    render: function(){
      // set the id on the empty div that currently exists
      this.$el.attr('id', this.idAttr);
      //This compiles the template
      this.template = _.template($('#beat-template').html());
      this.$el.html(this.template());
      //This appends it to the DOM
      $('#'+this.container).append(this.el);
      return this;
    },
    toggleBeatModel: function() {
      this.model.set('selected', !this.model.get('selected'));
      this.trigger('beat:toggle');
    }
  });

  return beat;
});

家长:

// our representation, which contains everything Backbone relating to the 'representation'
define("representation", ["jquery", "underscore", "backbone", "beat"], function($, _, Backbone, Beat) {
  var representation = {};

  //The model for our representation
  representation.Model = Backbone.Model.extend({
    initialize: function(options) {
      this.idAttr = options.idAttr;
      this.type = options.type;
      this.beatsCollection = options.beatsCollection;
      //Not sure why we have to directly access the numOfBeats by .attributes, but w/e
    } 
  });  

  //The collection for our representations
  representation.Collection = Backbone.Collection.extend({
    model: representation.Model,
    initialize: function(){
    }
  });

  //A view for our representation
  representation.View = Backbone.View.extend({
    events: {
      'click .remove-representation' : 'removeRepresentation',
      'click .toggle-representation' : 'toggleRepType',
      'click .add-beat' : 'addBeat',
      'click .remove-beat' : 'removeBeat'
    },
    initialize: function(options) {
      if(options.model){this.model=options.model;}
      // Dont use change per http://stackoverflow.com/questions/24811524/listen-to-a-collection-add-change-as-a-model-attribute-of-a-view#24811700
      this.listenTo(this.model.beatsCollection,  'add remove reset', this.render);
      this.listenTo(this.model, 'change', this.render);
    },
    render: function(){
      // this.$el is a shortcut provided by Backbone to get the jQuery selector HTML object of this.el
      // so this.$el === $(this.el)
      // set the id on the empty div that currently exists
      this.$el.attr('id', this.idAttr);
      //This compiles the template
      this.template = _.template($('#representation-template').html());
      this.$el.html(this.template());
      //This appends it to the DOM
      $('#measure-rep-container').append(this.el);
      _.each(this.model.beatsCollection.models, function(beat, index){
        var beatView = new Beat.View({container:'beat-container-'+this.model.idAttr, model:beat, idAttr:this.model.idAttr+'-'+index }); 
      }, this);
      return this;
    },
    removeRepresentation: function() {
      console.log("Removing " + this.idAttr);
      this.model.destroy();
      this.remove();
    },
    //remove: function() {
    //  this.$el.remove();
    //},
    toggleRepType: function() {
      console.log('Toggling ' + this.idAttr + ' type from ' + this.model.get('type'));
      this.model.set('type', (this.model.get('type') == 'line' ? 'circle' : 'line'));
      console.log('Toggled ' + this.idAttr + ' type to ' + this.model.get('type'));
      this.trigger('rep:toggle');
    },
    addBeat: function() {
      this.trigger('rep:addbeat');      
    },
    removeBeat: function() {
      this.trigger('rep:removebeat');      
    }
  });

  return representation;
});

此答案应适用于所有视图,能够创建或删除视图而不影响非相关视图,并更改属性并使相关视图自动更新。再次,这是用作教学示例,以展示如何在没有僵尸视图的情况下正确设置骨干应用程序......

1 个答案:

答案 0 :(得分:1)

问题

您看到创建重复视图的原因在于Beat视图的render()函数:

render: function(){
  // set the id on the empty div that currently exists
  this.$el.attr('id', this.idAttr);
  //This compiles the template
  this.template = _.template($('#beat-template').html());
  this.$el.html(this.template());
  //This appends it to the DOM
  $('#'+this.container).append(this.el);
  return this;
}

在以下情况下调用此函数:

  1. 与视图关联的模型更改
  2. 首先初始化节拍视图
  3. 第一个电话是导致问题的电话。 initialize()使用事件侦听器来监视模型的更改,以便在必要时重新呈现它:

    initialize: function(options) {
      ...
      this.model.on('change', this.render, this); // case #1 above
      this.render(); // case #2 above
      ...
    },
    

    通常,这很好,除了render()包含将视图推送到DOM 的代码。这意味着每次与视图关联的模型更改状态时,视图不仅会重新呈现,而且会在DOM中重复。

    这似乎在事件监听器被错误绑定方面引起了一大堆问题。据我所知,原因是当只有一个节拍存在时不会引起这种现象是因为表示本身也会重新渲染并移除旧的僵尸视图。我并不完全理解这种行为,但它肯定与表示方式监视beatCollection的方式有关。

    解决方案

    修复非常简单:更改视图将自身附加到DOM的位置。 render()中的这一行:

    $('#'+this.container).append(this.el);
    
    应该将

    移动到初始化,如下所示:

    initialize: function(options) {
      if(options.model){
        this.model=options.model;
        this.container = options.container;
        this.idAttr = options.idAttr;
      }
      this.model.on('change', this.render, this);
      this.render();
      $('#'+this.container).append(this.el); // add to the DOM after rendering/updating template
    },
    

    Plnkr demo with solution applied