Monday, February 11, 2013

Part 1: Application: A Thorough Introduction To Backbone.Marionette

Part 1: Application: A Thorough Introduction To Backbone.Marionette:


  
Backbone.js is quickly becoming the most popular framework for building modular client-side JavaScript applications. This is largely due to its low barrier to entry; getting started with it is super-simple. However, unlike Ember.js, Backbone, being so minimal, also leaves a lot up to the developer to figure out.
So, once you start getting into more advanced applications, it’s no longer so simple. Backbone.Marionette was created to alleviate a lot of the growing pains of Backbone development. Backbone.Marionette “make[s] your Backbone.js apps dance with a composite application architecture!,” according to its author.
This “composite” architecture refers mainly to the numerous view types that have been provided to help with subview management. We won’t be discussing those views today (although we will touch on regions, which are a small part of the subview management that Marionette offers), but you can find documentation for this project in the GitHub repository. It offers numerous components that extend Backbone and that enable you to write less boilerplate and do more stuff with little to no hassle, especially when it comes to views.

The Central Application Object

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 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 designed to handle this task.
Derick Bailey, the creator of Marionette, had a better idea. He created a “class” that you could instantiate an object from that is specifically designed to handle the responsibilities of being the go-to root object of the entire application. You create a new application with var App = new Backbone.Marionette.Application(), and then, when everything is set, you start the application with App.start(options). I’ll discuss the options argument soon. For now, just remember that it’s optional.

Initializers

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 main.js 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.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.

Event Aggregator

The Application object brings even more possibilities for decoupling a Backbone application through the use of an event aggregator. A while back I wrote a post about scalable JavaScript applications, 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. 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 that is automatically attached to the application object. While this is only one of the mechanisms that I wrote about in that article, it is a start and can be very useful in even smaller 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 object. 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 its own module too, so you can add it to any object you want, just like Backbone’s Event.

Regions

Region is another module for 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 — that’s a topic for another day — but I’ll cover it briefly and explain how to use them with Application.
A region is an object — usually created with new Backbone.Marionette.Region({ el: 'selector'}) — that manages an area where you attach a view. You would add a view and automatically render it by using show. You can then close out that view (meaning it will remove it from the DOM and, if you’re using one of the Marionette views, undo any bindings made by the view) 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 in code rather than English:
// Create a region. It will control what's in the #container element.
var region = new Backbone.Marionette.Region({
 el: "#container"
});

// Add a view to the region. It will automatically render immediately.
region.show(new MyView());

// Close out the view that's currently there 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), there’s 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.

Selector

Simply supply a selector, and a standard region will be created that uses that selector as its el property.
App.addRegions({
 container: "#container",
 footer:    "#footer"
});

// This is equivalent to
App.container = new Backbone.Marionette.Region({el:"#container"});
App.footer    = new Backbone.Marionette.Region({el:"#footer"});

Custom Region Type

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.
var ContainerRegion = Backbone.Marionette.Region.extend({
 el: "#container", // Must be defined for this syntax
 // Whatever other custom stuff you want
});

var FooterRegion = Backbone.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();

Custom Region Type with Selector

If you don’t define el — or you want to override it — in your custom region type, then you can use this syntax:
var ContainerRegion = Backbone.Marionette.Region.extend({});

var FooterRegion = Backbone.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), and they add a lot of useful functionality.

Conclusion

As you can already see, Marionette adds a ton of great features to make Backbone development simpler, and we’ve covered only one of many modules that it provides (plus, we’ve touched on a couple of other modules that Application itself uses, but there’s plenty more to learn about those). I hope this will entice Backbone programmers a bit and make you eager to read the rest of this series, when I’ll cover more of the modules.
Credits of image on start page: Dmitry Baranovskiy.
(al)

© Joseph Zimmerman for Smashing Magazine, 2013.

No comments:

Post a Comment