Rapid Prototyping with JS

Rapid Prototyping with JS

Agile JavaScript Development

About


Rapid Prototyping with JS: Agile JavaScript Development is a hands-on book which introduces you to agile JavaScript web and mobile software development using the latest cutting-edge front-end and back-end technologies including:

Topics and Tutorials

HTML, CSS/LESS
Twitter Bootstrap
Javascript/jQuery
Parse.com
Node.js
Express.js, Derby
MongoDB
Git
Heroku/Windows Azure
JSON/BSON
Agile Methodologies

Over 12 Coding Examples

Backbone.js + Parse.com Node.js + MongoDB jQuery Other
Buy PDF, EPUB & MOBI $19.99

Contents


  • What Readers Say
  • Rapid Prototyping with JS on the Internet
  • Introduction
  • Why RPJS?
  • LeanPub
  • What to Expect
  • Who This Book is For
  • What This Book is Not
  • Prerequisites
  • How to Use the Book
  • Examples
  • Notation
  • Terms
  • I Quick Start
  • 1 Basics
  • 1.1 Front-End Definitions
  • 1.1.1 Bigger Picture
  • 1.1.2 HyperText Markup Language
  • 1.1.3 Cascading Style Sheets
  • 1.1.4 JavaScript
  • 1.2 Agile Methodologies
  • 1.2.1 Scrum
  • 1.2.2 Test-Driven Development
  • 1.2.3 Continuous Deployment and Integration
  • 1.2.4 Pair Programming
  • 1.3 Back-End Definitions
  • 1.3.1 Node.js
  • 1.3.2 NoSQL and MongoDB
  • 1.3.3 Cloud Computing
  • 1.3.4 HTTP Requests and Responses
  • 1.3.5 RESTful API
  • 2 Setup
  • 2.1 Local Setup
  • 2.1.1 Development Folder
  • 2.1.2 Browsers
  • 2.1.3 IDEs and Text Editors
  • 2.1.4 Version Control Systems
  • 2.1.5 Local HTTP Servers
  • 2.1.6 Database: MongoDB
  • 2.1.7 Other Components
  • 2.2 Cloud Setup
  • 2.2.1 SSH Keys
  • 2.2.2 GitHub
  • 2.2.3 Windows Azure
  • 2.2.4 Heroku
  • 2.2.5 Cloud9
  • II Front-End Prototyping
  • 3 jQuery and Parse.com
  • 3.1 Definitions
  • 3.1.1 JavaScript Object Notation
  • 3.1.2 AJAX
  • 3.1.3 Cross-Domain Calls
  • 3.2 jQuery
  • 3.3 Twitter Bootstrap
  • 3.4 LESS
  • 3.4.1 Variables
  • 3.4.2 Mixins
  • 3.4.3 Operations
  • 3.5 Example of using 3rd-party API (Twitter) and jQuery
  • 3.6 Parse.com
  • 3.7 Message Board with Parse.com Overview
  • 3.8 Message Board with Parse.com: REST API and jQuery version
  • 3.9 Pushing to GitHub
  • 3.10 Deployment to Windows Azure
  • 3.11 Deployment to Heroku
  • 3.12 Updating and Deleting of Messages
  • 4 Intro to Backbone.js
  • 4.1 Setting up Backbone.js App from Scratch
  • 4.1.1 Dependencies
  • 4.2 Working with Collections
  • 4.3 Event Binding
  • 4.4 Views and Subviews with Underscore.js
  • 4.5 Refactoring
  • 4.6 AMD and Require.js for Development
  • 4.7 Require.js for Production
  • 4.8 Super Simple Backbone Starter Kit
  • 5 Backbone.js and Parse.com
  • 5.1 Message Board with Parse.com: JavaScript SDK and Backbone.js version
  • 5.2 Deploying Message Board to PaaS
  • 5.3 Enhancing Message Board
  • III Back-End Prototyping
  • 6 Node.js and MongoDB
  • 6.1 Node.js
  • 6.1.1 Building “Hello World” in Node.js
  • 6.1.2 Node.js Core Modules
  • 6.1.3 Node Package Manager
  • 6.1.4 Deploying “Hello World” to PaaS
  • 6.1.5 Deploying to Windows Azure
  • 6.1.6 Deploying to Heroku
  • 6.2 Message Board: Run-Time Memory Version
  • 6.3 Test Case for Message Board
  • 6.4 MongoDB
  • 6.4.1 MongoDB Shell
  • 6.4.2 MongoDB Native Driver
  • 6.4.3 MongoDB on Heroku: MongoHQ
  • 6.4.4 BSON
  • 6.5 Message Board: MongoDB Version
  • 7 Putting It All Together
  • 7.1 Different Domain Deployment
  • 7.2 Changing Endpoints
  • 7.3 Message Board Application
  • 7.4 Deployment
  • 7.5 Same Domain Deployment
  • 8 Advanced Node.js Topics
  • 8.1 Asynchronicity in Node
  • 8.1.1 Non-Blocking I/O
  • 8.1.2 Asynchronous Way of Coding
  • 8.2 MongoDB Migration with Monk
  • 8.3 TDD in Node.js with Mocha
  • 8.3.1 Who Needs Test-Driven Development?
  • 8.3.2 Quick Start Guide
  • 8.4 Wintersmith — Static Site Generator
  • 8.4.1 Getting Started with Wintersmith
  • 8.4.2 Other Static Site Generators
  • 8.5 Intro to Express.js: Simple REST API app with Monk and MongoDB
  • 8.5.1 REST API app with Express.js and Monk
  • 8.6 Intro to Express.js: Parameters, Error Handling and Other Middleware
  • 8.6.1 Request Handlers
  • 8.6.2 Parameters Middleware
  • 8.6.3 Error Handling
  • 8.6.4 Other Middleware
  • 8.6.5 Abstraction
  • 8.7 Node.js MVC: Express.js + Derby Hello World Tutorial
  • 8.7.1 Node MVC Framework
  • 8.7.2 Derby Installation
  • 8.7.3 File Structure
  • 8.7.4 Dependencies
  • 8.7.5 Views
  • 8.7.6 Main Server
  • 8.7.7 Derby Application
  • 8.7.8 Launching Hello World App
  • 8.7.9 Passing Values to Back-End
  • Conclusion and Further Reading
  • Conclusion
  • Further Reading
  • JavaScript resources and free ebooks
  • JavaScript books
  • Node.js resources and free ebooks
  • Node.js books
  • Interactive online classes and courses
  • Startup books and blogs
  • Acknowledgment
  • About the Author

Intro to Backbone.js


Buy PDF, EPUB & MOBI $19.99

Summary: demonstration of how to build Backbone.js application from scratch and use views, collections, subviews, models, event binding, AMD, Require.js on the example of the apple database application.

“Code is not an asset. It’s a liability. The more you write, the more you’ll have to maintain later.” — Unknown

Setting up Backbone.js App from Scratch

We’re going to build a typical starter “Hello World” application using Backbone.js and Mode-View-Controller (MVC) architecture. I know it might sound like overkill in the beginning, but as we go along we’ll add more and more complexity, including Models, Subviews and Collections.

A full source code for the “Hello World” app is available at GitHub under github.com/azat-co/rpjs/backbone/hello-world.

Dependencies

Download the following libraries:

And include these frameworks in the index.html file like this:

<!DOCTYPE>
<html>
<head>
  <script src="jquery.js"></script>
  <script src="underscore.js"></script>
  <script src="backbone.js"></script>

  <script>
    //TODO write some awesome JS code!
  </script>

</head>
<body>
</body>
</html>

Note: We can also put <script> tags right after the </body> tag in the end of the file. This will change the order in which scripts and the rest of HTML are loaded, and impact performance in large files.

Let’s define a simple Backbone.js Router inside of a <script> tag:

  ...
  var router = Backbone.Router.extend({
  });
  ...

Note: For now, to Keep It Simple Stupid (KISS), we’ll be putting all of our JavaScript code right into the index.html file. This is not a good idea for a real development or production code. We’ll refactor it later.

Then set up a special routes property inside of an extend call:

  var router = Backbone.Router.extend({
    routes: {
    }
  });

The Backbone.js routes property needs to be in the following format: 'path/:param':'action' which will result in the filename#path/param URL triggering a function named action (defined in the Router object). For now, we’ll add a single home route:

  var router = Backbone.Router.extend({
    routes: {
      '': 'home'
    }
  });

This is good, but now we need to add a home function:

  var router = Backbone.Router.extend({
    routes: {
      '': 'home'
    },
    home: function(){
      //TODO render html
    }
  });

We’ll come back to the home function later to add more logic for creating and rendering of a View. Right now we should define our homeView:

  var homeView = Backbone.View.extend({
  });

It looks familiar, right? Backbone.js uses similar syntax for all of its components: the extend function and a JSON object as a parameter to it.

There are a multiple ways to proceed from now on, but the best practice is to use the el and template properties, which are magical, i.e., special in Backbone.js:

  var homeView = Backbone.View.extend({
    el: 'body',
    template: _.template('Hello World')
  });

The property el is just a string that holds the jQuery selector (you can use class name with ‘.’ and id name with ‘#’). The template property has been assigned an Underscore.js function template with just a plain text ‘Hello World’.

To render our homeView we use this.$el which is a compiled jQuery object referencing element in an el property, and the jQuery .html() function to replace HTML with this.template() value. Here is what the full code for our Backbone.js View looks like:

  var homeView = Backbone.View.extend({
    el: 'body',
    template: _.template('Hello World'),
    render: function(){
      this.$el.html(this.template({}));
    }
  });

Now, if we go back to the router we can add these two lines to the home function:

  var router = Backbone.Router.extend({
    routes: {
      '': 'home'
    },
    initialize: function(){

    },
    home: function(){
      this.homeView = new homeView;
      this.homeView.render();
    }
  });

The first line will create the homeView object and assign it to the homeView property of the router. The second line will call the render() method in the homeView object, triggering the ‘Hello World’ output.

Finally, to start a Backbone app, we call new Router inside of a document-ready wrapper to make sure that the file’s DOM is fully loaded:

var app;
$(document).ready(function(){
  app = new router;
  Backbone.history.start();
})

Here is the full code of the index.html file:


<!DOCTYPE>
<html>
<head>
  <script src="jquery.js"></script>
  <script src="underscore.js"></script>
  <script src="backbone.js"></script>

  <script>
    var app;
    var router = Backbone.Router.extend({
      routes: {
        '': 'home'
      },
      initialize: function(){
       //some code to execute
       //when the object is instantiated
      },
      home: function(){
        this.homeView = new homeView;
        this.homeView.render();
      }
    });

    var homeView = Backbone.View.extend({
      el: 'body',
      template: _.template('Hello World'),
      render: function(){
        this.$el.html(this.template({}));
      }
    });

    $(document).ready(function(){
      app = new router;
      Backbone.history.start();
    })

  </script>
</head>
<body>
  <div></div>
</body>
</html>

Open index.html in the browser to see if it works, i.e., the ‘Hello World’ message should be on the page.

Working with Collections

The full source code of this example is under rpjs/backbone/collections. It’s built on top of “Hello World” example from the Setting up Backbone.js App from Scratch exercise which is available for download at rpjs/backbone/hello-world.

We should add some data to play around with, and to hydrate our views. To do this, add this right after the script tag and before the other code:

  var appleData = [
    {
      name: "fuji",
      url: "img/fuji.jpg"
    },
    {
      name: "gala",
      url: "img/gala.jpg"
    }
  ];

This is our apple database. :-) Or to be more correct, REST API endpoint-substitute, which provides us with names and image URLs of the apples (data models).

Note: This mock dataset can be easily substituted by assigning REST API endpoints of your back-end to url properties in Backbone.js Collections and/or Models, and calling the fetch() method on them.

Now to make the User Experience (UX) a little bit better, we can add a new route to the routes object in the Backbone Route:

  ...
  routes: {
    '': 'home',
    'apples/:appleName': 'loadApple'
  },
  ...

This will allow users to go to index.html#apples/SOMENAME and expect to see some information about an apple. This information will be fetched and rendered by the loadApple function in the Backbone Router definition:

  loadApple: function(appleName){
    this.appleView.render(appleName);
  }

Have you noticed an appleName variable? It’s exactly the same name as the one that we’ve used in route. This is how we can access query string parameters (e.g, ?param=value&q=search) in Backbone.js.

Now we’ll need to refactor some more code to create a Backbone Collection, populate it with data in our appleData variable, and to pass the collection to homeView and appleView. Conveniently enough, we do it all in the Router constructor method initialize:

  initialize: function(){
    var apples = new Apples();
    apples.reset(appleData);
    this.homeView = new homeView({collection: apples});
    this.appleView = new appleView({collection: apples});
  },

At this point, we’re pretty much done with the Router class and it should look like this:

  var router = Backbone.Router.extend({
    routes: {
      '': 'home',
      'apples/:appleName': 'loadApple'
    },
    initialize: function(){
      var apples = new Apples();
      apples.reset(appleData);
      this.homeView = new homeView({collection: apples});
      this.appleView = new appleView({collection: apples});
    },
    home: function(){
      this.homeView.render();
    },
    loadApple: function(appleName){
      this.appleView.render(appleName);
    }
  });

Let’s modify our homeView a bit to see the whole database:

  var homeView = Backbone.View.extend({
    el: 'body',
    template: _.template('Apple data: <%= data %>'),
    render: function(){
      this.$el.html(this.template({
      data: JSON.stringify(this.collection.models)
    }));
    }
  });

For now, we just output the string representation of the JSON object in the browser. This is not user-friendly at all, but later we’ll improve it by using a list and subviews.

Our apple Backbone Collection is very clean and simple:

  var Apples = Backbone.Collection.extend({
  });

Note: Backbone automatically creates models inside of a collection when we use the fetch() or reset() functions.

Apple view is not any more complex; it has only two properties: template and render. In a template, we want to display figure, img and figcaption tags with specific values. The Underscore.js template engine is handy at this task:

  var appleView = Backbone.View.extend({
    template: _.template(
          '<figure>\
             <img src="<%= attributes.url %>"/>\
             <figcaption><%= attributes.name %></figcaption>\
           </figure>'),
  ...
  });

To make a JavaScript string, which has HTML tags in it, more readable we can use the backslash line breaker escape (\) symbol, or close strings and concatenate them with a plus sign (+). This is an example of appleView above, which is refactored using the latter approach:

  var appleView = Backbone.View.extend({
    template: _.template(
        '<figure>'+
          +'<img src="<%= attributes.url %>"/>'+
          +'<figcaption><%= attributes.name %></figcaption>'+
        +'</figure>'),
  ...

Please note the ‘<%=’ and ‘%>’ symbols; they are the instructions for Undescore.js to print values in properties url and name of the attributes object.

Finally, we’re adding the render function to the appleView class.

  render: function(appleName){
    var appleModel = this.collection.where({name:appleName})[0];
    var appleHtml = this.template(appleModel);
    $('body').html(appleHtml);
  }

We find a model within the collection via where() method and use [] to pick the first element. Right now, the render function is responsible for both loading the data and rendering it. Later we’ll refactor the function to separate these two functionalities into different methods.

The whole app, which is in the rpjs/backbone/collections/index.html folder, looks like this:

<!DOCTYPE>
<html>
<head>
  <script src="jquery.js"></script>
  <script src="underscore.js"></script>
  <script src="backbone.js"></script>

  <script>
   var appleData = [
      {
        name: "fuji",
        url: "img/fuji.jpg"
      },
      {
        name: "gala",
        url: "img/gala.jpg"
      }
    ];
    var app;
    var router = Backbone.Router.extend({
      routes: {
        "": "home",
        "apples/:appleName": "loadApple"
      },
      initialize: function(){
        var apples = new Apples();
        apples.reset(appleData);
        this.homeView = new homeView({collection: apples});
        this.appleView = new appleView({collection: apples});
      },
      home: function(){
        this.homeView.render();
      },
      loadApple: function(appleName){
        this.appleView.render(appleName);
      }
    });

    var homeView = Backbone.View.extend({
      el: 'body',
      template: _.template('Apple data: <%= data %>'),
      render: function(){
        this.$el.html(this.template({
        data: JSON.stringify(this.collection.models)
      }));
      }
      //TODO subviews
    });

    var Apples = Backbone.Collection.extend({

    });
    var appleView = Backbone.View.extend({
      template: _.template('<figure>\
                  <img src="<%= attributes.url %>"/>\
                  <figcaption><%= attributes.name %></figcaption>\
                </figure>'),
      //TODO re-write with load apple and event binding
      render: function(appleName){
        var appleModel = this.collection.where({
          name:appleName
        })[0];
        var appleHtml = this.template(appleModel);
        $('body').html(appleHtml);
      }
    });
    $(document).ready(function(){
      app = new router;
      Backbone.history.start();
    })

  </script>
</head>
<body>
  <div></div>
</body>
</html>

Open collections/index.html file in your browser. You should see the data from our “database”, i.e., Apple data: [{"name":"fuji","url":"img/fuji.jpg"},{"name":"gala","url":"img/gala.jpg"}].

Now, let’ go to collections/index.html#apples/fuji or collections/index.html#apples/gala in your browser. We expect to see an image with a caption. It’s a detailed view of an item, which in this case is an apple. Nice work!

Event Binding

In real life, getting data does not happen instantaneously, so let’s refactor our code to simulate it. For a better UI/UX, we’ll also have to show a loading icon (a.k.a. spinner or ajax-loader) to users to notify them that the information is being loaded.

It’s a good thing that we have event binding in Backbone. Without it, we’ll have to pass a function that renders HTML as a callback to the data loading function, to make sure that the rendering function is not executed before we have the actual data to display.

Therefore, when a user goes to detailed view (apples/:id) we only call the function that loads the data. Then, with the proper event listeners, our view will automagically (this is not a typo) update itself, when there is a new data (or on a data change, Backbone.js supports multiple and even custom events).

Let’s change the code in the router:

  ...
    loadApple: function(appleName){
      this.appleView.loadApple(appleName);
    }
  ...

Everything else remains the same utill we get to the appleView class. We’ll need to add a constructor or an initialize method, which is a special word/property in the Backbone.js framework. It’s called each time we create an instance of an object, i.e., var someObj = new SomeObject(). We can also pass extra parameters to the initialize function, as we did with our views (we passed an object with the key collection and the value of apples Backbone Collection). Read more on Backbone.js constructors at backbonejs.org/#View-constructor.

  ...
  var appleView = Backbone.View.extend({
    initialize: function(){
      //TODO: create and setup model (aka an apple)
    },
  ...

Great, we have our initialize function. Now we need to create a model which will represent a single apple and set up proper event listeners on the model. We’ll use two types of events, change and a custom event called spinner. To do that, we are going to use the on() function, which takes these properties: on(event, actions, context) — read more about it at backbonejs.org/#Events-on:

  ...
  var appleView = Backbone.View.extend({
      this.model = new (Backbone.Model.extend({}));
      this.model.bind('change', this.render, this);
      this.bind('spinner',this.showSpinner, this);
    },
  ...      

The code above basically boils down to two simple things:

  1. Call render() function of appleView object when the model has changed
  2. Call showSpinner() method of appleView object when event spinner has been fired.

So far, so good, right? But what about the spinner, a GIF icon? Let’s create a new property in appleView:

  ...
    templateSpinner: '<img src="img/spinner.gif" width="30"/>',
  ...    

Remember the loadApple call in the router? This is how we can implement the function in appleView:

  ...
  loadApple:function(appleName){
    this.trigger('spinner');
    //show spinner GIF image
    var view = this;
    //we'll need to access that inside of a closure
    setTimeout(function(){
    //simulates real time lag when
    //fetching data from the remote server
      view.model.set(view.collection.where({
        name:appleName
      })[0].attributes);
    },1000);
  },
  ...

The first line will trigger the spinner event (the function for which we still have to write).

The second line is just for scoping issues (so we can use appleView inside of the closure).

The setTimeout function is simulating a time lag of a real remote server response. Inside of it, we assign attributes of a selected model to our view’s model by using a model.set() function and a model.attributes property (which returns the properties of a model).

Now we can remove an extra code from the render method and implement the showSpinner function:

  render: function(appleName){
    var appleHtml = this.template(this.model);
    $('body').html(appleHtml);
  },
  showSpinner: function(){
    $('body').html(this.templateSpinner);
  }
  ...

That’s all! Open index.html#apples/gala or index.html#apples/fuji in your browser and enjoy the loading animation while waiting for an apple image to load.

The full code of the index.html file:

<!DOCTYPE>
<html>
<head>
  <script src="jquery.js"></script>
  <script src="underscore.js"></script>
  <script src="backbone.js"></script>

  <script>
   var appleData = [
      {
        name: "fuji",
        url: "img/fuji.jpg"
      },
      {
        name: "gala",
        url: "img/gala.jpg"
      }
    ];
    var app;
    var router = Backbone.Router.extend({
      routes: {
        "": "home",
        "apples/:appleName": "loadApple"
      },
      initialize: function(){
        var apples = new Apples();
        apples.reset(appleData);
        this.homeView = new homeView({collection: apples});
        this.appleView = new appleView({collection: apples});
      },
      home: function(){
        this.homeView.render();
      },
      loadApple: function(appleName){
        this.appleView.loadApple(appleName);

      }
    });

    var homeView = Backbone.View.extend({
      el: 'body',
      template: _.template('Apple data: <%= data %>'),
      render: function(){
        this.$el.html(this.template({
          data: JSON.stringify(this.collection.models)
        }));
      }
      //TODO subviews
    });

    var Apples = Backbone.Collection.extend({

    });
    var appleView = Backbone.View.extend({
      initialize: function(){
        this.model = new (Backbone.Model.extend({}));
        this.model.on('change', this.render, this);
        this.on('spinner',this.showSpinner, this);
      },
      template: _.template('<figure>\
                <img src="<%= attributes.url %>"/>\
                <figcaption><%= attributes.name %></figcaption>\
                </figure>'),
      templateSpinner: '<img src="img/spinner.gif" width="30"/>',

      loadApple:function(appleName){
        this.trigger('spinner');
        var view = this; //we'll need to access
        //that inside of a closure
        setTimeout(function(){ //simulates real time
        //lag when fetching data from the remote server
          view.model.set(view.collection.where({
            name:appleName
          })[0].attributes);
        },1000);

      },

      render: function(appleName){
        var appleHtml = this.template(this.model);
        $('body').html(appleHtml);
      },
      showSpinner: function(){
        $('body').html(this.templateSpinner);
      }

    });
    $(document).ready(function(){
      app = new router;
      Backbone.history.start();
    })

  </script>
</head>
<body>
  <a href="#apples/fuji">fuji</a>
  <div></div>
</body>
</html>

Views and Subviews with Underscore.js

This example is available at rpjs/backbone/subview.

Subviews are Backbone Views that are created and used inside of another Backbone View. A subviews concept is a great way to abstract (separate) UI events (e.g., clicks), and templates for similarly structured elements (e.g., apples).

A use case of a Subview might include a row in a table, a list item in a list, a paragraph, a new line, etc.

We’ll refactor our home page to show a nice list of apples. Each list item will have an apple name and a “buy” link with an onClick event. Let’s start by creating a subview for a single apple with our standard Backbone extend() function:

  ...
  var appleItemView = Backbone.View.extend({
    tagName: 'li',
    template: _.template(''
           +'<a href="#apples/<%=name%>" target="_blank">'
          +'<%=name%>'
          +'</a>&nbsp;<a class="add-to-cart" href="#">buy</a>'),
    events: {
      'click .add-to-cart': 'addToCart'
    },
    render: function() {
      this.$el.html(this.template(this.model.attributes));
    },
    addToCart: function(){
      this.model.collection.trigger('addToCart', this.model);
    }
  });
  ...

Now we can populate the object with tagName, template, events, render and addToCart properties/methods.

  ...
  tagName: 'li',
  ...

tagName automatically allows Backbone.js to create an HTML element with the specified tag name, in this case <li> — list item. This will be a representation of a single apple, a row in our list.

  ...
  template: _.template(''
         +'<a href="#apples/<%=name%>" target="_blank">'
        +'<%=name%>'
        +'</a>&nbsp;<a class="add-to-cart" href="#">buy</a>'),
  ...

The template is just a string with Undescore.js instructions. They are wrapped in <% and %> symbols. <%= simply means print a value. The same code can be written with backslash escapes:

  ...
  template: _.template('\
         <a href="#apples/<%=name%>" target="_blank">\
        <%=name%>\
        </a>&nbsp;<a class="add-to-cart" href="#">buy</a>\
        '),
  ...

Each <li> will have two anchor elements (<a>), links to a detailed apple view (#apples/:appleName) and a buy button. Now we’re going to attach an event listener to the buy button:

  ...
  events: {
    'click .add-to-cart': 'addToCart'
  },
  ...

The syntax follows this rule:

event + jQuery element selector: function name

Both the key and the value (right and left parts separated by the colon) are strings. For example:

'click .add-to-cart': 'addToCart'

or

'click #load-more': 'loadMoreData'

To render each item in the list, we’ll use the jQuery html() function on the this.$el jQuery object, which is the <li> HTML element based on our tagName attribute:

  ...
  render: function() {
    this.$el.html(this.template(this.model.attributes));
  },
  ...

addToCart will use the trigger() function to notify the collection that this particular model (apple) is up for the purchase by the user:

  ...
    addToCart: function(){
      this.model.collection.trigger('addToCart', this.model);
    }
  ...

Here is the full code of the appleItemView Backbone View class:

  ...
  var appleItemView = Backbone.View.extend({
    tagName: 'li',
    template: _.template(''
           +'<a href="#apples/<%=name%>" target="_blank">'
          +'<%=name%>'
          +'</a>&nbsp;<a class="add-to-cart" href="#">buy</a>'),
    events: {
      'click .add-to-cart': 'addToCart'
    },
    render: function() {
      this.$el.html(this.template(this.model.attributes));
    },
    addToCart: function(){
      this.model.collection.trigger('addToCart', this.model);
    }
  });
  ...

Easy peasy! But what about the master view, which is supposed to render all of our items (apples) and provide a wrapper <ul> container for <li> HTML elements? We need to modify and enhance our homeView.

To begin with, we can add extra properties of string type understandable by jQuery as selectors to homeView:

  ...
  el: 'body',
  listEl: '.apples-list',
  cartEl: '.cart-box',
  ...

We can use properties from above in the template, or just hard-code them (we’ll refactor our code later) in homeView:

  ...
  template: _.template('Apple data: \
    <ul class="apples-list">\
    </ul>\
    <div class="cart-box"></div>'),
  ...

The initialize function will be called when homeView is created (new homeView()) — in it we render our template (with our favorite by now html() function), and attach an event listener to the collection (which is a set of apple models):

  ...
    initialize: function() {
      this.$el.html(this.template);
      this.collection.on('addToCart', this.showCart, this);
    },
  ...

The syntax for the binding event is covered in the previous section. In essence, it is calling the showCart() function of homeView. In this function, we append appleName to the cart (along with a line break, a <br/> element):

  ...
    showCart: function(appleModel) {
      $(this.cartEl).append(appleModel.attributes.name+'<br/>');
    },
  ...

Finally, here is our long-awaited render() method, in which we iterate through each model in the collection (each apple), create an appleItemView for each apple, create an <li> element for each apple, and append that element to view.listEl<ul> element with a class apples-list in the DOM:

  ...
  render: function(){
    view = this;
    //so we can use view inside of closure
    this.collection.each(function(apple){
      var appleSubView = new appleItemView({model:apple});
      // creates subview with model apple
      appleSubView.render();
      // compiles template and single apple data
      $(view.listEl).append(appleSubView.$el);
      //append jQuery object from single
      //apple to apples-list DOM element
    });
  }
  ...

Let’s make sure we didn’t miss anything in the homeView Backbone View:

  ...
  var homeView = Backbone.View.extend({
    el: 'body',
    listEl: '.apples-list',
    cartEl: '.cart-box',
    template: _.template('Apple data: \
      <ul class="apples-list">\
      </ul>\
      <div class="cart-box"></div>'),
    initialize: function() {
      this.$el.html(this.template);
      this.collection.on('addToCart', this.showCart, this);
    },
    showCart: function(appleModel) {
      $(this.cartEl).append(appleModel.attributes.name+'<br/>');
    },
    render: function(){
      view = this; //so we can use view inside of closure
      this.collection.each(function(apple){
        var appleSubView = new appleItemView({model:apple});
        // create subview with model apple
        appleSubView.render();
        // compiles tempalte and single apple data
        $(view.listEl).append(appleSubView.$el);
        //append jQuery object from single apple
        //to apples-list DOM element
      });
    }
  });
  ...

You should be able to click on the buy, and the cart will populate with the apples of your choice. Looking at an individual apple does not require typing its name in the URL address bar of the browser anymore. We can click on the name and it opens a new window with a detailed view.

The list of apples rendered by subviews.

By using subviews, we reused the template for all of the items (apples) and attached a specific event to each of them. Those events are smart enough to pass the information about the model to other objects: views and collections.

Just in case, here is the full code for the subviews example, which is also available at rpjs/backbone/subview/index.html:

<!DOCTYPE>
<html>
<head>
  <script src="jquery.js"></script>
  <script src="underscore.js"></script>
  <script src="backbone.js"></script>

  <script>
   var appleData = [
      {
        name: "fuji",
        url: "img/fuji.jpg"
      },
      {
        name: "gala",
        url: "img/gala.jpg"
      }
    ];
    var app;
    var router = Backbone.Router.extend({
      routes: {
        "": "home",
        "apples/:appleName": "loadApple"
      },
      initialize: function(){
        var apples = new Apples();
        apples.reset(appleData);
        this.homeView = new homeView({collection: apples});
        this.appleView = new appleView({collection: apples});
      },
      home: function(){
        this.homeView.render();
      },
      loadApple: function(appleName){
        this.appleView.loadApple(appleName);

      }
    });
    var appleItemView = Backbone.View.extend({
      tagName: 'li',
      // template: _.template(''
      //    +'<a href="#apples/<%=name%>" target="_blank">'
      //    +'<%=name%>'
      //    +'</a>&nbsp;<a class="add-to-cart" href="#">buy</a>'),
      template: _.template('\
             <a href="#apples/<%=name%>" target="_blank">\
            <%=name%>\
            </a>&nbsp;<a class="add-to-cart" href="#">buy</a>\
            '),

      events: {
        'click .add-to-cart': 'addToCart'
      },
      render: function() {
        this.$el.html(this.template(this.model.attributes));
      },
      addToCart: function(){
        this.model.collection.trigger('addToCart', this.model);
      }
    });

    var homeView = Backbone.View.extend({
      el: 'body',
      listEl: '.apples-list',
      cartEl: '.cart-box',
      template: _.template('Apple data: \
        <ul class="apples-list">\
        </ul>\
        <div class="cart-box"></div>'),
      initialize: function() {
        this.$el.html(this.template);
        this.collection.on('addToCart', this.showCart, this);
      },
      showCart: function(appleModel) {
        $(this.cartEl).append(appleModel.attributes.name+'<br/>');
      },
      render: function(){
        view = this; //so we can use view inside of closure
        this.collection.each(function(apple){
          var appleSubView = new appleItemView({model:apple});
          // create subview with model apple
          appleSubView.render();
          // compiles tempalte and single apple data
          $(view.listEl).append(appleSubView.$el);
          //append jQuery object from
          //single apple to apples-list DOM element
        });
      }
    });

    var Apples = Backbone.Collection.extend({
    });

    var appleView = Backbone.View.extend({
      initialize: function(){
        this.model = new (Backbone.Model.extend({}));
        this.model.on('change', this.render, this);
        this.on('spinner',this.showSpinner, this);
      },
      template: _.template('<figure>\
                <img src="<%= attributes.url %>"/>\
                <figcaption><%= attributes.name %></figcaption>\
              </figure>'),
      templateSpinner: '<img src="img/spinner.gif" width="30"/>',
      loadApple:function(appleName){
        this.trigger('spinner');
        var view = this;
        //we'll need to access that inside of a closure
        setTimeout(function(){
        //simulates real time lag when fetching data
        // from the remote server
          view.model.set(view.collection.where({
            name:appleName
          })[0].attributes);
        },1000);
      },
      render: function(appleName){
        var appleHtml = this.template(this.model);
        $('body').html(appleHtml);
      },
      showSpinner: function(){
        $('body').html(this.templateSpinner);
      }
    });

    $(document).ready(function(){
      app = new router;
      Backbone.history.start();
    })

  </script>
</head>
<body>
  <div></div>
</body>
</html>

The link to an individual item, e.g., collections/index.html#apples/fuji, also should work independently, by typing it in the browser address bar.

Refactoring

At this point you are probably wondering what is the benefit of using the framework and still having multiple classes, objects and elements with different functionalities in one single file. This was done for the purpose of Keep it Simple Stupid (KISS).

The bigger your application is, the more pain there is in unorganized code base. Let’s break down our application into multiple files where each file will be one of these types:

  • view
  • template
  • router
  • collection
  • model

Let’s write these scripts to include tags into our index.html head — or body, as noted previously:

  <script src="apple-item.view.js"></script>
  <script src="apple-home.view.js"></script>
  <script src="apple.view.js"></script>
  <script src="apples.js"></script>
  <script src="apple-app.js"></script>

The names don’t have to follow the convention of dashes and dots, as long as it’s easy to tell what each file is supposed to do.

Now, let’s copy our objects/classes into the corresponding files.

Our main index.html file should look very minimalistic:

<!DOCTYPE>
<html>
<head>
  <script src="jquery.js"></script>
  <script src="underscore.js"></script>
  <script src="backbone.js"></script>

  <script src="apple-item.view.js"></script>
  <script src="apple-home.view.js"></script>
  <script src="apple.view.js"></script>
  <script src="apples.js"></script>
  <script src="apple-app.js"></script>

</head>
<body>
  <div></div>
</body>
</html>

The other files just have the code that corresponds to their filenames.

The content of apple-item.view.js:

  var appleView = Backbone.View.extend({
    initialize: function(){
      this.model = new (Backbone.Model.extend({}));
      this.model.on('change', this.render, this);
      this.on('spinner',this.showSpinner, this);
    },
    template: _.template('<figure>\
              <img src="<%= attributes.url %>"/>\
              <figcaption><%= attributes.name %></figcaption>\
            </figure>'),
    templateSpinner: '<img src="img/spinner.gif" width="30"/>',

    loadApple:function(appleName){
      this.trigger('spinner');
      var view = this;
      //we'll need to access that inside of a closure
      setTimeout(function(){
      //simulates real time lag when fetching
      //data from the remote server
        view.model.set(view.collection.where({
          name:appleName
        })[0].attributes);
      },1000);

    },

    render: function(appleName){
      var appleHtml = this.template(this.model);
      $('body').html(appleHtml);
    },
    showSpinner: function(){
      $('body').html(this.templateSpinner);
    }

  });

The apple-home.view.js file has the homeView object:

  var homeView = Backbone.View.extend({
    el: 'body',
    listEl: '.apples-list',
    cartEl: '.cart-box',
    template: _.template('Apple data: \
      <ul class="apples-list">\
      </ul>\
      <div class="cart-box"></div>'),
    initialize: function() {
      this.$el.html(this.template);
      this.collection.on('addToCart', this.showCart, this);
    },
    showCart: function(appleModel) {
      $(this.cartEl).append(appleModel.attributes.name+'<br/>');
    },
    render: function(){
      view = this; //so we can use view inside of closure
      this.collection.each(function(apple){
        var appleSubView = new appleItemView({model:apple});
        // create subview with model apple
        appleSubView.render();
        // compiles tempalte and single apple data
        $(view.listEl).append(appleSubView.$el);
        //append jQuery object from
        //single apple to apples-list DOM element
      });
    }
  });

The apple.view.js file contains the master apples’ list:

  var appleView = Backbone.View.extend({
    initialize: function(){
      this.model = new (Backbone.Model.extend({}));
      this.model.on('change', this.render, this);
      this.on('spinner',this.showSpinner, this);
    },
    template: _.template('<figure>\
            <img src="<%= attributes.url %>"/>\
            <figcaption><%= attributes.name %></figcaption>\
          </figure>'),
    templateSpinner: '<img src="img/spinner.gif" width="30"/>',
    loadApple:function(appleName){
      this.trigger('spinner');
      var view = this;
      //we'll need to access that inside of a closure
      setTimeout(function(){
      //simulates real time lag when
      //fetching data from the remote server
        view.model.set(view.collection.where({
          name:appleName
        })[0].attributes);
      },1000);
    },
    render: function(appleName){
      var appleHtml = this.template(this.model);
      $('body').html(appleHtml);
    },
    showSpinner: function(){
      $('body').html(this.templateSpinner);
    }
  });

apples.js is an empty collection:

    var Apples = Backbone.Collection.extend({
    });

apple-app.js is the main application file with the data, the router, and the starting command:

   var appleData = [
      {
        name: "fuji",
        url: "img/fuji.jpg"
      },
      {
        name: "gala",
        url: "img/gala.jpg"
      }
    ];
    var app;
    var router = Backbone.Router.extend({
      routes: {
        '': 'home',
        'apples/:appleName': 'loadApple'
      },
      initialize: function(){
        var apples = new Apples();
        apples.reset(appleData);
        this.homeView = new homeView({collection: apples});
        this.appleView = new appleView({collection: apples});
      },
      home: function(){
        this.homeView.render();
      },
      loadApple: function(appleName){
        this.appleView.loadApple(appleName);
      }
    });
    $(document).ready(function(){
      app = new router;
      Backbone.history.start();
    })

Now let’s try to open the application. It should work exactly the same as in the previous Subviews example.

It’s a way better code organization, but it’s still far from perfect, because we still have HTML templates directly in the JavaScript code. The problem is that designers and developers can’t work on the same files, and any change to the presentation requires a change in the main code base.

We can add a few more JS files to our index.html file:

  <script src="apple-item.tpl.js"></script>
  <script src="apple-home.tpl.js"></script>
  <script src="apple-spinner.tpl.js"></script>
  <script src="apple.tpl.js"></script>

Usually, one Backbone view has one template, but in the case of our appleView — detailed view of an apple in a separate window — we also have a spinner, a “loading” GIF animation.

The contents of the files are just global variables which are assigned some string values. Later we can use these variables in our views, when we call the Underscore.js helper method _.template().

The apple-item.tpl.js file:

var appleItemTpl = '\
     <a href="#apples/<%=name%>" target="_blank">\
    <%=name%>\
    </a>&nbsp;<a class="add-to-cart" href="#">buy</a>\
    ';

The apple-home.tpl.js file:

var appleHomeTpl = 'Apple data: \
        <ul class="apples-list">\
        </ul>\
        <div class="cart-box"></div>';

The apple-spinner.tpl.js file:

var appleSpinnerTpl = '<img src="img/spinner.gif" width="30"/>';

The apple.tpl.js file:

var appleTpl = '<figure>\
                <img src="<%= attributes.url %>"/>\
                <figcaption><%= attributes.name %></figcaption>\
              </figure>';

Try to start the application now. The full code is under the rpjs/backbone/refactor folder.

As you can see in the previous example, we used global scoped variables (without the keyword window).

W>## Warning W> W> Be careful when you introduce a lot of variables into the global namespace (window keyword). There might be conflicts and other unpredictable consequences. For example, if you wrote an open source library and other developers started using the methods and properties directly, instead of using the interface, what happens later when you decide to finally remove/deprecate those global leaks? To prevent this, properly written libraries and applications use JavaScript closures.

Example of using closure and a global variable module definition:

(function() {
  var apple= function() {
  ...//do something useful like return apple object
  };
  window.Apple = apple;
}())

Or in case when we need to access the app object (which creates a dependency on that object):

(function() {
  var app = this.app;
  //equivalent of window.appliation
  //in case we need a dependency (app)
  this.apple = function() {
  ...//return apple object/class
  //use app variable
  }
  // eqivalent of window.apple = function(){...};
}())

As you can see, we’ve created the function and called it immediately while also wrapping everything in parentheses ().

AMD and Require.js for Development

AMD allows us to organize development code into modules, manage dependencies, and load them asynchronously. This article does a great job at explaining why AMD is a good thing: WHY AMD?

Start your local HTTP server, e.g., MAMP.

Let’s enhance our code by using the Require.js library.

Our index.html will shrink even more:

<!DOCTYPE>
<html>
<head>
  <script src="jquery.js"></script>
  <script src="underscore.js"></script>
  <script src="backbone.js"></script>
  <script src="require.js"></script>
  <script src="apple-app.js"></script>
</head>
<body>
  <div></div>
</body>
</html>

We only included libraries and the single JavaScript file with our application. This file has the following structure:

require([...],function(...){...});

Or in a more explanatory way:

require([
  'name-of-the-module',
  ...
  'name-of-the-other-module'
  ],function(referenceToModule, ..., referenceToOtherModule){
  ...//some useful code
  referenceToModule.someMethod();
});

Basically, we tell a browser to load the files from the array of filenames — first parameter of the require() function — and then pass our modules from those files to the anonymous callback function (second argument) as variables. Inside of the main function (anonymous callback) we can use our modules by referencing those variables. Therefore, our apple-app.js metamorphoses into:

  require([
    'apple-item.tpl', //can use shim plugin
    'apple-home.tpl',
    'apple-spinner.tpl',
    'apple.tpl',
    'apple-item.view',
    'apple-home.view',
    'apple.view',
    'apples'
  ],function(
    appleItemTpl,
    appleHomeTpl,
    appleSpinnerTpl,
    appleTpl,
    appelItemView,
    homeView,
    appleView,
    Apples
    ){
   var appleData = [
      {
        name: "fuji",
        url: "img/fuji.jpg"
      },
      {
        name: "gala",
        url: "img/gala.jpg"
      }
    ];
    var app;
    var router = Backbone.Router.extend({
    //check if need to be required
      routes: {
        '': 'home',
        'apples/:appleName': 'loadApple'
      },
      initialize: function(){
        var apples = new Apples();
        apples.reset(appleData);
        this.homeView = new homeView({collection: apples});
        this.appleView = new appleView({collection: apples});
      },
      home: function(){
        this.homeView.render();
      },
      loadApple: function(appleName){
        this.appleView.loadApple(appleName);

      }
    });

    $(document).ready(function(){
      app = new router;
      Backbone.history.start();
    })
});    

We put all of the code inside the function which is a second argument of require(), mentioned modules by their filenames, and used dependencies via corresponding parameters. Now we should define the module itself. This is how we can do it with the define() method:

define([...],function(...){...})

The meaning is similar to the require() function: dependencies are strings of filenames (and paths) in the array which is passed as the first argument. The second argument is the main function that accepts other libraries as parameters (the order of parameters and modules in the array is important):

define(['name-of-the-module'],function(nameOfModule){
  var b = nameOfModule.render();
  return b;
})

Note: There is no need to append .js to filenames. Require.js does it automatically. Shim plugin is used for importing text files such as HTML templates.

Let’s start with the templates and convert them into the Require.js modules.

The new apple-item.tpl.js file:

define(function() {
  return '\
             <a href="#apples/<%=name%>" target="_blank">\
            <%=name%>\
            </a>&nbsp;<a class="add-to-cart" href="#">buy</a>\
            '
});   

The apple-home.tpl file:

define(function(){
  return 'Apple data: \
        <ul class="apples-list">\
        </ul>\
        <div class="cart-box"></div>';
});

The apple-spinner.tpl.js file:

define(function(){
  return '<img src="img/spinner.gif" width="30"/>';
});

The apple.tpl.js file:

define(function(){
  return '<figure>\
          <img src="<%= attributes.url %>"/>\
          <figcaption><%= attributes.name %></figcaption>\
        </figure>';
 });                           

The apple-item.view.js file:

define(function() {
  return '\
             <a href="#apples/<%=name%>" target="_blank">\
            <%=name%>\
            </a>&nbsp;<a class="add-to-cart" href="#">buy</a>\
            '
});   

In the apple-home.view.js file, we need to declare dependencies on apple-home.tpl and apple-item.view.js files:

define(['apple-home.tpl','apple-item.view'],function(
  appleHomeTpl,
  appleItemView){
return  Backbone.View.extend({
      el: 'body',
      listEl: '.apples-list',
      cartEl: '.cart-box',
      template: _.template(appleHomeTpl),
      initialize: function() {
        this.$el.html(this.template);
        this.collection.on('addToCart', this.showCart, this);
      },
      showCart: function(appleModel) {
        $(this.cartEl).append(appleModel.attributes.name+'<br/>');
      },
      render: function(){
        view = this; //so we can use view inside of closure
        this.collection.each(function(apple){
          var appleSubView = new appleItemView({model:apple});
          // create subview with model apple
          appleSubView.render();
          // compiles tempalte and single apple data
          $(view.listEl).append(appleSubView.$el);
          //append jQuery object from
          //single apple to apples-list DOM element
        });
      }
    });
})

The apple.view.js file depends on two templates:

define([
  'apple.tpl',
  'apple-spinner.tpl'
],function(appleTpl,appleSpinnerTpl){
  return  Backbone.View.extend({
    initialize: function(){
      this.model = new (Backbone.Model.extend({}));
      this.model.on('change', this.render, this);
      this.on('spinner',this.showSpinner, this);
    },
    template: _.template(appleTpl),
    templateSpinner: appleSpinnerTpl,
    loadApple:function(appleName){
      this.trigger('spinner');
      var view = this;
      //we'll need to access that inside of a closure
      setTimeout(function(){
      //simulates real time lag when
      //fetching data from the remote server
        view.model.set(view.collection.where({
          name:appleName
        })[0].attributes);
      },1000);
    },
    render: function(appleName){
      var appleHtml = this.template(this.model);
      $('body').html(appleHtml);
    },
    showSpinner: function(){
      $('body').html(this.templateSpinner);
    }
  });
});

The apples.js file:

define(function(){
    return Backbone.Collection.extend({})
});   

I hope you can see the pattern by now. All of our code is split into the separate files based on the logic (e.g., view class, collection class, template). The main file loads all of the dependencies with the require() function. If we need some module in a non-main file, then we can ask for it in the define() method. Usually, in modules we want to return an object, e.g., in templates we return strings and in views we return Backbone View classes/objects.

Try launching the example under the rpjs/backbone/amd folder. Visually, there shouldn’t be any changes. If you open the Network tab in the Developers Tool, you can see a difference in how the files are loaded. The old rpjs/backbone/refactor/index.html file loads our JS scripts in a serial manner while the new the new rpjs/backbone/amd/index.html file loads them in parallel.

The old rpjs/backbone/refactor/index.html file

The new rpjs/backbone/amd/index.html file

Require.js has a lot of configuration options which are defined through requirejs.config() call in a top level of an HTML page. More information can be found at requirejs.org/docs/api.html#config.

Let’s add a bust parameter to our example. The bust argument will be appended to the URL of each file preventing a browser from caching the files. Perfect for development and terrible for production. :-)

Add this to the apple-app.js file in front of everything else:

requirejs.config({
  urlArgs: "bust=" +  (new Date()).getTime()
});
require([
...

Network Tab with bust parameter added

Please note that each file request now has status 200 instead of 304 (not modified).

Require.js for Production

We’ll use the Node Package Manager (NPM) to install the requirejs library (it’s not a typo; there’s no period in the name). In your project folder, run this command in a terminal:

$ npm install requirejs

Or add -g for global installation:

$ npm install -g requirejs

Create a file app.build.js:

({
    appDir: "./js",
    baseUrl: "./",
    dir: "build",
    modules: [
        {
            name: "apple-app"
        }
    ]
})

Move the script files under the js folder (appDir property). The builded files will be placed under the build folder (dir parameter). For more information on the build file, check out this extensive example with comments: https://github.com/jrburke/r.js/blob/master/build/example.build.js.

Now everything should be ready for building one gigantic JavaScript file, which will have all of our dependencies/modules:

$ r.js -o app.build.js

or

$ node_modules/requirejs/bin/r.js -o app.build.js

You should get a list of the r.js processed files.

A list of the r.js processed files.

Open index.html from the build folder in a browser window, and check if the Network Tab shows any improvement now with just one request/file to load.

Performance improvement with one request/file to load.

For more information, check out the official r.js documentation at requirejs.org/docs/optimization.html.

The example code is available under the rpjs/backbone/r and rpjs/backbone/r/build folders.

For uglification of JS files (decreases the files’ sizes), we can use the Uglify2 module. To install it with NPM, use:

$ npm install uglify-js

Then update the app.build.js file with the optimize: "uglify2" property:

({
    appDir: "./js",
    baseUrl: "./",
    dir: "build",
    optimize: "uglify2",
    modules: [
        {
            name: "apple-app"
        }
    ]
})

Run r.js with:

$ node_modules/requirejs/bin/r.js -o app.build.js

You should get something like this:

define("apple-item.tpl",[],function(){return'             <a href="#apples/<%=name%>" target="_blank">            <%=name%>            </a>&nbsp;<a class="add-to-cart" href="#">buy</a>            '}),define("apple-home.tpl",[],function(){return'Apple data:         <ul class="apples-list">        </ul>        <div class="cart-box"></div>'}),define("apple-spinner.tpl",[],function(){return'<img src="img/spinner.gif" width="30"/>'}),define("apple.tpl",[],function(){return'<figure>                              <img src="<%= attributes.url %>"/>                              <figcaption><%= attributes.name %></figcaption>                            </figure>'}),define("apple-item.view",["apple-item.tpl"],function(e){return Backbone.View.extend({tagName:"li",template:_.template(e),events:{"click .add-to-cart":"addToCart"},render:function(){this.$el.html(this.template(this.model.attributes))},addToCart:function(){this.model.collection.trigger("addToCart",this.model)}})}),define("apple-home.view",["apple-home.tpl","apple-item.view"],function(e,t){return Backbone.View.extend({el:"body",listEl:".apples-list",cartEl:".cart-box",template:_.template(e),initialize:function(){this.$el.html(this.template),this.collection.on("addToCart",this.showCart,this)},showCart:function(e){$(this.cartEl).append(e.attributes.name+"<br/>")},render:function(){view=this,this.collection.each(function(e){var i=new t({model:e});i.render(),$(view.listEl).append(i.$el)})}})}),define("apple.view",["apple.tpl","apple-spinner.tpl"],function(e,t){return Backbone.View.extend({initialize:function(){this.model=new(Backbone.Model.extend({})),this.model.on("change",this.render,this),this.on("spinner",this.showSpinner,this)},template:_.template(e),templateSpinner:t,loadApple:function(e){this.trigger("spinner");var t=this;setTimeout(function(){t.model.set(t.collection.where({name:e})[0].attributes)},1e3)},render:function(){var e=this.template(this.model);$("body").html(e)},showSpinner:function(){$("body").html(this.templateSpinner)}})}),define("apples",[],function(){return Backbone.Collection.extend({})}),requirejs.config({urlArgs:"bust="+(new Date).getTime()}),require(["apple-item.tpl","apple-home.tpl","apple-spinner.tpl","apple.tpl","apple-item.view","apple-home.view","apple.view","apples"],function(e,t,i,n,a,l,p,o){var r,s=[{name:"fuji",url:"img/fuji.jpg"},{name:"gala",url:"img/gala.jpg"}],c=Backbone.Router.extend({routes:{"":"home","apples/:appleName":"loadApple"},initialize:function(){var e=new o;e.reset(s),this.homeView=new l({collection:e}),this.appleView=new p({collection:e})},home:function(){this.homeView.render()},loadApple:function(e){this.appleView.loadApple(e)}});$(document).ready(function(){r=new c,Backbone.history.start()})}),define("apple-app",function(){});

Note: The file is not formatted on purpose to show how Uglify2 works. Without the line break escape symbols, the code is on one line. Also notice that variables and objects’ names are shortened.

Super Simple Backbone Starter Kit

To jump-start your Backbone.js development, consider using Super Simple Backbone Starter Kit or similar projects:

Azat Mardanov


Azat Mardanov has over 12 years of experience in web, mobile and software development. With a Bachelor’s Degree in Informatics and a Master of Science in Information Systems Technology degree, Azat possesses deep academic knowledge as well as extensive practical experience.

Recently, Azat has worked as a CTO/co-founder at Gizmo — an enterprise cloud platform for mobile marketing campaigns, and has undertaken the prestigious 500 Startups business accelerator program. Previously, he was developing mission-critical applications for government agencies in Washington, DC: National Institutes of Health, National Center for Biotechnology Information, Federal Deposit Insurance Corporation, and Lockheed Martin. Azat is a frequent attendee at Bay Area tech meet-ups and hackathons (AngelHack hackathon ’12 finalist with team FashionMetric.com).

Currently, he works as an engineer at the curated social media news aggregator website, Storify.com. He mentors entrepreneurs as a hacker in residence at startup accelerator and fund StartupMonthly, where he is teaching technical Rapid Prototyping with JavaScript and Node.js training to much acclaim. In his spare time, Azat writes about technology on his blog: webAppLog.com.

Azat Mardanov

Contact


Let's stay in touch!

If you have any questions please use the contact form or choose a method below.

Twitter: @RPJSBook