Darrell
Darrell

Reputation: 2175

Amd, requirejs - want to ensure something always executes last

I'm trying to avoid global scope for the following problem, but cannot see a way to avoid it.

I have a singleton Javascript object called "Application". This is an AMD module.

I then have many "Modules" (not to be confused with AMD modules) which are just javascript objects that I'd like to register with the "Application" instance.

For example:

 require(['Application'], function(app) {

        var module = {
            name: "theme-switcher",
            start: function() { console.log("started") } }

        app.registerModule(module)
 }

The architecture I am going for, is i'd like for each "Module" on the page to register itself with the "Application" instance.

Here is the tricky part: Only once ALL modules have been registered with the application, do I then want the "Application" instance, to loop through those registered modules and call their "Start()" methods.

The way I thought to do this was to just add another require block at the bottom of the page like this:

 <script type="text/javascript">
require(['Application'], function (app) {
    // start all the registered modules running.
    app.start(() => {
        // this could be a call back function once all modules started..
    });
});

Niavely thinking, that just because this require call was last, that it would allways be executed last. But actually, sometimes this gets fired BEFORE the require calls above - so the Application attempts to Start() all registered modules BEFORE the modules themselves have all registered themselves with the Application.

No matter how I think about this problem, I am often led back to the fact that I need to keep some state in global scope, Something like this:

Module 1:

var app = Application.Instance;
var moduleStart = function(){
    require(['jquery'], function(jquery) {

 // do module goodness here.
}};

app.registerModule({name: "theme-switcher", start: moduleStart })

// later on in page - some other widget // Module 2

var app = Application.Instance;
var moduleStart = function(){
    require(['jquery'], function(jquery) {

 // do second module goodness here.
}};

app.registerModule({name: "foo", start: moduleStart })


And then at the bottom of the page,
var app = Application.Instance;
app.Start(); // loops through registered modules calling start() method.

Surely there must be a way to do this avoiding global scope?

The reason for me wanting to do this, is that I want the "Application" to manage the lifecycle for registered modules on the page - including starting / pausing / stopping them etc. I'd also like the Application to publish an event once ALL modules gave been started - as this is when I would typically stop displaying my "loading" animation, and actually display the DOM - as modules will often manipulate the DOM in their start() methods and I don't want the page to be visible before everything is started.

Upvotes: 2

Views: 42

Answers (1)

Shakespeare
Shakespeare

Reputation: 1286

This will do it. If you make every object you are wanting an AMD module, which I think you should be doing if you have RequireJS in place anyway, then you'll just need an array of strings defining those AMD module names to pass as an argument to the app's init.

Application.js : -

define(function (require, exports, module) {
    "use strict";

    var modules = {};
    var moduleNames = [];
    var numberOfModules = 0;
    var loadedModules = 0;

    exports.init = function (dependencies) {
        numberOfModules = dependencies.length;

        for (var i = 0; i < numberOfModules; i++){
            var name = dependencies[i];
            moduleNames.push(name);

            require([name], function (moduleRef) {
                loadedModules++;
                modules[name] = moduleRef;

                if (numberOfModules === loadedModules) {
                    exports.start();
                }
            });
        }
    };

    exports.start = function () {
        // all modules available
        // use modules.myModuleName to access the module.
        modules.myModuleName.functionName();

        // or if they all have start() function and it needs calling

        for (var i = 0; i < moduleNames.length; i++) {
            modules[moduleNames[i]].start();
        }
    };

});

USAGE Depending on how you are loading your app, assuming you have an Application reference somewhere, just call:

// names of modules RequireJS uses to require, can be changed for each page.
var dependencies = ['moduleOne', 'moduleTwo', 'myModuleName'];
app.init(dependencies);

CodePen of this code, slightly altered to work on one page... http://codepen.io/owenayres/pen/MyMJYa

Upvotes: 1

Related Questions