2

I would like something like the following behavior:

  1. Call get() on a collection.
  2. Fetch the model only if it has not yet been fetched.
  3. Return the model or fire an event signifying that it is ready.

Is there a standard way to do this in backbone?

elplatt
  • 3,227
  • 3
  • 18
  • 20
  • The "standard" way would be to override the method you want to define a different behavior for, in this case the get method on a collection. So if I understand correctly, you want to call get on a collection, and if, internally, that method can't find the model in the current collection, to fetch it from the data-source and add it to the collection? – kinakuta Aug 27 '14 at 22:30
  • That's correct. I don't what to reinvent the wheel if backbone already provides this behavior, which is what I'm asking. – elplatt Aug 28 '14 at 01:34
  • AFAIK you cant manage it without overriding collection `get` method. Plus you will have to take care about async model `fetch` and sync `get` behavior. – Evgeniy Aug 28 '14 at 05:31
  • In short, there's not a standard way to do this without writing a custom get. @Evgeniy makes a good point about the asynchronous nature of fetch makes using it inside a get operation problematic in my view. Why not just fetch a collection first and go from there? If there are lots of models, then paging is a standard approach to dealing with large datasets. – kinakuta Aug 28 '14 at 06:11

1 Answers1

1

There is no standard method in backbone to do this and requires you to modify the model and collection. I know that you haven't mentioned jQuery in the question, I just use it in the example I am providing for its deferred object capabilities and can be swapped out for any deferred object library.

The problem with this approach is that the collections get() method will have to return a deferred object for you to work with rather than the model. This is an unfortunate by product of building an asynchronous operation into the collections get() method.

For the model we will need a way of tracking whether it is fetched or not. Using a timestamp for this can potentially allow you to 'timeout' the model and re fetch it if it is has expired. To do this we override the fetch method on the model.

Warning: this code is untested but provides a way to approach this

var Model = Backbone.Model.extend({

    /**
     * Last fetched public variable
     * @var {Object} Date || null
     */
    lastFetched : null,


    /**
     * Custom fetch always adds a timestamp to the model
     * when it was last fetched from the server. This
     * allows us to prevent unnecessary calls to the backend
     * when a model is still fresh.
     * @return {Object} jQuery deferred
     */
    fetch : function() {

        if ( _.isNull(this.lastFetched) ) {
            this.lastFetched = new Date().getTime();

            return Backbone.Model.prototype.fetch.apply(this, arguments);
        } else {
            return new $.Deferred().resolve();
        }
    }
});

The collection needs to have it's get method overridden to include the model fetch routine. Instead of returning a model, the get method will return a deferred object that can be used to chain callbacks for when the model is ready.

var Collection = Backbone.Collection.extend({

    model : Model,

    /**
     * Custom get retrieves a model if it exists and returns the result
     * of the models custom fetch method
     * @param {Integer} id | cid
     * @return {Object}
     */
    get : function(id) {
        var model = Backbone.Collection.prototype.get.apply(this, args);

        if ( ! model )
            model = new this.model({id : id});

        return {
            deferred : model.fetch(),
            model : model
        };
    }
});

Usage

var collection = new Collection(),
    result = collection.get(1);

result.deferred.done(function() {
    console.log(result.model);
});

This would allow you to use the workflow you provided in the question.

David Barker
  • 14,484
  • 3
  • 48
  • 77