Writing maintainable code is not easy in any frameworks or libraries. The fact that BackboneJs does not tie your hands makes your work even harder. Therefore, I will give you ten tips on tidying up your Backbone code. While some of these advice apply to most JavaScript applications written even without using BackboneJs, most of the fixes will take the structure of BackboneJs into consideration.

1. Manipulating DOM elements outside the scope of a Backbone View

Suppose you have a code editor in one Backbone View, and a menu bar containing an undo and a redo button in another. The undo button is initially disabled. As soon as you make a change to the displayed code in the editor, the undo button becomes enabled. How would you enable the button?

var CodeEditorView = Backbone.View.extend({ events : { 'change .js-code-textarea': this.codeChanged }, codeChanged : function( e ) { var contents = $( e.target ).val(); this.collection.saveSnapshot( contents ); $( '.js-undo-button' ).prop( 'disabled', false ); } }); 1 2 3 4 5 6 7 8 9 10 11 12 var CodeEditorView = Backbone . View . extend ( { events : { 'change .js-code-textarea' : this . codeChanged } , codeChanged : function ( e ) { var contents = $ ( e . target ) . val ( ) ; this . collection . saveSnapshot ( contents ) ; $ ( '.js-undo-button' ) . prop ( 'disabled' , false ) ; } } ) ;

Each Backbone View is associated with an element, accessible in the el attribute. Either with or without jQuery, we can easily access DOM nodes outside the el node. Don't do it! Suppose that you would like to debug your menu bar view. How easily would you locate all the code in your application that could potentially modify it? If you made the menu bar view exclusively responsible for toggling the state of its buttons, your search efforts would be limited to finding the file of the menu bar. Otherwise, you would have to search the whole application and determine which seemingly unrelated code snippet executes when. Avoid Spaghetti code!

The solution lies in using Backbone Events. You should trigger an event and make sure that the menu bar is informed about the change. There are multiple options:

propagate the event to the common parent of the code editor and the menu bar and make sure the common parent informs the code editor via a method call

use an event bus

use a shared model or collection

The following solution uses the event bus for the purpose of illustrating Backbone Events.

app.eventBus = _.extend( {}, Backbone.Events ); var CodeEditorView = Backbone.View.extend({ events : { 'change .js-code-textarea': this.codeChanged }, codeChanged : function( e ) { var contents = $( e.target ).val(); this.collection.saveSnapshot( contents ); var depth = this.collection.length; app.eventBus.trigger( 'mainView:codeEditor:snapshotTaken', depth ); } }); var MenuBarView = Backbone.View.extend({ initialize : function() { this.listenTo( app.eventBus, 'mainView:codeEditor:snapshotTaken', this.enableUndo ); } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 app . eventBus = _ . extend ( { } , Backbone . Events ) ; var CodeEditorView = Backbone . View . extend ( { events : { 'change .js-code-textarea' : this . codeChanged } , codeChanged : function ( e ) { var contents = $ ( e . target ) . val ( ) ; this . collection . saveSnapshot ( contents ) ; var depth = this . collection . length ; app . eventBus . trigger ( 'mainView:codeEditor:snapshotTaken' , depth ) ; } } ) ; var MenuBarView = Backbone . View . extend ( { initialize : function ( ) { this . listenTo ( app . eventBus , 'mainView:codeEditor:snapshotTaken' , this . enableUndo ) ; } } ) ;

While in some cases, using the event bus simplifies your work, in this example, I discourage using it and prefer using the method described as "shared model or collection". The essence of the solution is that both the CodeEditorView and the MenuBarView have access to the same collection.

var CodeEditorView = Backbone.View.extend({ events : { 'change .js-code-textarea': this.codeChanged }, codeChanged : function( e ) { var contents = $( e.target ).val(); this.collection.saveSnapshot( contents ); } }); var MenuBarView = Backbone.View.extend({ initialize : function() { this.listenTo( this.collection, 'change', this.enableUndo ); } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var CodeEditorView = Backbone . View . extend ( { events : { 'change .js-code-textarea' : this . codeChanged } , codeChanged : function ( e ) { var contents = $ ( e . target ) . val ( ) ; this . collection . saveSnapshot ( contents ) ; } } ) ; var MenuBarView = Backbone . View . extend ( { initialize : function ( ) { this . listenTo ( this . collection , 'change' , this . enableUndo ) ; } } ) ;

2. Abusing the Event Bus

We saw in the above example that using the event bus was syntactically correct, but the essence of the problem was not captured semantically. The undo and redo buttons are semantically linked to the data in the code editor. Therefore, it is natural to link the collection of changes to the menu bar view. As the code is presented by the CodeEditorView , the same collection should also be linked to the code editor presenter. Using the event bus helped us make the mistake of not realizing the seemingly natural relation between the menu bar and the collection of code changes.

The anti-patterns of relying on the event bus too much has to be avoided in complex applications. In some cases, you can avoid using events altogether. Sometimes developers were so obsessed with events that they didn't even realize that they triggered an event just to catch it in the exact same Backbone View instead of a simple method call. In other cases, it is often easy to realize that two of your views communicate so much that instead of using events, you are better off solving the problem on design level, clarifying responsibilities. A third possibility is to use the built-in events of your Backbone objects.

If you have one globally accessible event bus for your whole application, it will be a shared resource that can be polluted very easily. If you don't name your events providing a hierarchy of non-ambiguous name constants, eventually you will lose the overview of what is going on.

Beyond a certain size, the global event bus will become hard to manage and maintain regardless your efforts of coming up with clear and hierarchical custom event names. In this case, it is also possible to create channels for a given purpose and pass the channel to the interested participants.

Other ideas are also possible to implement such as using the unidirectional event flow model. You don't even need Flux to apply ideas from it.

3. Accessing the reference of the parent

In a view hierarchy, the parent view holds a reference of its children. This is natural as the parent view is responsible for initializing, rendering and destroying them. The child view however should only mind its own business. Responsibility-wise, the child view should not know anything about its surroundings. Therefore, accessing the parent view should never be an option. The child view should trigger an event and the parent view can decide to listen to it if the event is interesting from the perspective of its responsibilities.

Collections and models are even more dangerous: if you add a model to a collection, Backbone gives you the option of using the collection attribute in each model. It's needless to say that misplacing the responsibilities in models is very easy. Suppose all the models of a collection have the following method:

function indicateLastChangeInModel() { this.collection.each( function( model ) { model.set( 'lastChanged', false ); }); this.set( 'lastChanged', true ); } 1 2 3 4 5 6 7 8 function indicateLastChangeInModel ( ) { this . collection . each ( function ( model ) { model . set ( 'lastChanged' , false ) ; } ) ; this . set ( 'lastChanged' , true ) ; }

Accessing other models of your collection is discouraged because implementing the responsibility of the collection in the model results in spaghetti code. Use the change event of the model instead and listen to it in the collection.

4. Rendering the whole view each time an attribute changes

Suppose that you have a report with up to 100 visible rows and up to 10 columns. An event stream updates the row belonging to the live data once per 100 milliseconds. It would be straightforward to rerender the view on change:

reportView.listenTo( reportView.collection, 'change', reportView.render ); 1 2 3 reportView . listenTo ( reportView . collection , 'change' , reportView . render ) ;

Worst case, we waste resources on rendering 1000 cells and some accompanying markup just for the purpose of changing the value of one cell. The solution is to create a method for rendering just the fraction that changes.

// Rerendering the whole row reportView.listenTo( reportView.collection, 'change', reportView.renderCurrentRow ); // Rerendering a cell reportView.listenTo( reportView.collection, 'change:cartValue', reportView.renderCurrentCartValue ); 1 2 3 4 5 6 7 8 9 10 11 12 13 // Rerendering the whole row reportView . listenTo ( reportView . collection , 'change' , reportView . renderCurrentRow ) ; // Rerendering a cell reportView . listenTo ( reportView . collection , 'change:cartValue' , reportView . renderCurrentCartValue ) ;

Remark: this case could easily be solved in MarionetteJs as we would have only had to create a CompositeView whose child views would map each element of the reporting data collection. Whenever there is an update, only the child view would be rerendered.

5. Using omit instead of pick in Models

Suppose the attributes of your Backbone Model match the fields in the JSON payload of a PUT request, except one read-only attribute. Many developers conclude that all we need to do is omit the read-only attribute and send the rest to the server. For instance, suppose you are dealing with a Backbone Model responsible for storing data on your changed password. We have three attributes: oldPassword , newPassword and retypePassword .

var ChangePasswordModel = Backbone.Model.extend({ toJSON : function() { return _.chain( this.attributes ) .clone() .omit( 'retypePassword' ) .value(); } }); 1 2 3 4 5 6 7 8 9 10 var ChangePasswordModel = Backbone . Model . extend ( { toJSON : function ( ) { return _ . chain ( this . attributes ) . clone ( ) . omit ( 'retypePassword' ) . value ( ) ; } } ) ;

One day, someone decides on adding a minLength attribute to the model. Naturally, this attribute will not be sent to the server. Would you remember to add minLength to the list of omitted attributes? If the answer is yes, ask yourself if you would be comfortable with forcing yourself to remember that you have to update the toJSON method each time your attributes change and update the parse method each time the API changes? Wouldn't it be easier to explicitly specify the fields the client sends to the server by pick ing them?

var ChangePasswordModel = Backbone.Model.extend({ toJSON : function() { return _.chain( this.attributes ) .clone() .pick( 'oldPassword', 'newPassword' ) .value(); } }); 1 2 3 4 5 6 7 8 9 10 var ChangePasswordModel = Backbone . Model . extend ( { toJSON : function ( ) { return _ . chain ( this . attributes ) . clone ( ) . pick ( 'oldPassword' , 'newPassword' ) . value ( ) ; } } ) ;

6. Reinventing the wheel

BackboneJs is a small library supported by a big community. Many developers have created excellent libraries responsible for solving most of the problems you could face with. Therefore, make your research before trying to implement an extension to BackboneJs. Examples:

If you would like to use two-way data binding in your application, use Backbone Stickit or Epoxy.js

Hierarchical models and collections can be handled with Backbone Relational

View hierarchies, layouts are both supported by MarionetteJs, a framework built on top of BackboneJs. Implementing collection views on your own is an unholy quest as layouts, composite views and collection views in Marionette provide you with all features you want

The website http://backplug.io/ displays a list of plugins that you can use with BackboneJs. Many generic problems have already been addressed by others. The most popular item on the list is MarionetteJs. I suggest taking a look at MarionetteJs whenever the size of your application justifies it. There are many excellent features and syntactic sugar that will eliminate a lot of boilerplate code for you.

7. Hard coded constants

This section is about a generic advice mostly independent from the framework or libraries you are using. Never hard code values in your application. Maintainability of your code will suffer.

Suppose that your task is administration of user accounts with the following statuses:

Deactivated: 0

Active: 1

Deleted: 2

If you see the following code appear in your application, think about rewriting the code right away.

model.set( 'status', 1 ); if( model.get( 'status' ) === 2 ) { ... } AccountModel = Backbone.Model.extend({ defaults: { status: 0 } }); 1 2 3 4 5 6 7 8 9 10 11 model . set ( 'status' , 1 ) ; if ( model . get ( 'status' ) === 2 ) { . . . } AccountModel = Backbone . Model . extend ( { defaults : { status : 0 } } ) ;

Create constants instead.

app.constants.account.statuses = { 'DEACTIVATED': 0, 'ACTIVE' : 1, 'DELETED': 2 }; 1 2 3 4 5 6 7 app . constants . account . statuses = { 'DEACTIVATED' : 0 , 'ACTIVE' : 1 , 'DELETED' : 2 } ;

accountStatuses = app.constants.account.statuses; model.set( 'status', accountStatuses.ACTIVE ); if( model.get( 'status' ) === accountStatuses.DELETED ) { ... } AccountModel = Backbone.Model.extend({ defaults: function() { return { status: accountStatuses.DEACTIVATED } } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 accountStatuses = app . constants . account . statuses ; model . set ( 'status' , accountStatuses . ACTIVE ) ; if ( model . get ( 'status' ) === accountStatuses . DELETED ) { . . . } AccountModel = Backbone . Model . extend ( { defaults : function ( ) { return { status : accountStatuses . DEACTIVATED } } } ) ;

If you have trouble using your constants in the Backbone Model defaults key, notice that the value does not have to be an object: it can also be returned by a function.

Your constants don't have to be exposed in your app object. They can also be packaged in a module and imported.

If the client and the server share some constants, consider creating a JSON object and maintain your constants in one place. There is nothing worse than doing double work for no reason.

8. Storing template strings in Backbone Views

Templates should be separated from Backbone Views. Storing a template in a Backbone View is very much like submitting Ajax request using $.ajax in views. As your models and collections deal with data, your Ajax requests should also be there. Similarly, a Backbone View is not responsible for defining your template, it is only responsible for its proper presentation. Reading a template string in a JavaScript file is not really maintainable. This is the same reason why I have doubts in the maintainability of widgets created by the jQuery UI Widget Factory: a jQuery UI widget is often a mixture of data, presentation, templates and patches hiding the bugs in one place.

If you have experience with writing JSX code in React, you may think that the above paragraph also holds for the separation of the html-like JSX code and the rest of the JavaScript content. While this separation is possible, I would like to point out that writing JSX is just syntactic sugar to writing JavaScript code rendering your component. Writing a template string in JavaScript is not a syntactic sugar to writing a template and does not come with any added benefits.

Loading templates is not hard. If you use RequireJs, use a plugin like tpl or text and require your template as a dependency. There are many other solutions, you can even write a simple PHP template loader in 20-30 lines of code.

9. Using CSS classes both for styling and functionality

Suppose that you are writing a sportsbook application where you can bet on two teams participating in matches: home and away. The events are defined as follows:

var BetView = Backbone.View.extend({ events: { 'click .home-button': 'betOnHomeTeam', 'click .away-button': 'betOnAwayTeam' } }); 1 2 3 4 5 6 7 8 var BetView = Backbone . View . extend ( { events : { 'click .home-button' : 'betOnHomeTeam' , 'click .away-button' : 'betOnAwayTeam' } } ) ;

The classes .home-button and .away-button are also used for styling. In case different teams are responsible for styling and functionality, this approach is not maintainable. Always separate the classes used for styling and functionality. I tend to follow the advice of cssguidelin.es and add the js- prefix to classes used in my Backbone code.

While this is a good rule in general, there are some exceptions. Classes such as invisible are some of them. Instead of manipulating the CSS properties in the Backbone code, it makes sense to describe these changes by toggling classes. While it is possible to re-render a template whenever the visibility of a node inside it changes, sometimes it is easier to just toggle the invisible class on the node like this:

this.$( '.js-description' ).addClass( 'invisible' ); 1 2 3 this . $ ( '.js-description' ) . addClass ( 'invisible' ) ;

10. Using Ids that are not necessarily unique

By definition, all HTML identifier attributes should be unique in your document. Therefore, your Id attributes act as a global resource. As a consequence, even though Ids act as the most efficient selectors, cssguidelin.es sacrifices some efficiency in favor of maintainability and suggests not using Id attributes in your CSS code.

Ids may still appear in your HTML and Javascript code. For instance, the easiest way to link a checkbox to its label is to match the for attribute of the label with the id attribute of the related checkbox. The following question arises during development: how can we make sure our Ids are unique in the whole document, preferably without text-searching the whole code base?

Backbone views all have an identifier called cid . All you have to do to make your identifiers unique is to find a unique name for each of your Id attributes in one Backbone View and append the cid of the view to make the Id unique on the whole HTML page. This is a template preprocessing step worth abstracting.

Summary

Writing robust code in BackboneJs requires a lot of practice. Pay attention to the tips you read in this article. Many of the problems are not even related to BackboneJs specifically, you can also improve the quality of your JavaScript applications.

These were just entry level problems that I have seen people make when writing applications in Backbone. We will address more complex problems in the future such as avoiding memory leaks.