Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone Routers: wait for data to be fetched first

I think I don’t quite get the idea behind the proper usage of Backbone routers. Here’s what I’ve got:

I have some data that I fetch from the server when the page loads and then pack it into models and collections. The number of those models and collections is indefinite. I want to use the router to be able to render the certain collection’s view directly from the start.

The problem is: Backbone router starts up early, and since I ask it to access a certain view and trigger its render action, it cannot do that, because those views are not yet created. That means I actually have to make my routes start up after the fetch is complete.

I don’t know if this is a proper way to do it, but the only idea I came up with is to:

  1. Wrap the routes definition and the Backbone.history.start(); bit into a separate top-level-accesible function (i.e. prepare to call it manually later on).
  2. Run that function as the success callback for my collections’s fetch()
  3. The number of those collections is unknown, also I have no way to find out when all of them have been fetched, and I don’t want to start the routes more than once. So I make use of _.defer() and _.once().

This works, but it sure looks very weird:

Routers:

    window.startRoutes = _.once(function() {

        var AccountPage = Backbone.Router.extend({

          routes: {
            'set/:id': 'renderSet',
          },

          renderSet: function(setId) {

              /** … **/

              // Call the rendering method on the respective CardView
              CardsViews[setId].render();

          }

        });

        var AccountPageRouter = new AccountPage;

        Backbone.history.start();

    });

Collection:

window.CardsCollection = Backbone.Collection.extend({

    model: Card,

    initialize: function(params) {

        /** … **/

        // Get the initial data
        this.fetch({success: function() {
            _.defer(startRoutes);
        }});

    },

});

So my question is… am I doing it right? Or is there a better way to do this (must be)?

like image 669
Arnold Avatar asked Oct 19 '11 14:10

Arnold


People also ask

What do backbone routers do?

A backbone router is a router designed to be used to construct backbone networks using leased lines. Backbone routers typically do not have any built-in digital dial-up wide-area network interfaces.

How does Backbone js work?

Backbone. js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.

What is the only method available in the backbone JS history?

There is only method named "start" can be used to manipulate the Backbone. js history.

Does backbone need jQuery?

You can use the Backbone. Model without jQuery, but Backbone. View will require either jQuery or Zepto, just like the docs state. Chances of not using view and router is low enough.


2 Answers

You can define your router ahead of time; it won't do anything until you call Backbone.History.start().

You can bind the "reset" event on your collection to start history like this:

my_collection.bind("reset", _.once(Backbone.History.start, Backbone.History))

Then the router will start doing stuff when your collection is fully loaded. I'm not sure if this is exactly what you're looking for (since you mentioned having a variable number of collections).

I have a similar situation, except that I know in advance which collections I want to have loaded before I start routing. I added a startAfter method to my Router, like so:

  window.Workspace = new (Backbone.Router.extend({
    . . .
    startAfter: function(collections) {
      // Start history when required collections are loaded
      var start = _.after(collections.length, _.once(function(){
        Backbone.history.start()
      }))
      _.each(collections, function(collection) {
        collection.bind('reset', start, Backbone.history)
      });
    }
  }));

and then after I've setup my collections

  Workspace.startAfter([collection_a, collection_b, ...])

This could be adapted to work with standalone models too, although I think you'd need to bind to something other than the 'reset' event.

I'm glad I read your example code, the use of _.once and _.defer pointed me in the right direction.

like image 115
2 revs Avatar answered Oct 21 '22 14:10

2 revs


I'm just checking in my .render() method that all required fields are filled, before using it. If it's not filled yet - i'm rendering an 'Loading...' widget.

And all my views are subscribed to model changes, by this.model.bind('change', this.render, this);, so just after model will be loaded, render() will be called again.

like image 27
Igor Artamonov Avatar answered Oct 21 '22 15:10

Igor Artamonov