Your First Backbone.js App - Service Chooser

Demo Download

Client-side MVC frameworks have gotten increasingly popular with the raise in complexity of in-browser web apps. These frameworks let you organize your JavaScript using the proven MVC pattern. Backbone.js is one of the most popular and is quickly becoming the go-to choice when considering such a framework.

Today we are going to make a service chooser form with Backbone.js, that lets you choose a set of items from a list. A total price field is going to be calculated in real-time with the aggregate price of the services.

Update: We now have a version of this form using AngularJS. See it here.

What is Backbone.js?

Backbone.js is a library that gives structure to web applications by providing models, collections and views, all hooked up together with custom events. It connects your application to your backend via a RESTful JSON interface, and can automatically fetch and save data. In this tutorial we won't be using the advanced features of the library - everything will be stored client-side. Backbone does not replace and does not depend on jQuery, but the two work together nicely.

Backbone won't magically solve your problems though - you still have to be smart in the way you organize your code, which can be an issue if you don't have prior experience with MVC frameworks. Backbone can also be an overkill for smaller applications, where a few lines of jQuery would suffice, so it would better be left for large code bases. The app that we are building here is in the first camp, but it does show the fundamental concepts behind the framework.

The HTML

We are starting off with a regular HTML5 document. I haven't added the HTML5 shim, so this might not look good in older IEs:

index.html

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8" />
        <title>Your first Backbone.js App | Tutorialzine </title>

        <!-- Google web fonts -->
        <link href="http://fonts.googleapis.com/css?family=PT+Sans:400,700" rel='stylesheet' />

        <!-- The main CSS file -->
        <link href="assets/css/style.css" rel="stylesheet" />

    </head>

    <body>

        <form id="main" method="post" action="submit.php">
            <h1>My Services</h1>

            <ul id="services">
                <!-- The services will be inserted here -->
            </ul>

            <p id="total">total: <span>$0</span></p>

            <input type="submit" id="order" value="Order" />

        </form>

        <!-- JavaScript Includes -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>

        <script src="assets/js/script.js"></script>

    </body>
</html>

The main element on the page is the form. The UL inside it will be populated with LI items for the services, and the span inside the #total paragraph will hold the price.

Before the closing </body> tag, I have included jQuery, Backbone and the Underscore library (backbone depends on its powerful utility functions). Last comes the script.js file, which you can see in the next section.

illustration.jpg
Services Chooser Form With Backbone.js

The JavaScript

Here is the overall idea of our Backbone code:

  1. First we will create a service model. It will have properties for the name of the service, the price, and checked - a status field showing whether this service has been chosen or not. An object of this class will be created for every service that we offer;
  2. Then we will create a Backbone collection that will store all the services. It will make it easier to listen for events on all objects at once. In larger apps, you would listen for when items are inserted or removed from the collection, and update the views accordingly. In our case, as the items in the collection are predetermined, we will only listen for the change event (which is raised whenever the checked property is updated).
  3. After this, we define a view for the services. Each view will be associated with a single model, and turn its properties into HTML. It listens for clicks and updates the checked property of the model.
  4. Lastly, we define a master view, which loops through all the services in the collection and creates views for them. It also listens for the change event on the collection and updates the total price.

Since I know you didn't read all of the above, here is the richly-commented source code:

assets/js/script.js

$(function(){

    // Create a model for the services
    var Service = Backbone.Model.extend({

        // Will contain three attributes.
        // These are their default values

        defaults:{
            title: 'My service',
            price: 100,
            checked: false
        },

        // Helper function for checking/unchecking a service
        toggle: function(){
            this.set('checked', !this.get('checked'));
        }
    });

    // Create a collection of services
    var ServiceList = Backbone.Collection.extend({

        // Will hold objects of the Service model
        model: Service,

        // Return an array only with the checked services
        getChecked: function(){
            return this.where({checked:true});
        }
    });

    // Prefill the collection with a number of services.
    var services = new ServiceList([
        new Service({ title: 'web development', price: 200}),
        new Service({ title: 'web design', price: 250}),
        new Service({ title: 'photography', price: 100}),
        new Service({ title: 'coffee drinking', price: 10})
        // Add more here
    ]);

    // This view turns a Service model into HTML. Will create LI elements.
    var ServiceView = Backbone.View.extend({
        tagName: 'li',

        events:{
            'click': 'toggleService'
        },

        initialize: function(){

            // Set up event listeners. The change backbone event
            // is raised when a property changes (like the checked field)

            this.listenTo(this.model, 'change', this.render);
        },

        render: function(){

            // Create the HTML

            this.$el.html('<input type="checkbox" value="1" name="' + this.model.get('title') + '" /> ' + this.model.get('title') + '<span>$' + this.model.get('price') + '</span>');
            this.$('input').prop('checked', this.model.get('checked'));

            // Returning the object is a good practice
            // that makes chaining possible
            return this;
        },

        toggleService: function(){
            this.model.toggle();
        }
    });

    // The main view of the application
    var App = Backbone.View.extend({

        // Base the view on an existing element
        el: $('#main'),

        initialize: function(){

            // Cache these selectors
            this.total = $('#total span');
            this.list = $('#services');

            // Listen for the change event on the collection.
            // This is equivalent to listening on every one of the 
            // service objects in the collection.
            this.listenTo(services, 'change', this.render);

            // Create views for every one of the services in the
            // collection and add them to the page

            services.each(function(service){

                var view = new ServiceView({ model: service });
                this.list.append(view.render().el);

            }, this);   // "this" is the context in the callback
        },

        render: function(){

            // Calculate the total order amount by agregating
            // the prices of only the checked elements

            var total = 0;

            _.each(services.getChecked(), function(elem){
                total += elem.get('price');
            });

            // Update the total price
            this.total.text('$'+total);

            return this;
        }
    });

    new App();

});

As you can see, you have to extend the classes provided by Backbone, and in the process override the methods that you want to perform differently (for the views you almost certainly want to override the render method). You can further extend them and build hierarchies of classes.

The views can either create their own HTML, as in the case of ServiceView, or be attached to existing elements. The main view of the application is App, which is bound to the #main form. It initializes the other views and updates the total price in its render method.

The PHP

I also included a line of PHP that will handle the form submissions. All it does is print the names of the selected checkbox fields:

submit.php

echo htmlspecialchars(implode(array_keys($_POST), ', '));

You are welcome to extend it with whatever functionality you need, like sending email, inserting the results in a database and more.

We're done!

With this our Backbone service chooser form is done! I hope that you find the form useful and that it gives you a good overview of the framework, so you can tell when the time is right to use it.

Resources and further reading:

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

Great stuff again! :)

Anh saker

Great! Thank Martin. I'm learning Backbone.js

This might sound like a really strange question, but why load jquery from google CDN and the rest from cdnjs, when cdnjs could load all three. Is there any reason for doing that?

Martin Angelov

You are right, that's an oversight on my part. Using cdnjs for all three would also save you a DNS lookup, so it could become a tiny bit faster.

How can we accomplish to see chosen option when we hit back button? Like history.. In my case I also need to have text fields filled with user's previous entry when back button clicked..
Thanks

Martin Angelov

There are probably better methods to do this, but you can submit the form via AJAX, which will preserve the state of the form. As for the back button, you can use history.pushstate in modern browsers or a library like jquery address.

Awesome as always. Congrats on new design, it looks very clean and professional.

Gaurav Chandra

How does Backbone compare to Angular JS? Is it better?

Martin Angelov

Backbone and Angular solve the problem differently. Backbone gives you the MVC pattern, but this means that it is suitable mostly for large applications. With Angular you don't have MVC and the code is much shorter. You can check out this guest post by Kevin P for the same form, but with AngularJS.

Nice. I didn't read it yet but i want to ask ... i am going to learn backbone in near future... and you make very good tutorials... dont you want to make a serial of tutorials with 'backbone theme' ? it would be sooo awesome... and i think veery popular as well.

Did you try with emberjs ?

Daniel Salazar

Hi,

this is awesome!!
I have one doubt, can backbone allow to populate a "quantity" field too?

Any ideas on how to accomplish this?

Regards

Martin Angelov

I can't write the code for you, but the overall idea would be to:

  • Extend the service model with one more field - quantity, with default value of 1;
  • Change the view so that it outputs a select dropdown with numbers from 1 to 10, and listens to the change event on that field. The callback should update the model with the correct quantity;
  • Change the render function of the App view, so it multiplies the quantities to the price.

This is pretty much it. The second step would take most of the work, and you will probably have to read through the docs and the example apps linked to in the "Read more" section of the tutorial.

Sedat Kumcu

Interesting. Thanks for this. good Works man.

I think the tutorial is great and well explained, but the tool is not appropriate.
You can definitely build solid applications using Backbone.js but this is so much code for a small application.

Look at the exact same application but built with AngularJS
I'm using your CSS and have a single JS include (AngularJS), no jQuery needed. My JS is just 15 lines of code.

Using the same technique I built a more complex application generating QRcodes, so AngularJS fits all application sizes.

Martin Angelov

This is a great Angular.js example. I will look into using the library in the future. I agree that in this case Backbone is a poor choice, but the tutorial was about demonstrating a simplified Backbone app, and (sadly) not to build the form with the best tool for the job.

The fiddle fails to load the demo stylesheet (hotlinking is blocked on the demo domain due to past abuse), but I updated it with the stylesheet inline for everyone who wants to check it out - http://jsfiddle.net/R4BuJ/3/

Ohio Web Design

Backbone.js is a personal favorite even though it requires more thought than Angular or knockout. You can get a very nice an responsive app built.

David Chase

Nice tutorial, but i think you could clean up a few things such as services...
Below would be the correct way to implement the collection, no need to do new Service, you already added to your collection under model:

var services = new ServiceList([
    {
        title: 'web development',
        price: 200
    },
    {
        title: 'web design',
        price: 250
    },
    {
        title: 'photography',
        price: 100
    },
    {
        title: 'coffee drinking',
        price: 10
    }
 ]);

Also you could have replaced all of that html in render with a template, such as micro template underscore provides..

Martin Angelov

Thank you for the suggestions, David!

Todd Smith

Good insight but i would have to say you can make the application simply with jQuery.. There's no need for backbone or angular in this case. If you write well javascript and just use jQuery as a helper.

Martin Angelov

Of course you can do it only with jQuery. The purpose of the tutorial was to demonstrate how Backbone.js works.

Brian Mann

I saw this backbone post, and also your angular post. Since this obvious conclusion here to new developers is that "Angular is vastly better" I decided to build your Backbone app using Backbone + Marionette for a professional take on the differences.

I put it on my github here: Backbone + Marionette - Service Chooser

Note: I built it using only 11 lines of Backbone code. That's right - ELEVEN LINES. Technically possible to reduce further but I wouldn't recommend it, in fact its already pretty unreadable. That's okay though, the fact is you are not considering that Angular is about 10x the SIZE of backbone in terms of LOC. Last check: angular is 14,760 lines, backbone is around 1500. That's not a fair fight. Angular includes functionality far reaching into UI widgets / helpers, etc. Backbone is only core material. So technically Backbone won by about 13,500 lines.

However, adding that stuff is super EASY and if I were to add a few plugins I could remove several more lines from my code and reduce each line's responsibility by a lot. At the end of the day if you inject those plugins into Backbone which do the same thing Angular has built in, then they become almost identical in terms of readability and LOC. The difference is you pick and choose what you need for your projects in Backbone, and you're writing logic in your views not plundering your templates with any logic.

I like the idea of doing an apples to apples comparison of Backbone vs Angular, so I'm going to add in those plugins (which automatically do 2-way data binding + collection selection) and will host a "duel" screencast at www.backbonerails.com in the future.

Its not all about lines of code.. reading your code is a lot harder compared to the angular one, + you have marionette and underscore wich adds another 5000 loc

Adicahyana

Another awesome stuff. nice great job Martin.
Thank's for sharing

how to show total price in php echo on submit.php ?
thx

Martin Angelov

You will have to have a copy of the services on the server side (in a regular array, in a database or in a config file) and calculate the price in PHP depending on the chosen services.

Phillipe Hensley

I disagree AngularJS is better than Backbone.js because of Lines-of-Code (LOC). I once saw a guy parse an entire domain model with one line of Regex. Next day, he couldn't remember what the Regex did.

It's a funny story, but the point is LOC is not a good measure of worth because it fails to account for maintainability. Here, Backbone.js has a clear structure and guidance of how to structure your JavaScript application because that is its biggest benefit. From there you can get more intricate and build larger apps.

The example here for AngularJS is less lines of code - that is correct. But, it's also very procedural and that's unfortunate.

Kaushik Bhat

Good tutorial. But you haven't shown the full potential of backbone. You should use routes and then show different views. Also when using a full fledged backbone app there should be no scope for a separate .php page. You are not handling your application in a completely RESTful way, for which Backbone.js is optimized to work with

Please check out: http://coenraets.org/directory/
simple to understand

allyraza

Hi Martin

Nice write up one tip though I am not sure if you find it useful in your

"App View" you are accessing the collection from the global context why not just pass it as param to the "App view" and access it from the current context.

I like the "Since I know you didn’t read all of the above..." part very much :)

Hello thanks for the tutorial! For the checkbox toggle is there a reason why you're using a method in the model instead of adding something like this in the event method in the view? this.model.save(completed: !this.model.get('completed'));

Paresh Radadiya

Great! Thank Martin. I'm learning Backbone.js and Angular.js

Good Example,thank you Martin

Sanchari Shome

Hey I just started working with backbone.js. I have a doubt how can I implement refresh on my backbone. Like I have three pages login, view1, view2. so whenever I am refreshing it goes back to login page instead reloading the same page. I checked many solutions but nothing actually worked. Can you please help me out? Thanks in advance.
Regards

José Alberto

Not very sure, but isn't there a code error at the line where you define el selector?
Yo declare el as:
el: $('#main')

and above in the code you are using el in the View declaration as:
this.$el.html('<input type="checkbox" value="1" ...

If I'm not wrong should be either:
this.$(el).html(...) as el has to be wrapped as a jQuery object OR maybe el in not well declared and you meant $el = $('#main') instead of el = $('#main')