Chapter 8. Data-driven Applications

In this chapter, we will create a data-driven application using data from the Human Development Data API from the United Nations website. We will use D3 to create a reusable chart component, and use Backbone to structure and maintain the application state. We will learn how to use Jekyll to create web applications using templates and a simplified markup language. We will also learn how to host our static site both on Amazon Simple Storage Service (S3) and GitHub Pages.

Creating the application

In this section, we will create a data visualization to explore the evolution of the Human Development Index (HDI) for different countries, and show the life expectancy, education, and income components of the index. We will create the visualization using D3 and Backbone.

The HDI is a composite statistic of life expectancy, education, and income created to compare and measure the quality of life in different countries. This indicator is used by the United Nations Development Program to measure and report the progress of the ranked countries in these areas.

In this visualization, we want to display how a particular country compares to other ranked countries in the evolution of the index. We will use the Human Development Data API to access the time series of the HDI for the ranked countries and to retrieve information about their main components.

The chart will show the evolution of the HDI for all the ranked countries, highlighting the selected country. In the right-hand side pane, we will display the main components of the HDI: life expectancy at birth, mean and expected years of schooling, and gross national income per capita (GNI). As there are almost two hundred ranked countries, we will add a search form with autocompletion to search among the countries. Selecting a country in the search input field will update the chart and the right-hand side pane.

Creating the application

A screenshot of the visualization elements

We will implement our application using D3 and Backbone, following the same pattern as presented in Chapter 6, Interaction between Charts. We will also leverage other libraries to provide the design elements and the functionality that we need.

The project setup

When creating software, we are continuously modifying our own work and the work developed for others. The ability to control how those changes are integrated in the project codebase and how to recover the previous versions is the heart of version control systems. As with many other tools, there are plenty of tools available, each with it's own characteristics.

Git is a popular version control system. It's distributed, which means that it's not necessary to have a centralized location as the reference point; each working copy of the repository can be used as a reference. In this section, we will use Git as a version control system. We can't include an introduction to Git in this book; you can learn about Git from its website (http://git-scm.com).

Some content in this chapter is specific to Git and GitHub, a code-hosting repository based on Git, specifically the sections on how to use GitHub Pages to host static pages and the setup of the project. The example application can be implemented even without a version control system or hosting service.

We will begin our application by creating a repository for it in GitHub. To create a repository, go to http://www.github.com, sign in with your account (or create an account), and select +New repository. To create a repository, we will need to add a name and description, and optionally, select a license and add a README file. After doing this, the repository URL will be generated; we will use this URL to clone the project and modify it. In our case, the URL of the repository is https://github.com/pnavarrc/hdi-explorer.git. Cloning the repository will create a new directory, containing the initial content set on GitHub, if any:

$ git clone https://github.com/pnavarrc/hdi-explorer.git

Alternatively, we could have created an empty directory and initialized a Git repository in it to begin working on it right away.

$ mkdir hdi-explorer
$ cd hdi-explorer
$ git init

If we decide to use GitHub, we can add a remote repository to push our code. Remote repositories are locations on the Internet or in a network to make our code accessible for others. It's customary to set the origin remote to the primary repository for the project. We can set the origin remote and push the initial version of our project by executing the following commands in the console:

$ git remote add origin https://github.com/pnavarrc/hdi-explorer.git
$ git push -u origin

In either case, we will have a configured repository that is ready to be worked on.

As you may remember, we learned how to use Bower to manage the frontend dependencies in Chapter 7, Creating a Charting Package. As we will be using several libraries, we will begin by creating the bower.json file using bower init. We will set the name, version, author, description, and home page of our project. Our bower.json file will contain the basic information of our project as follows:

{
  "name": "hdi-explorer"
  "version": "0.1.0"
  "authors": [
    "Pablo Navarro <[email protected]>"
  ],
  "description": "Human Development Index Explorer"
  "main": "index.html"
  "homepage": "http://pnavarrc.github.io/hdi-explorer"
  "private": true,
  "dependencies": {}
}

As mentioned before, we will use D3 and Backbone to create the charts and structure our application. We will install the dependencies in our project:

$ bower install --save-dev d3 backbone underscore

This will create the bower_components directory and download the packages for us. We will also need Bootstrap and Font Awesome to include the HDI component icons. We will install these packages as well. As jQuery is a dependency of Bootstrap, Bower will install it automatically:

$ bower install --save-dev bootstrap font-awesome typeahead.js

We will use the Typeahead library from Twitter to add autocompletion to our search form. Installing packages with the --save-dev option will update our bower.json file, adding the packages to the development dependencies. These libraries would normally be regular dependencies; we are including them as development dependencies because we will later create a file with all the dependencies in that file.

{
  "name": "hdi-explorer"
  // ...
  "devDependencies": {
    "d3": "~3.4.1"
    "bootstrap": "~3.1.0"
    "backbone": "~1.1.0"
    "underscore": "~1.5.2"
    "font-awesome": "~4.0.3"
    "typeahead.js": "~0.10.0"
  }
}

Using Bower will help us to manage our dependencies and update the packages without breaking our application. Bower requires its packages to adhere to semantic version numbering, and it's smart enough to update only the nonbackwards-incompatible releases.

Generating a static site with Jekyll

In the previous chapters, we covered how to create data-driven applications and how to use tools to make this task easier. In this section, we will cover how to generate websites using Jekyll and host web applications using Jekyll and GitHub Pages.

Jekyll is a simple, blog-aware, static site generator written in Ruby. This means that we can create a website or blog without having to install and configure web servers or databases to create our content. To create pages, we can write them in a simple markup language and compile them in HTML. Jekyll also supports the use of templates and partial HTML code to create the pages.

In Linux and OS X, we can install Jekyll from the command line. Remember that in order to use Jekyll, Ruby and RubyGems should be installed. To install Jekyll, run the following command in the console:

$ gem install jekyll

For other platforms and more installation options, refer to the documentation available at http://jekyllrb.com/docs/installation/. Jekyll includes a generator that configures and creates the project boilerplate with sample content and templates (see jekyll new --help), but this time, we will create the templates, content, and configuration from scratch. We will begin by creating the subdirectories and files needed for the Jekyll components:

hdi-explorer/
   _includes/
      navbar.html
   _layouts/
      main.html
   _data/
   _drafts/
   _posts/
   index.md
   _config.yml

The _config.yml file is a configuration file written in YAML, a serialization standard similar to JSON but with additional types, support for comments, and which uses indentation instead of brackets to indicate nesting (for more information, see http://www.yaml.org/). The content in this file defines how Jekyll will generate the content and defines some site-wide variables. For our project, the _config.yml file defines some Jekyll options and the name, base URL, and repository of our site:

# Jekyll Configuration
safe: true
markdown: rdiscount
permalink: pretty

# Site
name: Human Development Index Explorer
baseurl: http://pnavarrc.github.io/hdi-explorer
github: http://github.com/pnavarrc/hdi-explorer.git

The safe option disables all the Jekyll plugins, the markdown option sets the markdown language that we will use, and the permalink option defines which kind of URL we want to generate. There are additional options to set the time zone and excluded files, among others.

The _layouts directory will contain page templates with placeholders to be replaced by content. On each page, we can declare which layout we will use in a special area called the YAML front matter, which we will describe later. The templates can have variables, such as the {{ content }} tag, and include the {% include navbar.html %} tag. The content of the variables is defined either in the front matter or in the _config.yml file. The include tags replace the block with the content of the corresponding file in the _includes directory. For instance, our main.html template contains the following base page structure:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{ page.title }}</title>
  <link href="{{ site.baseurl }}/hdi.css" rel="stylesheet">
</head>
<body>
  <!-- Navigation Bar -->
  {% include navbar.html %}

  <!-- Content -->
  <div class="container-fluid">
    {{ content }}
  </div>
</body>
</html>

The value of the {{ site.baseurl }} variable is set in the _config.yml file, the {{ page.title }} variable will use the value of the font matter of the pages using the template, and the {{ content }} variable will be replaced with the content of the files that use the template. In the _config.yml file, we defined the baseurl variable to http://pnavarrc.github.io/hdi-explorer. When Jekyll uses this template to generate content, it will replace the {{ site.baseurl }} variable with http://pnavarrc.github.io/hdi-explorer, and the generated page will have the complete URL for the CSS style, http://pnavarrc.github.io/hdi-explorer/hdi.css. A complete reference to the Liquid Templating language is available at http://liquidmarkup.org/.

The _includes directory contains the HTML fragments to be included in other pages. This is useful to modularize some parts of our page, such as to separate the footer, header, or navigation bar. Here, we created the navbar.html file with the content of our navigation bar:

<!-- Navigation Bar -->
<nav class="navbar navbar-default" role="navigation">
  <div class="container-fluid">
    <!-- ... more elements -->
    <a class="navbar-brand" href="#">{{ site.name }}</a>
    <!-- ... -->
  </div>
</nav>

The content of the navbar.html file will replace the {% include navbar.html %} liquid tag in the templates. The files in the _includes directory can also contain liquid tags, which will be replaced properly.

The _posts directory contains blog posts. Each blog post should be a file with a name in the YEAR-MONTH-DAY-title.MARKDOWN form. Jekyll will use the filename to compute the date and URL of each post. The _data directory contains additional site-wide variables, and the _draft directory contains posts that we will want to publish later. In this project, we won't create posts or drafts.

The index.md file contains content to be rendered using some of the layouts in the project. The beginning of the file contains the YAML front matter, that is, the lines between three dashes. This fragment of the file is interpreted as YAML code and is used to render the templates. For instance, in the main template, we had the {{ page.title }} placeholder. When rendering the page, Jekyll will replace this with the title variable in the page's front matter. The content after the front matter will replace the {{ content }} tag in the template.

---
layout: main
title: HDI Explorer
---

<!-- Content -->
Hello World

We can add any number of variables to the front matter, but the layout variable is mandatory; so, Jekyll knows which layout should be used to render the page. In this case, we will use the main.html layout and set the title of the page to HDI Explorer. The content of the page can be written in HTML or in text-to-HTML languages, such as Markdown or Textile. Once we have created the layouts and initial content, we can use Jekyll to generate the site.

$ jekyll build

This will create the _site directory, which contains all the content of the directory except the Jekyll-related directories such as _layouts and _includes. In our case, it will generate a directory containing an index.html file. This file is the result of injecting the contents of the index.md, navbar.html, and _config.yml files in the main.html layout. The generated file will look as follows:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>HDI Explorer</title>
  <link href="http://pnavarrc.github.io/hdi-explorer/hdi.css" rel="stylesheet">
</head>
<body>
  <!-- Navigation Bar -->
  <nav class="navbar navbar-default" role="navigation">
    <div class="container-fluid">
      <!-- ... more elements -->
      <a class="navbar-brand" href="#">
          Human Development Index Explorer
      </a>
      <!-- ... -->
    </div>
  </nav>

  <!-- Content -->
  <div class="content">
    <!-- Content -->
    <p>Hello World</p>
  </div>
</body>
</html>

We can see that the generated file is pure HTML. Using Jekyll allows us to modularize the page, separate its components, and allows us to focus on writing the actual content of each page.

Jekyll also allows us to serve the generated pages locally, watching for changes in the project files. To do this, we need to overwrite the baseurl variable in order to use the localhost address instead of the value defined in the configuration file. As the addresses will be relative to the project directory, we can set the base URL to an empty string:

$ jekyll serve --watch --baseurl=

We can now access our site by pointing the browser to http://localhost:4000 and use our site. In the next section, we will create the contents of our application using D3 and Backbone, integrating the JavaScript files, styles, and markup with the Jekyll templates and pages created in this section.

Creating the application components

We will separate the Backbone application components from the chart; we will put the models, collections, views, and setup in the js/app directory:

js/
  app/
    models/
      app.js
      country.js
    collections/
      countries.js
    views/
      country.js
      countries.js
    app.js
    setup.js

In the app.js file, we just define a variable that will have the components of our application:

// Application container
var app = {};

The setup.js file contains the creation of the model, collection, and view instances; the binding of the events; and the callbacks of different components. We will review the models, collections, and views in detail later.

Creating the models and collections

The application model will reflect the application state. In our application, the selected country defines the state of the application; we will use three-letter country codes as the only attribute of the application model:

// Application Model
app.ApplicationModel = Backbone.Model.extend({
    // Code of the Selected Country
    defaults: {
        code: ''
    }
});

We will have two additional models: the CountryInformation model will represent information about the current HDI value and its main components, and the CountryTrend model will contain information about the country and time series of HDI measurements.

The data source for these models will be an endpoint of the Human Development Data API. The API allows us to retrieve data about poverty, education, health, social integration, and migrations, among many others. A complete list of the API endpoints and some examples of queries are available at the Human Development Data API website (http://hdr.undp.org/en/data/api). The API exposes the data in several formats and receives parameters to filter the data.

The CountryInformation model will retrieve information about the Human Development Index and its Components endpoint. For instance, we can access this endpoint by passing name=Germany as the parameter of the request. The request to http://data.undp.org/resource/wxub-qc5k.json?name=Germany will return a JSON file with the main components of the HDI:

[ 
  {
    "_2011_expected_years_of_schooling_note" :"e",
    "_2012_life_expectancy_at_birth" :"80.6",
    "_2012_gni_per_capita_rank_minus_hdi_rank" :"10",
    "_2012_hdi_value" :"0.920",
    "type" : "Ranked Country",
    "abbreviation" : "DEU",
    "_2010_mean_years_of_schooling" :"12.2",
    "_2011_expected_years_of_schooling" :"16.4",
    "name" : "Germany",
    "_2012_hdi_rank" :"5",
    "_2012_gross_national_income_gni_per_capita" :"35431",
    "_2012_nonincome_hdi_value" :"0.948"
  }
]

We will define the model such that it has the name and code attributes and some of the information provided by the JSON file. We will add the url, baseurl, and urltpl attributes to construct the URL for each country, as follows:

// Country Information Model
app.CountryInformation = Backbone.Model.extend({

    // Default attributes, the name and code of the country
    defaults: {
        code: '', 
        name: ''
    },

    // URL to fetch the model data
    url: '',

    // Base URL
    baseurl: 'http://data.undp.org/resource/wxub-qc5k.json',

    // URL Template
    urltpl: _.template('<%= baseurl %>?Abbreviation=<%= code %>')
});

Each country has the abbreviation field; this field contains the code for the country. We will use this code as the ID of the country. The names of the attributes of the JSON object contain data; for instance, the _2012_life_expectancy_at_birth attribute contains the year of the measurement. If we create an instance of the model and invoke its fetch method, it will retrieve the data from the JSON endpoint and add attributes to each attribute of the retrieved object. This will be a problem because the endpoint returns an array and not all the countries have up-to-date measurements. In the case of Germany, the only object in the array has the _2012_life_expectancy_at_birth attribute, but in the case of other countries, the most recent measurements could be from 2010.

To have a uniform representation of the data, we can strip the year out of the attribute names before we set the attributes for the model. We can do this by setting the parse method, which is invoked when the data is fetched from the server. In this method, we will get the first element of the retrieved array and strip the first part of the attributes beginning with _ to only have attributes of the form life_expectancy_at_birth:

    // Parse the response and set the model contents
    parse: function(response) {

        // Get the first item of the response
        var item = response.pop(),
            data = {
                code: item.abbreviation, 
                name: item.name
            };

        // Parse each attribute
        for (var attr in item) {
            if (attr[0] === '_') {
                // Extract the attribute name after the year
                data[attr.slice(6)] = item[attr];
            }
        }

        // Return the parsed data
        return data;
    }

We will also add a method to update the model with the selected country in the application. The setState method will receive the application model and use its code attribute to construct the URL for the selected country and to fetch the new information, as follows:

setState: function(state) {
    // Construct the URL and fetch the data
    this.url = this.urltpl({
        baseurl: this.baseurl, 
        code: state.get('code')
    });
    this.fetch({reset: true});
}

We will create the CountryTrend model to store the trends of the HDI for each country, and the Countries collection to store the CountryTrend instances. The CountryTrend model will hold the country code and name, a flag to indicate whether the country has been selected or not, and a series of HDI measurements for different years. We will use the code of the country as an ID attribute:

// Country Trend Model
app.CountryTrend = Backbone.Model.extend({

    // Default values for the Country Trend Model
    defaults: {
        name: '',
        code: '',
        selected: false,
        hdiSeries: []
    },

    // The country code identifies uniquely the model
    idAttribute: 'code'
});

The Countries collection will contain a set of the CountryTrend instances. The collection will have an endpoint to retrieve the information for the model instances. We will need to define the parse method in the CountryTrend model, which will be invoked automatically before new CountryTrend instances are generated. In the parse method, we construct an object with the attributes of the new model instance. The data retrieved for the collection will have the following structure:

{
  _1990_hdi: "0.852"
  _1980_hdi: "0.804"
  _2000_hdi: "0.922"
  // ...
  _2012_hdi: "0.955"
}

Here, the year of the HDI measurement is contained in the key of the object. There are other attributes also, which we will ignore. We will split the attribute name using the _ character to extract the year:

    // Parse the country fields before instantiating the model
    parse: function(response) {

        var data = {
            code: response.country_code,
            name: response.country_name,
            selected: false,
            hdiSeries: []
        };

        // Compute the HDI Series
        for (var attr in response) {
            var part = attr.split('_'),
                series = [];

            if ((part.length === 3) && (part[2] === 'hdi')) {
                data.hdiSeries.push({
                    year: parseInt(part[1], 10),
                    hdi: parseFloat(response[attr])
                });
            }
        }

        // Sort the data items
        data.hdiSeries.sort(function(a, b) { 
            return b.year - a.year; 
        });

        return data;
    }

The data object will contain the country's code, name, the selected flag, and the array of hdiSeries, which will contain objects that have the year and HDI value. The Countries collection will contain the CountryTrend instances for each country. The data will be retrieved from the Human Development Index Trends endpoint. We set the collection model, the endpoint URL, and a parse method, which will filter the items that have a country code (there are items for groups of countries). We will also add a method to set a selected item in order to ensure that there is only one selected item:

// Countries Collection
app.Countries = Backbone.Collection.extend({

    // Model
    model: app.CountryTrend,

    // JSON Endpoint URL
    url: 'http://data.undp.org/resource/efc4-gjvq.json',

    // Remove non-country items
    parse: function(response) {
        return response.filter(function(d) { 
            return d.country_code; 
        });
    },

    // Set the selected country
    setSelected: function(code) {

        var selected = this.findWhere({selected: true});

        if (selected) { 
            selected.set('selected', false); 
        }

        // Set the new selected item
        selected = this.findWhere({code: code});
        if (selected) {
            selected.set('selected', true); 
        }
    }
});

We will proceed to create the views for the models.

Creating the views

The application will have three views: the chart with the trends of HDI values for the ranked countries, the information view at the right-hand side, and the search form.

CountriesTrendView is a view of the Countries collection, which displays the evolution of the HDI for the ranked countries. As we did in Chapter 6, Interaction between Charts, we will create a Backbone View that will contain an instance of a D3-based chart:

// Countries Trend View
app.CountriesTrendView = Backbone.View.extend({

    // Initialization and render
    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
        this.listenTo(this.collection, 'change:selected', this.render);
    }
});

In the initialize method, we start listening for the reset event of the collection and the change:selected event, which are triggered when an element of the collection is selected. In both cases, we will render the view. We will add and configure an instance of the D3-based chart, hdi.chart.trends:

app.CountriesTrendView = Backbone.View.extend({
    // Initalization and render...

    // Initialize the trend chart
    chart: hdi.chart.trend()
        .series(function(d) { return d.hdiSeries; })
        .x(function(d) { return d.year; })
        .y(function(d) { return d.hdi; }),

    // Initialize and render methods...
});

We will skip the description of the hdi.chart.trend chart for brevity, but as usual, the chart is implemented using the reusable chart pattern and has accessor methods to configure its behavior. The chart displays the time series of HDI measurements for all the ranked countries, highlighting the line bound to a data item with the selected attribute set to true.

Creating the views

The HDI trend chart

In the render method, we get the width of the container element of the view and update the width of the chart using this value. We select the container element, bind the collection data to the selection, and invoke the chart:

    // Update the chart width and bind the updated data
    render: function() {
        // Update the width of the chart
        this.chart.width(this.$el.width());

        // Rebind and render the chart
        d3.select(this.el)
            .data([this.collection.toJSON()])
            .call(this.chart);
    },

We will also add the setState method to change the selected country of the underlying collection. This method will help us to update the selected item of the collection when the application model changes the selected country. We will do this later in the application setup:

    // Update the state of the application model
    setState: function(state) {
        this.collection.setSelected(state.get('code'));
    }

The search form will allow the user to search among the ranked countries to select one of them. We will use the Typeahead jQuery plugin from Twitter (http://twitter.github.io/typeahead.js/) to provide autocompletion, and we will populate the suggestion list with the items in the Countries collection. The Typeahead plugin contains two components: Bloodhound, the autocompletion engine, and Typeahead, the plugin that adds autocompletion capabilities to an input field.

In the initialize method, we bind the reset event of the collection to the render method in order to update the view when the list of country trends is retrieved:

// Search Form View
app.CountriesSearchView = Backbone.View.extend({

    // Initialize
    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
    },

    // Events and render methods...
});

The DOM element associated with this view will be a div containing the search form, which is located in the navigation bar. We assign an ID to the div and to the input field:

<div class="form-group" id="search-country">
  <input type="text" class="form-control typeahead"
         placeholder="Search country"id="search-country-input">
</div>

To provide autocompletion, we need to initialize the autocompletion engine and add the autocompletion features to the search input item:

    // Render the component
    render: function() {
        // Initialize the autocompletion engine
        // Add autocompletion to the input field
    },

We initialize the Typeahead autocompletion engine; setting the datumTokenizer option is a function that, given a data element, returns a list of strings that should be associated with the element. In our case, we want to match the country names; so, we use the whitespace tokenizer and return the country name split by whitespace characters. The input of the search field will be split using whitespace characters too. We add the list of elements among which we want to search, which in our case are the elements in the collection:

    // Render the component
    render: function() {
        // Initialize the autocompletion engine
        this.engine = new Bloodhound({
            datumTokenizer: function(d) { 
                return Bloodhound.tokenizers.whitespace(d.name); 
            },
            queryTokenizer: Bloodhound.tokenizers.whitespace,
            local: this.collection.toJSON()
        });
        this.engine.initialize();

        // Add autocompletion to the input field...
    },

To add the autocompletion features to the search form, we select the input element of the view and configure the typeahead options. In our case, we just want to show the name of the country and use the engine dataset as the source for the autocompletion:

        // Render the element
        this.$el.children('#search-country-input')
            .typeahead(null, {
                displayKey: 'name',
                source: this.engine.ttAdapter()
            });

When the user begins to type in the input field, the options that match the input will be displayed. When the user selects an option, the typeahead:selected event will be triggered by the input element. We will add this to the event's hash of the view, binding the event to the setSelected callback:

    events: {
        'typeahead:selected input[type=text]': 'setSelected'
    },

Note that the typeahead:selected event is a jQuery event. The callback will receive the event and the data item selected by the user, and it will update the selected item in the collection, as follows:

    //  Update the selected item in the collection
    setSelected: function(event, datum) {
        this.collection.setSelected(datum.code);
    }
Creating the views

The Typeahead autocompletion in action

The last view will be CountryInformationView. This view is a visual representation of the CountryInformation model. For this view, we will add the _includes/country-information.html file with the contents of the template and include it in the index.md file:

---
layout: main
title: HDI Explorer
---

{% include country-information.html %}

<!-- More content... -->

The template will contain several internal div elements; we will show only a part of the template here:

<!-- Country Information Template -->
<script type="text/template" id="country-summary-template">

<!-- Country Name and Rank -->
<div class="row country-summary-title">
  <div class="col-xs-8"><%= name %></div>
  <div class="col-xs-4 text-right">#<%= hdi_rank %></div>
</div>

<!-- HDI Value and Rank of the Country -->
<div class="row country-summary-box">
  <!-- Header -->
  <div class="col-xs-12 country-summary-box-header">
    <i class="fa fa-bar-chart-o fa-fw"></i>
    human development index
  </div>
  <!-- HDI Index -->
  <div class="col-xs-12">
    <div class="col-xs-9">human development index</div>
    <div class="col-xs-3 text-right"><%= hdi_value %></div>
  </div>
  <!-- Country Rank -->
  <div class="col-xs-12">
    <div class="col-xs-9">hdi rank</div>
    <div class="col-xs-3 text-right"><%= hdi_rank %></div>
  </div>
</div>

<!-- More divs with additional information... -->
</script>

Here, we create the structure of the bar on the right-hand side, which will contain the Human Development Index, rank, life expectancy, education statistics, and income for the selected country. We will use the Underscore templates to render this view. The view structure is simpler in this case; we just compile the template, listen to the changes of country name in the model, and render the template with the model data:

// Country Information View
app.CountryInformationView = Backbone.View.extend({
    // View template
    template: _.template($('#country-summary-template').html()),

    initialize: function() {
        // Update the view on name changes
        this.listenTo(this.model, 'change:name', this.render);
    },

    render: function() {
        // Render the template
        this.$el.html(this.template(this.model.toJSON()));
    }
});
Creating the views

The rendered view will display the current values for the HDI components

The application setup

With the models, collections, and views created, we can create the respective instances and bind events to callbacks in order to keep the views in sync. We begin by creating an instance of the application model and the collection of country HDI trends. In the js/app/setup.js file, we create and configure the model, collection, and view instances:

// Application Model
app.state = new app.ApplicationModel();

// HDI Country Trends Collection
app.countries = new app.Countries();

After the application's state changes, we will have to update the selected item in the Countries collection. We bind the change:code event of the application model to the callback that will update the selected item in the collection:

// Update the selected item in the countries collection
app.countries.listenTo(app.state, 'change:code', function(state){
    this.setSelected(state.get('code'));
});

We need to update the application state when the Countries collection is populated for the first time. We will set the application state's code attribute to the code of the first element in the collection of countries. We also bind the change:selected event of the collection to update the application model:

app.countries.on({
    'reset': function() {
        app.state.set('code', this.first().get('code'));
    },

    'change:selected': function() {
        var selected = this.findWhere({selected: true});
        if (selected) {
            app.state.set('code', selected.get('code'));
        }
    }
});

Note that when we are selecting an item, we are also deselecting another item. Both the items will trigger the change:selected event, but the application should change its state only when an item is selected. We can now fetch the countries data, passing the reset flag to ensure that any existing data is overwritten:

app.countries.fetch({reset: true});

We create an instance of the CountryInformation model and bind the changes to the code attribute of the application to the changes of the state in the model. The setState method will fetch the information for the code given by the application state:

// HDI Information
app.country = new app.CountryInformation();
app.country.listenTo(app.state, 'change:code', app.country.setState);

We can now create instances of the views. We will create an instance of the CountriesTrendView. This view will be rendered in the div element with the #chart ID:

// Countries Trend View
app.trendView = new app.CountriesTrendView({
    el: $('div#chart'),
    collection: app.countries
});

We create an instance and configure the CountriesSearchView. This view will be rendered in the navigation bar:

app.searchView = new app.CountriesSearchView({
    el: $('#search-country'),
    collection: app.countries
});

We also create a CountryInformationView instance, which will be rendered in the right-hand side of the page:

app.infoView = new app.CountryInformationView({
    el: $('div#table'),
    model: app.country
});

In the index.md file, we create the elements where the views will be rendered and include the application files. In the main.html layout, we include the CSS styles of the application, Bootstrap and Font Awesome:

---
layout: main
title: HDI Explorer
---

{% include country-information.html %}

<div class="container-fluid">
  <div class="row">
    <div class="col-md-8" id="chart"></div>
    <div class="col-md-4 country-summary" id="table"></div>
  </div>
</div>

<scriptsrc="{{ site.baseurl }}/dependencies.min.js"></script>
<scriptsrc="{{ site.baseurl }}/hdi.min.js"></script>
The application setup

The application served by Jekyll on a localhost

Here, we consolidated jQuery, Bootstrap, Underscore, Backbone, Typeahead, and D3 in the dependencies.min.js file and the application models, collections, views, and chart in the hdi.min.js file. To create these consolidated files, we created a Gruntfile and configured concatenation and minification tasks as we did in Chapter 7, Creating a Charting Package. As the configuration of the tasks is similar to the configuration presented in the previous chapter, we will skip its description.

It is also worth mentioning that in general, it is not a good practice to include complete libraries. For instance, we included the complete Bootstrap styles and JavaScript components, but in the application, we used only a small part of the features.

Bootstrap allows you to include the components individually, reducing the payload of the page and improving the performance. We also included the Font Awesome fonts and styles only to include four icons. In a project where performance is crucial, we will probably include only the components that we really need.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset