Backbone Collection.fetch()返回第一个null项

时间:2012-11-04 18:04:36

标签: collections backbone.js

我在视图中使用以下代码从服务器获取我的集合:

initialize: function () {
        _this = this;
        this.collection.fetch({
            success : function(collection, response) {
                _.each(response, function(i){  
                    var todo = new TodosModel({
                        id: i.id,
                        content: i.content,
                        completed: i.completed
                    });
                    // Add to collection
                    _this.collection.add(todo);
                    // Render
                    _this.render(todo);
                });
            },       
            error : function(collection, response) {
                console.log('ERROR GETTING COLLECTION!');
            }
        });
    },

这似乎有效 - 这是我服务器的输出:

{
  "0": {
    "id": 1,
    "content": "one",
    "completed": false
  },
  "3": {
    "id": 4,
    "content": "two",
    "completed": true
  },
  "4": {
    "id": 5,
    "content": "tester",
    "completed": false
  }
}

除非我退出我的收藏,否则第一个位置会有一个null条目:

enter image description here

然后会导致问题,好像我添加了一个项目,它需要最后一个元素的ID。我是骨干的新手,希望我能错过一些简单的东西。

1 个答案:

答案 0 :(得分:1)

这是我快速浏览代码的问题。我没有测试任何东西所以可能有拼写错误。我仍然不确定流浪空模型的来源,但如果您按照下面的描述重组您的应用程序,我怀疑问题会消失。

模型和集合看起来没问题,所以让我们看看你的观点。

el: $('#todos'),
listBlock: $('#todos-list'),
newTodoField: $('#add input'),
//...
template: $('#todo-template').html(),
//...
events: { /* ... */ },

这些应该没问题但是你需要确保在加载视图“class”时所有这些元素都在DOM中。通常你会编译一次模板:

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

然后只使用this.template作为获取HTML的函数。我假设template是下面的编译模板函数。

initialize: function () {
    _this = this;

这里有一个偶然的全局变量,这可能会导致有趣的错误。你想说var _this = this;

    this.el = $(this.el);

Backbone已经在$el为您提供了el的jQuery'd版本,因此您无需执行此操作,只需使用this.$el

    this.collection.fetch({
        success : function(collection, response) {
            _.each(response, function(i) {
                var todo = new TodosModel({ /* ... */ });
                // Add to collection
                _this.collection.add(todo);
                // Render
                _this.render(todo);
            });
        },
        //...

集合的fetch会在调用success处理程序之前将模型添加到集合中,因此您不必创建新模型或向集合添加任何内容。通常,render方法渲染整个事物而不是仅渲染一个部分,并将视图的render绑定到集合的"reset"事件; fetch调用将在获取时触发"reset"事件,因此通常的模式如下所示:

initialize: function() {
    // So we don't have to worry about the context. Do this before you
    // use `render` or you'll have reference problems.
    _.bindAll(this, 'render');

    // Trigger a call to render when the collection has some stuff.
    this.collection.on('reset', this.render);

    // And go get the stuff we want. You can put your `error` callback in
    // here if you want it, wanting it is a good idea.
    this.collection.fetch();
}

现在为render

render: function (todo) {
    var templ = _.template(this.template);
    this.listBlock.append(templ({
        id: todo.get('id'),
        content: todo.get('content'),
        completed: todo.get('completed')
    }));
    // Mark completed
    if(todo.get('completed')) {
        this.listBlock.children('li[data-id="'+todo.get('id')+'"]')
                      .addClass('todo-completed');
    }
}

通常情况下,这将分为两部分:

  1. render渲染整个集合。
  2. 另一种方法,例如renderOne,用于呈现单个模型。这也允许您将renderOne绑定到集合的"add"事件。
  3. 这样的事情很典型:

    render: function() {
        // Clear it out so that we can start with a clean slate. This may or
        // may not be what you want depending on the structure of your HTML.
        // You might want `this.listBlock.empty()` instead.
        this.$el.empty();
    
        // Punt to `renderOne` for each item. You can use the second argument
        // to get the right `this` or add `renderOne` to the `_.bindAll` list
        // up in `initialize`.
        this.collection.each(this.renderOne, this);
    },
    
    renderOne: function(todo) {
        this.listBlock.append(
            this.template({
                todo: todo.toJSON()
            })
        )
        // Mark completed
        if(todo.get('completed')) {
            this.listBlock.find('li[data-id="' + todo.id + '"]')
                          .addClass('todo-completed');
        }
    }
    

    请注意使用toJSON向模板提供数据。 Backbone模型和集合使用toJSON方法为您提供数据的简化版本,以便您也可以使用它。模型id可用作属性,因此您无需使用get即可获取该属性。您可以(也可能应该)将todo-completed逻辑推入模板,只需一点

    <% if(completed) { %>class="completed"<% } %>
    

    在正确的地方应该做到这一点。

    addTodo: function (e) {
        //...
    
            var todo = new TodosModel({
                id: todoID,
                content: todoContent,
                completed: todoCompleted
            });
            this.render(todo);
            todo.save();
            _this.collection.add(todo);
    

    您可以将renderOne绑定到集合的"add"事件,以处理渲染新模型的问题。然后使用save回调来完成它:

    var _this = this;
    var todo  = new TodosModel({ /* ... */ });
    todo.save({}, {
        wait: true,
        success: function(model, response) {
            // Let the events deal with rendering...
            _this.collection.add(model);
        }
    });
    

    同样,error上的save回调可能会很好。

    completeTodo: function (e) {
        //...
        todo.save({
            completed: todoCompleted
        });
    }
    

    此处save调用将触发'change:completed'事件,因此您可以绑定该事件以调整HTML。

    removeTodo: function (e) {
        //...
    }
    

    destroy来电将触发模型and on the collection上的"destroy"事件:

      

    在集合中的模型上触发的任何事件也将   为方便起见,直接在集合上触发。这个   允许您监听任何模型中特定属性的更改   在一个集合中,[...]

    因此,您可以在集合上侦听"destroy"个事件,并使用它们从显示中删除TODO。并且破坏模型应该在没有您干预的情况下将其从集合中删除。

    printColl: function () {
        this.collection.each(function (todo) {
            console.log('ID: '+todo.get('id')+' | CONTENT: '+todo.get('content')+' | COMPLETED: '+todo.get('completed'));
        });
    }
    

    你可以console.log(this.collection.toJSON())代替, 你必须点击一下才能打开东西 控制台,但你不会错过任何方式。

    集合的所有事件绑定都将在您的 查看initialize方法。如果您要删除视图 你想覆盖remove取消绑定集合 防止内存泄漏:

    remove: function() {
        // Call this.collection.off(...) to undo all the bindings from
        // `initialize`.
        //...
    
        // Then do what the default `remove` does.
        this.$el.remove()
    }
    

    您也可以为每个TODO项目使用单独的视图,但这对于简单的事情可能过度。