Modification to my Marionette Module Minions


The Background

In a previous post I talked about how to make MarionetteJS modules into little reusable apps for use within your big app. However, whilst using this at work I realized that there was something missing.


The Problem

One of the ways I'm using modules like this is with a configuration modal. The configuration can be a new configuration, or it can be an edit to an existing one. The module that is starting up the configuration module already has all of the information that will be needed to edit a particular configuration, it just needs to get it into the freshly started module.

Following? I'm not sure even I am.

Let me try again.

Sometimes I need to pass info to a module when its starting up that may change by the next time the module is started up again.


The Solution...or so I thought

Well, here's where I admit to a mistake. The documentation says you can pass in "any custom arguments" so I tried to do this:

module1.js

define(['marionette', 'mainLayout'], function (Marionette, MainLayout) {  
    "use strict";

    return function(Module1, App, Backbone, Marionette, $, _, options ){
        ...
        this.addInitializer(function(){
            //Do something with the options here, like send them to the layout
            Module1.regions.main.show(new MainLayout({options: options));
        });
        ...
    };

});

And then start it up like this (in App.js or in another module)

var options = {config: some_pre_determined_configuration};  
App.module(name, functionDef, options);  

Technically this works, but not the way I thought. I was thinking that I could pass in arguments every time I started it back up. Turns out that the module only gets the options the first time the "App.module" method is called on the definition function.

Maybe I should've read the docs a bit more closely, because I couldn't figure out why the options I was sending in on the 2nd and 3rd times weren't there.


The (real)Solution

To get around this, I turned to a new old friend: RequestResponse.
I created a "moduleOptions" object in the parent who's doing the starting up the modules.  It will hold any information that any of my modules may need for the next time they start up.

In App.js or in another module that is calling a minion

App.moduleOptions = {};  

Then I setup request handlers as a "getter" and a "setter" for moduleOptions

In App.js or in another module that is calling a minion

reqres.setHandlers({  
    setModuleOptions: function(moduleName, options){
        App.moduleOptions[moduleName] = options;
    },
    getModuleOptions: function(moduleName){
        return App.moduleOptions[moduleName];
    }
});

As you can see, both handlers require a module name.  This is in case you have multiple modules that need options.  It prevents you from having to explicitly define a new variable under App every time you add a new module type.  Instead they just get thrown under App.moduleOptions, keeping your App code nice and clean.

Ok, so now that I've got a way to set the module's start up options and tell it about those options, I just have to make the module ask for them when he/she starts up.

Module1.js

this.addInitializer(function(){  
    ...
    Module1.startOptions = reqres.request('getModuleOptions', "Module1");
    ...
});

I typically don't like to manually put in "Module1" as the argument in case I decide to change the name of the module.  So I pass in Module1.moduleName instead of "Module1".  The less I have to change when I make a change, the better.

Full Examples

App.js

define( ["marionette", "module1”, “app_reqres”], function (Marionette, Module1, reqres) {  
 "use strict";

    var App = new Marionette.Application();
    App.moduleOptions = {};

    //These could be called directly in App.js, but I'm using the reqres
    //to show how one might start another minion from a module.
    reqres.request("setModuleOptions", "Module1", {
        version: "version#",
        regionContainer: ".module1-##" //substitute ## for a unique identifier. I just use an auto-incremented counter
    });
    reqres.request("startModule", null, "someName", Module1);

    App.infoToShare = “Share Me”;

    reqres.setHandlers({
        startModule: function(parent, name, functionDef){
            //Handle sub-modules
            if(parent != null){
                App[parent.moduleName].module(name, functionDef);
            }
            //Handle top-level modules
            else{
                App.module(name, functionDef);
            }
        },
        getInfo: function(){
            return App.infoToShare;
        },
        setModuleOptions: function(moduleName, options){
            App.moduleOptions[moduleName] = options;
        },
        getModuleOptions: function(moduleName){
            return App.moduleOptions[moduleName];
        }
    });
});

Module1.js

define(['marionette', 'app_reqres', 'mainLayout'], function (Marionette, app_reqres, MainLayout) {  
    "use strict";

    return function(Module1, App, Backbone, Marionette, $, _){
        this.addInitializer(function(){
            var rm = new Marionette.RegionManager();

            Module1.startOptions = app_reqres.request('getModuleOptions', Module1.moduleName);

            this.regions  = rm.addRegions({
                main: Module1.startOptions.regionConainer
            });

            Module1.regions.main.show(new MainLayout());
        });

        this.addFinalizer(function(){
            Module1.regions.main.close();
        });
    };

});

Final Thoughts

  • I totally get now why it didn't work the way I thought it would.
  • Wreqr makes me happy.
  • When all else fails, read the docs a little closer.

comments powered by Disqus