Introduction
In this post, we will be learning backbone.js for basic create, read, update and delete operations. We will create a basic application which would help us understand how to code using backbone.js.
What is Backbone.js
Whenever we are working with JavaScript application, we tend to put huge effort in structuring the code and putting the right peice at the right place. Which actually may or may not be very maintainable or scalable. From the backbone official site:
It's all too easy to create JavaScript applications that ends up as tangled piles of jQuery selectors and callbacks, all trying frantically to keep data in sync between the HTML UI, your JavaScript logic, and the database on your server. For rich client-side applications, a more structured approach is often helpful.
Now there comes Backbone.js. It is a very light framework which allows you to structure your application in MV* fashion. MV stands for the basic Model and Views what we have in general terms. But what's important is *. Frankly, I am not the developer behind Backbone so it's hard to assume that it fits closely towards MVC framework. I assume it to be Controller that in this case allows you to save the state of your JavaScript application via a hashtag URL. More details from Stackoverflow.
Example
The very first example which strikes me is Gmail where huge chunks of data are being downloaded when you are logging in. Then things remains sweet and simple and the background processes take charge. There is never a whole page refresh that takes place. Awesome!
Note
I will be ignoring the backed code as of now. Assuming we have ready API for Create/Read/Update/Delete.
In this tutorial, I have used ASP.NET MVC API for the CRUD operations and toastr for notifications. I am a huge fan of this lib, it's just awesome.
What We Will Do
We will try to create an application that will allow creation of user, displaying list of users, editing specific user and deleting specific user.
Setting Up the Environment
Let's start:
- Create a new ASP.NET MVC project (e.g.,
UserManagement
). - Select Internet as your template.
- Backbone.js is not present by default. So we need to right click on References and select Manage Nuget Packages and search for Backbone.
- Install Backbone.js from the list of Packages.
- Go to BundleConfig.cs and add a new bundle and reference it in _Layout.cshtml or you could side line it and add your backbone.js in an existing bundle which is being referenced in _layout.cshtml.
When you add a new bundle:
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/backbone").Include(
"~/Scripts/underscore.js",
"~/Scripts/backbone.js"));
}
Run your application to check if it throws any error in console.
Setting Up the User Model (Class)
Once you are done with the above step, let's start with creating a new model class for our User Management application.
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
Assuming the above class, we will proceed with client side scripting. You can create your CRUD APIs.
Displaying List of Users
Creating Route
var Router = Backbone.Router.extend({
routes: {
'': 'home',
'create': 'create',
'edit/:id': 'edit'
}
});
Now, we are creating routes for Listing/Edit/Create of a User. When our URL is:
http://localhost/#
We will be displaying list of users.
http://localhost/#create
We will be displaying user details screen which will be used for creating a user, i.e., we need to display Create page for user.
http://localhost/#edit
We will be displaying user details screen with populated data which will be used for editing/updating a user.
Defining the Routes
var route = new Router();
route.on('route:home', function () {
console.log("Display Create Page");
});
route.on('route:create', function () {
console.log("Display Create Page");
});
route.on('route:edit', function () {
console.log("Display Edit Page");
});
Now, we define the routes as above, what it will do is, when the hashtag changes in the URL, the corresponding route definition will get triggered. We will come back to this section very soon.
Run your application to check if it throws any error in console. Change the url to /#
or /#create
or /#edit/1
, you should see corresponding console statements being printed.
Creating Model
What the officials say:
Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control. You extend Backbone.Model
with your domain-specific methods, and Model provides a basic set of functionality for managing changes.
So without wasting much time, let's create a User Model for our Backbone
.
var userModel = Backbone.Model.extend({
defaults: {
Id: null,
FirstName: null,
LastName: null,
Age: null,
Email: null,
Phone: null
},
initialize: function () {
}
});
Creating Collection
What the officials say:
Collections are ordered sets of models. You can bind "change" events to be notified when any model in the collection has been modified, listen for "add" and "remove" events, fetch the collection from the server, and use a full suite of Underscore.js methods.
Any event that is triggered on a model in a collection will also be triggered on the collection directly, for convenience. This allows you to listen for changes to specific attributes in any model in a collection, for example: documents.on("change:selected", ...)
.
Backbone
collection are collection of models, no doubt it has much more in it. It has more or less than 28 Underscore methods which support it and allows you keep a watch on them and do necessary operations when something gets modified or changed. Along with that, it supports RESTful APIs. Awesome!!
var listOfUsers = Backbone.Collection.extend({
model: userModel,
url: '/api/User'
});
Simple and sweet code. We provide our previously created model and default API which is ready to return List<User>
. You could even try and hardcode your data in case you are feeling lazy to create a backend. All is fine.
Creating View
What the officials say:
Backbone views are almost more convention than they are code — they don't determine anything about your HTML or CSS for you, and can be used with any JavaScript templating library. The general idea is to organize your interface into logical views, backed by models, each of which can be updated independently when the model changes, without having to redraw the page. Instead of digging into a JSON object, looking up an element in the DOM, and updating the HTML by hand, you can bind your view's render function to the model's "change" event — and now everywhere that model data is displayed in the UI, it is always immediately up to date.
Sound's good and informative. More details.
Before we jump for creting a view, let's first decide our el
element. The "el
" property references the DOM object created in the browser. Every Backbone.js view has an "el
" property, and if it not defined, Backbone.js will construct its own, which is an empty div
element.
Go to the page where you want to render your user listing page and add a div
tag specifying the class name or id. In my case, I have removed the Home/Index.cshtml code and added the below line:
@{
ViewBag.Title = "Home Page";
}
<div class="user-management"></div>
We are done setting up the element. Let's come back to creating a view which will render the data.
var listOfUsersView = Backbone.View.extend({
el: '.user-management',
render: function () {
var self = this;
var _userList = new listOfUsers();
_userList.fetch({
success: function (data) {
var _userTemplate = _.template($('#user-list-template').html(),
{ users: data.models });
self.$el.html(_userTemplate);
}
});
}
});
What I am doing is, I have created a Backbone View having an 'el
' property which would hold the reference to the DOM object created. Secondly, the "render()
" function will load our template into the view's "el
" property using jQuery. This method is responsible for fetching the data and creating a template with the data fetched from server (render
method should not have the logic for fetching data from server, we have ways to refactor it, as of now we can live with that). Now the question comes what is:
_.template($('#user-list-template').html(), { users: data.models });
This is Backbone.js which is dependent on Underscore.js, which includes its own templating solution. Compiles JavaScript templates into functions that can be evaluated for rendering. Useful for rendering complicated bits of HTML from JSON data sources. Read more about underscore templates.
Creating Template
Now creating a template would be straight forward, if you have worked with Knockout or Angular, then you would be able to relate it well. In case you have not... No worries... Have a look at the template:
<script type="text/template" id="user-list-template">
<h3>Following are the user's:</h3>
<form class="list-group-form">
<a href="/#create" class="btn btn-default ">Create User</a>
<table class="table striped">
<thead>
<tr>
<th></th>
<th>Id</th>
<th>FirstName</th>
<th>LastName</th>
<th>Age</th>
<th>Actions</th>
</tr>
</thead>
<% _.each(users,function(user){%>
<tr>
<td>
<input type="checkbox" /></td>
<td><%= user.get('Id')%></td>
<td><%= user.get('FirstName')%></td>
<td><%= user.get('LastName')%></td>
<td><%= user.get('Age')%></td>
<td>
<a href="/#edit/<%= user.get('Id')%>"
class="btn btn-primary">Edit</a></td>
</tr>
<%});%>
</table>
</form>
</script>
The underscore template is a <script>
tag with type as "text/template
", which actually prevents the browser from rendering it at first shot. It is not a script that the browser can understand, and so the browser will simply ignore it. You can put anything here which can be used as a template. You can use many other libraries like Handlebar
/Mustache
, etc.
<script type="text/template" id="user-list-template">
</script>
We are almost set with creating a User Model, then creating a User Collection of it. Then we have created a view which would create an object of User Collection and fire a fetch request. When the API returns the data, we are getting our template and binding the data to it. Finally, display it.
Lastly, we need to call our render
method from the /#
route(our default route). So let's jump back a little where we had:
route.on('route:home', function () {
console.log("Display Create Page");
});
Now, what we need to do is call our view's render
method.
route.on('route:home', function () {
var objList = new listOfUsersView();
objList.render();
});
Run your application to check if it throws any error in console. If all is fine, then you should be able to see the list of users from the database.
Note: In case you are not, then open the network tab and check the response you are getting from the API. Check the fields which the API is returning and match the properties with what is defined in the template.
Creation of Users
Now, when we are all set to display list of users, then let's actually create a user and update the database. Come back to the listing application and check whether user got created or now.
When you click on the link Create User, you would see the /#create
getting appended to the URL and console would give you back "Display Create Page". So we need to start writing our code there. Before that, we need a Create View which would be responsible for displaying our user model.
Creating the Template and Basic View
Let's start with creating our user view. We would be using the same view for Create
and Update
.
var specificUserView = Backbone.View.extend({
el: '.user-management',
render: function() {
}
});
Okay. Now what!!
Let's not jump straight away to form fields , what I would love to do is create one more template with simple text written. We would display that when user clicks on Create User hyperlink.
<script type="text/template" id="user-detail-template">
<h3>User Details:</h3>
@* Todo: Add the form tag and more *@
</script>
I would love to render the template and see what happens when I click on Create User Hyperlink.
var specificUserView = Backbone.View.extend({
el: '.user-management',
render: function() {
var _userDetailTemplate = _.template($('#user-detail-template').html());
this.$el.html(_userDetailTemplate);
}
});
Now, over here, I am simply rendering the template and not doing anything else. Call this view when the url changes to /#Create
. So for that, we go back to the place where we have defined our create route and it's time we call the render
method of specificUserView
.
route.on('route:create', function () {
var _objUser = new specificUserView();
_objUser.render();
});
Run your application to check if it throws any error in console. If all is fine, then you should be able to see our text being printed. Let's add our form elements ready and create our form which would accept the required properties. When we add our input fields, the form would look quite similar to:
<script type="text/template" id="user-detail-template">
<h3>User Details</h3>
<table>
<tr>
<td>First Name</td>
<td>:</td>
<td>
<input type="text" class="first-name" /></td>
</tr>
<tr>
<td>Last Name</td>
<td>:</td>
<td>
<input type="text" class="last-name" /></td>
</tr>
<tr>
<td>Age</td>
<td>:</td>
<td>
<input type="text" class="age" /></td>
</tr>
</table>
<input type="button" value="Save" id="saveUserDetails"/>
</script>
Creating Save Event
Every Backbone View allows us to define events. For e.g.:
var DocumentView = Backbone.View.extend({
events: {
"dblclick": "open",
"click .icon.doc": "select"
}
});
delegateEvents takes the events: { ... }
declaration for your view instance, and binds the specified events to the specified DOM elements, with the specified callback methods to handle the events.
We need to have a submit button click. So we create a event for this in our:
specificUserView
as below:
var specificUserView = Backbone.View.extend({
el: '.user-management',
events: {
'click #saveUserDetails': 'saveUserDetails'
},
render: function() {
var _userDetailTemplate = _.template($('#user-detail-template').html());
this.$el.html(_userDetailTemplate);
},
saveUserDetails: function() {
console.log("Save user details");
}
});
Run your application to check if it throws any error in console. Click on the Save button to check if the console message is reflecting in F12 console window.
Now, we need work on saveUserDetails
method. This method is responsible for creating a model
object with current filled properties and saving the details to the server.
I am trying to keep it simple as:
saveUserDetails: function () {
var model = new userModel({
id: null,
FirstName: $('.first-name').val(),
LastName: $('.last-name').val(),
Age: $('.age').val()
});
model.save({}, {
success: function () {
console.log('Data Saved');
route.navigate('', { trigger: true });
}
});
}
Editing a User
Now, here comes the maximum challenge. We would be using our create form to display the edit information also. For that, we need to make some changes to the code.
Routing
route.on('route:edit', function (userId) {
var _objUserEdit = new specificUserView();
_objUserEdit.render(userId);
});
Our routing would accept Id
as parameter and it will be passed to the render
method.
View & Template
Now, we need to change our view to accept our User Id and fetch data with respect to the User Id. Also, we need to update our view which would display value when provided. So, for that, I changed my specific user View var
specificUserView
as :
render: function (userId) {
var userDetails=null;
if (userId) {
var self = this;
var _userModel = new userModel({ id: userId });
_userModel.fetch({
data: {id:userId},
success: function (data) {
var _userDetailTemplate = _.template($('#user-detail-template').html(),
{ user: data });
self.$el.html(_userDetailTemplate);
}
});
}
else
{
var _userDetailTemplate = _.template($('#user-detail-template').html(),
{ user: null });
this.$el.html(_userDetailTemplate);
}
}
Similarly, we bind our HTML with the model data.
<tr>
<td>First Name</td>
<td>:</td>
<td>
<input type="text" class="first-name"
value="<%= user ? user.get('FirstName') : '' %>"/></td>
</tr>
<tr>
<td>Last Name</td>
<td>:</td>
<td>
<input type="text" class="last-name"
value="<%= user ? user.get('LastName') : '' %>"/></td>
</tr>
<tr>
<td>Age</td>
<td>:</td>
<td>
<input type="text" class="age" value="<%= user ? user.get('Age') : '' %>"/></td>
</tr>
So, if you can see, now all the input elements are binded with the value:
user.get('FirstName')
It is not essential to keep your template file like the way it has been done above, you have alternatives like handlebar.js and more which would allow you to create template in different file. Compile it prehand or at run time. More details about Handlebars.
Areas of Refactoring
The above code is a very raw version of using Backbone.js. We would need refactoring on the code mentioned above. Like when you save a user, we should navigate back to the listing page to display list of users., e.g.:
route.navigate('', { trigger: true });
Secondly, one important thing would be destroying the View:
kill: function() {
this.undelegateEvents();
this.$el.removeData().unbind();
this.remove();
Backbone.View.prototype.remove.call(this);
}
Would try to write more on memory leak.
Feedback and Comments for Improvement
Being my first post, I heartily welcome your suggestions for improvement and mistakes. I may have lost patience composing this page, so please feel free to criticize.