Contents
A lot of my JavaScript work involves using Backbone.js and Marionette.js for the front-end. I really like other frameworks such as Angular.js too. Despite the framework I find myself in, I immediately gravitate toward whatever modular patterns that framework facilitates. More specifically, I am interested in how I can build reusable components within the framework. Components are the objects that encapsulate some specific functionality and typically operate independently of the application as a whole. For example, imagine the cliché stock widget that displays stock symbols along with their current prices. It functions entirely on its own, but together with other components it makes up a dashboard page.
Building and Maintaining Applications with Components
Components are critical to scaleable applications because they encourage separation of concerns. Each piece of an application, including components, should have one responsibility and not heavily depend on the other pieces. Applications with tightly coupled modules or a lack of division of work will become incredibly more difficult to maintain and amend with newer features. Adding a single feature could require changing code in several places. Altering a function here breaks a function there, which then requires this other function to handle another parameter. Discovering everything that breaks usually involves a trial-and-error process of integration tests with your eyeballs. Suddenly, you’re traveling down the recursive Twilight Zone of refactoring ad nauseam and other smart-people Latin phrases.
Components also encourage the DRY principle. In case you don’t know, the DRY principle stands for “Don’t Repeat Yourself.” You will waste extra time in the long run if you repeatedly write the same code, especially when you need to go back to refactor or add another feature. If you need to build a few datagrids with the same style but different data, then you should abstract that out into a reusable datagrid component. Then, all you need to do is configure each instance of the datagrid with its data. Components GOOD, monolithic applications BAD.
Framework Components
Now, for the purpose of this article, we should have a shared idea of what a component is. I’ve already hinted at my understanding of a component. A component is akin to a sub-application, with its own views, models, state, business logic, and even sub-components. When it comes to JavaScript frameworks, Angular overtly offers components in the form of directives. Directives allow you to create custom HTML elements via a handful of syntactical options. You may also define a controller and scope for the directive to handle related business logic and state, respectively. The beauty of directives is how easily reusable they are. Define your own HTML element and then just plop it into your application views where you need it. You can even use them inside other directives in a way that would make Xzibit proud (fine, old joke, shame on me).
However, when it comes to Backbone.js, we don’t necessarily have our go-to object to handle components. Yes, we have views which are reusable, but we would need to add several methods to a view to make it like a component. You would need both business logic and the boilerplate code to facilitate adding and removing to the DOM (without creating memory leaks). If you try to do this all with a single view, I’m fairly certain you’ll violate the single responsibilty principle.
Well, this leaves us with rolling our own solution, which isn’t a terrible idea. I advocate trying it out yourself and stretching your current understanding and abilities. However, countless other people have faced a similar issue, and someone has probably already built a robust solution that solves the problem.
Modularity and Components in Marionette
In steps Marionette.js. Marionette is an awesome library created by Derick Bailey to eliminate a host of the pains associated with building applications in vanilla Backbone. It offers a sensible boilerplate that supplies several view types, an application router, modules, an event aggregator, and many other objects. In my opinion, the greatest benefit of Marionette is that it empowers you to easily build modular Backbone applications.
As previously stated, Marionette offers modules, which are our go-to objects for segregating the different pieces of our application. They almost sound like components, but there is still a distinction. Modules operate as sub-applications and namespaces that separate the different concerns of our application. A music streaming application may have an account module for handling user account information and an albums module for displaying and filtering albums to listen to. Within the account module, we would have a component for updating billing information and another component for updating the email address and password.
However, we can use modules to act as components in Marionette. Let’s walk through building a datagrid component for an orders dashboard page. (If you’re unfamiliar with Marionette’s objects and syntax, I encourage you to look at the docs on the GitHub repo marionettejs/backbone.marionette first.)
Here we set up our base application called MyApp
, attach an existing HTML element with id #main
to the app’s mainRegion
, and listen for the app’s start
event to make sure Backbone history is running. We also add a simple helper method startSubApp
for managing the currently running sub-application, or module.
This is our main orders module. We define the module, a router, and a controller for orders-related functionality. We ensure everything is properly wired up with a call to addInitializer
on the module as well. Notice in Orders.Controller#ordersDashboard
we start up and show the Orders.Dashboard
submodule, which we define below.
This is our Dashboard
submodule. Notice we set Dashboard.startWithParent = false
. This prevents the module from automatically running because we will want it to start only when we need it. In the controller after we show the layout, we fetch the dashboard orders and display the incomplete orders in a datagrid component via showGrid
and _showGridComponent
. If our module is stopped, we make sure to also stop the datagrid component in onDestroy
.
This is our DataGrid
module component. Notice, we’ve set it up similar to the Dashboard
module. We can instantiate our component and use it with DataGrid.start
followed by DataGrid.controller.show
.
Finally, our HTML view.
Components Are Reusable
So, the datagrid module works almost perfectly as a component. It has its own logic, state, and view, but something isn’t right. Remember components are reusable. If we set up a datagrid component using a module and then want to use it multiple times, we would encounter a problem. We’re locked down to one instance of the component at any one time by the controller
property set in the addInitializer
call. We need to tweak our module to allow for multiple instances.
Before, we depended on DataGrid.start
to create a single instance of the component. Now, we have an interface for creating multiple component instances via our DataGrid.newComponent
method. Therefore, we redefine our component module to automatically start with its parent, and we remove initializers and finalizers because we don’t have to depend on start
to use our component.
OK, now we have a component that is reusable. We instantiate our datagrid three times to display different categories of orders on our dashboard page. This seems to work well, but we can’t deny the little bit of code smell.
Create A Component Type
If we want to create more components in our application, we will have to repeat the same pattern of creating a new module and setting the newComponent
method on it. We don’t want to manually add the method to every module component. Yes, we could create a custom module with that method defined on its prototype and then let our module components inherit from it. But, that still doesn’t address the issue of using modules for something they’re not. All we’ve done is use a module as a glorified wrapper for other objects. In fact, our datagrid is really just two views and a controller. If all we really need to do is wrap other objects, then we should create a custom component type to use as our facade.
So, we’ve defined a new type: Marionette.Component
. This component type is based off work Derick Bailey did when he and I developed together on a project. To actually create or use a component, we call component
on an instance of a Marionette application or module. It attaches the component to the receiver and takes in options to add to the component prototype. We can also call define
on our component type to add other properties to the prototype.
Notice the component type makes some assumptions about how it will be used. It takes a region for displaying itself along with a model and/or a collection. It has a show
method, where it calls showView
. Notice that showView
calls getView
but doesn’t define it. When you create your own custom component type through this interface, you need to supply a getView
method that returns your custom view instance (item view, collection view, layout view, etc.).
The component also listens for the show
event on the view and triggers an onShowView
method via the this.triggerMethod('show:view')
call. So, you can also define an onShowView
method if you need to do additional work after the view is displayed. Finally, the component displays the view in the supplied region.
Let’s use our new component type to redefine our datagrid component.
Notice that we still define the component within a module, but we’re just reopening up our dashboard module to create the datagrid component on it. We still define the views in the same way, and then we define the datagrid type. Since we made assumptions about how components should be used (for example, take some basic options in their constructor and provide a show
method), all we need to define is our getView
method and any other custom logic we require.
We make sure to update our dashboard controller to instantiate the datagrid components with the new component type constructor.
Finally, we add the component source file to our loaded scripts.
Conclusion
So, we now understand how to build reusable components in Backbone.js and Marionette.js. We’ve learned that components allow us to reduce the amount of code we need to write and afford us more scaleable applications. I think this simple approach to components in Marionette can go a long way toward building modular applications. I’m sure there are ways to improve our component type and reduce even more repeated patterns. I know our method of defining and using modules could use some refactoring, possibly incorporating some of the same ideas we used to make our custom component type.
I hope you enjoyed this post, and I look forward to any questions or feedback!