How to load bootstrapped models in Backbone.js while using AMD (require.js)
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);
}();
});
opengrid
Updated on July 09, 2022Comments
-
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 almost 12 yearsI 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 almost 12 yearsWhile 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 almost 12 years@opengrid - that's why it's in a <script> tag
-
Peter Davis almost 12 yearsI 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 over 11 yearsI 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 over 11 years@BraveDave's answer works for me, but this one doesn't, although I wish it did.
-
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 over 11 yearsAren't the Collection instantiated everytime that
require(['Models']);
is called? -
Colin Young over 11 yearsI 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 about 11 yearsThis 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 about 11 yearsNice 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 (egotherModule.js
) it needs to be in theconfig : { 'otherModule': otherModuleBootstrapData }
-
shrooma about 11 yearsIs 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 userequire
instead ofdefine
. To say it another way, could you copy all code from app.js into main.js, and then change the config toconfig.main
instead ofconfig.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 about 11 yearsAs long it's a module, it's able to retrieve the data assigned to it in
module.config()[property/module name]
. However, typicallymain.js
is reserved for requiring core modules within the application as dependencies by way of therequire
statement. -
Tom Doe about 11 yearsI may not have used the best module name in the example above. Instead of
app.js
pretend it'sapis/GoogleAnalytics.js
since the data it's retrieving isapi_key
. This would be it's own module that is required frommain.js
, not the entire application itself. In which case the configuration-object would look like:require = { config: { 'apis/GoogleAnalytics' : { 'api_key': '0123456789-abc' } } };
-
opengrid about 11 yearsGood point about the race condition. I noticed the same problem. The solution that worked for me was to use @OllieEdwards way - excludeShallow: ["config"]
-
opengrid about 11 years@dlrust please update your answer using Asgaroth recommendation
-
Dan Abramov about 10 yearsI 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 over 9 yearsThis 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 almost 9 yearsanyone have a link that describes this @ notation/syntax? I assume it's a RequireJS thing?
-
Willie over 8 yearsGreat answer. Note that one does have to use
define()
(as shown in the answer) rather thanrequire()
in order for module.config() to work.