Introduction

In the previous article we’ve built a REST API for a voting application using sequelize. In this article we will consume this API and build the front end using emberjs. We also mentioned about, generator-emberfs, a yeoman generator for fullstack applications using emberjs and express, that we used to scaffold our application.

The voting application will be where users can vote for a poll and see the results of the poll. You can see a live example here.

Scaffold Ember Application

In the previous article we used generator-emberfs for server side, we will continue using it for client side as well. It supports scaffolding, asset compilation, optimization for production, live reloading and test driven development. It uses gulp for build system.

You can launch the server and listen for changes using the command:

gulp devserver

Or you can build and optimize your application for production:

gulp build

Directory Structure

This is our directory structure for client side. vendor folder contains third party libraries, templates folder contains handlebars templates, styles folder contains css styles in sass, scripts folder contains the emberjs application code.

app/client
|-- scripts
|   |-- app.js
|   |-- common.js
|   |-- components
|   |   `-- progress-bar.js
|   |-- controllers
|   |   `-- index_controller.js
|   |-- main.js
|   |-- mixins
|   |-- models
|   |   `-- poll_model.js
|   |-- router.js
|   |-- routes
|   |   `-- index_route.js
|   `-- views
|-- styles
|   `-- style.scss
|-- templates
|   `-- components
`-- vendor

Note that this folder structure is boilerplate for the generator-emberfs. It uses requirejs with AMD modules. But, yet we won’t get into details of requirejs in this article, rather stick to essential codes for emberjs.

Routes in Ember

We won’t define any particular route for this simple application. Rather we will use the default IndexRoute Ember provides.

App.Router.map(function() {
});

App.router.reopen({
  location: 'history'
});

Our IndexRoute will have the poll #1 as its model. Remember we have provided a seed model as the poll #1 in sequelize, in the previous article.

App.IndexRoute = Ember.Route.extend({
  model: function() {
    // GET api/v1/polls/1
    return this.store.find('poll', 1);
  }
});

this.store uses ember-data to pull the models. It uses an adapter, which is in our case a DS.RESTAdapter. So this code will make a request to api/v1/polls/1 to build a model. Later we can use the model in our controller and templates.

Templates in Handlebars

Let’s drop a skeleton template using bootstrap for style and handlebars for bindings. I will use emblem formatting just for efficient view.

File: templates/index.hbs

Here, we have a ` binding, main header for the poll. and onediv binding for showing the poll choices and allowing vote. and adiv ` binding for showing the poll results.

In the poll results we use a, progress-bar text=text percent=vote-percent, ember component to display the results, which we will mention later.

Finally we have various actions for communicating with the model click="showResults", and click="vote id". These actions are handled in ember controller and will be discussed later.

Models in Ember Data

Just as we defined data models for sequelize, we will define the same models for emberjs using ember-data. We will use ember-data DS.RESTAdapter for communicating with the REST API.

In the previous article We mentioned about our three models Poll, Choice, and Vote and the relationships between them. Here we define the same relationships using ember-data.

File: models/poll_model.js

App.Poll = DS.Model.extend({
    question: DS.attr('string'),
    choices: DS.hasMany('choice')
  });

  App.Choice = DS.Model.extend({
    text: DS.attr('string'),
    description: DS.attr('string'),
    count: DS.attr('number'),
    votes: DS.hasMany('vote'),
    poll: DS.belongsTo('poll')
  });

  App.Vote = DS.Model.extend({
    choice: DS.belongsTo('choice')
  });

Note that ember uses DS.attr for defining attributes, and DS.hasMany, DS.belongsTo methods for defining relationships.

If you visit /api/v1/polls/1 in your local server, you will see the json response for the poll #1:

{"poll":{
  "id":1,
  "question":"What features would you like to see",
  "choices":[{
    "id":1,
    "text":"Improved UI",
    "description":"New, fancy interface",
    "PollId":1,
    "count":0},{
    "id":2,
    "text":"Improved Performance",
    "description":"Faster response times",
    "PollId":1,
    "count":0
    }]
}}

Notice choices attribute is embedded within the poll model. Normally ember-data expects this to be side loaded. So we will have to use DS.EmbeddedRecordsMixin to enable ember-data to parse our embedded response.

App.PollSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
  attrs: {
    choices: { embedded: 'always' }
  }
});

Controllers in Ember

We will extend the default IndexController in our application. In ember, controllers communicate between the template and the model the extra properties defined in controller will be also available to the template. So ember controllers is in a way model decorators.

Controller Computed Properties (Model Decorators)

IndexController extends Ember.ObjectController and decorates the poll model. We have various computed properties that will be available to our template, and they will be live bounded to our model.

File scripts/controllers/index_controller.js

App.IndexController = Ember.ObjectController.extend({
  resultsHidden: true,
  
  pollChoices: function() {
    var choices = this.get('model.choices');
    
    var totalVotes = this.get('totalVotes');

    choices.map(function(item) {
      var votePercent = (item.get('count') / totalVotes) * 100;
      item.set('vote-percent', votePercent.toFixed(2));
      return item;
    });
    return choices;
  }.property('model.choices'),

  totalVotes: function() {
    var choices = this.get('model.choices');

    var totalVotes = choices.reduce(function(prev, item) {
      return prev + item.get('count');
    }, 0);

    return totalVotes;
  }.property('model.choices'),

  // ...
});

resultsHidden property toggles between poll results and voting. pollChoices computed property computes the total percentage of votes for each choice. totalVotes computed property computes the total number of votes for a poll. Note that .property('model.choices') function that is appended to computed property methods. That binds the model.choices to our computed property so whenever a change occurs computed property updates live.

Controller Actions

Controller also communicates between template and model by means of actions. Remember we had three actions in our template, we handle those actions inside our controller.

File: scripts/controllers/index_controller.js

actions: {
    showResults: function() {
      this.get('model').reload().then(function() {
        this.set('resultsHidden', false);
      }.bind(this));
    },
    hideResults: function() {
      this.set('resultsHidden', true);
    },

    vote: function(choiceId) {
      var choices = this.get('model.choices');

      var vote = this.store.createRecord('vote', {
        choice: choices.findBy('id', '' + choiceId)
      });

      var self = this;

      vote.save().then(function(vote) {
        self.send('showResults');
      }, function(vote) {
        self.send('showResults');
      });
    }
  }

showResults action toggles the resultsHidden property to false so the results are shown. Note that before showing the results, it reloads the model so we get the latest results. hideResults is the opposite, it goes back to the voting view. vote action is where the user votes. It takes a choiceId parameter that is the poll choice. We create a new vote record and save it.

Progress Bar Ember Component

Now let’s see how we define an ember component. In our case a progress bar for displaying the percentage of votes for a poll choice.

First we define a template for our ember component.

File: templates/components/progress-bar.hbs

Next we define the code for our ember component.

File: scripts/components/progress-bar.js

App.ProgressBarComponent = Ember.Component.extend({
  'progress-bar-type': function() {
    var barTypes = [
      'progress-bar-danger',
      'progress-bar-warning',
      'progress-bar-info',
      'progress-bar-success'
    ];
    
    var percent = Math.min(this.get('percent'), 100);
    var barType = barTypes[Math.floor((percent - 1) / 25)];

    return barType;
  }.property('percent'),

  'percent-style': function() {
    return 'width:' + this.get('percent') + '%';
  }.property('percent')
});

Here, we calculate progress-bar-type based on percentage, that will apply the bootstrap classes to the div and make the progress bar change colors with the percentage.

Setup the Ember Application

Finally we start our ember application and extend our ember application adapter.

var App = Ember.Application.create();

App.ApplicationAdapter = DS.RESTAdapter.extend({
  namespace: 'api/v1'
});

Note that extend the DS.RESTAdapter and we provide a namespace option to tell it that our REST API resides at api/v1 namespace.

Development and Production

You can use gulp test to run your client side tests. generator-emberfs uses coffeescript for test code. You can use ember-qunit for unit testing and ember test-helpers for integration tests. It uses testem as the test runner. But, yet since we are using a server side REST API, You have to provide an API Proxy configuration to testem to redirect your API calls to your server, and launch your server before running the tests.

At this point we have finished the application and it’s ready to ship. But first you need to build it using the command gulp build. This will compile all your assets, minify and optimize and concatenate your script and css files into the public directory. Now you can launch your server using the command: node config/app.

Conclusion

In this article we have built a front end for a REST API where users can vote for a poll and see the results of the poll using emberjs. We’ve built a template in handlebars, setup the models in ember-data, extended an ember controller and an ember component for displaying progress bars.

The full repository is at github. A live example is available here