How to load bootstrapped models in Backbone.js while using AMD (require.js)

26,527

Solution 1

This is how we bootstrap data in a way that it doesn't pollute the global namespace. Instead it uses require.js exclusively. It also helps you provide the initial app configuration based on variables within the template.

Within your rendered page

<script src="require.js"></script>
<script>
define('config', function() {
  return {
    bootstrappedAccounts: <%= @accounts.to_json %>,
    bootstrappedProjects: <%= @projects.to_json(:collaborators => true) %>
  };
});
</script>
<script src="app.js"></script>

globals.js

This file checks for config and extends itself using any of the data returned

define([
  'config',
  'underscore'
], function(config) {

  var globals = {
  };
  _.extend(globals, config);
  return globals;

});

config.js

This file is needed if you want be able to load the app regardless of if you have defined config in the page.

define(function() {
  // empty array for cases where `config` is not defined in-page
  return {};
});

app.js

require([
  'globals',
  'underscore',
  'backbone'
], function(globals) {

  if (globals.bootstrappedAccounts) {
    var accounts = new Backbone.Collection(globals.bootstrappedAccounts);
  }
  if (globals.bootstrappedProjects) {
    var projects = new Backbone.Collection(globals.bootstrappedProjects);
  }

});

Solution 2

Looks like you can use the require.config() function or the "require" global with the "config" option in order to pass data to a module through the special dependency "module". See http://requirejs.org/docs/api.html#config-moduleconfig:

There is a common need to pass configuration info to a module. That configuration info is usually known as part of the application, and there needs to be a way to pass that down to a module. In RequireJS, that is done with the config option for requirejs.config(). Modules can then read that info by asking for the special dependency "module" and calling module.config().

So, for bootstrapping models we have, in the top level HTML page:

<script>
var require = {
    config: {
        'app': {
            bootstrappedAccounts: <%= @accounts.to_json %>
            bootstrappedProjects: <%= @projects.to_json(:collaborators => true) %>
        }
    }
};
</script>
<script src="scripts/require.js"></script>

Then in the app module (app.js), we have:

define(['module'], function (module) {
    var accounts = new Backbone.Collection( module.config().bootstrappedAccounts );
    var bootstrappedProjects = new Backbone.Collection( module.config().bootstrappedProjects );
});

Here "module" is a special dependency supplied for these types of cases.

This is untested but looks pretty sure from the documentation.

Solution 3

In RequireJS this is done with the config option for requirejs.config(). Modules can then read that info by asking for the special dependency "module" and calling module.config(). Example:

index.html

<script>
  var require = {
    config: {
      'app': {
        'api_key': '0123456789-abc'
      }
    }
  };
</script>
<script src="js/libs/require.js" data-main="js/main"></script>

main.js

require( ['app'], function(App) {
  new App();
});

app.js

define( ['module'], function(module) {
  var App = function() {
    console.log( 'API Key:', module.config().api_key );
  };

  return App;
});

Just note that the name of the configuration-object must match the name of the module. In my example the name of the module was app, so the name of the configuration-object needed to be named app as well. In the module, you will need to include ['module'] as a dependency and call module.config()[property name] to retrieve the configuration-data.

Read the documentation about this: http://requirejs.org/docs/api.html#config-moduleconfig

Solution 4

Some of the answers here got me close to my similar problem but nothing nailed it. In particular the top ranked and accepted answer gave seemed to give me a nasty race condition where sometimes the dummy object would load first. This also happened 100% of the time when used with the optimiser. It also uses explicit string names for the module which the require documentation specifically advises you not to do.

Here's how I worked it. Similar to Brave Dave, I use the config object to capture parameters (in my case from a jsp page) like so

<script type="text/javascript">
    var require = {
        config: {
            options : { 
                bootstrappedModels : ${models}
            }
        }
    }
</script>

In particular note that my parameters are in an object called options. This name is not optional! Though the documentation makes no mention of this, the following is how require will load your config (line 564 in requirejs 2.1.1) :

config: function () {
    return (config.config && config.config[mod.map.id]) || {};
},

The key point is that there has to be property on the config object with the key mod.map.id which resolves to 'options'.

From here you can now access the models like so

define(['module'], function(module){
    console.log(module.config().bootstrappedModels);
    //...
});

Solution 5

You could add a loopy function at the end of your AMD module to check for when the init method is defined (so that it can be populated after body, or loaded from an include). that way the module is guaranteed available and initialization can happen when it's ready.

require(...,function (...) {
   //define models collections, etc..

   var initme = function () {
     if(document.initThisModule) 
       document.initThisModule();
     else
       setTimeout(initme, 10);
   }();
});
Share:
26,527
opengrid
Author by

opengrid

Updated on July 09, 2022

Comments

  • opengrid
    opengrid almost 2 years

    Backbone.js documentation suggest loading bootstrapped models this way:

    <script>
    var Accounts = new Backbone.Collection;
    Accounts.reset(<%= @accounts.to_json %>);
    var Projects = new Backbone.Collection;
    Projects.reset(<%= @projects.to_json(:collaborators => true) %>);
    </script>
    

    But this is a pattern that can't be used in AMD approach (using require.js)

    The only possible solution is to declare global variable storing JSON data and use this variable later in relevant initialize methods.

    Is there a better way to do this (without globals)?

  • opengrid
    opengrid almost 12 years
    I noticed also that config.js is needed for require.js optimizer to work without errors. Please explain why you suggest to add another layer using globals.js.
  • Peter Davis
    Peter Davis almost 12 years
    While this would work (as would a global, per the original question), it is contrary to the AMD philosophy. An AMD module should declare all of its dependencies and ideally not rely on globals, which "document" certainly is (used by $ to look up the HTML).
  • Peter Davis
    Peter Davis almost 12 years
    @opengrid - that's why it's in a <script> tag
  • Peter Davis
    Peter Davis almost 12 years
    I like this as sort of an inversion of @dlrust's answer. Although, it relies on your views and collections being able to handle any data that is 'reset' after they might already have been rendered, which while generally a good idea can involve a lot of extra bookkeeping.
  • Ollie Edwards
    Ollie Edwards over 11 years
    I find that this can work but because the modules are loaded async, sometimes the dummy will load before the real options. I also found when optimising the dummy will be preferred to the real options. It's an interesting idea but in my experience this doesn't work well at all, and I think there are other better ways to achieve this in the other answers.
  • Chris Salzberg
    Chris Salzberg over 11 years
    @BraveDave's answer works for me, but this one doesn't, although I wish it did.
  • Ollie Edwards
    Ollie Edwards over 11 years
    @shioyama, interesting. What version of requirejs are you using? What actually happened when you tried this way? Seems there are some fairly major inconsistencies if we're all getting different results.
  • andho
    andho over 11 years
    Aren't the Collection instantiated everytime that require(['Models']); is called?
  • Colin Young
    Colin Young over 11 years
    I haven't been able to get @BraveDave's solution, or this one to work. For now I'm using the sloppy global variable method. In my module, module.config().myVar is always null.
  • Asgaroth
    Asgaroth about 11 years
    This is the setup that is working for me, what I do in order to make it work with the optimizer is to excludeShallow my config.js that way it will always use the module defined in the html
  • d4kris
    d4kris about 11 years
    Nice solution, worked for me and should also work with the optimizer (havent come that far yet). I noticed that the 'app' corresponds to the module where the objects are loaded, so if you want to use the bootstrapped data in another module (eg otherModule.js) it needs to be in the config : { 'otherModule': otherModuleBootstrapData }
  • shrooma
    shrooma about 11 years
    Is there a reason you can't do a define within main.js? The main.js file seems a little unnecessary here, unless main needs to use require instead of define. To say it another way, could you copy all code from app.js into main.js, and then change the config to config.main instead of config.app? Then you could delete app.js all together. In testing, this seems to work, but as I'm new to requirejs, it might be bad practice.
  • Tom Doe
    Tom Doe about 11 years
    As long it's a module, it's able to retrieve the data assigned to it in module.config()[property/module name]. However, typically main.js is reserved for requiring core modules within the application as dependencies by way of the require statement.
  • Tom Doe
    Tom Doe about 11 years
    I may not have used the best module name in the example above. Instead of app.js pretend it's apis/GoogleAnalytics.js since the data it's retrieving is api_key. This would be it's own module that is required from main.js, not the entire application itself. In which case the configuration-object would look like: require = { config: { 'apis/GoogleAnalytics' : { 'api_key': '0123456789-abc' } } };
  • opengrid
    opengrid about 11 years
    Good point about the race condition. I noticed the same problem. The solution that worked for me was to use @OllieEdwards way - excludeShallow: ["config"]
  • opengrid
    opengrid about 11 years
    @dlrust please update your answer using Asgaroth recommendation
  • Dan Abramov
    Dan Abramov about 10 years
    I wish I found your solution earlier! Just spent three hours and made the same conclusion. Was about to post it as Q&A and found your reply. :-)
  • Admin
    Admin over 9 years
    This works, but a word of caution. Ensure you don't have a '</script>' anywhere in your data, otherwise it'll get processed and mess up your tags in spectacular fashion.
  • Alexander Mills
    Alexander Mills almost 9 years
    anyone have a link that describes this @ notation/syntax? I assume it's a RequireJS thing?
  • Willie
    Willie over 8 years
    Great answer. Note that one does have to use define() (as shown in the answer) rather than require() in order for module.config() to work.