Tutorial: a full Backbone.Marionette application (part 1)

In previous posts, I introduced you to Backbone.Marionette which provides many niceties to help you build complex Backbone.js apps. We’ll cover more advanced topics here, such as Backbone.History, modal windows, organizing code into sub applications, and more. The resulting app can bee seen live here. Let’s get started!

Update (June 2013): I’ve written a book on Marionette. It guides you through developing a complete Marionette application, and will teach you how to structure your code so large apps remain manageable.

Also, since this tutorial was written, Marionette has evolved with new functionality and better techniques to write scalable javascript apps. These are naturally covered in the book.

Today, we’re going to rewrite and expand Atinux’s “Backbone Books” example with Marionette. Even if you don’t read French, check out his demonstrations section for backbone “toy apps” to learn from. Atinux was kind enough to grant me permission to rewrite his app and blog about it, but keep in mind that all graphic design and CSS elements are his…

Since we’ve got a log of ground to cover, this post is going to go faster and have less hand holding as we go along. If you read the introduction to Marionette that I wrote, you should be comfortable enough to follow along. You can always refer to the full source code, but more often than not you’ll need to view the linked diffs to see what was written at each step (not all code is copied over here).

Getting started

We’ll start with this code base and just like last time, we’re simply going to create a Marionette application that is going to live within the “#content” DOM element (code). But this time, we’re going to structure our code so that the app we write will actually be a “sub application” that can be removed and replaced with another set of functionality (e.g. if the user clicks on a menu link).

How are we going to manage this? Essentially, we are going to namespace everything: instead of calling our collection “Books” and putting it in the global scope, we’ll call it “MyApp.LibraryApp.Books”, where “LibraryApp” is the name of our sub application.

The downside is that we’d always have to prefix everything with “MyApp.LibraryApp”, which will become annoying really fast… So the trick we’ll use is to leverage an Immediately-invoked Function Expression (IIFE), like so:


MyApp.LibraryApp = function(){
  var LibraryApp = {};

  return LibraryApp;

}();

We’re declaring “MyApp.LibraryApp” as a function that gets called immediately (notice the parentheses after the function definition). Basically,we’re doing something like this:


var myFunction = function(){
  var LibraryApp = {};

  return LibraryApp;

}

MyApp.LibraryApp = myFunction();

So what actually takes place within this magical IIFE? Well, currently very little is happening: we simply declare an empty object, and then we return it. Why bother? It allows us to declare “public” and “private members, like so:


MyApp.LibraryApp = function(){
  var LibraryApp = {};

  LibraryApp.alert = function(message){

    alert(message);

  };

  LibraryApp.privateAlert = function(message){

    privateMessage(message);

  };

  var privateMessage = function(message){

    alert('private: ' + message);

  };

  return LibraryApp;

}();

Any function attached to the LibraryApp object that gets returned by our IIFE will be callable form anywhere within the main application using “MyApp.LibraryApp.functionName”.

So within our code, we’ll be able to call “MyApp.LibraryApp.alert(‘My message’);” and ”MyApp.LibraryApp.privateAlert(‘My message’);”. Both will display alerts. However, if we call “MyApp.LibraryApp.privateMessage(‘My message’);” we’ll get an error, since it’s essentially a private function that can only be called within LibraryApp. Voilà, easy encapsulation!

Layouts

Before we get to coding, let’s think about our application. If you look at the example, you can see 2 major areas: a search area, and a list of books corresponding to the search criteria.

To keep these areas manageable (showing views, etc.), while being able to completely remove the sub application and avoid memory leaks, our sub application will be using a Marionette layout. Layouts basically work just like the regions we used in the previous tutorial: you define areas within DOM elements, and then show or close views in those areas.

Here it goes (code):


MyApp.LibraryApp = function(){
var LibraryApp = {};

var Layout = Backbone.Marionette.Layout.extend({
template: "#library-layout",

regions: {
search: "#searchBar",
books: "#bookContainer"
}
});

LibraryApp.initializeLayout = function(){
LibraryApp.layout = new Layout();

LibraryApp.layout.on("show", function(){
MyApp.vent.trigger("layout:rendered");
});
MyApp.content.show(MyApp.LibraryApp.layout);
};

return LibraryApp;
}();

MyApp.addInitializer(function(){
MyApp.LibraryApp.initializeLayout();
});

First, we declare a layout with both regions we talked about above. Then, we declare a function to initialize and show the layout:

  1. it creates a new layout instance;
  2. when the layout triggers the “show” event, we trigger another event-wide event that we’ll use later on (in the current code, we simply created a basic listener to check our code is working properly);
  3. it displays the layout within the overall application’s “content” region.

Then, outside our LibraryApp, we declare an initializer to call this function. Note how our encapsulation works: Layout is private and can’t be called from outside the object, but we can call “MyApp.LibraryApp.initializeLayout” without any trouble.

Getting books

Our app will need books will need some books to display, so let’s fetch some (code):


var Book = Backbone.Model.extend();

var Books = Backbone.Collection.extend({
model: Book,

initialize: function(){
var self = this;

// the number of books we fetch each time
this.maxResults = 40;
// the results "page" we last fetched
this.page = 0;

// flags whether the collection is currently in the process of fetching
// more results from the API (to avoid multiple simultaneous calls
this.loading = false;

// the maximum number of results for the previous search
this.totalItems = null;
},

search: function(searchTerm){
this.page = 0;

var self = this;
this.fetchBooks(searchTerm, function(books){
console.log(books);
});
},

fetchBooks: function(searchTerm, callback){
if(this.loading) return true;

this.loading = true;

var self = this;
MyApp.vent.trigger("search:start");

var query = encodeURIComponent(searchTerm)+'maxResults='+this.maxResults+'&startIndex='+(this.page * this.maxResults)+'&fields=totalItems,items(id,volumeInfo/title,volumeInfo/subtitle,volumeInfo/authors,volumeInfo/publishedDate,volumeInfo/description,volumeInfo/imageLinks)';

$.ajax({
url: 'https://www.googleapis.com/books/v1/volumes',
dataType: 'jsonp',
data: 'q='+query,
success: function (res) {
MyApp.vent.trigger("search:stop");
if(res.totalItems == 0){
callback([]);
return [];
}
if(res.items){
self.page++;
self.totalItems = res.totalItems;
var searchResults = [];
_.each(res.items, function(item){
var thumbnail = null;
if(item.volumeInfo && item.volumeInfo.imageLinks && item.volumeInfo.imageLinks.thumbnail){
thumbnail = item.volumeInfo.imageLinks.thumbnail;
}
searchResults[searchResults.length] = new Book({
thumbnail: thumbnail,
title: item.volumeInfo.title,
subtitle: item.volumeInfo.subtitle,
description: item.volumeInfo.description,
googleId: item.id
});
});
callback(searchResults);
self.loading = false;
return searchResults;
}
else if (res.error) {
MyApp.vent.trigger("search:error");
self.loading = false;
}
}
});
}
});

You’ll notice we trigger some events in our “fetchBooks” function, to tell the app what is happening.

We’ll also add some code to check everything is working by dumping a search into our trusty console:


MyApp.addInitializer(function(){
  LibraryApp.Books.search("Neuromarketing");
});

Now, if you refresh the page, you should see all sorts of books related to “Neuromarketing” getting dumped in the console.

Showing books

Now that we’ve got books on hand, let’s display them by defining views and templates (code):


MyApp.LibraryApp.BookList = function(){
var BookList = {};

var BookView = Backbone.Marionette.ItemView.extend({
template: "#book-template"
});

var BookListView = Backbone.Marionette.CompositeView.extend({
template: "#book-list-template",
id: "bookList",
itemView: BookView,

appendHtml: function(collectionView, itemView){
collectionView.$(".books").append(itemView.el);
}
});

BookList.showBooks = function(books){
var bookListView = new BookListView({ collection: books });
MyApp.LibraryApp.layout.books.show(bookListView);
};

return BookList;
}();

MyApp.vent.on("layout:rendered", function(){
MyApp.LibraryApp.BookList.showBooks(MyApp.LibraryApp.Books);
});

UPDATE: Backbone.Marionette’s CompositeView now lets you specify an itemViewContainer, which in many cases can be used instead of appendHtml. For reference, another tutorial I wrote uses the itemViewContainer within a CompositeView.

Looking at the code, you’ll notice we’ve put this into a new file. This is because we’ll use “controller” object that will take care of processing data and calling the views, and a separate object (MyApp.LibraryApp.BookList) that will take care of managing the views.

To get these views displayed, we’ll need to search for books to add them to the LibraryApp.Books collections, and then call LibraryApp.BookList.showBooks with the collection. In addition, we need to trigger the collection’s “reset” event so that the CollectionView knows it needs to rerender itself when the collection of books changes (see the code).

One thing that’s important to note here, is that we’re using the “layout:rendered” event to trigger showing the display. If we use a basic initializer, we run the risk of the code being run before the layout is ready, resulting in a runtime error.

Attaching the search view

Now let’s create a search view that will fire the search action based on the term we provide. The twist is that our search view is already fully rendered in the template, so all we’re going to do is use Backbone to manage events, and other things. This is the basic concept of progressive enhancement, which Derick Bailey has covered in two blog posts. Since we don’t want to render content that is already present on the page, we’ll simply attach the search view to its layout region (code):


var SearchView = Backbone.View.extend({

el: "#searchBar",

events: {
'change #searchTerm': 'search'
},

search: function() {
var searchTerm = this.$('#searchTerm').val().trim();
console.log("searching for ", searchTerm);
}
});

MyApp.vent.on("layout:rendered", function(){
var searchView = new SearchView();
MyApp.LibraryApp.layout.search.attachView(searchView);
});

All we do here, is declare a search view that will output the search term into the console, and then we create an instance and attach it to the view. You’ll notice we’re once again using the “layout:rendered” we’ve set on our main app, before attaching the view to its “search” region.

This last bit is important: by calling “attachView” instead of “show”, we ensure we’re not double rendering an existing view. In passing, notice we’re using the “layout:rendered” event to know when the layout has been rendered, so that we can attach our search view to existing DOM elements.

If you look closely at the code for MyApp.LibraryApp.BookList, you’ll see that we have 2 things that happen on the “layout:rendered” event, one is declared within the BookList object, and one outside. The reason we create a search view from within the BookList object, is that the SearchView is private, and we wouldn’t have access to it from outside the object. The showBooks function, however, is attached to the BookList and is therefore part of the public API, making it callable from outside of the BookList object.

So now, if you enter a search term and hit enter, you’ll see it appear in the console.

Managing searches with events

Displaying the books we search for in the search bar is pretty easy using events (code).

In our search view:


search: function() {
  var searchTerm = this.$('#searchTerm').val().trim();
  MyApp.vent.trigger("search:term", searchTerm);
}

And in our Books collection:


initialize: function(){
  var self = this;
  _.bindAll(this, "search");
  MyApp.vent.on("search:term", function(term){ self.search(term); });

}

If you now enter “dogs” in the search bar and hit enter, you’ll see different books appear. Great! Our search is working!

However, the spinner just continues spinning for ever, which is pretty annoying. Instead, let’s use it to indicate the app is searching for books. For that, we need to display it when a search starts, and hide it as soon as a search ends.

Remember the “search:start” and “search:end” events we defined? Let’s use them for that in our search view (code):


initialize: function(){
  var self = this;
  var $spinner = self.$('#spinner');
  MyApp.vent.on("search:start", function(){ $spinner.fadeIn(); });
  MyApp.vent.on("search:stop", function(){ $spinner.fadeOut(); });
}

Let’s do one last thing before we take a break: right now, our first search doesn’t display anything in the search bar. One easy way to remedy that is for our search view to listen for the “search:term” event within its initializer (code):

MyApp.vent.on("search:term", function(term){
  self.$('#searchTerm').val(term);
});

Then, instead of directly calling the book collection’s search method, we’ll just trigger the event:


MyApp.vent.trigger("search:term", "Neuromarketing");

And that’s it for part 1! See part 2

This entry was posted in Backbone.js, Backbone.Marionette. Bookmark the permalink.

12 Responses to Tutorial: a full Backbone.Marionette application (part 1)

  1. Charles says:

    Very nice tutorial, cant wait for the next part :)

  2. david says:

    Just a few more days ;-)

    On Sunday, you’ll hopefully get your fill with lots of Backbone and Marionette goodness !

  3. Wen says:

    David,

    I’ve been looking for a good tutorial using Marionette and stumbled across your post. You have alot of great stuff goin gon here.

    I do have one question though. I see on this commit, on line 19, of app.library_app.js the application is loading this layout into its content.

    Should this be happening in the library_app? It seems like this is going to be tightly coupled to the application.

    Your thoughts please :)

  4. david says:

    Well this app is the one displayed by default, so the layout is loading in the content section on startup.

    In the second part of the tutorial, you can see how a second “application” can replace the library app (by replacing the content area: Marionette takes care of closing the views, etc.).

    Derick Bailey has since included submodule funcionality in Marionette (see https://github.com/derickbailey/backbone.marionette/blob/master/apidoc.md#marionetteapplicationmodule), so this is what should be used to manage multiple sub-applications.

    Does this answer your question?

  5. Pingback: Tutoriel Backbone.js Books avec Backbone.Marionnette | Atinux

  6. uria hitite says:

    I think there is a mistake in the 3rd example:
    isn’t
    LibraryApp.privateAlert = function(message) {..}

    putting privateAlert in the public interface?

  7. Michael Hayman says:

    This is amazingly good. You should write a book.

  8. Anil Kommareddi says:

    Great article, David!

    A small typo:

    So within our code, we’ll be able to call “MyApp.LibraryApp.alert(‘My message’);” and ”MyApp.LibraryApp.alert(‘My message’);”. Both will display alerts.

    should be:

    So within our code, we’ll be able to call “MyApp.LibraryApp.alert(‘My message’);” and ”MyApp.LibraryApp.privateAlert(‘My message’);”. Both will display alerts.

  9. david says:

    @Michael: Thanks for the kind words, I’m glad it helped you out !

    I’ve actually been considering writing an eBook on the subject, and was wondering if there’d be any interst in it. Thoughts ?

  10. david says:

    @Anil: Thanks for the catch, I’ve corrected the typo.

  11. Will Vaughn says:

    I really want to read this tutorial, and I’m glad to have found it. But the syntax highlighter you’re using on this blog is not formatting things in a readable way. Its painful to follow code that isn’t indented properly and has sent me into a rage. Please take the time to install and use a wordpress plugin http://wordpress.org/extend/plugins/wp-syntaxhighlighter/

    It would go a long way in helping get the message across more clearly in your code snippets.

  12. david says:

    Hi Will,

    Unfortunately, I don’t have time to address the indentation issues with the syntax highlighting plugin I have, or to move to a new one. Hopefully I will be able to soon.

    In the meantime, I suggest you click on the “code” links in each section, where you’ll find properly formatted code in addition to its context. Or simply fork/clone the code on Github and study it in your favorite editor.