CHAPTER 11

image

Best Practices with Backbone

In the previous chapters we’ve seen how to use Backbone, along with some useful add-ons, to create high-quality web applications. However, as your app scales and begins to gain traction with real customers, you need to be confident in how you use the underlying technology. While frameworks such as Marionette and Thorax reduce the margin of error when it comes to view management, it is still worth understanding the techniques that can ensure you get the most from Backbone. This chapter compiles best practices that developers have employed when using Backbone. You’ll see how to manage templates effectively, avoid memory leaks, and ensure that your application has the best performance possible with the tips listed in the following sections.

General Best Practices

Before we go into detailed practices, there are some simple things you should keep in mind when building Backbone applications.

Maintain a Clear Separation of Concerns

Much is made of Backbone’s unopinionated nature, where you are free to use the library as you want. Without controls in place, it can be pretty easy to have your code run out of control. It is worth keeping the “separation of concerns” principle in mind. The main example of where this should be upheld is where references are made to views from with a model. While this may appeal in some cases, you should resist this as much as possible.

While a view has a reference to a model or collection in order to populate the data, the model never needs to know about the view that it is being rendered in. Enforcing this separation has a number of benefits:

  • Models/collections can be tested without the view needing to be rendered.
  • View-related logic will be kept out of the model and left into the view (because the model doesn’t know anything about the view).
  • If separate views are built for mobile and desktop browsers, there is no additional complication for the model. It remains the same regardless.

All your data classes will be more modular and reusable as a result.

Add Error Handling Patterns to Your Application

For any application that involves the input of data, you will want to have a consistent error handling and notification system in place. When building your application, this should be one of the first things you put in place. It ensures that all other developers in your team follow the same pattern and reduces the amount of duplicated code. It also ensures a better user experience because errors are displayed in a consistent and helpful manner.

Handling Validation Errors

One common pattern is to return an error object from the validate function. When the view is notified of a failure in the validation of the model, through the invalid event, it can then investigate this error object to update the view accordingly.

The following validate method returns an error array containing the individual errors that are encountered during validation. Note that if there are no errors, then nothing is returned from the function, implying a valid model.

validate: function(attrs) {
   var errors = [];

   if(attrs.email.indexOf('@') == -1) {
       errors.push({
           'message': 'Invalid email address',
           'attribute': 'email'
       });
   }

   if(attrs.name.length < 1) {
       errors.push({
           'message': 'Name must be specified',
           'attribute': 'name'
       });
   }

   //if there are no errors and no return, then there is no invalid event.
   if(errors.length) {
       return errors;
   }
}

The view listens for the invalid event being triggered from the model, inspects the second parameter (the return value from the validate function) for errors, and updates the view accordingly.

this.model.on('invalid', function(model, errors) {
       for(var i =0 ; i < errors.length; i++){
           if(errors[i].attribute === 'email'){
               //highlight the email field with errors[i].message
           }
           if(errors[i].attribute === 'name'){
               //highlight the name field with errors[i].message
           }
       }
});

A more advanced variation of this pattern suggested by Philip Whisenhunt and Derick Bailey is to trigger individual events for each error that can occur in the validate function, with the advantage that each error can then be handled individually in the view, rather than having a large function as a result of an invalid event being fired. The validate function is much the same, triggering events rather than adding errors.

validate: function(attrs) {
   var errors = [];

   if(attrs.email.indexOf('@') == -1) {
      this.trigger('invalid:email', 'Invalid email address', this);
     }

   if(attrs.name.length < 1) {
        this.trigger('invalid:name', 'Name must be specified', this);
   }

}

On the view, each of the error events is listened for.

this.model.on('invalid:email', function(error) {
       //highlight the email field with the error
});
this.model.on('invalid:name', function(error) {
       //highlight the name field with the error
});

As you can see, this provides a more elegant, decoupled approach to error handling.

Add a General Error Alert

For a custom user interface, it is worthwhile to add a single alert dialog that can be used across all of your views. This can be done by extending Backbone.View to create your own superclass or by using the mixin approach detailed in the “Sharing Common Code Between Views” section later in this chapter.

Avoiding Memory Leaks

One of the most common errors across all applications, regardless of programming language or framework, is memory leaks. A memory leak occurs when objects or views that should no longer be in use are still “alive” in the system. This is usually a result of objects not being correctly disposed. In Backbone, this is a common problem, but it’s easy to solve once you know how.

Unbind Events When Closing Views

One of the most common reasons for memory leaks in Backbone is when views that are not currently in use, as a result of navigating to a new view, are still bound to events. We’ve referred to these previously in the book as zombie views.

As we’ve already seen, events are an essential part of the Backbone application infrastructure, allowing real decoupling between views, models, and collections. A symptom for event bindings that are being handled incorrectly would be where a single event is being handled multiple times unexpectedly.

Let’s first recap the different ways to handle events in a view. One way that a view can bind itself to change events in a model is through the on function.

MyView = Backbone.View.extend({
 initialize: function(){
   this.model.on('change', this.render, this);
 },
});

With the previous code, any changes in the model will result in the render function in the view being invoked. Note that in previous versions of Backbone, this function was named bind rather than on; the old name is still supported, but it’s best to use on.

Views also use the events hash to bind DOM events to handlers.

MyView = Backbone.View.extend({
 events: {
   'click #search': 'runSearch'
 },
 runSearch: function(){
     //search code
 },
});

When the application is finished with the view and you render a new view, these events are still bound, and because of this, the view is kept in memory. Developers new to Backbone may assume that the remove function, which removes the view from the DOM, will deal with all events, but this is not true.

You can take a few different approaches in your application code to deal with this.

Use listenTo for Event Handling

One of the advantages of using listenTo for your event handling is that when the remove function is called for a view, the stopListening function is called in turn, thus unbinding any of the events that have been bound using listenTo. The following is an example of how this function can be used to bind to all change events in a model:

MyView = Backbone.View.extend({
 initialize: function(){
   this.listenTo(model, 'change', this.render);
 },
});

It can be a good rule of thumb to use listenTo in place of either the bind or on function when dealing with events.

Use a Pattern to Close Views Correctly

Another option is to write some additional code for views to ensure that they are disposed of properly. When switching between views in a router, you could ensure that as new views are displayed, any previous view is closed. Derick Bailey proposes a useful pattern for this on his blog, at http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/, which is used in his Marionette library and is also available in his EventBinder plug-in.

The pattern begins with the addition of a close function for Backbone.View. This can be achieved in a number of ways, but the most effective is to extend the Backbone.Views prototype with a close function. The close function will encapsulate both removing the view from the DOM and unbinding any events.

Backbone.View.prototype.close = function(){
       this.remove();
       this.unbind();
       if (this.onClose){
               this.onClose();
       }
}

Note that the final few lines of this function refer to onClose(). This is another additional function that can be added to each individual view that should unbind all events that the view has added throughout its life cycle.

var MyView = Backbone.View.extend({

   initialize: function() {
     this.model.on('change', this.render, this);
  },
   render: function() {
       // render a template
   },
   onClose: function(){
        //unbind events bound during the views lifecycle
        this.model.off('change'),
   }
});

As you can see in the previous code sample, the view registers to listen to the change event on the model during initialization and deregisters this listener when the onClose function is invoked.

To make calls to close views, there needs to be an object that keeps track of which view is currently in focus. To do this, Derek Bailey suggested adding a single AppView controller that can be used by the router as views are switched.

function AppView(){
  this.showView(view) {
   if (this.currentView){
     this.currentView.close();
   }

   this.currentView = view;
   this.currentView.render();
   $("#mainContent").html(this.currentView.el);
 }
}

The AppView object exposes one function, showView. This function closes the currentView, if one exists, before rendering the new view and adding to the page.

Router can now utilize this AppView object when views need to be shown as various routes are triggered. First the view is created, and then the showView function is used to display the view and ensure the previous view is properly disposed.

MyRouter = Backbone.Router.extend({
 routes: {
   "": "home",
   "search/:query": "showResults"
 },
 initialize: function(options){
   this.appView = options.appView;
 },
 home: function(){
   var homeView = new HomeView();
   this.appView.showView(homeView);
 },
 showPost: function(query){
   //some code to get search results...
   var searchResultsView = new SearchResultsView({model: results});
   this.appView.showView(searchResultsView);
 }
});

Once again, it’s worth noting that leveraging frameworks such as Marionette will help you achieve similar results as the previous, but it’s always useful to have a better understanding of what is really going on when views are removed.

Rendering Views

You can use a number of practices when dealing with views. These range from improving the granularity of the render functions to improving how you use templates.

Render Only What You Need

All examples so far in this book have dealt with single render functions for each view. While this makes sense, it doesn’t take long before you have complex render functions for more involved views. More importantly, there will be cases where you want to ensure that your views remain fresh. Take the Twitter application, for example: there is a single render function that is done when we retrieve new data that renders all the tweets in the timeline. These tweets will appear with timestamps such as “one minute ago.” It would be nice to have these times update so they don’t become stale, but it is overkill to render the entire collection, or even the single tweet section, each time. It would be more useful if the rendering could be split up so that the timestamp can be updated on its own.

One way of dealing with this is to break up the render function into separate areas. By splitting up templates, it is possible to have the main render function call a number of smaller functions with defined responsibility.

Taking a single tweet, for example, the render function could be split into two parts: one for the main content and one for the timestamp.

render: function(){
        this.renderContent().
            renderTimestamp();
        return this;
    },

The advantage to this approach is that you can later update just part of the view, without needing to rerun the entire render function. The following code snippet shows how such a view could be constructed:

var com = com || {};
com.apress = com.apress || {};
com.apress.view = com.apress.view || {};

com.apress.view.TweetView = Backbone.View.extend({

    el: '#tweet',
    $timestamp: null,
    $content : null,
    tweetTemplate: Handlebars.compile($("#tweet-template").html()),
    timestampTemplate: Handlebars.compile($("#timestamp-template").html()),
    tweet : null,

    initialize: function(options){
        this.tweet = options.tweet;
        this.render();
    },

    render: function(){
        this.renderContent().
            renderTimestamp();
        return this;
    },

    renderContent: function(){
        //if the content is already rendered remove
        if(this.$content){
            this.$content.remove();
        }
        //deal with the main content template
        $content = this.tweetTemplate({
                tweet: this.tweet.toJSON()
            });
        this.$el.append($content);
        return this;
    },

    renderTimestamp: function(){
        //if the timestamp is already rendered remove
        if(this.$timestamp){
            this.$timestamp.remove();
        }
        //deal with the timestamp template
        $timestamp = this.timestampTemplate({
                time: this.tweet.getTimestamp()
            });
        this.$('#timestamp').append($timestamp);

        return this;
    }

});

Note that there are two separate templates used here and that as each of the lower-level render functions is called, they will first clear the element if it has previously been rendered. Each of the functions also returns a pointer to this, allowing the render methods to be chained in a neater way.

The templates used for the view are as follows. The main content template includes a placeholder for the timestamp.

<script type="text/x-handlebars-template" id="tweet-template">
 <p>{{tweet.text}}</p>
 <div id="timestamp"></div>
</script>

<script type="text/x-handlebars-template" id="timestamp-template">
  <p><i>{{time}}</i></p>
</script>

For completeness, the code to populate and display this view follows. The Tweet model is a simple Backbone.Model, with just a simple text attribute, as well as a way of representing the timestamp in a readable fashion using moment.js.

Tweet = Backbone.Model.extend({

    timestamp: null,
    initialize: function(){
        timestamp = new Date();
    },

    getTimestamp: function(){
        var friendly = moment(timestamp).fromNow();
        return friendly;
    }

    });

var tweet = new Tweet({text: 'James'});

var tweetView = new com.apress.view.TweetView({tweet: tweet});

Even in cases where you do not need to update parts of the view at specified intervals, splitting up the render function will make your code more readable to others and reduce the risk of the view code becoming unmaintainable.

Reduce Template Bloat with Partials

As an application grows, there will undoubtedly be some repetition in the definition of your templates as parts of some templates are copied into newer ones. This can lead to maintenance problems and breaks the well-respected Don’t Repeat Yourself (DRY) principle of software development. This section will look at the solutions available in both Handlebars and Underscore.

Partials in Handlebars

Handlebars provides out-of-the-box support for partials through the registerPartial function. When one template includes another partial template in its definition, the {{> partialname}} helper is used to place it in the correct location. The following timeline template example shows how this would be possible:

<script id='timeline-template' type='text/x-handlebars-template'>
 {{#each tweetlist}}
   {{> tweet}}
 {{/each}}
</script>

The template expects another partial to be registered, with the name tweet. As mentioned, this is done using the Handlebars.registerPartial function.

Handlebars.registerPartial('tweet', $('#tweet-partial').html());

The partial itself is defined like any Handlebars template would be. It can be a useful convention to add -partial to the end of the template ID for the purpose of fast identification.

<script id='tweet-partial' type='text/x-handlebars-template'>
 <div class='tweet'>
   <h2>{{username}}</h2>
   <div class='text'>{{text}}</div>
 </div>
</script>

The main template can be compiled as normal within your Backbone view once your partial has been registered in time.

Partials in Underscore

It’s also possible to provide partials in Underscore templates. Although there is no helper, like there is in Handlebars, it is still quite simple because of Underscore’s JavaScript syntax.

First, the partial template is defined to display a single item, in this case a book.

<script type='text/template' id='book-template'>
     <li>
       <b><%=name %></b> by <%=author %>
     </li>
</script>

To utilize this partial template, the main template can refer to it as if it were a JavaScript function, taking the item as a parameter.

<script type='text/template' id='library-template'>
<ul>
   <% for (var i = 0; i < library.length; i++) { %>
     <% itemTemplate(library[i]);  %>
   <% } %>
 </ul>
</script>

In the Backbone view, both templates need to be available to the render function.

LibraryView = Backbone.View.extend({
        template: _.template($('#library-template').html()),
itemTemplate: _.template($('#book-template').html()),

 render: function() {
   var html = this.template({
     library: library /* a collection */,
     itemTemplate: this.bookTemplate
   });

   $(this.el).append(html);
 }
    });

This approach can be used across all reusable Underscore templates in your application.

Precompile Templates in Handlebars

Template precompilation is the practice of preparing templates at build time to reduce the compilation time required when rendering a view. It can also help you reduce the size of the templating library because the compilation engine is no longer required for the application in production. Handlebars already provides a separation between the full version and the runtime version, as described in Chapter 4.

The savings made when using a precompiled template are not insignificant, especially when dealing with mobile devices.

In Chapter 4, we created a simple Handlebars example that provided a template for the library collection. To precompile this template for using in a Backbone application, you will first need to install the Handlebars node package.

npm install handlebars -g

Next you will need to take the template out of the HTML page and move it to a separate file. Typically each template is moved to a .handlebars file. In the case of the library template, it makes sense to move it to library.handlebars.

<script type="text/x-handlebars-template" id="library-template">
<ul>
    {{#each library}}
       <li>
       {{#log name 4}}{{/log}}
        <em>{{name}}</em> by {{author}}
     </li>
    {{/each}}
 </ul>
</script>

To precompile this template, the handlebars command takes the following form:

handlebars <input> -f <output>

In the case of the library.handlebars file, this will be as follows:

handlebars library.handlebars -f templates.js

This will generate a templates.js file that can be used in the HTML. The template will be identified as library in this case because the compiler uses the name of the input file as the template name. You can verify this by opening templates.js where you will see that the templates array includes an entry for library at the beginning of the file.

(function() {
 var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['library'] = template(function (Handlebars,depth0,helpers,partials,data) {

image Note  You will be using this array (Handlebars.templates[]) to access the precompiled template later.

The templates.js file needs to be included in the HTML page, and provided there are no calls in your JavaScript code to compile templates, the Handlebars runtime can be used instead.

<script src="js/external/handlebars-runtime.js"></script>
<script src="js/templates/templates.js"></script>

Finally, rather than using Handlebars.compile for the template in Backbone.View, you can access the template through the Handlebars.templates array.

template: Handlebars.templates['library'],

The refactored LibraryView remains the same otherwise.

LibraryView = Backbone.View.extend({
    initialize: function(){
        console.log('View created'),
    },

    events: {
        'click ' : 'alertBook'
    },

    template: Handlebars.templates['library'],
    render: function(){
        var self = this;
        var output = self.template({'library': self.collection.toJSON()});
        self.$el.append(output);
        return self;
   },

   alertBook: function(e){
       alert('Book clicked'),
   }
                            
});

View Management

There can be a lot of complication around views once your application gets larger. The following section deals with some common issues that Backbone developers find in this area, including how to share common code between views and how to communicate between subviews and their parent view.

Sharing Common Code Between Views

When writing views in an application, it’s likely you’ll find that there is code that you want to share between them. There are a few approaches to dealing with this; one is to use inheritance. But the most useful technique to sharing code that I have found is by providing mixins, where the common code is provided in a separate JavaScript file that can be incorporated on demand.

As an example, imagine that you wanted common alert functionality across a selected number of views. All functions that could be shared, related to alerts, would be placed in a single file.

var Mixins = Mixins || {};

Mixins.Alerts = {

    showSuccessMessage: function(message){
        console.log('Show success message'),
    },

    showFailureMessage: function(message){
        console.log('Show failure message'),
    }
};

Then, when a view is defined, the mixin could be incorporated using the _.extend function.

var com = com || {};
com.apress = com.apress || {};
com.apress.view = com.apress.view || {};

com.apress.view.TweetView = Backbone.View.extend(
    _.extend({}, Mixins.Alerts, {
    //standard Backbone view code
}));

As a result of this, all the functions defined in the mixin are now available to the TweetView object.

Updating a Parent View from a Subview

At some point in your career as a Backbone developer you will hit a situation where a parent view needs to be updated as a result of a change in a subview. There are two approaches you can take to solve this: one involves passing a reference to the parent view to the subview, and the other relies on the event mechanism.

Passing a reference is the easiest to read and understand but leads to a coupling between the parent and child views. The following code shows how this could be achieved:

var MainView = Backbone.View.extend({
     initialize: function() {
   var self = this;
   this._views = [];
   // create a sub view for each model
   this.collection.each(function(model) {
     self._views.push(new SubView({
       model: model,
       parentView: self
     }));
   });
 }
});

Now that each SubView has a reference back to the parent view, the call to rerender the parent view could be made from the subview using this:

this.parentView.render();

The neater event-based solution requires no reference to be passed. Instead, the parent view simply needs to listen for events from the subview.

var MainView = Backbone.View.extend({
 initialize: function() {
   var self = this;
   this._views = [];
   // create a sub view for each model
   this.collection.each(function(model) {
       var subview = new SubView({
       model: model,
       parentView: self
     });
    self._views.push(subview);
     self.listenTo(subview, 'event', self.render);
   });
 }
});

The subview just needs to trigger the event in order to get the parent view to enter its render function.

this.trigger('event'),

Network Performance

Once you have proven the functionality of an application, the performance of that application will come under scrutiny. There are a wealth of tools available to help you diagnose where you can make potential improvements, and a number of articles give good advice about this. We’ve already covered some of these tips previously in the book.

For example, the convention is that all CSS styles should be at the head of an HTML page, and the scripts should all be loaded at the bottom. This ensures that the page will render correctly at the beginning because waiting for CSS scripts to load will block rendering. Meanwhile, downloading JavaScript resources will block other downloads on the page, so these should happen at the end.

However, there are other tips that you should keep in mind when building your Backbone applications.

Minimize Requests on Page Load

One of the top tips with regard to network performance is to make fewer requests. Every single asset that you need in your page is an extra HTTP request. Rather than eagerly populating each of the Backbone.Model and Backbone.Collection objects in your application, take the time to consider which of these are needed immediately when the application is presented to the user.

One option is to use lazy loading, where the call to populate the objects from the server is made just at the time the user requires the data. While this can help minimize the page load time, it can lead to delays when the user does actually request the information.

If your application is built with server-side technologies, such as Grails, JSP, or .NET, it is possible that you could populate some of the initial models and collections with JSON data. This technique has become more popular, especially with Airbnb making its Rendr library available to all as an open source project on GitHub (https://github.com/airbnb/rendr). This project allows Backbone code to be rendered both on the client and on the server. More importantly, it makes it easy to pass the Backbone model content to your page when it is being rendered on the browser, allowing the server to perform the bulk of the network operations.

Finally, you may decide to refactor the API calls that are made from the client to server side as a result of some performance analysis of your application. Building end points that reduce data duplication across the API can be a useful exercise. It is always worth revisiting the API and refactoring as you go along.

Perceived Performance

You can utilize some tricks in your application to make the responsiveness seem to be much faster. In general, you should always be critical of the responsiveness of your app. Developers who are working with the same code from day to day tend to get used to the reaction times of an application, where a user may not be so forgiving.

Optimistic Network Calls

There are certain situations where it is safe to assume that a call to the back end will complete successfully, especially if all validation is carried out on the client side of the application. If you have a view where you save some model attributes to the server, it is typically that you wait for the success handler to be invoked before these changes are displayed or before a success message displays to the user.

var MyView = Backbone.View.extend({
 events: {
   'click .save': 'save'
 },

 save: function(e) {
   // save the model
   this.model.save(model.attributes, {
     success: function(model, response, options) {
       // render changes
     },
     error: function(model, xhr, options) {
       // render error
     }
   });
 }

});

If you are confident that the call will be successful and that all necessary validation has been carried out on the client side, then you don’t need to wait to display the result of the successful transaction. If something does go wrong, the error handler can pick up the change and run some code to undo the change and inform the user that the operation failed.

There may be cases that this type of optimization is too risky, so it is worth weighing the risk versus the benefit of such changes.

Use Document Fragment for Rendering Collections

When a single view corresponds with an entire collection, updating the view following a change in the collection can appear to happen pretty smoothly because one single render function is invoked, with one call to append to the view’s el. However, when you have a separate view for each model object in your collection, every time the collection changes, the rerendering of each of subview can affect performance and make the UI appear to be jumpy.

Consider the following code that takes this approach for a collection:

var MainView = Backbone.View.extend({
     initialize: function() {
   var self = this;   this._views = [];
   // create a sub view for each model
    this.collection.each(function(model) {
     self._views.push(new SubView({
       model: model     }));   }); },
     }));
   });
},
 render: function() {
   var self = this;
   this.$el.empty();
   // render each subview, and append
     for(var i=0; i < this.views.length; i++){
     self.$el.append(views[i].render().el);
   });}

Each time that an append occurs, the DOM needs to recalculate the position and size of everything in the DOM tree, which is an expensive operation when there are a large number of models in a collection. It would be much cleaner to build up the element contents first and have a single append to the DOM. That is exactly what the createDocumentFragement function of the DOM helps you achieve. Supported on all the major browsers, the function creates a Node object in the background, allowing you to prepare the contents before appending to the real document.

This results in the render function in the previous code sample changing to the following:

render: function() {
   var self = this;
   self.$el.empty();
   var fragment = document.createDocumentFragment();
     // render each subview, and append
     for(var i=0; i < this.views.length; i++)
      fragment.$el.append(views[i].render().el);
    });
    this.$el.append(fragment);
}

Note how a fragment is first created and each of the subviews is added to this. Once the loop has completed and the fragment has all views added, the entire fragment can be appended to the root el for the view.

Use Models to Store Extra Data

The default implementation of most Backbone models is to represent the data that gets persisted to the server only. While this sounds good in principle, there are cases where it causes more trouble. Consider an example where we want to track when an item in a view is selected. In the view class, it’s simply a matter of adding a listener for the click event to track which item is selected.

com.apress.view.TweetView = Backbone.View.extend({
    //other code
    tweet : null,
    events: {
        'click': 'markSelected'
    },

    markSelected: function(options){
        console.log('marking..'),
        var self = this,
            $target = $(options.currentTarget);
        $target.addClass('selected'),

    },
    //other code
});

If you want to check what the currently selected model object is, you need to traverse the DOM and find which element has the selected class.

In cases like this, it makes sense to track the state in the model object. In this way, you can iterate through the collection to find the model object where the selected attribute is equal to true. This would change the markSelected function to the following:

markSelected: function(options){
        console.log('marking..'),
        var self = this,
            $target = $(options.currentTarget);
        self.tweet.set('selected', true);
        $target.addClass('selected'),
    },

Note that this does add some overhead of managing variables and deciding which variables should be persisted to the server. However, discovering item state within the application is not as expensive as discovering which classes are applied to the corresponding DOM element. It also helps with the testability of the model objects because the selected state is effectively decoupled from the view.

Cache Objects Where Possible

Rather than retrieving new instances of views or models that are used across the application, it is good practice to cache such objects. A user object is a good example of this, where the object is shared across many views in the application. If you created a new instance of the user object each time you moved to a new page, it would be wasteful, particularly if the object doesn’t frequently change.

The router is the best example of a place where such a pattern can be useful. When initialized, the user model is set to undefined.

initialize: function() {
   this.cached = {
      userModel: undefined
   }
},

If a route is triggered within the router, the model can be retrieved if undefined, or otherwise the cached version can be returned.

mainView: function(parameter) {
   this.cached.model = this.cached.model || new UserModel({});
   new View({
       userModel: this.cached.userModel
   });
},

If another view uses this, such as a profile view, it doesn’t need to retrieve the model from the server.

profileView: function(parameter) {
   this.cached.model = this.cached.model || new UserModel({});
   new ProfileView({
       userModel: this.cached.userModel
   });
},

There may be cases where you need to force the model to be retrieved. In these cases, you can add a change listener in the model itself to prompt a fetch to take place. The following example forces a fetch to occur if the password is changed:

initialize: function() {
   this.on("change:password", this.fetch, this);
}

Caching objects can be useful and will increase the performance of the application, at the cost of a slight memory overhead as the objects are kept alive. You should also be aware of cases where the model object isn’t updating as you’d expect because of caching. The best advice is to use this pattern with caution.

Summary

This chapter listed a number of the most important practices to keep in mind when building professional Backbone applications. Memory management will be key among the concerns of most app developers, while performance, responsiveness, and general code management are also high on the list. Because of Backbone’s unopinionated nature, developers are afforded a lot of freedom on how to construct applications. With the practices in this chapter in mind, you will be able to steer your team away from some of the common traps that exist for Backbone developers.

With all of this in mind, it can make a lot of sense to utilize the plug-ins listed in Chapter 7 or to leverage the Marionette or Thorax libraries discussed in Chapter 10, particularly because most errors in Backbone applications are a result of badly managed views.

Thanks to Phillip Whisenhunt, Addy Osmani, Derick Bailey, Ian Storm Taylor, Rico Sta Cruz, and Oz Katz for their insightful articles that highlighted a number of the best practices, tips, and patterns used in this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.175.151