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

Javascriptbackbone.jsRequirejsJs Amd

Javascript Problem Overview


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)?

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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);
   }();
});

Solution 6 - Javascript

I'm not (too) familiar with the AMD approach, but instead of using a global variable, why don't you add the JSON to the dom.

for example:

var json = ...,
$jsonContainer = $(json).wrap("<script id='json-container' type='text/javascript'>").appendTo($("body"));

Then, instead of an embedded script tag as suggested by the backbone documentation, inside the document ready:

$(function(){
    MyCollection.reset($("#json-container").html());
    ...
});

Solution 7 - Javascript

How about doing something like this:

<script>
define('Models', ['backbone'], function(Backbone) {
    var Models = {
        Accounts: new Backbone.Collection,
        Projects: new Backbone.Collection
    };

    Models.Accounts.reset(<%= @accounts.to_json %>);
    Models.Projects.reset(<%= @projects.to_json(:collaborators => true) %>);

    return Models;
});
</script>

Then you'll be able to use Models in other modules like this:

var models = require(['Models']);
models.Accounts.doWhatYouNeed();

or this:

define(['any', 'dependencies', 'and', 'Models'], function(a, b, c, Models) {
   // Models will be available here
});

Solution 8 - Javascript

Like described above, the 'data module' (or config, or whatever you want to call it) could be included in a file that is already generated anyway (e.g. index.html) but I think this is rather ugly.

Another way would be to declare it in its own module file but this would require an extra roundtrip to the server in production environments. As soon as you want to build and optimize your requirejs dependencies, the data module cannot be included because it is dynamically generated upon page load.

A third option might be to append it to one of the files that you serve (for example, the optimized requirejs file), but I have no idea of how/if that could be done.

Solution 9 - Javascript

Ansewr by @dlrust work but it not able extend param and pass more than from one place in code. If you try do something like this in your render template:

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

and in another file add some data

<script>
    define('config', function() {
        return {
            goods: <%= some data %>,
            showcaseList: <%= some json or array %>
        };
    });
</script>

it was overwrite (NOT EXTEND!!!). In config will be only last declared data.

My solution: used Backbone model with set/get data.

#app.js#

define("App", [], function() {
    window.App = {
        // модели
        Model: {},
        // коллекции
        Collection: {},
        // виды
        View: {},
        // роутеры
        Router: {},
        // модальные окна
        Modal: {},
        // UI компоненты
        UI: {}
    };
    return window.App;
});

#global.js#

define(["App", "underscore", "backbone"], function(App, _, Backbone) {
    "use strict";
    
    // модель глобальных данных
    App.Model.Global = Backbone.Model.extend({
        defaults: {}
    });
    
    return new App.Model.Global;    
});

#index.php#

<!DOCTYPE html>
<html>
	<head>
		<!--HEAD_START-->
        <script type="text/javascript" data-main="/app/init" src="/app/require/require.js"></script>
        <!--HEAD_END-->
	</head>
    
	<body>			
		<div id="tm-inner-wrap">
            <div id="loader"><i class="uk-icon-refresh uk-icon-spin"></i></div>
            <!--HEADER_START-->
            <?= $this->includeTpl('header_view'); ?>
            <!--HEADER_END-->
    		
    		<!--CONTENT_START-->
            <div>your html content data</div>
            <!--CONTENT_END-->
    		
    		<!--FOOTER_START-->
            <?= $this->includeTpl('footer_view');?>
            <!--FOOTER_END-->
            
            <script>
                require(["global"], function(Global) {
                    Global.set("notifyList", <?=json_encode($this->notifyList);?>);
                });
            </script>
        </div>
	</body>
</html>

another template #someTemplate.php#

<div class="tm-inner-body">
    <div class="uk-container uk-container-center">
        // content data
    </div>
</div>

<script>    
    require(["global", "module/index"], function(Global) {
        Global.set("goodList", <?=json_encode($this->goodList);?>);
    });
</script>

#index.js#

require(["App", "core", "jquery", "uikit!uikit-addons-min", "underscore", "backbone", "global", "module/good/goodView"], function(App, Core, $, UIkit, _, Backbone, Global, goodView) {
    "use strict";
    
    // Global.get("notifyList"); its too able

    App.Collection.Good = new Backbone.Collection(Global.get("showcaseList")["items"]);
    
    // вид списка товаров
    App.View.GoodList = Backbone.View.extend({
        // елемент
        el: ".tm-good-list",
        // init
        initialize: function() {
            this.collection = App.Collection.Good;
            // список товаров
            this.drawList();
        },
        // отрисовка списка
        drawList: function() {
            this.$el.empty();
            this.collection.each(function(item, index) {
                this.$el.append(this.drawItem(item));
            }, this);
        },
        // отрисовка елемента
        drawItem: function(data) {
            var good = new goodView({model: data});
            return good.render().el;
        }
    });
    
    App.View.Index = Backbone.View.extend({
        el: "body",
        // пользовательские события
        events: {
            //
        },
        // init
        initialize: function() {
            var $this = this;
            if(Global.get("showcaseList")) new App.View.GoodList();
        }
    });
    
    new App.View.Index();
});

##File structure:##

file structure

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionopengridView Question on Stackoverflow
Solution 1 - JavascriptdlrustView Answer on Stackoverflow
Solution 2 - JavascriptBrave DaveView Answer on Stackoverflow
Solution 3 - JavascriptTom DoeView Answer on Stackoverflow
Solution 4 - JavascriptOllie EdwardsView Answer on Stackoverflow
Solution 5 - JavascriptJustin AlexanderView Answer on Stackoverflow
Solution 6 - JavascriptidbentleyView Answer on Stackoverflow
Solution 7 - JavascriptYauheni LeichanokView Answer on Stackoverflow
Solution 8 - JavascriptgodspeedelbowView Answer on Stackoverflow
Solution 9 - JavascriptVitaliyView Answer on Stackoverflow