Dan Caddigan
Dan Caddigan

Reputation: 1598

Backbone.js modify newly fetched collection before reset events occur

I'm trying to do the following:

  1. Fetch data from the server
  2. Add a zero-based index to the models before the views are notified
  3. Finally run have the 'render' events fire for the views

I was trying to do this by using a success callback in the collection

View Before

initialize: () ->
  @collection.on 'reset', @render, this

render: () -> ...render code...

Collection Before

search: () ->
  @fetch { 
    success: @fetch_success
  }  

fetch_success: () ->
  for i in [0...collection.models.length]
    collection.models[i].set('go_index', i)

Doing things this way was causing the views to fire their render events before the collection was updated by the success callback. The solution I came up with was to have the views listen to a fetched event, then have the collection fire that after it successfully modifies the collection:

View After

initialize: () ->
  @collection.on 'fetched', @render, this

render: () -> ...render code...

Collection After

initialize: () ->
  @on 'reset', @add_index_and_notify, this

add_index_and_notify: () ->
  for i in [[email protected]]
    @models[i].set('go_index', i)
  @trigger('fetched')

This works fine, I'm just wondering if this is the most elegant way to accomplish this or if there is a built-in way that I'm missing.

UPDATE 3/15

I've come up with a cleaner solution that doesn't require the view to do any of the dirty work and I don't have to create a custom event. The trick is to listen to the sync event (which fires after reset)

View Final

initialize: () ->
  @collection.on 'sync', @render, this

render: () -> ...render code...

Collection Final

initialize: () ->
  @on 'reset', @add_index, this

add_index: () ->
  for i in [[email protected]]
    @models[i].set('go_index', i)

Hopefully this pattern can help somebody searching in the future.

Upvotes: 1

Views: 893

Answers (3)

Peter Lyons
Peter Lyons

Reputation: 146064

Your view should get both the model and it's index separately from the collection because the index isn't really part of the model record itself. Try having your view use collection.each to loop over the models as the callback function there will get model, index, collection as parameters. Remember a view can pass more than just a single model to its template.

class CollectionView1 extends Backbone.View
  render: =>
    $el = @$el
    $el.empty()
    @collection.each (model, index) ->
      modelView = new ModelView1 {model, index}
      $el.append modelView.render().el
    return this

Upvotes: 1

Dan Caddigan
Dan Caddigan

Reputation: 1598

I've already posted the solution in the original question, but I figured I'd formally post as an answer:

The cleaner solution doesn't require the view to do any of the dirty work and doesn't require a custom event. The trick is to listen to the sync event (which fires after reset)

View Final

initialize: () ->
  @collection.on 'sync', @render, this

render: () -> ...render code...

Collection Final

initialize: () ->
  @on 'reset', @add_index, this

add_index: () ->
  for i in [[email protected]]
    @models[i].set('go_index', i)

Hopefully this pattern can help somebody searching in the future.

Upvotes: 3

Sushanth --
Sushanth --

Reputation: 55750

Why don't you listen to the add event of the collection..

initialize: function() {
  this.listenTo(this.collection, 'reset', this.render);
  this.listenTo(this.collection , 'add' , this.add_index_and_notify);
  this.index = 0;
},

add_index_and_notify: function(model){
    model.set({go_index : this.index++}, {silent : true});
    // Render the model here
},

render: function(){
   this.$el.empty().append(Your template);
   this.index= 0;
   _.each(this.collection.models, function(model){
         this.add_index_and_notify(model);
   }
}

Upvotes: 0

Related Questions