Introduction
In this article, we will discuss about the backbone.js and marionette.js frameworks and see how we can use them to create single page JavaScript applications.
Background
It was a long time ago (almost a decade back) when most software applications were getting built as standalone applications. These applications were targeted at a single user and ran on their operating systems. Then came the need to share data across multiple users and a need to store data at a central location. This need gave birth to distributed applications and web applications. Distributed applications ran as standalone applications on the user's machine giving him a rich user interface (desktop like experience) to work with, and behind the scenes, these applications sent data to a server. Web applications, on the contrary, were sandboxed in a web browser and HTML and HTTP were used to let the user perform operations on the data which is stored on the remote server.
The major differentiating factor for these two applications was that the distributed applications provided an interactive user experience whereas web applications provided very limited features (due to technology limitations). The downside of distributed applications was that it was very difficult to roll-out and ensure the application updated across all users. Web applications had no such problems because once the application is updated on the server, all users got the updated applications.
Both these approaches had pros and cons and something needed to be done to get the best of both worlds. This is where browser plugin based applications like Flash applications and Silverlight applications came into the picture. These technologies filled in the gap for all the functionality not possible with HTML. They provided the possibility of creating rich internet applications that ran in the browser. The only downside of this was that the user needed to install the browser plug-in to get these applications to work.
Then came the time when browsers became more capable and HTML became more mature. Creating a rich internet application became possible only using browser based client side technologies. This led developers to write client side code using HTML and JavaScript to create rich internet applications. No need for plugins like Flash and Silverlight. But since HTML and JavaScript were never meant to be used for writing full fledged web applications, these applications had all the HTML and JavaScript code intermingled. This led to spaghetti code and these client side HTML/JavaScript applications (Single Page Applications or SPAs) became a maintenance nightmare.
So why should we write single page apps when they lead to bad code? The main reason for wanting to create a single page application is that they allow us to create a more native-like/desktop-like/device-like application experience to the user. So the need was to create SPAs in a structured manner and this created a need for JavaScript frameworks and libraries that provided some structure to single page applications.
Currently, there are quite a few Open Source JavaScript frameworks available that help us solve the problem of spaghetti code. These frameworks let us design our applications in a structured manner. In this article, we will be discussing about backbone.js which unarguably is one of the best frameworks to develop single page applications. In this article, we will look into the basics of the backbone.js framework and Marionette.js framework and will try to implement a simple application to see these concepts in action.
Using the Code
What is Backbone.js and Marionette.js
Backbone.js is a lightweight framework that lets us create single page applications in a structured manner. It is based on the Model-View-Controller (MVC) pattern. It is best suited for creating single page applications using a RESTful service for persisting data.
Marionette.js is a composite application library built on top of backbone.js and it allows us to build large scale JavaScript single page applications. Backbone.js does not enforce the use of strict MVC pattern but Backbone Marionette enforces the use of strict MVC pattern. Marionette also provides us View
classes that we can create and extend simple view, collection views, and composite views. It also gives us the possibility of creating Controller classes and eventing mechanism across applications.
Separation of Concerns and MVC
One of the best things about a good application architecture is the Separation of Concerns (SoC). The best part of the Backbone Marionette framework is the ability to provide this separation of concerns using the MVC pattern. The Model will represent the business objects needed to implement the solution. The view is the part that is visible to the user. The user can simply consume the data using views or act upon the data, i.e., CRUD operations. The controller is the one that provides the mechanism for interaction between models and views. The controller's responsibility is to react on the user's input and orchestrate the application by updating the models and views. The Models and Views remain loosely coupled, i.e., the Models don't know anything about the View and the View has a Model object (association) to extract information and display it to the user.
Models
What are models? In the typical MVC or MVVM patterns, models represent business entities. Models represent the business entity with the possibility of performing some business logic and business rule validations. Models also expose events so that whoever is using these models (typically views) can subscribe to them and get notified whenever the state of the model is changed. Models are the most essential part of Backbone applications. Like we discussed, they represent entities that our application will be using. These are typically created by looking at the RESTful API. We create our models based on the REST API interface. So let's say we have a RESTful service providing a CRUD operation on a book entity. The JSON payload for this entity is:
{
"ID": 3,
"BookName": "Test Book 3"
}
Note: For the purpose of this demo, I will use a WebAPI for creating the RESTful service. The source code for this service is attached in the sample code and the documentation for this WebAPI can be found here:
So for the above payload, we need to create a Backbone model as:
var Book = Backbone.Model.extend({
defaults: {
ID: null,
BookName: null
},
urlRoot: 'http://localhost:51377/api/Books'
});
Here Book
is the name of the model. It contains two attributes: ID
and BookName
. These attributes are created by looking at the JSON payload. Typically, every attribute in JSON payload will have an attribute in the Model
class. urlRoot
species the URL that should be used to perform CRUD operations for this model (on the server).
Now the question is: How can we create this model?
var book = new Book({BookName: "Backbone Book 1"});
The reason we are not passing any ID
attribute is that the ID
attribute is an auto generated column on the server end. Now let us see how we can perform CRUD operations on this model.
Create
To create a new entity on the server, we need to populate the non identity fields in the model (other than ID
in this case) and then call the Save
method on the model.
var book = new Book({BookName: "Backbone Book 1"});
book.save({}, {
success: function (model, respose, options) {
console.log("The model has been saved to the server");
},
error: function (model, xhr, options) {
console.log("Something went wrong while saving the model");
}
});
Read
To read a single book entity, we need to create the book entity with the identity attribute populated, i.e., the ID of the book we want to read. Then we need to call the fetch
method on the model
object.
var book1 = new Book({ id: 10 });
book1.fetch({
success: function (bookResponse) {
console.log("Found the book: " + bookResponse.get("BookName"));
}
});
Update
Now let's say we want to update the name of the book retrieved in the earlier fetch call. All we need to do is set the attributes we need to update and call the save
method again.
bookResponse.set("BookName", bookResponse.get("BookName") + "_updated");
bookResponse.save({}, {
success: function (model, respose, options) {
console.log("The model has been updated to the server");
},
error: function (model, xhr, options) {
console.log("Something went wrong while updating the model");
}
});
Delete
Now to delete a Model
, we just need to call the destroy
method of the model
object.
var book2 = new Book({ id: 13 });
book2.destroy({
success: function (model, respose, options) {
console.log("The model has deleted the server");
},
error: function (model, xhr, options) {
console.log("Something went wrong while deleting the model");
}
});
Collections
So we have seen how we can create models and perform CRUD operations on the models. But what if I want to fetch multiple models, i.e., a collection of models from the server? This is where Backbone collections will come in handy. We can create a Backbone collection that will work on a collection of models. So if we need to create a BooksCollection
object in our application:
var BooksCollection = Backbone.Collection.extend({
model: Book,
url: 'http://localhost:51377/api/Books'
});
The url
property points to the server location which will be used to retrieve the list of books. So if we need to fetch a list of books and populate them in the BooksCollection
object, we can do it like:
var books = new BooksCollection();
books.fetch({
success: function(books) {
console.log(books.length + " books found");
}
});
Note: Now we know what are Backbone models and how we can perform CRUD operations using these models. The important thing to mention here is that the models along with data and CRUD operations provide a lot of other features surrounding that model. We can perform validation, conversion, and a lot of other things using models. Performing these operations are not covered in this article.
Views
With the Models in place, we get to achieve some structure in our application as all our JSON parsing, JavaScript objects, validations, and AJAX calls to perform CRUD operations are now abstracted in the form of a model. But if we still use JavaScript embedded in HTML to show the model data and to manipulate DOM elements on user actions, we might end up with a lot of bad scattered code. How can we solve this problem?
Backbone helps us solve this problem by providing Views. Backbone gives us the possibility of creating View classes. The view classes do not know anything about the HTML and CSS, i.e., the actual UI part. The view classes are more like a glue that holds an HTML template with the model object. Also, this provides the mechanism to handle the events raised from the model and update the UI and handle UI events and act on them (perform some operations on the model). So in a way, we can say that the views are just observers who are listening to the model and UI events which makes them a perfect place to handle all the events and act upon them.
Marionette goes one step further and gives us the possibility of creating multiple types of views, i.e., ItemView
(to show a single model), CollectionView
(to show a collection of models), and CompositeView
(to show a collection of models using a custom template).
So let's say we want to create a very small single page application to perform CRUD operations on the book
entity provided by the REST API. We need a view to display the list of books. A view to add new books, a view to update any book, and a possibility to delete a book from the list view. So let us start by creating a simple view to display a single book entity.
<script type="text/template" id="bookView">
< td> < % =BookName % > < /td>
< td> < input type="submit" id="btnEditBook" value="edit"/> < /td>
< td> < input type="submit" id="btnDeleteBook" value="delete"/> < /td>
< /script>
This is a template that will render the book's name from the model and a button to edit the book name and a button to delete the book name. Since this is wrapped inside the script
template tags the browser will not display them but the Backbone Marionette view classes can use them to render the model data. So let us see how the view class for displaying a single book entry looks like:
var BookView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: "#bookView",
initialize: function() {
this.listenTo(this.model, "change", this.render);
},
events: {
'click #btnDeleteBook': "deleteBook",
'click #btnEditBook': "editBook"
},
deleteBook: function() {
},
editBook: function() {
}
});
What this class specifies is the template it will be using, what HTML tagName
it will create while rendering the template, and the functions to handle the UI events. Now if we want to create this view and render the template on the screen:
var book = new Book ({BookName: "Test Book"});
var bookView = new BookView({model: book});
On the same lines, if we want to create a view to show the list of books, we need to create the CollectionView
.
var BooksCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: BookView,
tagName: 'table',
});
It simply specifies what HTML tagName
should be used while rendering and what view to use to render the individual items of the collection. To create this collection view:
var books = new BooksCollection();
var booksView = new BooksCollectionView({ collection: books });
And to create a new book, we need one more view so the template for this view will look like:
<script type="text/template" id="addBookView">
<input type="textbox" id="txtBookName"/>
<input type="submit" id="btnAddBook" value="Add Book"/>
</script>
And the view class to render this view will look like:
var AddBookView = Backbone.Marionette.ItemView.extend({
template: "#addBookView",
events: {
'click #btnAddBook': "addBook"
},
addBook: function () {
}
});
So now we have views ready to show a list of books, show one book, add a book. Also, the UI has the element to be able to edit and delete the book. Now to understand how these views will be rendered, let's first look at the controllers.
Controllers
Controller classes provide the glue code for interaction between the view and the Model. So I have created a simple controller class that will perform CRUD operations on the model. The Controller will also take care of creating the views and rendering them. Later, we will hook it with the UI events.
var BooksController = Backbone.Marionette.Controller.extend({
ShowBooksList: function (options) {
this.collection = new BooksCollection();
var self = this;
this.collection.fetch({
success: function (books) {
var booksView = new BooksCollectionView({ collection: self.collection });
options.region.show(booksView);
}
});
},
ShowAddBookView: function (options) {
var addBookView = new AddBookView();
options.region.show(addBookView);
},
AddBook: function (book) {
var BookToSave = book;
var self = this;
BookToSave.save({}, {
success: function (model, respose, options) {
console.log("The model has been saved to the server");
self.collection.push(model);
},
error: function (model, xhr, options) {
console.log("Something went wrong while saving the model");
}
});
},
DeleteBook: function (book) {
var BookToDelete = book;
var self = this;
BookToDelete.id = BookToDelete.get("ID");
BookToDelete.destroy({
success: function (model, respose, options) {
console.log("The model has deleted the server");
self.collection.remove(model);
},
error: function (model, xhr, options) {
console.log("Something went wrong while deleting the model");
}
});
},
UpdateBook: function (book) {
var BookToUpdate = book;
var self = this;
BookToUpdate.id = BookToUpdate.get("ID");
BookToUpdate.save({}, {
success: function (model, respose, options) {
console.log("The model has been updated to the server");
self.collection.push(model, { merge: true });
},
error: function (model, xhr, options) {
console.log("Something went wrong while updating the model");
}
});
}
});
The ShowBooksList
function of the controller will fetch the books from the server, create a BooksCollectionView
, and then render it on the screen using the show
method (which I will explain in a moment). ShowAddBookView
simply creates the AddBookView
and renders it on the screen. Other functions like AddBook
, DeleteBook
, and UpdateBook
are to perform CRUD operations on the book
entity.
So now we have our model classes that represent the business entities and know how to perform CRUD operations on them. We have our view templates that specify the HTML/CSS part and the View classes that take the models and renders them using templates. These Views also act as observers and listen for model changes to update the view and listen for UI events to act upon models. Finally, we have the controller class that provides the interaction between the views and models. Now let us see how all this fits in the big picture and how we can create the complete application.
The Marionette Application Model
Marionette gives us the possibility of creating a central Application
object. So let us go ahead and create the Marionette Application
object first.
var sampleApp = new Backbone.Marionette.Application();
Then we need to specify the major regions of our application. Our application will only have two regions, e.g., one to add a new book and the other to show the list of books.
sampleApp.addRegions({
listRegion: "#listRegion",
addRegion: "#addRegion"
});
These regions are just div
s in main.html. Now let's create the controller
object, pass these regions in the respective methods so that the controller
can render them after fetching the data from the server.
var booksController = new BooksController();
booksController.ShowBooksList({ region: sampleApp.listRegion });
booksController.ShowAddBookView({ region: sampleApp.addRegion });
Once this is done, we will see the list of books on the screen.
Now the only thing left is to wire up the UI event from the View
classes to the Controller
class functions. So let us wire up the view events and call the controller
functions. Once done, the view
classes will look like:
var BookView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: "#bookView",
initialize: function() {
this.listenTo(this.model, "change", this.render);
},
events: {
'click #btnDeleteBook': "deleteBook",
'click #btnEditBook': "editBook"
},
deleteBook: function() {
if(confirm("Are you sure you want to delete this book")) {
sampleApp.trigger("bookDelete", this.model);
}
},
editBook: function() {
var newName = prompt("Enter new name for the book");
if(newName) {
this.model.set("BookName", newName);
sampleApp.trigger("bookEdit", this.model);
}
}
});
var BooksCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: BookView,
tagName: 'table',
});
var AddBookView = Backbone.Marionette.ItemView.extend({
template: "#addBookView",
events: {
'click #btnAddBook': "addBook"
},
addBook: function () {
var bookName = $('#txtBookName').val();
var book = new Book({ BookName: bookName });
sampleApp.trigger("bookAdd", book);
}
});
The model
classes will now look like:
var Book = Backbone.Model.extend({
defaults: {
ID: "",
BookName: ""
},
urlRoot: 'http://localhost:51377/api/Books'
});
var BooksCollection = Backbone.Collection.extend({
model: Book,
url: 'http://localhost:51377/api/Books'
});
And finally, the controller
class that is orchestrating the show looks like:
var BooksController = Backbone.Marionette.Controller.extend({
initialize: function (options) {
var self = this;
sampleApp.on("bookAdd", function (book) {
self.AddBook(book);
});
sampleApp.on("bookDelete", function (book) {
self.DeleteBook(book);
});
sampleApp.on("bookEdit", function (book) {
self.UpdateBook(book);
});
},
ShowBooksList: function (options) {
this.collection = new BooksCollection();
var self = this;
this.collection.fetch({
success: function (books) {
var booksView = new BooksCollectionView({ collection: self.collection });
options.region.show(booksView);
}
});
},
ShowAddBookView: function (options) {
var addBookView = new AddBookView();
options.region.show(addBookView);
},
AddBook: function (book) {
var BookToSave = book;
var self = this;
BookToSave.save({}, {
success: function (model, respose, options) {
console.log("The model has been saved to the server");
self.collection.push(model);
},
error: function (model, xhr, options) {
console.log("Something went wrong while saving the model");
}
});
},
DeleteBook: function (book) {
var BookToDelete = book;
var self = this;
BookToDelete.id = BookToDelete.get("ID");
BookToDelete.destroy({
success: function (model, respose, options) {
console.log("The model has deleted the server");
self.collection.remove(model);
},
error: function (model, xhr, options) {
console.log("Something went wrong while deleting the model");
}
});
},
UpdateBook: function (book) {
var BookToUpdate = book;
var self = this;
BookToUpdate.id = BookToUpdate.get("ID");
BookToUpdate.save({}, {
success: function (model, respose, options) {
console.log("The model has been updated to the server");
self.collection.push(model, { merge: true });
},
error: function (model, xhr, options) {
console.log("Something went wrong while updating the model");
}
});
}
});
Now, we have a rudimentary backbone application ready with all the CRUD operations on the Book
entity. To run this application, the WebAPI project should be downloaded and ran first and then the backbone sample application should be ran. In case WebAPI runs on some other port, the URLs in the model classes need to be updated before running the application.
Point of Interest
In this article, I have talked about how to create JavaScript single page applications using the Backbone Marionette framework. This article was intended for beginner programmers who have just started with single page applications and Backbone Marionette. The article was more inclined towards the Marionette framework because I personally like marionette a lot and a backbone together with marionette is a complete package to create single page applications.
I recommend looking at and debugging the sample application as this is the only way we can understand the application structure completely. I hope this has been informative.
History
- 18th December, 2013: First version