Chapter 2: Modules

At the beginning of chapter 1, I mentioned the two meanings that composite architecture had in the context of Marionette. The first meaning referred to application components built in a hierarchical module structure. Now that we’ve covered the root node of that hierarchy, let’s look at how we start to branch out into a hierarchy of modules.

What Are Modules?

Before we get into the details of how to use Marionette’s module system, we should make sure we have a decent definition of what a module is. A module is an independent unit of code that ideally does one thing. A module is used in conjunction with other modules to create an entire system. The more independent a unit of code is, the more easily it can be exchanged or internally modified without affecting other parts of the system, and the more easily it can be tested.

For this book, that’s about as far as we need to define modules, but if you want to learn more about writing modular code and its benefits, plenty of resources exist throughout the internet, of which the “Modularity1 chapter of the MSDN book, Project Silk: Client-Side Web Development for Modern Browsers, is one of the better resources out there.

The JavaScript language doesn’t currently have any built-in means for defining or loading modules (the next version should change that2), but several libraries have arisen to provide support for defining and loading modules. Marionette’s module system is quite different from all of these in a few ways:

1. It doesn’t provide a means of loading modules from other script files. A module must already be loaded and defined before you can use it.

2. Modules are not available completely independently. They are attached to the Application object as namespaced objects.

3. Marionette modules have built-in functionality. Most module systems have you define the entire module, but Marionette’s modules are instantiated Marionette.Module objects.

I won’t elaborate on the first point, but the details of the other two will become clear as you read more. First, let’s learn how to define a module, and we’ll move on from there.

Module Definition

Let’s start with the most basic module definition. As mentioned, modules are accessible through the Application, so first we need one of those. Then we can use its module method to define a module.

var App = new Backbone.Marionette.Application();

var myModule = App.module('myModule');

That’s pretty simple, right? Well, it is, but that’s the simplest module we can create. What exactly did we create, though? Essentially, we told the application that we want a barebones module, with no custom functionality, and that it will be named myModule (according to the argument we passed to the module function). But what is a barebones module? In short, it’s an instantiation of the Marionette.Module class.

Module comes with a bit of functionality baked in, such as: events (through EventAggregator, which we’ll discuss thoroughly in chapter 8); starting; initializers (just like Application has); stopping; and finalizers (we’ll go over these in the “Starting and Stopping Modules” section later in this chapter).

Standard Module Definition

Now let’s look at how to define a module with some of our own functionality.

App.module("myModule", function(myModule, App, Backbone, Marionette, $, _){
    // Private Data And Functions
    var privateData = "this is private data";

    var privateFunction = function(){
        console.log(privateData);
    };

    // Public Data And Functions
    myModule.someData = "public data";

    myModule.someFunction = function(){
        privateFunction();
        console.log(myModule.someData);
    };
});

As you can see, there’s a lot of stuff in there. Let’s look at the top line and work our way down. Just like before, we call App.module and provide a name for the module. But now we’re also passing in a function, too. The function is passed several arguments. I bet you can figure out what they are, based on the names I’ve given them, but I’ll still explain them all:

myModule is the very module you’re trying to create. Remember, it’s already created for you, and it’s a new instantiation of Module. You’re probably going to want to extend this with some new properties or methods; otherwise, you might as well stick with the short syntax that doesn’t require you to pass in a function.

App is the Application object that you called module on.

Backbone is, of course, the reference to the Backbone library.

Marionette is the reference to the Backbone.Marionette library. It is actually available through Backbone, but this allows you to alias it and make it a shorter name.

$ is your DOM library, which will be either jQuery or Zepto (or possibly something else in the future).

_ is a reference to Underscore or Lodash, whichever you’re using.

After that, you can actually pass in and use custom arguments. We’ll go over this in a moment.

Normally, I would say that most of these arguments are unnecessary; after all, why wouldn’t you already have access to these references? However, I can see these being useful in a couple of situations:

A minifier can shorten the names of the arguments, saving some bytes.

If you’re using RequireJS or some other module loader, you only need to pull in the Application object as a dependency. The rest will be available through the arguments given to you by Module.

Anyway, let’s get back to explaining the rest of what’s going on in the code above. Inside the function, you can use the closure to create private variables and functions, which is what we’ve done. You can also expose data and functions publicly by adding them as properties of myModule. This is how we create and extend our module. There is no need to return anything because the module will be accessible directly through App, as I’ll explain in the “Accessing a Module” section below.

Note: Make sure that you only add properties to your module variable and do not set it equal to something (for example, myModule = {…}), because when you set your module variable to something, that changes what the variable’s name references, and none of the changes you specify will show up in your module later.

Overall, we’re essentially just using a mechanism similar to the module pattern3, except first, the anonymous function is being sent as a parameter to a different function instead of just being called immediately; and second, the module we’re defining is already instantiated and is passed into our function instead of being returned from the function and assigned to an outside variable.

Getting Help From Underscore

Since the module we’re trying to define is already an instantiated object, we obviously can’t just assign an object literal to the module — as I’ve already mentioned — but there is an alternative to messes such as the one below:

App.module("myModule", function(myModule, App, Backbone, Marionette, $, _){
    myModule.data1 = "public data";
    myModule.data2 = "some data";
    myModule.method1 = function() {
        // Do Stuff
    };
    myModule.method2 = function(){
        // Do Stuff
    };
});

That just requires writing myModule too many times. Instead, we can use Underscore’s extend method to simplify things for us. The great thing is that since Underscore is already a required dependency for Backbone, it’s not something extra we need to add to the project.

Below you’ll find the same code as above, except it uses Underscore to make it easier to read and faster to type:

App.module("myModule", function(myModule, App, Backbone, Marionette, $, _){
    _.extend(myModule, {
        data1: "public data",
        data2: "some data",
        method1: function() {
            // Do Stuff
        },
        method2: function(){
            // Do Stuff
        }
    });
});

You aren’t required to use Underscore to do it this way, but it’s a simple and useful technique, so feel free to have at it.

Custom Arguments

Earlier, I noted that you can send in custom arguments. In fact, you can send in as many custom arguments as you want. Take a look at the code below to see how it’s done.

App.module("myModule", function(myModule, App, Backbone, Marionette, $, _, customArg1, customArg2){
    // Create Your Module
}, customArg1, customArg2);

As you can see, if you pass additional arguments to module, they will be passed in to the function that you are defining your module in. As with the other arguments passed into your definition function, the biggest benefit I see from this is saving some bytes after minification.

Another potential benefit can come from isolating global variable changes. If the custom arguments that you pass in are primitive values (not objects), then when the variable is changed either outside or inside the definition function, the changes don’t affect the value in the other scope. I don’t see this issue coming up often, but it’s an option if you need it.

Overall, though, these spare arguments aren’t all that useful and you’ll be hard pressed to find real use cases for them. Even Derick Bailey doesn’t recommend using them.

This is the Module

Another thing to note is that the this keyword is available within the function and actually refers to the module. This means you don’t even need the first myModule argument, but you would lose the advantage of minification. Let’s rewrite that first code using this so that you can see that it’s exactly the same as myModule.

App.module("myModule", function(){
    // Private Data And Functions
    var privateData = "this is private data";

    var privateFunction = function(){
        console.log(privateData);
    }

    // Public Data And Functions
    this.someData = "public data";

    this.someFunction = function(){
        privateFunction();
        console.log(this.someData);
    }
});

As you can see, because I’m not using any of the arguments, I decided not to list any of them this time. It should also be obvious that you can skip the first argument and just use this. This is entirely a stylistic choice and you can decide for yourself exactly what you would like to do. If someone reading your code understands Marionette, then it should be just as readable either way.

Split Definitions

The final thing I’ll mention about defining modules is that we can split up the definitions. This can be useful for others to extend your module, or it can also help keep files sizes smaller by splitting the definitions up. If you’re using a module loader — like RequireJS — then this can be done in a such way that split definitions are completely pointless, but without the module loader it’s probably the best way to keep things organized. Here’s an example of split definitions:

// File 1
App.module("myModule", function(){
    this.someData = "public data";
});

// File 2 
App.module("myModule", function(){
    // Private Data And Functions
    var privateData = "this is private data";

    var privateFunction = function(){
        console.log(privateData);
    }

    this.someFunction = function(){
        privateFunction();
        console.log(this.someData);
    }
});

This gives us the same result as the previous example, but it’s split up. This works because in File 2, the module that we defined in File 1 is being given to us (assuming that File 1 was run before File 2).

Of course, if you’re trying to access a private variable or function, it has to be defined in the module definition where it is used, because it’s only available within the scope where it is defined.

As you’ll find out in the next section, you access modules the exact same way you define them, and, in fact, if the module is not defined at the point it is being accessed, a plain module will be created for the definition.

// I want to use the module, but it isn't defined
var mod = App.module('myModule');

// `mod` is now a barebones module, so we can call built-in functions
// you'll see what these are later in the chapter
mod.addInitializer(function(){...});

// Later we have the module definition and everything still works
App.module("myModule", function(){
    this.someData = "public data";

    var privateData = "this is private data";

    var privateFunction = function(){
        console.log(privateData);
    }

    this.someFunction = function(){
        privateFunction();
        console.log(this.someData);
    }
});

Since we’re only extending the module given to us, people can use the module before we even define our additions to it. They’ll run into errors if they try to access any of our functionality before it’s defined, but when will that ever happen?

Accessing A Module

What good is creating modules if we can’t access them? We need to be able to access them in order to use them. Well, in the very first code snippet of this article, you saw that when I called module, I assigned its return value to a variable. That’s because we use the very same method to both define and retrieve modules.

var myModule = App.module("myModule");

Normally, if you’re just trying to retrieve the module, you’ll pass in the first argument, and module will go out and grab the module with that name for you. But if you pass in a function as the second argument, the module will be augmented with your new functionality, and it will still return your newly created or modified module. This means you can define your module and retrieve it all with a single method call.

This isn’t the only way to retrieve modules, though. When a module is created, it is attached directly to the Application object that it was constructed with. This means you can also use the normal dot notation to access your module; but this time, it must be defined beforehand, otherwise you’ll get undefined back.

// Works but I don't recommend it
var myModule = App.myModule;

While this syntax is shorter, it doesn’t convey the same meaning to other developers. I would recommend using module to access your modules so that it is obvious you are accessing a module and not some other property of App. The convenience and very slight danger here is that it will create the module if it doesn’t already exist. The danger comes if you misspell the name of the module: you won’t have any way of knowing that you didn’t get the correct module until you try to access a property on it that doesn’t exist.

Submodules

Modules can also have submodules. Sadly, Module doesn’t have its own module method, so you can’t add submodules to it directly, but that won’t stop us. Instead, to create submodules you call module on App, just like you used to; but for the name of the module, you need to put a dot (.) after the parent module’s name and then put the name of the submodule.

App.module('myModule.newModule', function(){
    ...
});

By using the dot separator in the module’s name, Marionette knows that it should create (or retrieve) a module as a submodule of the module named before the dot. The cool (and potentially dangerous) part is that if the parent module isn’t created at the time that you call this, it will be created along with its submodule. This can be dangerous because of the same potential for misspelling that I just mentioned. You could end up creating a module you didn’t mean to, and the submodule would be attached to it, instead of the module you intended.

Accessing Submodules

As before, submodules can be accessed the same way they are defined, or you can access them as properties of the module.

// These all work. The first example is recommended
var newModule = App.module('myModule.newModule');
var newModule = App.module('myModule').newModule;
var newModule = App.myModule.newModule;

// These don't work. Modules don't have a 'module' function
var newModule = App.myModule.module('newModule');
var newModule = App.module('myModule').module('newModule');

Any of these methods of accessing the submodule will work equally well if both the module and submodule have already been created. As noted in the code’s comment, though, the first method is recommended

Starting And Stopping Modules

If you’re not jumping around between this book’s chapters randomly, you will know that you can start an Application with its start method. Starting modules works in the same way, and they can also be stopped with the stop method.

If you recall, you can add initializers with addInitializer to an Application, and they will run when it is started (or will run immediately if the Application has already started). A few other things happen when you start an Application. Here are all of the events, in order:

fires the initialize:before event,

starts all of the defined modules,

runs all of the initializers in the order they were added,

fires the initialize:after event,

fires the start event.

A Module behaves in a very similar way. The number of events and some of the names of the events are different, but overall it is the same process. When a module is started, it:

fires the before:start event,

starts all of its defined submodules,

runs all of its initializers in the order they were added,

fires the start event.

The stop method is also very similar. Instead of adding initializers, though, you need to add finalizers. You do this with addFinalizer and by passing in a function to run when stop is called. Unlike with initializers, no data or options are passed along to each of the functions. When stop is called, it:

fires the before:stop event,

stops its submodules,

runs its finalizers in the order they were added,

fires the stop event.

Initializers and finalizers are intended to be used within the module definitions for setup and tear-down, rather than by external components. It can be helpful to use them outside, but generally, listening for the events mentioned should be done instead.

When using initializers and finalizers inside the module definitions, you can define a module without actually instantiating any objects, but then write your initializers to start instantiating the objects and setting them up, such as in the example below.

App.module("myModule", function(myModule){
    myModule.startWithParent = false;

    var UsefulClass = function() {...}; // Constructor definition
    UsefulClass.prototype ... // Finish defining UsefulClass
    ...

    myModule.addInitializer(function() {
        myModule.useful = new UsefulClass();
        // More setup
    });

    myModule.addFinalizer(function() {
        myModule.useful = null;
        // More tear down
    });
});

Normally, UsefulClass should be defined outside of the module definition (and probably loaded in via a module loader), but the idea is the same. Do instantiations and setup in the initializers and clear out the now useless objects in the finalizers to free up memory.

Automatic And Manual Starting

When a module is defined, by default it will automatically start at the same time that its parent starts (either the root Application object or a parent module). If a module is defined on a parent that has already started, it will start immediately.

You can tell a module not to start automatically by changing its definition in one of two ways: inside the definition, you can set a module’s startWithParent property to false; or you can pass an object (instead of a function) to module that has a startWithParent property set to false and a define property to replace the usual function.

// Set 'startWithParent' inside function
App.module("myModule", function(){
    // Assign 'startWithParent' to false
    this.startWithParent = false;
});

// -- or --

// Pass in object 
App.module("myModule", {
    startWithParent: false,

    define: function(){
        // Define module here
    }
});

App.start();

// myModule wasn't started, so we need to do it manually
App.module('myModule').start("Data that will be passed along");

Now the module won’t automatically start with its parent. You must call start manually to start it, as I did in the example above. The data that is passed to start could be anything of any type, and it will be passed along to the submodules when they’re started, to the initializers, and to the before:start and start events.

As I mentioned earlier, though, data isn’t passed along like this when you call stop. Also, stop must be called manually on a module (since Application doesn’t have a stop method, and it will always call stop on submodules — there is no way around this. This makes sense because a submodule shouldn’t be running when its parent isn’t running, although there are cases when a submodule should be off when its parent is running.

Other Events And Built-In Functionality

I mentioned that Module comes with some baked-in functionality, such as the EventAggregator. As discussed, we can use the on method on a module to watch for events related to starting and stopping. That’s not all. There are no other built-in events, but a module can define and trigger its own events. Take a look:

App.module('myModule', function(myModule) {
    myModule.doSomething = function() {
        // Do some stuff
        myModule.trigger('something happened', randomData);
    }
});

Now, whenever we call doSomething on the module, it will trigger the something happened event, which you can subscribe to:

App.module('myModule').on('something happened', function(data) {
    // Whatever arguments were passed to `trigger` after the name 
    // of the event will show up as arguments to this function

    // Respond to the event
});

This is very similar to the way we do things with events on collections, models, and views in normal Backbone code.

How Should We Use Modules?

The modules in Marionette can definitely be used to define modules very similarly to any other module definition library, but that’s actually not how it was intended to be used. The built-in start and stop methods are an indication of this. The modules that Marionette includes are for creating and managing relatively large subapplications of the main larger application. As an example, let’s look at Gmail.

Gmail is a single application that actually contains several smaller applications: email client, chat client, phone client, and contact manager. Each of these is independent — it can exist on its own — but they are all found within the same application and are able to interact with one another. When we first start up Gmail, the contact manager isn’t up, and neither is the chat window. If we were to represent this with a Marionette application, each of those subapplications would be a module. When a user clicks the button to open the contact manager, we would stop the email application (because it becomes hidden — although, for speed, we could keep it running and just make sure it doesn’t show in the DOM), and start the contacts manager.

Another example would be an application built largely out of widgets. Each widget would be a module that you can start and stop in order to show or hide it. This would be like a customizable dashboard such as iGoogle or the dashboard in the back-end of WordPress.

Of course, we’re not limited to using Marionette’s modules in this way, but it’s tedious to use them in the traditional sense. This is because Marionette’s modules are fully instantiated objects with built-in functionality, while traditional modules are generally classes that are meant for instantiation later.

Marionette’s modules have caused a lot of confusion, since they lack much resemblance with anything else related to modules (with the exception of the module pattern, though there are still many differences). Personally, I think the name “SubApplication” would be more semantic.

No matter what the name is, though, Derick Bailey is looking to revamp them in version 2.0 (whenever he gets there). He doesn’t like that these modules are responsible for namespacing, encapsulation, and starting and stopping subapplications. We both agree that it is unfortunate that these three concerns are crammed together in this way, especially considering how simple it is to do namespacing and encapsulation by yourself.

Before 2.0 is released, this chapter is perfectly usable and can be used with abandon, but beware the usefulness of this chapter once Mr. Bailey gets his hands dirty and resolves these issues.

Summary

Despite the convoluted mess that Marionette’s modules bring to the table, they still offer us a great way of easily creating powerful subapplications that can be started and stopped. This is a great way to organize and architect your large applications — splitting them into smaller, more manageable applications.

They also provide a built-in event system to continually help decouple the components of your applications. Overall, if your applications are growing large, modules will likely be helpful to you.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset