Backbone Routers: wait for data to be fetched firs

2019-03-18 14:58发布

问题:

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)?

回答1:

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.



回答2:

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.