Building Modular Web Applications with Backbone.js and RequireJS — Sample App

directory

In recent months, I have been sharing different versions of the Employee Directory sample application built with different technology stacks, different frameworks, and different back-end (REST services) implementations.

A number of you have asked for a modular version of the application built using RequireJS. So here it is.

RequireJS

If you are already familiar with RequireJS and just want to dive into the Backbone.js/RequireJS sample, you can safely skip this section.

A complete discussion of the benefits of Asynchronous Module Definition (AMD) and RequireJS is beyond the scope of this article, but a quick look at index.html in a recent version of the employee directory app highlights one of the problems when you don’t use a script loader like RequireJS:

<script src="lib/jquery-1.9.1.min.js"></script>
<script src="lib/underscore-min.js"></script>
<script src="lib/backbone-min.js"></script>
<script src="bootstrap/js/bootstrap.js"></script>
<script src="js/app.js"></script>
<script src="js/views/shell.js"></script>
<script src="js/views/home.js"></script>
<script src="js/views/contact.js"></script>
<script src="js/views/employeelist.js"></script>
<script src="js/views/employeedetails.js"></script>
<script src="js/models/model-in-memory.js"></script>

While small, the Employee Directory sample application imports eleven scripts. Imagine what a large application would look like. Of course, you can (and should) use a build process to concatenate and minify these scripts before deploying your application. But that doesn’t solve all the problems: In what order should these scripts be loaded to make sure all dependencies can be resolved? If this application is built by a team of developers, what’s the process for managing the list of dependencies between scripts/modules and make sure they can still be resolved as new modules are added by members of the team? How do you write code that actually minimizes hard dependencies between modules?

RequireJS is a script and module loader that addresses these issues:

  • When you run the application during development, it will load modules from separate files as needed and automatically resolve dependencies. Before deploying the application, you can run the RequireJS optimizer to combine scripts and minify them using UglifyJS (or the Closure Compiler).
  • It provides an implementation of the module pattern.
  • It manages dependencies between modules, ensuring a module is loaded before it is used by other modules.
  • It allows you to centrally manage dependency mappings (using a map config) so you can easily swap the version of a module that is injected in other modules. For example, you could choose to inject a mock dataprovider for testing, and later replace it with the actual JSON dataprovider for deployment. See the “Map Config” section below for an example.

There are other ways to achieve these goals. A discussion of the pros and cons of AMD compared to other options is beyond the scope of this article. The goal of this post is simply to share a practical example of a modular application built with Backbone.js and RequireJS.

The Sample Application

Click here to run the Backbone.js/RequireJS version of the application.

Below are a few simplified examples of the modularized Backbone.js components used in the application. Check out the source code of the application on GitHub for the complete implementation.

Modularized Model

define(function (require) {

    "use strict";

    var $           = require('jquery'),
        Backbone    = require('backbone'),

        Employee = Backbone.Model.extend({

            urlRoot: "http://localhost:3000/employees",

            initialize: function () {
                this.reports = new EmployeeCollection();
                this.reports.url = this.urlRoot + "/" + this.id + "/reports";
            }

        }),

        EmployeeCollection = Backbone.Collection.extend({

            model: Employee,

            url: "http://localhost:3000/employees"

        });

    return {
        Employee: Employee,
        EmployeeCollection: EmployeeCollection
    };

});
This application uses the syntactic sugar described here. This version of the define() function is less error-prone, and easier to read and to manage.

Modularized View

define(function (require) {

    "use strict";

    var $           = require('jquery'),
        _           = require('underscore'),
        Backbone    = require('backbone'),
        tpl         = require('text!tpl/Employee.html'),

        template = _.template(tpl);

    return Backbone.View.extend({

        render: function () {
            this.$el.html(template());
            return this;
        }

    });

});

Modularized Router

define(function (require) {

    "use strict";

    var $           = require('jquery'),
        Backbone    = require('backbone'),
        
        $content = $("#content");

    return Backbone.Router.extend({

        routes: {
            "":                 "home",
            "employees/:id":    "employee"
        },

        home: function () {
            require(["app/views/Home"], function (HomeView) {
                var view = new HomeView({el: $content});
                view.render();
            });
        },

        employee: function (id) {
            require(["app/views/Employee", "app/models"], function (EmployeeView, models) {
                var employee = new models.Employee({id: id});
                employee.fetch({
                    success: function (data) {
                        var view = new EmployeeView({model: data, el: $content});
                        view.render();
                    }
                });
            });
        }

    });

});

Loading Templates with the Text Plugin

It is a lot easier to author your HTML templates in separate files. The RequireJS text plugin makes it easy to load these templates as dependencies.

var tpl = require('text!tpl/Employee.html');

Map Config

RequireJS allows you to centrally manage dependency mappings (using a map config) so you can easily replace the version of a module that is injected in other modules. In the map configuration below, I mapped the ‘app/models/employee’ dependency to the ‘app/models/memory/employee’ module which is a simple in-memory data provider. You can easily replace the in-memory data with actual JSON services throughout the application by replacing ‘app/models/memory/employee’ with ‘app/models/json/employee’ in the mapping below defined in app.js.

require.config({

    baseUrl: 'js/lib',

    paths: {
        app: '../app',
        tpl: '../tpl'
    },

    map: {
        '*': {
            'app/models/employee': 'app/models/memory/employee'
        }
    },

    shim: {
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        }
    }
});

Source Code

The source code for this application is available in this GitHub repository.

Because the modules of this application are loaded using XMLHTTPRequest, you will get a cross domain error (Access-Control-Allow-Origin) if you load index.html from the file system (with the file:// protocol). Make sure you load the application from a web server. For example: http://localhost/directory-backbone-bootstrap-require.

If you want to go beyond the in-memory datastore, you can download the REST services in the following repositories:

directory-rest-nodejs (Node.js/MongoDB implementation)
directory-rest-php (PHP implementation)
directory-rest-java (Java implementation coming soon)

  • Pingback: Building Modular Mobile / PhoneGap Apps with Backbone.js, RequireJS and Topcoat | Christophe Coenraets()

  • Roland

    Great! Thank you so much for that gem tutorial!

  • Aaron

    Thanks for the post – i’ve had trouble with slowness at startup when trying to use require with phonegap but this example is quite snappy.

    One question – the navigation between the employee list and detail screens is done with regular anchor tags. After integrating into phonegap, I’m noticing that there is a short lag after tapping one of these elements and the page transitioning – presumably due to “click” vs “touch” on the phone. What’s the best way to make that more responsive?

  • Noah

    Thanks for this tutorial Christophe. Just a query. How would I call RESTful APIs in this modular structure? Would I use .ajax ?

    • Since this is a Backbone.js application, calls to the RESTful API will be handled automatically by the models. In general, the fact that an application is modular doesn’t change the way you call your RESTful services.

      • Viswanathan

        please update restful web service using java and also db insert query.

  • Helga B

    Thank you for this informative sample.

    • Viswanathan

      I want run this app using android+phonegap with localhost java base restful service can you please update the project with all this. really your are doing great job.

      Thanks a Lot :-)

  • Thank you for this informative sample

  • “RequireJS allows you to centrally manage dependency mappings (using a map config) so you can easily replace the version of a module that is injected in other modules.” This is a great possibility but I have a question. I’m working on web application development servicesand would like to know, the map fits under all of these projects?

  • hi

    is there anyway you could make an example combining the google map. I have a difficulty to load it with backbone

  • Thanks for your helpful information.

  • Hi chris

    i try to follow your example by combine the google map. I dont really understand / familiar with your programming style. Hope you can enlighten my by form it on my github

    https://github.com/muhaimincs/gmap_backbone

    thanks chris

  • Jason

    Hi Chris,

    Thanks for the wonderful tutorial. I’m facing a problem here running the programme that I hope you can help me with. I’m using your Node.js/MongoDB implementation to run the app from localhost:3000, but I ended up with an empty homepage i.e. the is not rendered. If I request for localhost:3000/employees/:id for example, I get the JSON object of that employee, so I believe that the server is able to retrieve the data from the database.

    I just can’t seem to get why I have an empty … It seems that there must be something wrong with the Backbone views rendering…

    Would appreciate any help!

    • Jason

      Sorry, I forgot to use the tag. I wanted to say I get an empty “body”

      • Longchi

        Hi Jason,

        Did you ever get a response to this answer? I am having the same problem.

        Thanks

  • Having read this I believed it was very enlightening.
    I appreciate you taking the time and energy to put this short article together.
    I once again find myself spending a significant amount of time both reading and leaving comments.
    But so what, it was still worth it!

  • Razvan

    Hi! I noticed that if I add a button in the employee template:
    Save
    And in the view I add:
    events : {
    “click #save” : “saveInfo”
    },

    and
    saveInfo: function() {
    console.log(“enter”);
    }

    If I walk a bit through the app, everytime I enter employee page, and press the button, the number of prints increases:
    The first time I enter the page I click save -> enter
    After this, I go to home page, I go back to employee, hit the button save -> enter\n enter
    and so on

  • Todd

    The directory-rest-php library doesn’t seem to work with this example. If i remove the reports section from the employee.html tpl file then it works … seems the php service isn’t returning reports correctly (what is this reportscount as opposed to just reports being returned by the memory service?).

    Removing the code from the tpl below makes the rest of the app function… how do i get the reports in there?

    0) { %>
    <a href="#employees//reports”>View Direct Reports

  • Pingback: Sample Mobile Application with AngularJS | Christophe Coenraets()

  • Max

    Nice article!!

    But, first of all i work on linux maschines so i had to rename all files spelled bad, because my webserver can not find them and require.js can not resolve the dependencys.

  • Any thoughts on implementing this in SharePoint 2013 Foundation?

    We are in need of an Employee Directory for our Intranet and I have yet to see one as good-looking or complete as yours so I want to try to build this on the SharePoint platform. Since SP2013 is all about using JavaScript rather than XSLT, I think it is possible.

    I am just wondering if you would suggest a specific framework (you’ve done this in Angular and BackBone, at least, maybe more) or if you have any other thoughts that may help me.

  • I have a new solution for routing AMD modules.

    RequireJS Router https://github.com/erikringsmuth/requirejs-router

    This takes the approach of lazy loading AMD modules as you navigate to each page. With the Backbone router you need to require all of your views as dependencies up front. This loads all of your apps Javascript on the first page load. The RequireJS Router lazy loads modules as you navigate to each route.

    Example main.js used to run your app


    define([], function() {
    'use strict';

    // Configure require.js paths and shims
    require.config({
    paths: {
    'text': 'bower_components/requirejs-text/text',
    'router': 'bower_components/requirejs-router/router'
    }
    });

    // Load the router and your layout
    require(['router', 'js/layout/layoutView'], function(router, LayoutView) {
    var layoutView = new LayoutView();
    // The layout's render method should draw the header, footer, and an empty main-content section
    // then load the content section.
    // render: function() {
    // this.$el.html(this.template({model: this.model}));
    // router.loadCurrentRoute();
    // }

    // Configure the router
    router
    .registerRoutes({
    home: {path: '/', moduleId: 'home/homeView'},
    order: {path: '/order', moduleId: 'order/orderView'},
    notFound: {path: '*', moduleId: 'notFound/notFoundView'}
    })
    .on('statechange', function() {
    // Render the layout before loading the current route's module
    layoutView.render.call(layoutView);
    })
    .on('routeload', function(module, routeArguments) {
    // Attach the content view to the layoutView's main-content section
    layoutView.$('#main-content').replaceWith(new module(routeArguments).render().el);
    })
    .init({
    // We're manually calling loadCurrentRoute() from layoutView.render()
    loadCurrentRouteOnStateChange: false
    });
    );
    );

  • It was not global over the entire earth, but it certainly was global if
    you were in Noahs shoes. That’s because the Bible is still the “Greatest Book Ever Written and the Greatest Story Ever Told. If we do this, more trees will be spared and less toxic greenhouse gases will be emitted into the atmosphere.

  • Heya! I understand this is sort of off-topic however I needed to
    ask. Does operating a well-established website such as yours require a lot
    of work? I am brand new to operating a blog however I do write in my diary on a daily
    basis. I’d like to start a blog so I can easily share my own experience and feelings online.

    Please let me know if you have any kind of suggestions or tips
    for new aspiring bloggers. Thankyou!

  • Sivanthi

    Thanks for nice tutorial. continue your writing………..

  • Ahaa, its good dialogue concerning this piece of writing here at this blog, I have
    read all that, so at this time me also commenting at
    this place.

  • My partner and I stumbled over here coming from a different web page
    and thought I should check things out. I like what I see
    so now i am following you. Look forward to looking
    over your web page repeatedly.

  • The actually ideal method to uncover qualified and correctly educated professionals
    to your cooking area storage organization is by posting adverts
    on-line collectively with by the use of other media
    to get outstanding ultimate final results.
    No, the measurements are taken by the experts who visit your home after
    you place a request over the phone. This is important because you never know what may go wrong
    with the modular components in the future.

  • Helpful info. Fortunate me I discovered your website accidentally, and I am
    surprised why this twist of fate didn’t took place
    earlier! I bookmarked it.

  • I am extremely inspired with your writing abilities and also with the layout on your
    weblog. Is this a paid subject or did you customize it yourself?
    Either way stay up the excellent high quality writing,
    it’s uncommon to see a great weblog like this one
    today..

  • Today, World of Warcraft has reserved a special place in the hearts of MMO lovers.
    However, there are also some downsides to linking up with a large blogging site.
    When games are classified as MMO, massive comes into effect when forty or fifty
    people are in one room fighting for the glory of their team- multiplied by
    hundreds of other rooms can equal thousands of challengers competing at the same time.

  • Thank you, I’ve just been looking for infoormation about this
    subject for a long time and yours is the bedst I’vecame upon so far.
    However, whatt about the bottom line? Aree you positive in regards to the source?

  • A number of tools come as bonus with the application. Yesterday,
    along with the second DA letter, I got a mysterious letter that read that I would receive an Android Touchpad Tablet Computer from
    their “gifting department”. Advantages for hiring
    talented Android application developer or programmer.

  • Hello! Thiis post could not be written any better! Reading through this post reminds me of myy
    good old room mate! He alpways kept talking about this. I will forward
    this article to him. Fairly certain he will have a good read.

    Thanks for sharing!

  • What i do not understood is in fact how you are not really
    a lot more neatly-liked than you might be right now.

    You’re very intelligent. You already know therefore significantly relating to this topic, produced
    me in my view imagine it from so many various angles. Its like women and men are not involved except
    it’s something to do with Woman gaga! Your personal stuffs
    excellent. Always care for it up!

  • Hello, every time i used to check blog posts here in the early hours in the morning,
    as i like to gain knowledge of more and more.

  • Hi! This post couldn’t be written any better! Reading this post reminds me of my good old room mate!
    He always kept chatting about this. I will forward this write-up to him.
    Fairly certain he will have a good read. Thank you for sharing!

  • Simply wish to say your article is as amazing.
    The clarity in your post is simply nice and i can assume you’re an expert on this subject.
    Well with your permission let me to grab your RSS
    feed to keep updated with forthcoming post. Thanks a million and please keep
    up the rewarding work.

  • robert

    Affordable EO can be resolved by various queries such as image look for, web look for, company look for and video look for. Natural SEO is turned out to be very useful for the large sites like e-commerce web page. important site

  • see post

    You’ll dramatically shift the advantage when you get all of these components cooperating. Ignore them, and you’ll identify yourself frustrated without any online traffic. see post

  • Robert

    You can then choose which content and video clips will actually be released on the weblog and which will be eliminated. serruriers

  • thanks. for sharing..

  • goood thanks admins

  • Thanks you admin coenraets Ka-ran-lik – Dead ASDa

  • glut

    Hi,

    thank you for your great post but something going wrong with the RequireJS version.

    If I add a dismissable alert to the Home.html template then it’s look okay BUT I can not close it. As far I see, the bootstrap javascript is not loaded.

    How can I fix that? Thank you,

    Peer

    The added template:

    ×

    Success! Well done its submitted.

  • Although an old post, yet found it very helpful as a newbie, Thanks for sharing. You nailed it to the cross.

  • Ewen

    Best Approach ever! It allows to require only the necessary modules for each routing path.

    Thank you so much! I’m working in a huge project and this solved me a lot of headaches!

    Thanks again.

  • HardWorker1

    First, it`s a great article! Thanks a lot for your detail tutorial!

    Today I`ve read one informative and laconic article, let me recommend it too.

  • kalite,fiyat uygunlugu ve imalattan.profesyonel ekip eşliğinden güvenilir işler yapılmaktadır……….

  • Pingback: Building Modular Web Applications with Backbone.js and RequireJS — Sample App | Pankaj Salunkhe SharePoint Knowledge()

  • Thanks for the sample app. I will implement that in to the app i am making currently. If require any help, i will contact you.

css.php