Despite its popularity, backbone.js has many drawbacks.
Despite its popularity, backbone.js has many drawbacks. With a steep learning curve, shortage of opinionated patterns, lots of boilerplate code, and poor memory management/cleanup strategies, backbone usage can be discouraging. In this article, I will focus on how to refactor a backbone application using marionette.js —a framework that can mitigate many of backbone’s shortcomings.
The best thing about marionette is that it builds on top of backbone. This means that many of its components extend backbone classes, which makes refactoring incredibly easy.
Get better Views with marionette
Views are the most abused component of backbone apps, where the bulk of ugly/boilerplate code gets thrown in. While backbone only has the Backbone.View class, marionette extends this class with 4 types of specialized views: ItemView, CollectionView, CompositeView and Layout.
Let’s see how we can apply each of these to improve our code.
ItemView is typically initialized with a model and is shown in the DOM. Let’s compare it to backbone (using coffeescript in the following examples).
You can see how marionette conventions improved our code:
- There’s no need to bind and unbind events from the model to avoid memory leaks: modelEvents does it for you.
- The render function is gone; marionette handles the rendering, provided you define a template.
- There are conventional hook methods to fire code to a specific point of the view lifecycle (onRender - after DOM is inserted into, onClose after DOM elements are removed)
- Our custom code (which is the most important thing here) is immediately visible; marionette handles all the common details.
The ItemView refactoring is the simplest one to perform, and you don’t even need to initialize marionette’s higher-level facilities to use it. You can then just go over your views one at the time.
Couple of things to watch out for are:
- conventional names of functions used for closing views (in my example it was ‘teardown’ while marionette uses ‘close’ by convention).
- if you were following a convention to return ‘this’ from ‘render’ methods, you should be good to mix the marionette ItemViews with the rest of your app. If you were lazy and return @$el instead, it can cause your marionette views to insert as blanks in the DOM.
We’re going to look at better approach to view management later on.
CollectionView / CompositeView
Another common view use-case is displaying a collection of similar sub-views. Marionette really shines here with CollectionView and CompositeView:
Benefits of using CollectionView include:
- Boilerplate code handling all collection changes are gone.
- Closing of sub-views is taken care of behind the scenes. Say goodbye to zombie objects and memory leaks.
- with the emptyView option, you don’t have to put if-statements in order to display a message for empty content. A separate view will be rendered automatically when the collection is empty.
- collectionEvents works similarly to modelEvents on ItemView.
CompositeView extends CollectionView allowing for custom templates and adding elements besides sub-views (e.g. pagination or table headers.)
Finding locations suitable for CollectionView/CompositeView refactoring is easy—just grep your codebase for looping over the collection in render method.
Remember, these classes extend from Marionette.View which in turn extends from Backbone.View. If you need to leave backbone stuff, you can still do it.
Layout / Regions
This is my personal favourite feature of marionette. Layout is a specialized view all about managing views in separate regions. It takes care of closing the previous view if the region was occupied, too. The process is better illustrated with a code sample:
You might see how view management with Layout is more about showing views in regions rather than inserting HTML into the DOM.
For refactoring, I’d first add a MainLayout class with one mainRegion, render it to a pageafter initialization, and then insert specific views (which should be marionette classes by now) into the mainRegion, when needed (typically in backbone router).
Then, I’d recommend to look for large ItemViews which initialize multiple other views and render it into the DOM, and convert them into Layouts (yes, Layout extends ItemView too). A typical example would be a Layout with 3 regions: header, content and footer. Because layout acts as a view, you can even show Layouts inside a region of another Layout, creating nested structures if needed.
Adding higher-level components
So far so good; We greatly improved our views without changing the app’s global architecture. The next steps are a little more complicated, as they involve adding controllers and routers.
In backbone, it’s a bit weird to place setup code for entities (e.g. models, collections), or invoke higher-level operations in router events or a view. Marionette provides a controller object to solve this.
The first step of refactoring involves adding a global marionette Application object.
Notice marionette’s pattern for initializing components, the module name, which can include sub-modules which are automatically created, and a function that takes 6 arguments: the module object, application object, backbone, marionette, jQuery and underscore.
It’s a good idea to change the definition of all the other components to the new way now.
When our Application object is defined, further refactoring would break up existing routers into controller/router tuples, living in their modules:
The Router defines a map of route-functions which a Controller implements. A good indication of a potential controller is a backbone route-function which initializes a large view (especially a Layout).
So, we have all these controllers and views, but how do they communicate with each other? Marionette solves this with patterns which help clean up the application, resulting in clean, modular code.
We can define a request-response handler to abstract some of the common initialization code (e.g. getting view instances or model instances). In this case, the caller cares about the return value.
The command pattern differs from request-response in that we don’t care about the return value. The component just invokes the command and lets the application handle it. While sometimes useful, I’d recommend having as little of them as possible. This is because commands tend to become bags of code that don’t really belong anywhere else. An example of a command would be ‘logout’ or ‘navigate’ event.
Marionette provides a global messaging bus (called a vent) where components can subscribe to or publish events. This is useful because it separates the event source from the reaction to it. One component can trigger an event, and multiple other components can react in different ways.
As I’ve hopefully demonstrated, the process of refactoring backbone applications to marionette is a continuous one. It can be nicely broken into phases, so even a large application can be converted with ease. You can also decide to not take full advantage of marionette’s power and stop at the VIew step, where the codebase will still be greatly improved.
Comparing the two applications reveals obvious benefits, like greater conventions in the code and reduced boilerplate (deleting unneeded code always feels great!). Non-visible benefits include better memory management. (Developers don’t need to think about js performance in 2013? Think twice;)
Notice that I didn’t touch upon models or collections; Marionette does not improve upon on them, meaning you can use your existing backbone plugins and extensions, which is great!
There’s more to marionette that I’ve covered in this post. Here are some interesting links if you’re interested in the topic. You’re welcome to ask questions in the comments, too :)
- marionette documentation - your to-go resource for all marionette questions
- great screencasts about building single-page apps - paid and free
- marionette annotated source: for those curious “how it works”:
- backbone annotated source is also an interesting read
You can also read How Developing SPA Influenced Me & My Code
More posts by this author