Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

CRUD Operation using Backbone.js

4.80/5 (4 votes)
3 Jul 2014CPOL11 min read 66.8K   743  
Learning backbone.js using basic CRUD operations

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:

  1. Create a new ASP.NET MVC project (e.g., UserManagement).
  2. Select Internet as your template.
  3. Backbone.js is not present by default. So we need to right click on References and select Manage Nuget Packages and search for Backbone.
  4. Install Backbone.js from the list of Packages.
  5. 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:

C#
public static void RegisterBundles(BundleCollection bundles)
{
    // existing code

    bundles.Add(new ScriptBundle("~/bundles/backbone").Include(
                "~/Scripts/underscore.js",
                "~/Scripts/backbone.js"));
    // existing code
}

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.

C#
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

C#
var Router = Backbone.Router.extend({
    routes: {
        '': 'home',//Default route, when no hash tag is available
        'create': 'create',// Create User, when user clicks on Create button
        '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

C#
var route = new Router();

// When hash tag has localhost# register the below route
route.on('route:home', function () {

    // Todo: code to render create page
    console.log("Display Create Page");
});

// When hash tag has localhost#create register the below route
route.on('route:create', function () {

    // Todo: code to render create page
    console.log("Display Create Page");
});

//When hash tag has localhost#edit/1 register the below route
route.on('route:edit', function () {

    // Todo: code to render edit page and render user details
    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.

C#
var userModel = Backbone.Model.extend({
        defaults: {
            Id: null,
            FirstName: null,
            LastName: null,
            Age: null,
            Email: null,
            Phone: null
        },
        initialize: function () {
           // Do stuff's which you want at the time of model creation
        }
    });

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!!

C#
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:

HTML
@{
    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.

C#
var listOfUsersView = Backbone.View.extend({
    el: '.user-management',// The element we defined in HTML
    render: function () {
        var self = this;// Saving the scope object
        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:

C#
_.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:

JavaScript
<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.

C#
<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:

C#
// When hash tag has localhost# register the below route
route.on('route:home', function () {
        
    // Todo: code to render create page
    console.log("Display Create Page");
});

Now, what we need to do is call our view's render method.

C#
// When hash tag has localhost# register the below route
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.

C#
var specificUserView = Backbone.View.extend({
        el: '.user-management',// The element we defined in HTML
        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.

HTML
<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.

C#
var specificUserView = Backbone.View.extend({
     el: '.user-management',// The element we defined in HTML
     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.

C#
// When hash tag has localhost#create register the below route
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:

HTML
<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.:

JavaScript
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:

C#
specificUserView 

as below:

JavaScript
var specificUserView = Backbone.View.extend({
    el: '.user-management',// The element we defined in HTML
    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:

C#
saveUserDetails: function () {

    // Create a user model to fill the form details
    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 });// Navigate back to listing page
        }
    });

}

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

C#
//When hash tag has localhost# register the below route
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 :

C#
render: function (userId) {
        var userDetails=null;
        if (userId) {
            var self = this;
             
            // User update. We need to fetch user details from server and
            // render the template.
            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
        {
            // User is created
            var _userDetailTemplate = _.template($('#user-detail-template').html(),
                { user: null });
            this.$el.html(_userDetailTemplate);
        }

    }

Similarly, we bind our HTML with the model data.

HTML
<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:

C#
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.:

C#
route.navigate('', { trigger: true });

Secondly, one important thing would be destroying the View:

C#
kill: function() {

    // COMPLETELY UNBIND THE VIEW
    this.undelegateEvents();

    this.$el.removeData().unbind(); 

    // Remove view from DOM
    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)