Backbone Collection communicating with Backbone Models

I'm trying to understand what the best practice is for communicating between the different components of a Backbone project. I've been re-implementing the Backbone version of TodoMVC and my question is around removing models from the collection object.

I understand that Backbone model instances trigger a change event whenever any of its model properties are modified via .set() and that the view associated with the model instance should .listenTo() changes from the model instance and then re-render. However, what's the best practice for communication between models and the collection object that houses it? For example, how should the communication work when a model is removed from the collection object?

Here's what I think: when a model is removed, I think the model instance should emit a custom event that the collection object is listening for and pass itself along. When the collection object hears this event, it should remove the model instance from the list (along with any event listeners attached to the model) and then the entire collection object should re-render itself. This re-rendering process will then create a new set of models and model-views.

Is this the best approach? I would love to hear your input! To me, this process of re-rendering sounds really expensive since you'll have to destroy the existing DOM elements, remove their event listeners, and then re-create them again.

UPDATE - 3/26/2015

To make this more concrete, I'll include the code that I have so far and point out where I feel my understanding is off.

File Structure

  • collections

    a. todoList.coffee

  • models

    a. todo.coffee

  • views

    a. todoView.coffee

    b. todoListView.coffee

  • app.coffee

  • app.coffee

    window.app = app = window.app || {} 
    
    data = [
      {
        title: 'Eat dinner',
        completed: false
      }
      {
        title: 'Go to gym',
        completed: true
      }
    ]
    
    app.todos = data.map (todo) -> new app.Todo todo
    
    app.todoList = new app.TodoList app.todos
    
    app.todoListView = new app.TodoListView
      collection: app.todoList
    
    app.$app = $('#todo-app')
    
    $('#todo-app').append app.todoListView.render().el
    

    todo.coffee

    window.app = app = window.app || {}
    
    app.Todo = Backbone.Model.extend
      defaults:
        title: ''
        completed: false
    
      toggleComplete: ->
        this.set 'completed', !this.get 'completed'
    

    todoList.coffee

    window.app = app = window.app || {}
    
    app.TodoList = Backbone.Collection.extend
      model: app.Todo
    
      initialize: () ->
        # This is what I don't like - creating 'remove-todo' event
        this.on 'remove-todo', this.removeTodoFromList
    
      removeTodoFromList: (model) ->
        this.remove model
    
      getCompleted: ->
        this.filter (model) -> model.completed
    
      getNotCompleted: ->
        this.filter (model) -> !model.completed
    

    todoView.coffee

    window.app = app = window.app || {}
    
    app.TodoView = Backbone.View.extend
      tagName: 'li'
    
      events:
        'click input'   : 'checkComplete'
        'click .delete' : 'removeTodo'
    
      checkComplete: (e) ->
        this.model.toggleComplete()
    
      removeTodo: (e) ->
        # I don't like how the collection is listening for this custom event 'remove-todo'
        this.model.trigger 'remove-todo', this.model
    
      initialize: ->
        this.listenTo this.model, 'change:completed', () ->
          this.render()
    
      render: ->
        template = _.template $('#todo-view').html()
        this.$el.html template this.model.toJSON()
        return this
    

    todoListView.coffee

    window.app = app = window.app || {}
    
    app.TodoListView = Backbone.View.extend
      tagName: 'ul'
    
      className: 'todo-list'
    
      initialize: ->
        this.collection.on 'remove', (() ->
          this.resetListView()
          this.render()
        ), this
    
      addOne: (model) ->
        todoView = new app.TodoView
          model: model
    
        this.$el.append todoView.render().el
    
      resetListView: ->
        this.$el.html('')
    
      render: ->
        _.each this.collection.models, ((model) -> this.addOne model), this
        return this
    

    Explanation of Code

    As you can see in my comments above, whenever a click happens on the remove button, my todoView triggers a custom event 'remove-todo'. The todoList collection listens to this event and removes the specific model from the collection. Since a 'remove' event is triggered whenever a collection removes a model, the todoListView listens for this 'remove' event and then re-renders. I feel like I'm off somewhere. Any advice?


    It seems you are talking about views when you talk about models. When a model is removed from a collection you don't need a custom event, a "remove" event is triggered. http://backbonejs.org/#Collection-remove

    If you want to remove the corresponding view, use http://backbonejs.org/#View-remove this will manage the DOM and listener.

    The re-rendering of view (I don't understand what you are talking about when you talk about rerendering collection) can be triggered if you listen to "remove" models from the collection, otherwise listen to http://backbonejs.org/#Collection-add, or http://backbonejs.org/#Model-hasChanged is you want to rerender your view when a model has changed.

    I hope it helps.

    链接地址: http://www.djcxy.com/p/65536.html

    上一篇: 防止骨干收集重置

    下一篇: 骨干集合与主干模型进行通信