Backbone + Rails collection fetching

Go To StackoverFlow.com

3

Previously, my Backbone router looked like this:

class App.Routers.ThingsRouter extends Backbone.Router
  routes: '': 'index'
  routes: 'previews/:id': 'show'

  initialize: ->
    @collection = new App.Collections.ThingsCollection
    @collection.fetch

  index: ->
    view = new App.Views.ThingsIndex(collection: @collection)
    $('#app-container').html(view.render().el)

  show: (id) ->
    @model = @collection.get(id)
    view = new App.Views.ThingsShow(model: @model)
    $('#app-container').html(view.render().el)

When navigating to http://localhost, I would get the index view rendered, and when clicking on individual elements, I would get the show view rendered. However, if I went to http://localhost/things/1 directly (i.e. by typing in the URL), the show view wouldn't be rendered. I realized that this was because the view was being rendered before @collection.fetch was completing. I changed my router to the following:

class App.Routers.ThingsRouter extends Backbone.Router
  routes: '': 'index'
  routes: 'previews/:id': 'show'

  initialize: ->
    @collection = new App.Collections.ThingsCollection

  index: ->
    @collection.fetch success: =>
      view = new App.Views.ThingsIndex(collection: @collection)
      $('#app-container').html(view.render().el)

  show: (id) ->
    @collection.fetch success: =>
      that.model = that.collection.get(id)
      view = new App.Views.ThingsShow(model: @model)
      $('#app-container').html(view.render().el)

Which works fine. However, there's obviously a little latency as the collection is re-fetched every time I switch routes. Is this good Backbone practice? Not sure if there's a better way of doing this.

2012-04-03 20:06
by clem
Wow, my exact same question. I didn't think I'd find the right combination of words to to google to find someone with the same problem, but I did - tybro0103 2013-01-03 22:31


6

This is a great use case for jQuery's Deferred() method.

Just create a Deferred object and attach it to the router. Then fetch the collection in the initialize method and call resolve() on the Deferred object. Your index and show methods can subscribe to the done callback and instantiate the view. This done callback won't be ran until the collection has been fetched. And if it has already been fetched, then it runs immediately.

class App.Routers.ThingsRouter extends Backbone.Router
  routes: '': 'index'
  routes: 'previews/:id': 'show'

  initialize: ->
    @collectionFetched = new $.Deferred
    @collection = new App.Collections.ThingsCollection
    @collection.fetch success: ->
      @collectionFetched.resolve()

  index: ->
    that = this
    @collectionFetched.done ->
      view = new App.Views.ThingsIndex(collection: that.collection)
      $('#app-container').html(view.render().el)

  show: (id) ->
    that = this
    @collectionFetched.done ->
      that.model = that.collection.get(id)
      view = new App.Views.ThingsShow(model: that.model)
      $('#app-container').html(view.render().el)
2012-04-03 22:04
by Paul
This works great! Will hang around for more suggestions to see what happens, but thanks - clem 2012-04-03 22:22