4

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?

sprugman
  • 19,351
  • 35
  • 110
  • 163
  • I think the right answer to this depends on what exactly you are trying to do. Kato has an excellent analysis of your various options. Could you share a bit more about why you want to know when the data is loaded (as opposed to simply rendering object as you receive 'add' events on the collection)? – Anant Mar 07 '13 at 16:49
  • I'm selecting a random item from a list of items. I need to know when the list is available... – sprugman Mar 07 '13 at 16:55
  • (The list won't change frequently.) – sprugman Mar 07 '13 at 17:02
  • How long with the list be? Hundreds? Thousands? Millions? – Kato Mar 07 '13 at 20:24
  • currently dozens. eventually, maybe hundreds to low thousands – sprugman Mar 07 '13 at 21:14
  • and I also want to present the option to choose from the list eventually -- e.g. via an autocomplete or combo-box – sprugman Mar 07 '13 at 21:15

2 Answers2

8

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

Kato
  • 40,352
  • 6
  • 119
  • 149
1

Here's one method that I figured out -- use the firebase object inside the collection:

todos.firebase.on('value', function(snapshot){
    // do stuff
});

Is that the best way?

sprugman
  • 19,351
  • 35
  • 110
  • 163
  • This will have the desired behavior and is the recommended practice when working with Firebase directly. I'm not sure if it's the best way to expose this functionality through backfire though. We'll investigate. – Andrew Lee Mar 07 '13 at 07:29