Monday, 29 April 2013

Simulation of Google Line Chart real time updates with Backbone.js and Require.js


This Backbone.js an Require.js AMD app is designed to load modular code asynchronously in the browser.

On the report section a simple line chart is created using google visualization API. Real  time updates of the chart are simulated by using a timer to add data incrementally to the chart.


It contains 2 separate views (about and reports sections) which are defined by a router and can be access directly via URL. It retrieves a collection, from a JSON API external to the application. It relies on google line chart visualization library to render the collection. Real – time updates are simulated by inserting extra rows  of cells that contain randomly generated data.  Nice transition animation is also included.
As usual we start by preparing our index.html page in the root directory:
index.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Simple Line Chart</title>
        <link href="css/bootstrap.css" rel="stylesheet">
        <script data-main="js/main" src="js/libs/require.js"></script>
    </head>
    <body>
        <div class="navbar">
            <div class="navbar-inner">
                <div class="container">
                    <a class="brand" href="#">
                        Simple Line Chart
                    </a>
                    <ul class="nav">
                        <li><a href="#about">About</a></li>
                        <li><a href="#reports">Reports</a></li>
                    </ul>
                </div>
            </div>
        </div>
        <div id="content"></div>

        <!-- Templates -->
        <script type="text/template" id="aboutApp">
            <table class="table table-bordered table-striped">
  <tr>
    <td>Create a small app using Require.js  and Backbone.js and other libraries of your choice with a reports section module.</td>
    <td>This Backbone.js an Require.js AMD app is designed to load modular code asynchronously in the browser.</td>
  </tr>
  <tr>
    <td>On the report section create a simple line chart using a library of your choice. Simulate real – time updates of the chart by using a timer to add data incrementally to the chart. Optionally include transition animation.</td>
    <td>It contains 2 separate views (about and reports sections) which are defined by a router and can be access directly via URL. It retrieves a collection, from a JSON API external to the application. It relies on google line chart visualization library to render the collection. Real – time updates are simulated by inserting extra rows  of cells that contain randomly generated data.  Nice transition animation is included.</td>
  </tr>
  <tr>
    <td>Include a nice layout using CSS.  Create clear separation between model , view , and controllers using OO inheritance , encapsulation, abstraction, and polymorphism.</td>
    <td>It uses Twitter Bootstrap framework which was made to not only look and behave great in the latest desktop browsers (as well as IE7!), but in tablet and smartphone browsers via responsive CSS as well. Backbone.js library provides structure to web applications by using models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface. In this test we configure Require.js to create a shortcut alias to commonly used scripts such as jQuery, Underscore and Backbone.</td>
  </tr>
  <tr>
    <td>The application should be able to scale with development. For example, the architecture should work to allow new modules and features added in the future.</td>
    <td>Applications are not created equal and the reality is that the answer probably consists of a collection of patterns that can be used as necessary given the situation. At the end of the day the goal should be to create a code-base that is easy to understand, implement and maintain. ;</td>
  </tr>
</table>
        </script>
        <script type="text/template" id="lineChart">
  <p>Simple line chart - in draw visualization:</p>
        <div id="lc" style="width:600px; height:300px;"></div>
        </script>
    </body>
</html>
The HTML file tells Require.js to execute the main.js file in the script directory.
main.js
// Require.js allows us to configure shortcut alias
require.config({
 paths: {
  jquery: 'libs/jquery',
  underscore: 'libs/underscore',
  backbone: 'libs/backbone'
 },
 shim: {
  underscore: {
   exports: "_"
  },
  backbone: {
   deps: ['underscore', 'jquery'],
   exports: 'Backbone'
  }
 }
});
require([
// Load our app module and pass it to our definition function
'app', ], function(App) {
 // The "app" dependency is passed in as "App"
 App.initialize();
});
We build our application main module in app.js:
app.js
define(['jquery', 'underscore', 'backbone', 'router' // Request router.js
], function($, _, Backbone, Router) {
 var initialize = function() {
  // Pass in our Router module and call it's initialize function
  Router.initialize();
 }
 return {
  initialize: initialize
 };
});
After initiliasing our main module we can set up the Backbone Router will load the correct dependencies depending on the current URL.
router.js
define(['jquery', 'underscore', 'backbone', 'views/titleListView', 'views/resultsListView'], function($, _, Backbone, AboutListView, ResultsListView) {
 //Define Namespace (RL shorthand for report list)
 var RL = RL || {};
 /* App Router */
 var AppRouter = Backbone.Router.extend({
  routes: {
   "about": "listAbout",
   "reports": "listReports",
   "": "listAbout"
  },
  initialize: function() {
   this.about = new RL.AboutCollection();
   this.lineChart = new RL.ChartCollection();
   this.lineChart.fetch();
   this.aboutView = new AboutListView({
    model: this.about
   });
   this.resultsView = new ResultsListView({
    model: this.lineChart
   });
  },
  listAbout: function() {
   window.clearInterval(this.interval)
   $('#content').html(this.aboutView.render().el);
  },
  listReports: function() {
   this.interval = window.setInterval(_.bind(this.resultsView.onTimerTick, this.resultsView), 1000);
   $('#content').html(this.resultsView.render().el);
  }
 });
 //Models
 //A About
 RL.About = Backbone.Model.extend({});
 // Collections 
 RL.AboutCollection = Backbone.Collection.extend({
  model: RL.About
 });
 // Collections 
 RL.ChartCollection = Backbone.Collection.extend({
  model: RL.About,
  url: "data/lineChart.json"
 });
 var initialize = function() {
  RL.app = new AppRouter();
  Backbone.history.start();
 }
 return {
  initialize: initialize
 };
});
Using our modular views we interact with the DOM and load in JavaScript templates.
resultListView.js
define(['jquery', 'underscore', 'backbone', 'https://www.google.com/jsapi'], function($, _, Backbone) {
 var ResultsListView = Backbone.View.extend({
  template: _.template($('#lineChart').html()),
  onTimerTick: function() {
   //removes the first row
   this.data.removeRow(0);
   //static var x is a tick counter
   this.x || (this.x = this.data.getNumberOfRows() + 1);
   this.x++;
   // Generating a random a, b pair and inserting it so rows are sorted.
   var a = Math.floor(Math.random() * 100);
   var b = Math.floor(Math.random() * 100);
   //adds extra row
   this.data.insertRows(this.data.getNumberOfRows(), [
    [this.x.toString(), a, b]
   ]);
   this.chart.draw(this.data, this.options);
  },
  render: function() {
   google.load('visualization', '1', {
    'callback': _.bind(this.drawVisualization, this),
    'packages': ['corechart']
   });
   return this;
  },
  drawVisualization: function() {
   this.data = new google.visualization.DataTable(this.model.models[0].attributes);
   //In draw visualization
   this.options = {
    animation: {
     duration: 800,
     easing: 'out',
    },
    vAxis: {
     minValue: 0,
     maxValue: 100
    }
   }
   $(this.el).html(this.template());
   this.chart = new google.visualization.LineChart(this.$('#lc').get(0));
   this.chart.draw(this.data, this.options);
  }
 });
 // Our module now returns our view
 return ResultsListView;
});

titleListView.js
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone) {
 var TitleListView = Backbone.View.extend({
  initialize: function() {
   this.model.bind("reset", this.render, this);
  },
  template: _.template($('#aboutApp').html()),
  render: function(e) {
   $(this.el).html(this.template());
   return this;
  }
 });
 // Our module now returns our view
 return TitleListView;
});

It uses Twitter Bootstrap framework which was made to not only look and behave great in the latest desktop browsers (as well as IE7!), but in tablet and smartphone browsers via responsive CSS as well. Backbone.js library provides structure to web applications by using models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface. In this test we configure Require.js to create a shortcut alias to commonly used scripts such as jQuery, Underscore and Backbone.

The application is scalable - e.g.: the architecture should work to allow new modules and features added in the future. In order to create a code-base that is easy to understand, implement and maintain  a clear separation between model , view, and controllers using OO inheritance, encapsulation, abstraction, and polymorphism is implemented.
  
You can fork this AMD app here: 

1 comment: