Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I know when my backfire collection has finished loading from the server?

I'm playing with firebase and backfire. The example doesn't explicitly say, but I've figured out that it loads data from the server when you instantiate an instance of your Backbone.Firebase.Collection. E.g.:

var TodoList = Backbone.Collection.extend({
  model: Todo,
  firebase: new Backbone.Firebase("https://<your-namespace>.firebaseio.com")
});
var todos = new TodoList(); // fetches data

How do I know when the retrieval is done?

like image 393
sprugman Avatar asked Jan 14 '23 00:01

sprugman


1 Answers

Having worked pretty extensively with Backfire's model, I have several thoughts on this. I hope some of them give you good ideas for your project.

Changing mental models to real-time environment

First of all, get out of the mentality of knowing when "all the data is loaded", assuming this troubles you as it did me early on. We're in a real-time environment now. Just start from zero and treat every record that comes in as an update. This saves a lot of time and energy trying to deal with states.

Lazy rendering and DOM bindings

Now with Backbone, I often find myself wanting to do a lazy render. That is, I want to deal with the following conditions logically:

  • start collecting data immediately, but don't show it until render is called
  • show a "loading" message until some data appears
  • when several records arrive close together, don't re-render for every single one

A good solution for frequently changing data is Backbone.ModelBinder's CollectionBinder tool, which manipulates each node of the DOM individually instead of re-rendering all the records. There are plenty of examples on their site so I won't go into detail here.

Debounce as a quick and dirty solution

Underscore's debounce method is a great solution for smaller scale DOM manipulations that don't need all the complexity of data bindings. Debounce with a wait of about 250 works well for me, ensuring render always occurs on data changes, but only once if we get a large clump of updates in a row.

Assuming we have created a collection that extends Backbone.Firebase.Collection, then we can do something like the following:

var View = Backbone.View.extend({

   initialize: function() {
      this.listenTo( this.collection, 'add remove', _.debounce(_.bind(this.dataChanged, this), 250) );
   },

   render: function() {
       this._isRendered = true;

       /* do all my rendering magic here */
   },


   dataChanged: function() {
      // wait until render has been called at least once
      // if so, re-render on any change
      this._isRendered && this.render();
   }
});

Using a Deferred to wait for loaded data

On my implementation of Backfire, I've added a stateful method that notifies me on the first load. I did this using jQuery's Deferred object. Then I just listen for the collection to fire a sync event:

this.collection.once('sync', /* data is loaded */);

A nice thing about Firebase is that the initial Firebase.on('child_added'...) results (the existing records) tend to come in one nice big clump--one after another. So as an added bonus, I use debounce to make my "loaded" method fire after the initial clump is completed, so I don't get one record, call loaded, and then immediately need to take some action for a series of updates.

Since this is implementation specific, I'm going to be a bit abstract here, but this is the gist of it:

// inside my wrapper for Backbone.Firebase.Collection
this.ready = $.Deferred();

// debounce our ready listener so it fires on the tail end of 
// the initial update clump which reduces the number of update 
// calls on the initial load process considerably
this.readyFn = _.debounce(this.ready.resolve, 250);

// start monitoring for the first series of updates
// this would need to be invoked before the sync is established
this.on( 'add', this.readyFn );

// wait for the data to come in 
this.ready.always( _.bind(function() { 
   // when the ready promise is fulfilled, we can turn off the listener
   this.off('add', this.readyFn);

   // this is where we trigger the listener event used above
   this.trigger('sync');
}, this) );

I'd use this solution with care. I find that in most cases I can simplify things greatly by forgetting about initial loads and initializing everything empty, then treating everything as an update.

I only utilize this in cases where I need to show some alternative view if no data exists (like instructions on getting started).

like image 116
Kato Avatar answered May 22 '23 09:05

Kato