yun_cn
yun_cn

Reputation: 83

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

I try to show the same data in two different viewers in the same page by modifying the Todo demo. Any data change will be reflected in both viewers. The code works well with the only one viewer but shows the results that I don't want to want after reloading the page by using fetch. The todolist is shown twice in the first viewer and half of results loose the bond events. Does anyone can help me? Thanks in advance.

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

});

Upvotes: 0

Views: 47

Answers (1)

StateLess
StateLess

Reputation: 5402

The reason is you are using only one collection for the both the Views.

  • initially app1 is created and in initialize you fetched collection and the trigger event is listened only by app1
  • Now the second view is created and the collection is fetched again, but now the reset event fired by collection is listened in both app1 and app2 and both are rendered

To solve this problem you can fetch the collection only once after both the views are initialized.

working fiddle

Upvotes: 1

Related Questions