Making Marionette Modules into My Minions


The Background

I’ve been using MarionetteJS for about 3-4 weeks now and absolutely love it.  It has cut down on almost all of the boilerplate I had written with straight Backbone.  However, with Marionette Modules I find myself right back in that same old familiar spot.

I did just see that David Sulc has written a book on this subject called Structuring Backbone Code with RequireJS and Marionette Modules.  I’ll be purchasing that book in the next week or two, so I thought it’d be interesting to see how my method compares to what he recommends...and if I’m even using Marionette Modules as they’re intended to be used.  So here’s an effective “Part 1” which will be followed by “Part 2: The Book Report”.


The Problem

When I think of a module, I think of it as a smaller app that can be controlled by the “master” Application object.  I want the controlling code in the master to be minimal.  I don’t want the application to really worry about the module unless it needs to shepherd communications to another Module.  I want each Module to be able to start up a “sub-module” underneath it in much the same way that an App does it. Mainly, I just want to give a module a place to live and then just let it go do its thing.

These requirements all stem from the nature of the application that I’m writing.  The application is essentially a data visualization tool that allows the user to open, close, and customize arbitrary numbers of different data-viewing widgets...which is fancy for: they can play with a bunch of charts and graphs and modals and spreadsheet-like things.


Addressing My Demands


I want the controlling code in the master app to be minimal.

Marionette pretty much takes care of this one for me.  All you really need is a function that describes the Module. If you define it in its own Require file and import it in your app’s define function, this is super clean and simple.  It looks something like this:

app.js

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

    var App = new Marionette.Application();
    App.module(“someName", Module1);
});

I don’t want the application to really worry about the module unless it needs to shepherd communications to another module.

Again, Marionette (and its friend) saves a lot of work here. Enter Wreqr. It basically sets up a communications system amongst components that looks and smells kind of like SOA.  

In this case, I’m interested in the RequestResponse functionality.  The first thing I’ll do is to create a singleton of it.  I like this pattern because it allows me to separate  communications by creating an independent RequestResponse for the App and one for each Module.  This really makes my modules...well...modular.

define(["backbone", "marionette"], function( Backbone, Marionette ){  
    return new Backbone.Wreqr.RequestResponse();
});

Next, I’ve got to set up the handlers to dispatch the requested info.  I’ll pass in the newly created RequestResponse and use its “setHandlers” function to create a “call” for other components to use.

app.js becomes

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

    var App = new Marionette.Application();
    App.module(“someName”, Module1);

    App.infoToShare = “Share Me”;

    reqres.setHandlers({
        getInfo: function(){
            return App.infoToShare;
        }
    });
});

I want each module to be able to start up a “sub-module” underneath it in much the same way that an application does it.

Again, Marionette’s on it.  Although this time, I thought it could use just a little help.

Marionette uses dot-notation in the module names to allow for sub-modules. For example, to have module1 start a module1a, you’d name the 1a module “module1.module1a”.  This can be kind of tricky when you’re trying to perform it a few layers down. I also wanted the starting of a sub-module to feel as close to the starting of a top-level module as possible.

We already have a communications system in place, so I just added a handler for starting modules.  Its arguments are the new module parent’s name, the new module’s name, and the function describing the new module.  Now each module can add its own sub modules just like the application will.

app.js is now

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

    var App = new Marionette.Application();

    //App.module(“someName”, Module1);
    //This module call could now be changed (if you want) to look like:
    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;
        }
    });
});

To create a sub-module you’d call the startModule request from the module like this:

define(['marionette', "app_reqres", “module1a”], function (Marionette, app_reqres, Module1a) {  
    "use strict";

    return function(Module1, App, Backbone, Marionette, $, _){
        //Add sub-module
        app_reqres.request(“startModule”, Module1.moduleName, “module1a”,  Module1a);
    };
});

The parent module name here is grabbed from Module1's moduleName attribute.

Note: I’ll probably switch this convenience function from a RequestResponse to a Command, since there’s no response needed.


I just want to give a module a place to live and then just let it go do its thing.

Ok, so far I’ve just added a few convenience features.  To make a module really act like I want it to, there’s more work to be had.

First, I need to give it a place to live.  

I’d thought about putting a Layout straight in the module definition.  Then I realized that I’d also have to worry about a lot of other little stuff that Marionette’s Views and Regions have built in...plus I’d rather not add anything more than necessary into the module’s definition function.

Fear not! Marionette’s got it covered...RegionManagers!  RegionManagers do all the dirty work behind the scenes so you don’t have to.  All we need to do is instantiate one, add a region to throw a Layout into, show it, and provide a way to close it all elegantly.

Creating the RegionManager instance is easy enough.

In module1.js

var rm = new Marionette.RegionManager();  

Adding a region is pretty straightforward too. The only interesting thing I do here is assign the new manager to this.regions.  This gives me the same syntax in the module that I have in the application or in the views.

this.regions  = rm.addRegions({  
   main: “#module1”
);

You could just as easily have the application or parent module pass in a selector for the region that it wants the new module to live in.

Ok, so now we’ve got to throw a Layout into the region.  I want the module to fully start up immediately, so I’m just going to show the region on the module's initialization.  You do that by importing the main layout for your module and adding an initialization function that opens it.

this.addInitializer(function(){  
   Module1.regions.main.show(new MainLayout());
});

Note: If you’re moderately new to Javascript you might not notice that I didn’t say “this.regions.main”.  That’s because “this” inside the function isn’t a reference to your module.  To get to it, just use a variable name in scope that's already been set to be a reference to your module (here, "Module1" has been defined prior...see full file below)  Closure is majik...and awesome.

Lastly, I need to provide a way to close it all when the module is being stopped.  Just as you can add a function that gets run on initialization, you can add one that runs on closing.  Here its called a “finalizer.”

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

The whole thing will look like this:

module1.js

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

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

        this.regions  = rm.addRegions({
            main: “#module1”
        });

        this.addInitializer(function(){
            Module1.regions.main.show(new MainLayout());
        });

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

});

And thats it...well, I also add in a lot of other stuff to make the module function like a little application (ie a local RequestResponse and vent to handle communications and events amongst the module’s parts and pieces)...but thats for a different post.


Final Thoughts

It will be interesting to see if my view of Marionette Modules changes after reading David’s book.  As nerdy as it makes me sound, I’m really looking forward to the read.  

Feel free to leave your thoughts on any of this in the comments.


comments powered by Disqus