Search code examples
javascriptbackbone.js

Backbone: adding new model to a collection is not working


I'm creating the very well known todo list app in backbone but there is one functionality that is not working, the mycollection.add() method.

Everything works fine when removing an item using todoItems.remove(todoItem.at(0)). The problem I am having is when adding a new item:

var newItem = new todoItem({ description: "cereal killer", status: "complete" });

todoItems.add(newItem) --> This won't add it to the collection, the length is still the same.

The only way I can add a new item is to remove one first and then it will add the item but it will only work with one.

Any clue?

Here is the main code:

app.js

var TodoItem = Backbone.Model.extend({
    defaults: {
        description: "Pick up milk", 
        status: "incomplete", 
        id: 1
    },
    toggleStatus: function(){
        if(this.get('status') === 'incomplete'){
          this.set({'status': 'complete'});
        } else {
          this.set({'status': 'incomplete'});
        }
        this.save();
    } 
});

var TodoItems = Backbone.Collection.extend({
    model: TodoItem,
    localStorage: new Backbone.LocalStorage("choose-some-identifier"),

    initialize: function () {
        this.on('remove', this.hideModel, this);
    },

    hideModel: function (model) {
        model.trigger('hide');
    }
});

var TodosView = Backbone.View.extend({
    initialize: function () {
        this.collection.on('add', this.addOne, this);
        this.collection.on('reset', this.addAll, this);
    },
    render: function() {
        this.addAll();
        // this.collection.forEach(this.addOne, this);
        return this;
    },

    addOne: function (todoItem) {
        var todoView = new TodoView({ model: todoItem });
        this.$el.append(todoView.render().$el);
    },

    addAll: function () {
        this.collection.forEach(this.addOne, this);     
    }

});

var TodoView = Backbone.View.extend({
    tagName: 'article',
    id: 'todo-view',
    className: 'todo',

    template: _.template('<h3 class="<%= status %>">' + 
        '<input type=checkbox ' + 
        '<% if (status === "complete") print("checked") %> />' + 
        '<%= description %></h3>'),

    events: {
        "change input": "toggleStatus"
    },

    toggleStatus: function () {
        this.model.toggleStatus();
    },

    remove: function(){
        this.$el.remove();
    },

    initialize: function(){
        this.render();
        this.model.on('change', this.render, this);
        this.model.on('destroy', this.remove, this);
        this.model.on('hide', this.remove, this);
    },

    render: function () {
        var attributes = this.model.toJSON();
        this.$el.html(this.template(attributes));
        return this;
    }
});

var todoItems = new TodoItems([
    {
        description: 'Jeffrey Way',
        status: "incomplete"
    },
    {
        description: 'John Doe',
        status: "incomplete"
    },
    {
        description: 'Sally Doe',
        status: "incomplete"
    }
]);

var todosView = new TodosView({ collection: todoItems});
$(document.body).append(todosView.render().el);

Solution

  • The problem is this:

    defaults: {
        description: "Pick up milk", 
        status: "incomplete", 
        id: 1
    }
    

    You have set an id as a default. An id should be unique, you are trying to add another model without specifying a new id so Backone helpfully notices you already have a model with that id so it doesn't add it.

    solution: You shouldn't be setting a default id, when creating models locally the normal process is: - Create a model without an id - At some point .save it - Server responds with all the attributes + the id after saving

    Of course fetching models that already existed on the server should already have an id.

    PS you don't need to make a hash for a singular set (or get):

    this.set('status', 'complete');
    

    PPS In your view it is a good idea to use listenTo:

    this.listenTo(this.collection, 'add', ...);
    

    This protects from memleaks in some instances and makes it easier to batch stop listening to events.