This article walks through the process of building a simple MVC web app application using Ember and Ember CLI. If you are unfamiliar with these technologies, it will be helpful to review the previous sections of this quickstart guide before continuing.

In the previous two sections we learned how to generate a basic app skeleton, view it in the browser, and access tools for testing and debugging the code. Now we're ready to build some real functionality into our basic skeleton — we'll create a simple clock application that displays and updates the current local time, and allows a user to compare selected timezones.

As we build our application, you will be introduced to some core concepts of Ember.js and Model View Controller (MVC) architecture, and useful commands available through Ember CLI. You can view a demo of the app, and check out the complete source code on Github.

Set up

At this point, we are assuming that you have followed the instructions in Creating your first app, and are ready to start adding code to your skeleton app directory. If you have not yet been to this stage, go through the previous sections and return here.

Routes

In a typical web-based MVC architecture, routes tell an app that a certain part of the content/functionality will exist at a certain URL. The framework you are using will handle the dynamic generation of the required content at build time, and application of the JavaScript, CSS, etc. that makes that content work. Ember handles routes pretty well. The route is created using the command:

ember generate route my-route-name

This will generate:

  1. A JavaScript file in app/routes to control the route.
  2. A Handlebars template in app/templates to define the content that will appear at the named URL.
  3. A unit test file in tests/unit/routes where you can define a test for the functionality at your route.

Adding the current time

We'll start by adding a clock page to our app, to display the current time. To create a new page, we need to generate a route — run the following command in your terminal:

ember generate route clock

If you now run the ember serve command, our clock template will be available at http://localhost:4200/clock. If you navigate to this URL, however, you'll notice that it displays the same welcome message as our home page. In order to display the current time instead, we'll need to update our clock template.

Templates

Various different HTML templating systems exist for web apps. These tend to provide superior functionality over plain HTML, such as the ability to include variables that are dynamically updated when the app's state or data changes. Templates form the basis of the "V" (View) in MVC.

Ember uses Handlebars for templating.

Updating our clock template

Our template file has already been generated at app/templates/clock.hbs. By default, this contains only the following:

{{outlet}}

Handlebars uses curly braces to differentiate between plain HTML and templating logic — this is a variable that can be replaced with whatever you want (as defined in a controller, see later on). At the moment we are including a default welcome message, but let's change this to include a variable to output our local time.

Open app/templates/clock.hbs and replace {{outlet}} with the following:

<h2>Local Time: {{localTime}}</h2>

By wrapping localTime in these braces, our template will know to replace the word localTime with whatever value we specify.

If you return to http://localhost:4200/clock in your browser, you'll notice the page simply renders "Local Time: ". You may have expected to see "Local Time: {{localTime}}" printed to the page, but Handlebars knows we want to replace anything inside curly braces with our custom data. Since we haven't yet provided our template with this value, nothing will render in its place. In order to display the current local time, we'll need to create a corresponding clock controller.

Before you move on to the next part, notice that your application display updated automatically upon editing your clock template — no need to refresh the page or restart the server. This is a rather nice feature of Ember.

Controllers

Controllers provide a way for us to update and manipulate application data; they provide the application logic that states what should happen when the model changes (e.g. state, or data). Controllers are the "C" within MVC.

Ember provides an easy way to generate controllers for our models/routes that are automatically associated with the correct ones. You just use the following syntax:

ember generate controller name-of-my-route

This will create two new files:

  1. A JavaScript file in app/controllers to control that particular model/view.
  2. A unit test file in tests/unit/controllers where you can define a test for the functionality in your controller.

Generating a controller for our clock display

In order to display the local time on our clock template, we need a clock controller to specify and update that value. Using your terminal, run the following command:

ember generate controller clock

We should now have a controller file at app/controllers/clock.js. To set the value of localTime, we will add it as a property within Ember.Controller.extend(). Update your app/controllers/clock.js file as follows:

import Ember from 'ember';

export default Ember.Controller.extend({
  localTime: new Date().toLocaleTimeString()
});

Any property inside the controller is accessible inside that controller’s template (in this case app/templates/clock.hbs). We've defined the localTime property, and it's carried into our template context. Ember uses the matching file names of our controller and template to send the information to the appropriate template.

Now when we view http://localhost:4200/clock, we should see the current local time output.

Note: You'll notice a line at the top of many of your JavaScript files that imports an Ember variable. This makes sure every file where we are taking advantage of Ember actually has access to to the framework. Without this line, we wouldn't be able to organize our code the way Ember has mapped out for us.

Making the clock update every second

So far, our clock display just shows the time when the controller was initialized — and then doesn't change. This is not very useful; we ideally want to update it every second.

Let's go back to app/controllers/clock.js and add an updateTime method:

import Ember from 'ember';

export default Ember.Controller.extend({
    init: function() {
        // Update the time.
        this.updateTime();
    },
 
    updateTime: function() {
        var _this = this;
 
        // Update the time every second.
        Ember.run.later(function() {
            _this.set('localTime', new Date().toLocaleTimeString());
            _this.updateTime();
        }, 1000);
    },
 
    localTime: new Date().toLocaleTimeString()
});

In this section we've added two functions:

  1. init() is a special method in Ember that will run automatically when the controller is first initialized. You can put whatever set up code you might need here; in this case we are just invoking the updateTime() method.
  2. updateTime() uses Ember.run.later() to run the code inside the anonymous function after 1000ms. The function sets a new value for the localTime property (using the ember set() method), then runs the updateTime() function again, so the time is updated after each new second has gone by.

Now you should see your clock automatically update every second. This demonstrates how to work with Ember’s data-binding between controllers and templates. If you change a value in a controller, model, or view that’s wired up to a template, the template will automatically change that data for you.

The Application Template

So far we've been viewing our app through our clock route at http://localhost:4200/clock. But since this is going to be the main page of the app, it'd be nice to have it appear when we go to the root of our domain.

To do this, generate a main application route by entering the command below:

ember generate route application

At this point you may be prompted with the question [?] Overwrite app/templates/application.hbs? It is fine to go ahead and override the main application template, so type Y and press enter to confirm, as we will be updating this template later anyway. Just like the last time you ran the ember generate route command, you will get a new route, template and unit test generated.

At this point, open your app/routes/application.js file and update the contents like so:

import Ember from 'ember';

export default Ember.Route.extend({
    redirect: function() {
        this.transitionTo('clock');
    }
});

Here we are extending the default Ember Route functionality with a redirect() method that will forward any requests for the root of our application to the clock route using transitionTo(). We will now be redirected to our ticking clock whenever we hit http://localhost:4200/.

In addition to showing the clock on our main page, we're going to want some navigation that allows users to toggle between the clock and a timezones display, where users will select which timezones they'd like to compare. In our application template, let's add two links. One will correspond to our clock route, and the other will link to a timezones route that we will be creating shortly.

Update the contents of app/templates/application.hbs to look like this:

<h1 id='title'>It's 5'o'clock somewhere</h1>

<ul>
    <li>{{#link-to 'clock'}}Clock{{/link-to}}</li>
    <li>{{#link-to 'timezones'}}Manage Timezones{{/link-to}}</li>
</ul>

{{outlet}}

You'll notice the same Handlebars convention of using curly braces that we saw in our clock template. In this example, {{#link-to}} is a built-in Handlebars helper for creating links.

{{outlet}} is Ember's way of reserving a space on the page for the content a user is requesting to view. So when we click the links for our clock route or timezones route, the outlet will automatically render the content from the requested route.

If you go to look at your application now, you'll notice the page is blank. We've lost our clock that updates with the current local time. We must have an error in our code somewhere, so let's go and investigate now.

Debugging Practice

 If you open the console tab in your developer tools, you should see an error coming specifically from Ember:

Ember Error

This is because we are trying to link to a timezones route that does not yet exist. We'll do this in the following section, and see if it clears up the problem.

Models and Resources

Next, let's gather some timezones that a person can compare against their local time, helping them schedule their meetings with friends in San Francisco, Buenos Aires, and London.

We will need to create a database to store the timezones a user has selected. Being able to retrieve and update these records relies on having a solid blueprint for our data. In order to achieve this, we will create a model (the "M" in MVC) for our timezone data. Models represent the form and structure of our application data.

Generally, when you create an Ember model, you'll also want an accompanying route and template so you can view and interact with your model data. Together, these make up a resource. While each of these files could be generated separately, Ember CLI offers a resource generator to speed up the process:

ember generate resource plural-name-of-model

This command generates:

  1. A JavaScript file in app/routes to control the route for this model.
  2. A Handlebars template in app/templates to define the content that will appear at the route URL.
  3. A unit test file in tests/unit/routes where you can define a test for the functionality at your route.
  4. A JavaScript file in app/models to define the model and its properties
  5. A unit test file in tests/unit/models where you can define a test for the functionality of your model.

Generating our resource

Let's go ahead and generate our resource. Run the following command inside your app directory:

ember generate resource timezones

The following files are generated:

  • app/models/timezone.js
  • tests/unit/models/timezone-test.js
  • app/routes/timezones.js
  • app/templates/timezones.hbs
  • tests/unit/routes/timezones-test.js

Note: Remember that the file naming conventions in Ember.js help make associations between related parts of your application.

Creating a timezone model with Ember Data

We want each timezone included in our app to have a name and an offset value. We will add them as model attributes using Ember Data — a library included with Ember CLI for managing application data.

Open up app/models/timezone.js in your text editor and add some data attributes by updating the code as follows:

import DS from 'ember-data';

export default DS.Model.extend({
    name: DS.attr('string'),
    offset: DS.attr('number')
});

Ember Data is unopinionated about the underlying storage mechanism for your application data. This gives us the flexibility to choose what kind of data store we'd like to use (e.g. localStorage, IndexedDB, etc.). We're going to make use of the LocalForage library that will automatically detect available data stores and select the most optimal store for our users.

Storing data with LocalForage

Now we'll install the LocalForage Adapter so we can use it with Ember Data. Do this by running the following command:

ember install ember-localforage-adapter

This should install LocalForage and its adapter using Bower, a package manager that lets you easily install third-party libraries or dependencies for your application.

Note: If you are prompted to select a version of ember-data after running this command, choose the version required by your application. You can select and persist your choice by entering the choice number followed by ! and hitting enter:

Bower prompt for dependencies

Once Bower has finished installing the LocalForage package, you should see a new localforage directory under world-clock/bower_components.

The /bower_components directory contains many of your application's dependencies. There are already quite a few folders in this directory, many of which are core dependencies for the Ember framework that come pre-installed.

Creating a new Ember adapter

Now that we've included LocalForage and the Ember LocalForage adapter into our app, we have access to an LFAdapter object that we can use to feed data from our data store into Ember Data: we'll use this to create our timezones database.

Ember allows you to generate an adapter file specifically to contain this code, meaning that it can be kept separate from your models, controllers, etc.

Let's generate our new adapter file using the following command — run this from the root of your project:

ember generate adapter application

This generates:

  • A JavaScript adapter file at app/adapters that contains our adapter code.
  • A JavaScript file at tests/unit/adapters for you to include an adapter unit test in.

You may also have to run bower install to install some missing dependencies — this is fine.

With this done, open up the app/adapters/application.js and replace the code in it with this:

import LFAdapter from 'ember-localforage-adapter/adapters/localforage';

export default LFAdapter.extend({
    namespace: 'WorldTimeZones'
});
 
 
 
 
 

This code creates a data store called WorldTimeZones in LocalForage and joins it to our Ember Data instance.

Retrieving Records from the Data Store

Finally, we'll want to return our timezone records as the model for our timezone route. Setting a model (using Ember.Route's model method, also see Specifying a route's model) on a route gives the controller and template access to the data specified so that it can be manipulated and displayed.

Open up app/routes/timezones.js and modify the code inside to look like this:

import Ember from 'ember';

export default Ember.Route.extend({
    model: function() {
        return this.get('store').findAll('timezone');
    }
});

Gathering Timezone Data

We're going to want to let users choose from a list of all timezones. For this we’ll use Moment Timezone, an awesome JavaScript library for dealing with dates and times in JavaScript. This library will give us a list of all available timezones, and allow us to format them in a more readable way.

To install the Moment Timezone library using bower, enter the following command inside your app root:

bower install moment#2.9.0 moment-timezone#0.3.0 --save

This installs the Moment library files in the bower_components/moment directory. The two files we're interested in using are:

  • bower_components/moment/moment.js — the core moment.js library, to help format dates and times
  • bower_components/moment-timezone/builds/moment-timezone-with-data-2010-2020.js — the data for world timezones

In order to make sure our app can access the moment scripts when formatting a timezone, we'll need to edit Brocfile.js ember-cli-build.js and .jshintrc in the root of our project. In ember-cli-build.js, we will import the moment scripts our application needs. Remember to restart your Ember server after editing this file.

app.import('bower_components/moment/moment.js');
app.import('bower_components/moment-timezone/builds/moment-timezone-with-data-2010-2020.js');

In the .jshintrc file, add moment to the predef array. This will prevent jshint, a code-quality tool included in Ember CLI apps, from throwing errors while checking your code for errors or potential problems.

"predef": [
    "document",
    "window",
    "-Promise",
    "moment"
  ]

Interacting with Timezone Models

Our application should allow users to add a timezone from a select menu, or delete a previously selected timezone. As mentioned before, Ember controllers can be used to manipulate data. When creating the clock, we sent information about the current local time from our clock controller to our clock template. In this example, we'll send information from our timezones template to our timezones controller through user interactions.

Let's create a timezones controller that adds the timezone data from Moment.js, and implements two actions: "add" and "remove". First of all, generate a new controller for your timezones by running the following inside your app root:

ember generate controller timezones

Now add the following code to app/controllers/timezones.js:

import Ember from 'ember';

export default Ember.Controller.extend({
      /* create array of timezones with name & offset */
      init: function() {
        var timezones = [];
        for (var i in moment.tz._zones) {
          timezones.push({
            name: moment.tz._zones[i].name,
            offset: moment.tz._zones[i].offset[0]
          });
        }
        this.set('timezones', timezones);
        this._super();
      },
      selectedTimezone: null,
      actions: {
        /* save a timezone record to our offline datastore */
        add: function() {
          var timezone = this.store.createRecord('timezone', {
            name: this.get('selectedTimezone').name,
            offset: this.get('selectedTimezone').offset
          });
          timezone.save();
        },        
        /* delete a timezone record from our offline datastore */
        remove: function(timezone) {
          timezone.destroyRecord();
        }
      }
}); 

Next we'll modify the timezones template to use the actions and variables we just created. We can use the Ember.SelectView and the {{action}} helper to call our add and remove methods — update app/templates/timezones.hbs so it looks like this:

<h2>Add Timezone</h2>

<div>{{view Ember.Select content=timezones selection=selectedTimezone
   optionValuePath='content.offset' optionLabelPath='content.name'}}</div>

<button {{action 'add'}}>Add Timezone</button>

<h2>My Timezones</h2>

<ul>
{{#each timezone in model}}
  <li>{{name}} <button {{action 'remove' this}}>Delete</button></li>
{{/each}}
</ul>

Now we have a timezones route at http://localhost:4200/timezones that allows us to add and remove timezones we want to track. This data persists between app refreshes.

Note: You may hit an error which indicates that you cannot connect to LocalForage because it violates the Content Security Policy Directive "connect-src 'self'". This error is caused by the default security settings for ember-cli, and can be fixed by editing the 'connect-src' property of ENV.contentSecurityPolicy inside config/environment.js .

Comparing Timezones

The last thing we need to do is show these times relative to our local time in our clock route. To do this we need to load all the timezone models in the clock route:

app/routes/clock.js
import Ember from 'ember';
export default Ember.Route.extend({
    model: function() {
        return this.get('store').find('timezone');
    }
});

In our clock controller, we will add each timezone's current time using moment.js:

app/controllers/clock.js
import Ember from 'ember';

export default Ember.Controller.extend({
    updateTime: function() {
        var _this = this;
 
        // Update the time every second.
        Ember.run.later(function() {
            _this.set('localTime', moment().format('h:mm:ss a'));
 
            _this.get('model').forEach(function(model) {
                model.set('time',
                          moment().tz(model.get('name')).format('h:mm:ss a'));
            });
 
            _this.updateTime();
        }, 1000);
    }.on('init'),

   localTime: moment().format('h:mm:ss a')
});

Finally, we will add an {{each}} helper to our clock template that will iterate over the timezones in our model and output their name and time properties:

app/templates/clock.hbs
<h2>Local Time: {{localTime}}</h2>
 
<ul>
  {{#each model}}
    <li>{{name}}: <strong>{{time}}</strong></li>
  {{/each}}
</ul>

Your application should now be feature-complete: an offline app that shows time zones in various places, saves the data offline, and updates every second.

Document Tags and Contributors

 Last updated by: chrisdavidmills,