In the introduction, the library’s author mentions something about “a composite application architecture”. This composite architecture refers to two different things:
•Application components built in a hierarchical module structure.
•Hierarchical view structures through nested (sub)views.
This chapter will discuss the root piece of that first point: the Application
object. We won’t be going into details about how to add subcomponents to your applications until the next chapter, when we talk about modules, but we’ll learn just about everything else there is to know about Application
.
Most of the time, when someone creates a Backbone application, they make a central object that everything is attached to, which is often referenced as App
or Application
. Backbone doesn’t offer anything to make this object from, so most people just create a main router or view and make that the app object. While it’s great that people are attaching things to a central object so that the global namespace isn’t so convoluted, the router was not intended to handle this task and it violates the single responsibility principle.
This can be a problem. Without a central application to use as a namespace (or a module system that keeps variables out of the global scope), most objects and variables will be made globally accessible. While this may seem like it makes things simpler, it poses a substantial risk because variables can easily and unwittingly be overwritten, which can cause bugs that are difficult to track down.
This is why so many people convert their routers and views into central application objects and attach all of their components as properties of this central application. As I mentioned earlier, though, this violates the single responsibility principle. A router is designed to convert URLs to actions, and a view is designed to display information — and that is all they should do. Those are their single responsibilities. Turning them into the central hubs of activity for your application is giving them new responsibilities, which makes them more verbose, makes them more difficult to understand and maintain, and convolutes their roles. You can find more information on this principle around the web1.
Derick Bailey decided to give users a prebuilt solution to these issues: the Application
class. Application
takes over the namespacing responsibility and is a mediator for your entire application. Now your routers can be routers and your views can be views.
As you’ll find out quite soon, Application
has plenty to offer right out of the box, but every application is different, so you’ll probably want to add your own functionality to your Application
object. You have two options to accomplish this:
•Use the built-in extend
function to create a subclass of Application
, the same way you do with Backbone’s components.
•Add properties to the already instantiated object.
Just like almost everything in Backbone — and also nearly everything in Marionette — you can call extend
on the class to create a subclass with your own functionality. Application
is no different.
var MyApp = Marionette.Application.extend({
// add your own properties and methods here
});
Unlike some classes, when you extend Application
, there are no properties you can add that will be used in a special way (such as the events
property for Backbone.View
). This simply allows you to create a new subclass of Application
.
Just like any other object in JavaScript, you can dynamically add properties to an instantiated Application
object any time you want. Simply plop a period and a property name onto the object and assign something to it. Obviously, you can use the square bracket notation too, but the point is there’s nothing stopping you from adding functionality after your Application
has already been instantiated.
var App = new Marionette.Application();
// Add property with dot notation
App.foo = function() {...};
// Add property with square bracket notation
App['bar'] = function() {...};
Either of these ways of extending Application
works just fine. While using extend
seems more correct, the fact that an Application
generally only has a single instance and is rarely reused makes it seem superfluous. Do whatever suits you.
One of the coolest things about Marionette’s Application
is the initializers. When your code is modular, several pieces will need to be initialized when the application starts. Rather than filling a single file with a load of code to initialize all of these objects, you can just set the modules up for initialization within the code for the module. You do this using addInitializer
. For example:
var SomeModule = function(o){
// Constructor for SomeModule
};
// App is an instantiated Application object
App.addInitializer(function(options) {
App.someModule = new SomeModule(options);
});
All of the initializers added this way will be run when App.start
is called. Notice the options
argument being passed into the initializer. This is the very same object that is passed in when you call App.start(options)
. This is great for allowing a configuration to be passed in so that every module can use it.
A few events are also fired when running through these initializers:
•initialize:before
fires just before the initializers are run.
•initialize:after
fires just after the initializers have all finished.
•start
fires after initialize:after
.
You can listen for these events and exert even more control. Listen for these events like this:
App.on('initialize:before', function(options) {
options.anotherThing = true; // Add more data to your options
});
App.on('initialize:after', function(options) {
console.log('Initialization Finished');
});
App.on('start', function(options) {
Backbone.history.start(); // Great time to do this
});
Pretty simple, and it gives you a ton of flexibility in how you start up your applications.
The Application
object brings even more possibilities for decoupling a Backbone application through the use of an event aggregator. A while back I wrote an article about scalable JavaScript applications2, in which I mentioned that modules of a system should be completely ignorant of one another, and that the only way they should be able to communicate with each other is through application-wide events provided by a mediator, which in this case is the Application
object. This way, every module that cares can listen for the changes and events they need to, so that they can react to them without anything else in the system even realizing it exists.
Marionette makes this kind of decoupling largely possible via the event aggregator (provided by the Backbone.Wreqr plugin) that is automatically attached to the application object. While this is only one of the mechanisms I wrote about in that article, it is a start and can be very useful even in small applications.
The event aggregator is available through a property in the application called vent
. You can subscribe and unsubscribe to events simply via the on
and off
methods, respectively (or bind
and unbind
, if you prefer). These functions might sound familiar, and that’s because the event aggregator is simply an extension of Backbone’s Event
object3. Really, the only thing new here that you need to worry about is that we’re using the events on an object that should be accessible everywhere within your app, so that every piece of your application can communicate through it. The event aggregator is available as a separate component too, so you can add it to any object you want, just like Backbone’s Event
.
There are a few other components that Backbone.Wreqr provides that are attached to Application
objects, but we’ll discuss those more in chapter 8. For now, just realize that Application
can be used as a mediator for communication throughout the system.
Region
is another component from Marionette that enables you to easily attach views to different regions of an HTML document. I won’t go into detail about how regions work here — they will be discussed in greater detail in chapter 7 — but I’ll cover it briefly and explain how they integrate with Application
.
A region is an object — normally created with new Marionette.Region({ el: 'selector'})
— that manages the insertion and removal of views from a certain location in the DOM. Add a view and automatically render it by using show
and then close out that view (meaning it will remove it from the DOM and clean up any event bindings) and render a different view simply by calling show
again, or you can just close the view by calling close
. Regions can do more than that, but the fact that they handle the rendering and closing for you with a single function call makes them extremely useful. Here’s a code sample for those who speak better in code than in English:
// Create a region. It will control what's in the #container element.
var region = new Marionette.Region({
el: "#container"
});
// Add a view to the region. Immediately renders the view.
region.show(new MyView());
// Close out the current view and render a different view.
region.show(new MyOtherView());
// Close out the view and display nothing in #container.
region.close();
If you want a Region
directly on your application object (e.g. App.someRegion
), Application
provides a simple way to add one quickly: addRegions
. There are three ways to use addRegions
. In every case, you would pass in an object whose property names will be added to the application as regions, but the value of each of these may be different depending on which way you wish to accomplish this.
Simply supply a selector, and a standard region will be created that manages views being attached to the DOM node that matches that selector.
App.addRegions({
container: "#container",
footer: "#footer"
});
// This is equivalent to
App.container = new Marionette.Region({el:"#container"});
App.footer = new Marionette.Region({el:"#footer"});
You can extend Region
to create your own types of regions. If you want to use your own type of region, you can use the syntax below. Note that, with this syntax, el
must already be defined within your region type, which is a selector used to determine which DOM node the region manages.
var ContainerRegion = Marionette.Region.extend({
el: "#container", // Must be defined for this syntax
// Whatever other custom stuff you want
});
var FooterRegion = Marionette.Region.extend({
el: "#footer", // Must be defined for this syntax
// Whatever other custom stuff you want
});
// Use these new Region types on App.
App.addRegions({
container: ContainerRegion,
footer: FooterRegion
});
// This is equivalent to:
App.container = new ContainerRegion();
App.footer = new FooterRegion();
If you don’t define el
— or you want to override it — in your custom region type, then you can use this syntax, which specifies both the region type and the selector:
var ContainerRegion = Marionette.Region.extend({});
var FooterRegion = Marionette.Region.extend({});
// Use these new Region types on App.
App.addRegions({
container: {
regionType: ContainerRegion,
selector: "#container"
},
footer: {
regionType: FooterRegion,
selector: "#footer"
}
});
// This is equivalent to:
App.container = new ContainerRegion({el:"#container"});
App.footer = new FooterRegion({el:"#footer"});
As you can see, adding application-wide regions is dead simple (especially if you’re using the normal Region
type). You’ll get a better sense of how useful this is when we discuss regions in more detail and put them to work in an application.
We’ve learned about the Application
object, which is used as the root of an application and a mediator between the other modules of the system. These are powerful features that offer a simple means for organizing and decoupling your JavaScript applications.
Marionette also adds many other great features to make Backbone development simpler. So far we’ve only covered one of the many components Marionette offers — though we have quickly touched on a couple other components used by Application
— and there is a lot more to learn, so keep reading!