Search code examples
javascriptbackbone.jsbackbone-viewsbackbone-modelbackbone.js-collections

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

  1. collections

    a. todoList.coffee

  2. models

    a. todo.coffee

  3. views

    a. todoView.coffee

    b. todoListView.coffee

  4. 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?


Solution

  • 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.