为什么 Backbone.Collection 对第一个观众显示了两次?

Why is Backbone.Collection shown twice for the first viewer?

我尝试通过修改Todo演示,在同一页面的两个不同查看器中显示相同的数据。任何数据更改都将反映在两个查看器中。该代码仅适用于一个查看器,但在使用 fetch 重新加载页面后显示了我不想要的结果。待办事项列表在第一个查看器中显示两次,并且一半的结果松散了绑定事件。有没有人可以帮助我?提前致谢。

The code can be played with the jsfiddle.

HTML:

<div id="todoapp">


    <header>
      <h1>Todos</h1>
      <input class="new-todo" type="text" placeholder="What needs to be done?">
    </header>

    <section class="main">
      <input id=toggle-all class="toggle-all" type="checkbox">
      <label for="toggle-all">Mark all as complete</label>
      <ul class="todo-list"></ul>
    </section>

    <footer>
      <a class="clear-completed">Clear completed</a>
      <div class="todo-count"></div>
    </footer>

  </div>


  <div id="todoapp2">

    <header>
      <h1>Todos</h1>
      <input class="new-todo" type="text" placeholder="What needs to be done?">
    </header>

    <section class="main">
      <input id=toggle-all-2  class="toggle-all" type="checkbox">
      <label for="toggle-all-2">Mark all as complete</label>
      <ul class="todo-list"></ul>
    </section>

    <footer>
      <a class="clear-completed">Clear completed</a>
      <div class="todo-count"></div>
    </footer>

  </div>

  <div id="instructions">
    Double-click to edit a todo.
  </div>

   <!-- Templates -->

  <script type="text/template" id="item-template">
    <div class="view">
      <input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
      <label><%- title %></label>
      <a class="destroy"> Destroy </a>
    </div>
    <input class="edit" type="text" value="<%- title %>" />
  </script>

  <script type="text/template" id="stats-template">
    <% if (done) { %>
      <a class="clear-completed">Clear <%= done %> completed <%= done == 1 ? 'item' : 'items' %></a>
    <% } %>
    <div class="todo-count"><b><%= remaining %></b> <%= remaining == 1 ? 'item' : 'items' %> left</div>
  </script>      

JS:

$(function () {

Todo = Backbone.Model.extend({

    defaults: function() {
      return {
        title: "empty todo...",
        order: Todos.nextOrder(),
        done: false
      };
    },

    toggle: function() {
      this.save({done: !this.get("done")});
    }

  });

TodoList = Backbone.Collection.extend({

    model: Todo,
    localStorage: new Backbone.LocalStorage("todos-backbone"),

    done: function() {
      return this.where({done: true});
    },

    remaining: function() {
      return this.where({done: false});
    },

    nextOrder: function() {
      if (!this.length) return 1;
      return this.last().get('order') + 1;
    },

    comparator: 'order'

  });


var Todos = new TodoList;


TodoView = Backbone.View.extend({

    tagName:  "li",

    template: _.template( $('#item-template').html() ),

    events: {
      "click .toggle"   : "toggleDone",
      "dblclick .view"  : "edit",
      "click a.destroy" : "clear",
      "keypress .edit"  : "updateOnEnter",
      "blur .edit"      : "close"
    },

    initialize: function() {

      this.listenTo(this.model, 'change', this.render);
      this.listenTo(this.model, 'destroy', this.remove);
    },

    render: function() {

      this.$el.html( this.template(this.model.toJSON()) );

      this.$el.toggleClass('done', this.model.get('done') );

      this.input = this.$('.edit');
        //alert(this.input.val() );


      return this;
    },

    toggleDone: function() {
      this.model.toggle();
    },

    edit: function() {
      this.$el.addClass("editing");
      this.input.focus();
    },

    close: function() {
      var value = this.input.val();
      if (!value) {
        this.clear();
      } else {
        this.model.save({title: value});
        this.$el.removeClass("editing");
      }
    },

    updateOnEnter: function(e) {
      if (e.keyCode == 13) this.close();
    },

    clear: function() {
      this.model.destroy();
    }

  });

  var AppView = Backbone.View.extend({

    //el: $("#todoapp"),

    statsTemplate: _.template($('#stats-template').html()),

    events: {
      "keypress .new-todo":  "createOnEnter",
      "click .clear-completed": "clearCompleted",
      "click .toggle-all": "toggleAllComplete"
    },

    initialize: function() {

      this.input = this.$(".new-todo");
      this.allCheckbox = this.$(".toggle-all")[0];

      this.listenTo(Todos, 'add', this.addOne);
      this.listenTo(Todos, 'reset', this.addAll);

      this.listenTo(Todos, 'all', this.render);

      this.footer = this.$('footer');
      this.main = this.$('.main');

        //Todos.fetch();
        Todos.fetch({reset:true});

    },

    render: function() {
      var done = Todos.done().length;
      var remaining = Todos.remaining().length;

      if (Todos.length) {
        this.main.show();
        this.footer.show();
        this.footer.html(this.statsTemplate({done: done, remaining: remaining}));
      } else {
        this.main.hide();
        this.footer.hide();
      }

      this.allCheckbox.checked = !remaining;
    },

    addOne: function(todo) {
      var view = new TodoView({model: todo});
        //console.log(view.render().el );

        //this.$(".todo-list").append( view.render().el );
        this.$(".todo-list").append( view.render().el );
        console.log( this.main.parent() );
    },

    addAll: function() {
      Todos.each(this.addOne, this);
    },

    createOnEnter: function(e) {
      if (e.keyCode != 13) return;
      if (!this.input.val()) return;

      Todos.create({title: this.input.val()});
      this.input.val('');
    },

    clearCompleted: function() {
      _.invoke(Todos.done(), 'destroy');
      return false;
    },

    toggleAllComplete: function () {
      var done = this.allCheckbox.checked;
      Todos.each(function (todo) { todo.save({'done': done}); });
    }

  });

  var app1 = new AppView({  el: $("#todoapp") });

  var app2 = new AppView({  el: $("#todoapp2") });

});

原因是您只为两个视图使用了一个集合。

  • 最初创建了 app1,并在初始化时获取了集合,触发事件仅由 app1 侦听
  • 现在创建了第二个视图并再次获取集合,但是现在在 app1 和 app2 中都监听了集合触发的重置事件,并且都被渲染了

为了解决这个问题,您可以在两个视图都初始化后只获取一次集合。

working fiddle