17
votes

I've been developing Backbone applications for a little while now, and am just starting to learn to use Backbone with Require.js.

In my backbone app that I am refactoring, I defined a namespace like this: App.model.repo. This model is used over and over again in different views. I do the same thing with a few collections, for example, App.collection.files. These models and collections are bootstrapped in with the initial index file request.

I did find this example, which looks like a great way to get that bootstrapped data in. However, I am struggling with the best way to reuse/share these models and collection between views.

I can think of three possible solutions. Which is best and why? Or is there another solution I am missing entirely?

Solution 1

Define these common modules and collections in the index (when they are bootstrapped in), and then pass them along to each Backbone view as an option (of initialize).

define(['jquery', 'underscore', 'backbone', 'handlebars', 'text!templates/NavBar.html'], 
    function($, _, Backbone, Handlebars, template){     
        return Backbone.View.extend({
            template: Handlebars.compile(template),
            initialize: function(options){
                this.repoModel = options.repoModel; // common model passed in
            }
        });
    }
);

These seems clean as far as separation, but could get funky quick, with tons of things being passed all over the place.

Solution 2

Define a globals module, and add commonly used models and collections to it.

// models/Repo.js
define(['backbone'],
    function(Backbone){
        return Backbone.Model.extend({
            idAttribute: 'repo_id'
        });
    }
);

// globals.js (within index.php, for bootstrapping data)
define(['underscore', 'models/Repo'], 
    function(_, RepoModel){     
        var globals = {};
        
        globals.repoModel = new Repo(<?php echo json_encode($repo); ?>);
        
        return globals
    }
);

define(['jquery', 'underscore', 'backbone', 'handlebars', 'text!templates/NavBar.html', 'globals'], 
    function($, _, Backbone, Handlebars, template, globals){
        var repoModel = globals.repoModel; // repoModel from globals
        
        return Backbone.View.extend({
            template: Handlebars.compile(template),
            initialize: function(options){

            }
        });
    }
);

Does this solution defeat the whole point of AMD?

Solution 3

Make some models and collections return an instance, instead of a constructor (effectively making them Singletons).

// models/repo.js
define(['backbone'],
    function(Backbone){
        // return instance
        return new Backbone.Model.extend({
            idAttribute: 'repo_id'
        });
    }
);

// Included in index.php for bootstrapping data
require(['jquery', 'backbone', 'models/repo', 'routers/Application'],
    function($, Backbone, repoModel, ApplicationRouter){
        repoModel.set(<?php echo json_encode($repo); ?>);

        new ApplicationRouter({el: $('.site-container')});
        Backbone.history.start();
    }
);

define(['jquery', 'underscore', 'backbone', 'handlebars', 'text!templates/NavBar.html', 'models/repo'], 
    function($, _, Backbone, Handlebars, template, repoModel){
        // repoModel has values set by index.php
        
        return Backbone.View.extend({
            template: Handlebars.compile(template),
            initialize: function(options){

            }
        });
    }
);

This I worry could get real confusing about what is a constructor and what is an instance.

End

If you read this far, you are awesome! Thanks for taking the time.

3
I tried both option 1 and 3, by refactoring my existing app. Option 1 quickly got hairy, with options being passed everywhere, often through one module that did not need it all all, just to get to a child module that did need it. In the end, option 3 feels much cleaner to me. Truth be told, I am using all 3 of these options in my current project. Option 1 for short lifecycle elements. Option 2 for simple things like "apiUrl" (common root url). Option 3 for backbone models and collections that are used project wide.Bart
Tangentially related: stackoverflow.com/a/15528785/158651Bart

3 Answers

4
votes

In my case, I prefer option 3. Although, to prevent confusion, I put every singleton instance in their own folder named instances. Also, I tend to separate the model/collection from the instance module.

Then, I just call them in:

define([
  "instance/photos"
], function( photos ) { /* do stuff */ });

I prefer this option as every module is forced to define its dependencies (which is not the case via namespace for example). The solution 2 could do the job, but if I'm using AMD, I want my module as small as possible - plus keeping them small make it easier to unit test.

And lastly, about unit test, I can just re-define the instance inside my unit test to use mock data. So, definitely, option 3.

You can see an example of this pattern on an Open source app I'm working on ATM: https://github.com/iplanwebsites/newtab-bookmarks/tree/master/app

1
votes

I would take a look at this example repo https://github.com/tbranyen/github-viewer

It is a working example of backbone boiler plate (https://github.com/tbranyen/backbone-boilerplate)

Backbone Boiler plate does a lot of unnecessary fluff, but what is really useful about it, is that it gives some clear directions on common patterns for developing complex javascript apps.

I'll try and come back later today to answer you question more specifically (if someone doesn't beat me to it :)

1
votes

I prefer Solution 1. It is generally good to avoid using singletons, and using globals is also something to avoid, especially since you are using RequireJS.

Here are some advantages I can think of for Solution 1:

It makes the view code more readable. Someone looking at the module for the first time can immediately see from looking at the initialize function which models it uses. If you use globals, something might be accessed 500 lines down in the file.

It makes it easier to write unit tests for the view code. Since you could possibly pass in fake models in your tests.