Top 8 Most Common Mistakes That Backbone.js Developers Make
Backbone.js equips the developer with an array of tools that are minimal but extremely flexible. This gives developers the option to design the applications the way they want, but also introduces a number of way things can go wrong. This article outlines a few common mistakes that Backbone.js developers can make, and how they may avoid them.
Backbone.js equips the developer with an array of tools that are minimal but extremely flexible. This gives developers the option to design the applications the way they want, but also introduces a number of way things can go wrong. This article outlines a few common mistakes that Backbone.js developers can make, and how they may avoid them.
Mahmud is a software developer with many years of experience and a knack for efficiency, scalability, and stable solutions.
Expertise
Backbone.js is a minimalistic framework which aims to provide a simple set of data structures and features that you can use to create a structured web application’s front-end. Out of the box, components of Backbone.js provide an intuitive environment that you may already be familiar with when working with models and views on the back-end. Models and collections in Backbone.js are simple, but they come with some very useful features, such as the option to easily integrate them with REST JSON APIs. But they are also flexible enough to be adapted to almost any practical use.
In this Backbone.js tutorial, we will take a look at some common mistakes that are often made by freelance developers taking their first stab at learning Backbone.js and ways to avoid them.
Mistake #1: Ignoring the Arsenal of Backbone.js Functionalities
Backbone.js may be a minimalist framework, but it (along with Underscore.js) provides a plethora of features and functionalities that can easily cover the most basic and some of the critical needs that arise when developing a modern web application. One common mistake that beginner developers often make is that they take Backbone.js to be yet another MVC-like client framework for the web. Although this section talks about something very obvious, when it comes to Backbone.js it is a really critical mistake to not explore the framework thoroughly. The framework may be small in size, but this is what makes it a great candidate for this thorough exploration. Especially its small and nicely annotated source code.
Backbone.js provides the bare minimum required to give your web application the structure that it can benefit from. With its extensibility and plethora of plugins, learning Backbone.js can be used to build some amazing web applications. Some of the most obvious features of Backbone.js are exposed through models, collections, and views. Router and history components provide a simple yet elegant mechanism to support client side routing. Although Underscore.js is a dependency of Backbone.js, it is pretty well integrated into the framework, as models and collections both benefit a lot from this amazing utility belt for JavaScript and is also available at your disposal.
The framework’s source code is so well written and annotated that one could probably read it all while drinking a cup of coffee. Beginners can benefit a lot from reading the source annotations, as they can learn a great deal about how the framework works internally, and also adopt a neat set of best-practices when it comes to JavaScript.
Mistake #2: Modifying DOM in Direct Response to Arbitrary Events
Something we tend to do when we first begin to learn Backbone.js is to not do things as recommended by Backbone.js. For example, we tend to handle events and view updates the way we would with jQuery on simple websites. Backbone.js is intended to give your web application a rigid structure through proper separation of concerns. What we often tend to do with Backbone.js is to update a view in response to arbitrary DOM events:
var AudioPlayerControls = Backbone.View.extend({
events: {
‘click .btn-play, .btn-pause’: function(event) {
$(event.target).toggleClass(‘btn-play btn-pause’)
}
},
// ...
})
This is something that should be avoided at all cost. It may be possible to come up with obscure examples where this may make sense; but in most cases, there are much better ways of doing it. In fact, one way that I could possibly exemplify here is to use the model to track the state the audio player and use that state information to render the button (or more specifically its class names):
var AudioPlayerControls = Backbone.View.extend({
events: {
‘click .btn-play, .btn-pause’: function(event) {
this.model.set(‘playing’, !this.model.get(‘playing’))
}
},
initialize: function() {
this.listenTo(this.model, ‘change’, this.render)
this.render()
},
// ...
})
<button class=”btn btn-<%- playing ? ‘pause’ : ‘play’ %>”></button>
There may be rare situations where direct manipulation of DOM from the event handlers will make sense, but the cost involved with managing complex DOM manipulations from event handlers is almost never worth it. This is something Backbone.js aims to solve. To use Backbone.js to do something like this is a mistake.
Mistake #3: Underestimating the Cost of Rendering
Since Backbone.js makes it very easy to render and re-render the DOM at will or in response to events, we often overlook how much of an impact this makes on the overall performance of the web application. There are many ways we can end up thrashing the render method on our views. Often this may not seem to be much, as modern web browsers are becoming highly performant pieces of software. But as the web application grows, and the amount of data it deals with grows, the drop in performance becomes more and more apparent.
We can see this in action through a contrived example where we start with a small collection of models, and render that into a list view:
var AudioPlayerPlaylist = Backbone.View.extend({
template: _.template(‘<ul> <% _.each(musics, function(m) { %> <li><%- m.title %></li> <% }) %> </ul>’),
initialize: function() {
this.listenTo(this.collection, ‘add’, this.render)
},
// ...
})
In this Backbone.js example, we are re-rendering whenever a model is added to the collection. This will work fine. However, since the “add” event is fired every time a model is added to the list, imagine fetching a large list of models from the server. The render method will be invoked multiple times consecutively, once for every model in the response from the server. A sufficiently large model will be enough to make your application stutter and ruin the user experience. Sometimes a small response is sufficient, depending on the complexity of the view being rendered.
A very quick solution to this is to simply not call the render method for every single model that is being added. In situations like these, models will be added in batch, and you can actually do something to make the render method fire only when it is invoked but not re-invoked within a specified amount of time. Backbone.js’s dependency Underscore.js comes with a handy utility function for this: “_.debounce”. All you need to take advantage of this is to change the event binding JavaScript line with this:
this.listenTo(this.collection, ‘add’, _.debounce(_.bind(this.render), 128))
This will cause the event callback to be fired every time the “add” event happens, however, it will wait for 128 milliseconds from the last event before actually invoking the render method.
In most cases, this will be considered a quick-fix like solution. In fact, there are more appropriate ways of avoiding render thrashing. Developers behind Trello once wrote a blog post discussing about their experience and approach in improving rendering performance while using Backbone.js.
Mistake #4: Leaving Event Listeners Bound Beyond Their Use
Leaving unused event listeners bound is probably something that can happen regardless of what JavaScript framework you use, or if you use one at all. Even though Backbone.js makes it easy to avoid this problem, it is certainly a mistake to still leave potential holes for easy memory leaks in your web application. The “Event” component of Backbone.js is certainly a pretty neat implementation. It allows JavaScript objects to easily implement event-based features. Since views are where most of our event consumption usually happens, it is easy to make this mistake there:
var AudioPlayerControl = Backbone.View.extend({
initialize: function() {
this.model.on(‘change’, _.bind(this.render, this))
// ...
},
// ...
})
The event binding line in this snippet of code is not very different from the one in the first example. All we have done here is we have changed the “this.listenTo(this.model, …)” to “this.model.on(…)”. Since we are very accustomed to the “.on()” call for event binding from our experience with some other JavaScript frameworks and libraries, when we start using Backbone.js we often tend to use the “.on()” calls to bind events. This would have been fine, only if we bothered to call “.off()” to unbind the event handlers when they are no longer necessary. But we rarely do that, and it ends up being a source of memory leaks.
Backbone.js offers a simple way to solve this problem. It is through the use of the “object.listenTo()” method. This enables the object that you are calling “listenTo()” on to keep track of what events it is listening for, and also make it easy to unbind all those events at once. Views, for example, automatically stop listening to all bound events as soon as you call “remove()” on it.
Mistake #5: Creating Monolithic Views
If you think about it, Backbone.js’s minimalism provides a tremendous amount of flexibility to how you want to architect your web application’s front-end. With models, collections, and views being the building blocks of your components, it is essential that you keep them as lightweight and as specific as possible. More often than not, it is views that end up becoming the heaviest aspect of your web application in terms of code. But it is really important that you do not end up making giant monolithic views that end up trying to do everything your application has to offer. Instead of making a giant “AudioPlayer” view with all the logic crammed into it, split it up into a number of logical views such as a view for the playlist, a view for the controls, a view for the visualizer, and so on. What sort of granularity you want to ensure is probably depend on the application you are trying to build.
This is because with granular views, where each view does something specific and does it right, developing an web application with Backbone.js becomes a breeze. Your code should be more maintainable and easy to extend or modify in the future. Then there is the other extreme, where you end up overdoing it. Backbone.js views are designed to make it convenient for you to work with a model or a collection, and this can probably work as a hint for how you should structure your application. Ian Storm Taylor shared some valuable ideas on his blog that you should probably keep in mind while implementing views.
Mistake #6: Not Realizing That Backbone.js Can Be Adapted to Non-RESTful APIs
Backbone.js works with JSON-based RESTful APIs out of the box. All you need for that is jQuery (or something that be a drop-in replacement for it, such as Zepto). However, Backbone.js is extremely extensible. In fact, Backbone.js can be adapted to use other types of APIs and other type of encoding formats even.
The component of Backbone.js that deals with the interaction of the front-end with the back-end services is “Sync”. This component exposes a number of attributes that you can easily override to customize Backbone.js’s way of interacting with the API endpoints. In fact, it is also possible to replace the default sync mechanism with something that is not-so-traditional to say the least, such as using localStorage to persist data, instead of back-end services.
Numerous plugins exist that make it easy to customize Backbone.js’s sync behavior. For example, a plugin called Backbone.dualStorage allows you to use both the back-end services and localStorage to persist data. When your application goes offline, the plugin uses localStorage to keep serving requests from cached data, and track changes that you may synchronize with the server later when you are online.
Although using Backbone.js with a back-end that is designed to be RESTful and be compatible with it is easier to use, it doesn’t mean that is all Backbone.js can work with. With some changes to the default Backbone.js sync mechanism, you can adapt it to a wide range of back-end service APIs and encoding formats.
It is worth mentioning that other parts of Backbone.js are also flexible, and in ways optional. For example, you do not have to use the default templating engine which comes with Underscore.js. You do not even have to use the view component of Backbone.js and can replace it with something else entirely if you want.
Mistake #7: Storing Data on Views Instead of in Models
One mistake that we may often make as a beginner learning Backbone.js is storing data directly on views as attributes. This data may be there to track some state or some user selection. This is something that should be avoided.
var AudioPlayerVisualizer = Backbone.View.extend({
events: {
‘click .btn-color’: function(event) {
this.colorHex = $(event.target).data(‘color-hex’)
this.render()
}
},
// ...
})
You can always create some additional models and collections without endpoints. These can help you store data that doesn’t necessarily have to be persisted on the back-end, or may be temporary in nature. Storing them in models gives you the benefit of being able to listen for changes. The relevant view, or even multiple views, can observe these models and re-render themselves as necessary.
Imagine if you actually stored state tracking variables on views and had to call the render method every time you changed them. Missing just one call to this render method could leave your application in a broken state, in terms of what the user is experiencing on screen. Moreover, with small views you may have to synchronize these state variables on multiple view objects and then have to call the render method on them as well.
Mistake #8: Using jQuery “.on()” Instead of Delegated Events
Backbone.js has, in my opinion, one magnificent way of handling DOM events. Not using it poses a bunch of disadvantages. jQuery’s “.on()” event binding function can feel convenient, but often turns out to be hassle on the long run. For example, when elements are detached from the DOM, jQuery automatically drops all event handlers bound to the elements using “.on()”. This means that any DOM event you try to bind to from within a view will need to be rebound if you detach the root element from the DOM and reattach it.
var AudioPlayerControls = Backbone.View.extend({
events: {
‘click .btn-play, .btn-pause’: function() { /* ... */ },
‘click .btn-prev’: function() { /* ... */ },
‘click .btn-next’: function() { /* ... */ },
‘click .btn-shuffle’: function() { /* ... */ },
‘click .btn-repeat’: function() { /* ... */ }
},
// ...
})
When the element corresponding to this view is re-attached to the DOM, all you have to do is call “delegateEvents()” on the view to bind all these events.
Note that it is important to understand how these events are bound. Instead of binding the event on the elements specified by the selector, Backbone.js actually binds the event handler to the root element of the view. This works fine in almost all cases, and in fact works better for most of our needs. Changing or replacing the child elements in the view’s DOM sub-tree do not require Backbone.js to bind each event again on the new elements. The existing listeners just keep working.
However, this prevents certain events from being listened to. One example is where you might want to listen for scroll events on “window” or on a child scrollable element. In case of child elements, you can make a sub-view for that element and handle the events there.
Conclusion
Backbone.js, being a very compact yet extensible framework, is an excellent choice for web applications that demand a great deal of flexibility behind the scene. Unlike frameworks such as Angular.js and Ember.js that are always there to tell you how to do what you want to do, Backbone.js takes a step back, gives you a powerful set of tools, and lets you decide how to use them. I hope this Backbone.js tutorial for beginners will help you avoid some of the common development mistakes and build something amazing with it.
Dhaka, Dhaka Division, Bangladesh
Member since January 16, 2014
About the author
Mahmud is a software developer with many years of experience and a knack for efficiency, scalability, and stable solutions.