Tuesday, April 30, 2013

A Thorough Introduction To Backbone.Marionette (Part 2)

A Thorough Introduction To Backbone.Marionette (Part 2):


  
In the first part of this series, we discussed Backbone.Marionette’s Application. This time around, we’ll discuss the module system that is included in Backbone.Marionette. Modules are accessible through the Application, but modules are a very large topic and deserve an article dedicated to them.

What Are Modules?

Before we get into the details of how to use Marionette’s module system, we should make sure we all have a decent definition of a module. A module is an independent unit of code that ideally does one thing. It can be 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.
For this article, that’s about as much as we need to define modules, but if you want to learn more about writing modular code, plenty of resources are on the Internet, of which the “Maintainability Depends on Modularity” chapter of Single Page Apps in Depth is one of the better ones out there.
The JavaScript language doesn’t currently have any built-in methods for defining modules (the next version should change that), but many libraries have arisen to provide support for defining and loading modules. Marionette’s module system, sadly, doesn’t provide support for loading modules from other files, but it does offer some functionality that other module systems do not have, such as the ability to start and stop a module. We’ll cover more of that later. Right now, we will just start with defining a module.

Module Definition

Let’s start with the most basic of module definitions. As mentioned, modules are accessible through the Application, so we need to instantiate 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 functionality added by us, and that it will be named myModule (according to the argument we passed into module). But what is a barebones module? It’s an instantiation of a Marionette.Module object.
Module comes with a bit of functionality baked in, such as events (through EventAggregator, which we’ll discuss thoroughly in a later article), starting with initializers (just like Application has), and stopping with finalizers (we’ll go over that in the “Starting and Stopping Modules” section).

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 a function in, 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 instance 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 bit.
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 could 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 utilize 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 try to 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 is referencing, and none of the changes you specify will show up in your module later.
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, it will pass those in to the function that you are defining your module in. Once again, the biggest benefit I see from this is saving some bytes after minification; other than that, I don’t see much value.
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 argument, but you would lose the advantage of minification if you didn’t use the argument. 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.

Split Definitions

The final thing I’ll mention about defining modules is that we can split up the definitions. I don’t know exactly why you would want to do this, but someone might want to extend your modules later, so splitting up the definitions might help them avoid touching your original code. 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 definition, 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 closure where it is defined.

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 that module 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 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 do; 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 be creating a module as a submodule of the module 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 create it along with its submodule. This can be dangerous because of the same potential for misspelling that I mentioned earlier. You could end up creating a module that you didn’t intend to create, and the submodule would be attached to it, instead of to the module you intended.

Accessing Submodules

As before, submodules can be accessed the very 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.

Starting And Stopping Modules

If you read the previous article in the series, about Application, you will know that you can start an Application with start. Well, starting modules is the same, and they can also be stopped with stop.
If you recall (assuming you’ve read the previous article), you can add initializers with addInitializer to an Application, and they will be 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 aren’t only meant for use by others. In fact, they are quite helpful when used inside the module definition. This way, you can define a module inside the definition without actually creating any objects to be used, but then write your initializers to start creating 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
    });
});

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 set up a module to not 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 normal 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 autostart. 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 said, data isn’t passed along like this when you call stop. Also, stop must be called manually, 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 their 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
    // Do something with `data`
});
This is very similar to the way we do things with events on collections, models and views in normal Backbone code.

How We Might Actually Use A Module

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 designed to be used. The built-in start and stop methods are an indication of this. The modules that Marionette includes are meant to represent somewhat large subsystems of an application. For 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 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 sub-applications 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, although it’s difficult to use it in the traditional sense. This is because Marionette’s modules are fully instantiated objects, while traditional modules are “classes” that are meant for instantiation later.

Conclusion

Phew! That’s a lot of information. If you’ve made it this far, I commend you (although it was much easier for you to read this than for me to write it). Anyway, I hope you’ve learned a lot about the way that Marionette handles defining, accessing, starting and stopping modules and submodules. You may find it to be a very handy tool, or you might choose to completely ignore its existence. That’s one of the great things about Backbone and Marionette: most of their features are largely independent, so you can pick and choose what you want to use.
Credits of image on front page: ruiwen
(al) (ea)

© Joseph Zimmerman for Smashing Magazine, 2013.

DIGITAL JUICE

No comments:

Post a Comment

Thank's!