1

I have a config.json that I am going to load into my app as a Backbone Model like:

var Config = Backbone.Model.extend({
    defaults: {
        base: ''
    },
    url: 'config.json'
});

Other models should be dependent on some data contained in Config like:

var ModelA = Backbone.Collection.extend({
    initialize: function(){
        //this.url should be set to Config.base + '/someEndpoint';
    }
});

In above example, ModelA's url property is dependent on Config's base property's value.

How do I go about setting this up properly in a Backbone app?

lbrahim
  • 3,710
  • 12
  • 57
  • 95
  • One way is override [`Backbone.sync`](http://backbonejs.org/#Sync) – hindmost Aug 06 '15 at 11:07
  • @hindmost A simple code sample would be appreciated. I am new to Backbone. Thanks. – lbrahim Aug 06 '15 at 11:12
  • 1
    Here can be no simple samples. However I suggest to look at [my JS app](https://github.com/hindmost/jslogflush-manager), [where](https://github.com/hindmost/jslogflush-manager/blob/master/manager.js#L5) Backbone.sync overriding is used for simliar task as your. – hindmost Aug 06 '15 at 13:45
  • @hindmost very nice, Ibrahim this is your answer. Alternatively you can use `backbone-associations` or `backbone-relational` to accomplish the same thing more generally. But the strategy is good - he says if missing dependencies, return `false` from `sync`, and otherwise compose it from them. – tacos_tacos_tacos Aug 07 '15 at 09:34
  • @tacos_tacos_tacos If I use `backbone-associations` for example then I would just make a 1:1 association between `Config` and `ModelA` and when fetching for `ModelA` `Config` will also be fetched and available to `ModelA` ? – lbrahim Aug 07 '15 at 10:29

1 Answers1

2

As I see it, your basic questions are:

  • How will we get an instance of the configuration model?
  • How will we use the configuration model to set the dependent model's url?
  • How can we make sure we don't use the url function on the dependent model too early?

There are a lot of ways to handle this, but I'm going to suggest some specifics so that I can just provide guidance and code and "get it done," so to speak.

I think the best way to handle the first problem is to make that configuration model a singleton. I'm going to provide code from backbone-singleton GitHub page below, but I don't want the answer to be vertically long until I'm done with the explanation, so read on...

var MakeBackboneSingleton = function (BackboneClass, options) { ... }

Next, we make a singleton AppConfiguration as well as a deferred property taking advantage of jQuery. The result of fetch will provide always(callback), done(callback), etc.

var AppConfiguration = MakeBackboneSingleton(Backbone.Model.extend({
    defaults: {
        base: null
    },
    initialize: function() {
        this.deferred = this.fetch();
    },
    url: function() {
        return 'config.json'
    }
}));

Now, time to define the dependent model DependentModel which looks like yours. It will call AppConfiguration() to get the instance.

Note that because of MakeBackboneSingleton the follow is all true:

var instance1 = AppConfiguration();
var instance2 = new AppConfiguration();
instance1 === instance2; // true
instance1 === AppConfiguration() // true

The model will automatically fetch when provided an id but only after we have completed the AppConfiguration's fetch. Note that you can use always, then, done, etc.

var DependentModel = Backbone.Model.extend({
    initialize: function() {
        AppConfiguration().deferred.then(function() {
            if (this.id)
                this.fetch();
        });
    },
    url: function() {
        return AppConfiguration().get('base') + '/someEndpoint';
    }
});

Now finally, putting it all together, you can instantiate some models.

var newModel = new DependentModel();   // no id => no fetch

var existingModel = new DependentModel({id: 15}); // id => fetch AFTER we have an AppConfiguration

The second one will auto-fetch as long as the AppConfiguration's fetch was successful.

Here's MakeBackboneSingleton for you (again from the GitHub repository):

var MakeBackboneSingleton = function (BackboneClass, options) {
    options || (options = {});

    // Helper to check for arguments. Throws an error if passed in.
    var checkArguments = function (args) {
        if (args.length) {
            throw new Error('cannot pass arguments into an already instantiated singleton');
        }
    };

    // Wrapper around the class. Allows us to call new without generating an error.
    var WrappedClass = function() {
        if (!BackboneClass.instance) {
            // Proxy class that allows us to pass through all arguments on singleton instantiation.
            var F = function (args) {
                return BackboneClass.apply(this, args);
            };

            // Extend the given Backbone class with a function that sets the instance for future use.
            BackboneClass = BackboneClass.extend({
                __setInstance: function () {
                    BackboneClass.instance = this;
                }
            });

            // Connect the proxy class to its counterpart class.
            F.prototype = BackboneClass.prototype;

            // Instantiate the proxy, passing through any arguments, then store the instance.
            (new F(arguments.length ? arguments : options.arguments)).__setInstance();
        }
        else {
            // Make sure we're not trying to instantiate it with arguments again.
            checkArguments(arguments);
        }

        return BackboneClass.instance;
    };

    // Immediately instantiate the class.
    if (options.instantiate) {
        var instance = WrappedClass.apply(WrappedClass, options.arguments);

        // Return the instantiated class wrapped in a function so we can call it with new without generating an error.
        return function () {
            checkArguments(arguments);

            return instance;
        };
    }
    else {
        return WrappedClass;
    }
};
tacos_tacos_tacos
  • 10,277
  • 11
  • 73
  • 126