Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 2, 2013 11:00 am

A Thorough Introduction To Backbone.Marionette (Part 2)


In the first part of this series, we discussed Backbone.Marionettes Application. This time around, well 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 Marionettes 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, thats 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 doesnt 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. Marionettes module system, sadly, doesnt 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. Well cover more of that later. Right now, we will just start with defining a module.

Module Definition

Lets 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);

Thats pretty simple, right? Well, it is, but thats 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? Its an instantiation of a Marionette.Module object.

Module comes with a bit of functionality baked in, such as events (through EventAggregator, which well discuss thoroughly in a later article), starting with initializers (just like Application has), and stopping with finalizers (well go over that in the Starting and Stopping Modules section).

Standard Module Definition

Now lets 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, theres a lot of stuff in there. Lets 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 were 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 Ive given them, but Ill still explain them all:

  • myModule is the very module youre trying to create. Remember, its already created for you, and its a new instance of Module. Youre probably going to want to extend this with some new properties or methods; otherwise, you might as well stick with the short syntax that doesnt 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 youre using.

After that, you can actually pass in and use custom arguments. Well go over this in a bit.

Normally, I would say that most of these arguments are unnecessary; after all, why wouldnt 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 youre 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, lets get back to explaining the rest of whats going on in the code above. Inside the function, you can utilize the closure to create private variables and functions, which is what weve 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 Ill 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 variables 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 its 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 dont 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 dont even need the first argument, but you would lose the advantage of minification if you didnt use the argument. Lets rewrite that first code using this so that you can see that its 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 Im 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 Ill mention about defining modules is that we can split up the definitions. I dont 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. Heres an example of split definitions:

// File 1App.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 its 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 youre trying to access a private variable or function, it has to be defined in the module definition where it is used because its only available within the closure where it is defined.

Accessing A Module

What good is creating modules if we cant 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. Thats because we use the very same method to both define and retrieve modules.

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

Normally, if youre just trying to retrieve the module, youll 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 isnt 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 youll get undefined back.

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

While this syntax is shorter, it doesnt 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 doesnt already exist. The danger comes if you misspell the name of the module; you wont have any way of knowing that you didnt get the correct module until you try to access a property on it that doesnt exist.

Submodules

Modules can also have submodules. Sadly, Module doesnt have its own module method, so you cant add submodules to it directly, but that wont 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 modules name and then put the name of the submodule.

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

By using the dot separator in the modules 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 isnt 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 didnt 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 recommendedvar 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' functionvar 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 youve 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 arent 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 modules 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 functionApp.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 manuallyApp.module('myModule').start("Data that will be passed along");

Now the module wont 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 theyre started, to the initializers, and to the before:start and start events.

As I said, data isnt 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 shouldnt be running when its parent isnt 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. Thats 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 thatsactually 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, lets 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 isnt 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 doesnt 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, were not limited to using Marionettes modules in this way, although its difficult to use it in the traditional sense. This is because Marionettes modules are fully instantiated objects, while traditional modules are classes that are meant for instantiation later.

Conclusion

Phew! Thats a lot of information. If youve 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 youve 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. Thats 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.


Original Link:

Share this article:    Share on Facebook
No Article Link

Smashing Magazine

Smashing Magazine delivers useful and innovative information to Web designers and developers.

More About this Source Visit Smashing Magazine