News

Build a reminder app with Node.js and Ember.js – part 2

Build the interface for part 1 of the reminder app with Ember, a practical MVC framework for JavaScript

Unclouded 4

Build a reminder app with Nodejs and Emberjs part 2

This is the second half of our two-part series on Ember.js. Previously we built the backend for our Ember app with Node; here we’re building the client-side for the app. We’re building a reminder app where our backend sends text messages when a reminder has expired and our frontend allows users to manage reminders.

Headed by Yehuda Katz (@wycats) and Tom Dale (@tomdale) with eight others in the core team, Ember has attracted a large, passionate community and has proven itself to be a strong contender in the MVC JavaScript market. If you haven’t used an MVC framework before, now is definitely the time to start. MVC is a design pattern: the Model stores what your data looks like (the type of properties), the Controller ‘decorates’ the model with certain properties and actions. The View uses the controller to work out what to display.

The code in this tutorial is based on the TodoMVC demo application, so if you get stuck you can head to emberjs.com/guides/getting-started.

DOWNLOAD TUTORIAL FILES

Set up page

Here we’re just setting up the skeleton of our page. Included on the disc are styles based on the TodoMVC project but modified with an Ember twist. If you didn’t follow the first half of this tutorial then you should still be able to follow by spoofing the back-end data.

001    <!doctype html>
002    <html lang=”en”>
003        <head>
004            <meta charset=”UTF-8”>
005            <title>Reminders</title>
006            <link rel=”stylesheet” href=”styles.css”>
007        </head>
008        <body>
009        </body>
010    </html>

Bower configuration

We’re going to use Bower for our front-end dependencies, which needs a configuration file called bower.json and we’ll list which projects our Ember project will use. If a project depends on another then that is also grabbed. Ember relies on jQuery and Bower resolves both of these dependencies.

001    {
002        “name”: “reminders”,
003        “version”: “1.0.0”,
004        “main”: “public/”,
005        “private”: true,
006        “ignore”: [
007            “.jshintrc”,
008            “**/*.txt”
009        ],
010        “dependencies”: {
011            “ember”: “1.5.x”,
012            “ember-data”: “1.0.0-beta.7”,
013            “moment”: “2.5.x”
014        }
015    }

Bower install

To install the dependencies we just specified all we have to do is run ‘bower install’. Bower’s install command will grab each project from its source and add it to a folder called ‘bower_components’. We’re also adding a flag of ‘-p’, which means it won’t include any developer dependencies (like test suites).

Script tags

Once Bower’s completed fetching and installing all of our dependencies, we’ll add them to the bottom of the index.html file. We keep our own JavaScript files away from ‘bower_components’ in the ‘js’ folder and separate out each distinct part into a folder following an MVC structure.

001    <script src=”bower_components/jquery/dist/jquery.js”>
</script>
002    <script src=”bower_components/moment/moment.js”>
</script>
003    <script src=”bower_components/handlebars/handlebars.js”>
</script>
004    <script src=”bower_components/ember/ember.js”></script>
005    <script src=”bower_components/ember-data/ember-data.js”>
</script>
006    <script src=”js/app.js”></script>
007    <script src=”js/router.js”></script>
008    <script src=”js/models/timer.js”></script>
009    <script src=”js/controllers/timers_controller.js”>
</script>
010    <script src=”js/controllers/timer_controller.js”>
</script>
011    <script src=”js/views/edit_timer_view.js”></script>

Handlebars template

Ember.js uses the popular Handlebars templating system; the creator of Handlebars is part of the core Ember team so there’s very tight integration between the two. We’re going to include the templates within the index page. The ‘data-template-name’ is the name Ember uses to know exactly which controller to bind to which template.

001    <script type=”text/x-handlebars” data-template-
name=”timers”>
002        <h1>reminders</h1>
003        <section class=”cf”>
004           <!-- next step -->
005        </section>
006        <footer id=”info”>
007            <p>Double-click to edit a reminder</p>
008        </footer>
009    </script>

Input helper

Here we’re using Ember’s special input Handlebars helper, which generates the HTML and also binds actions and default values to it. We will define the ‘createTimer’ action later on, within our controller. ‘{{outlet}}’ works with the Ember router and the appropriate template based on the status of the app will be rendered into here.

001    <header id=”header”>
002            {{input type=”text” id=”new-timer” 
placeholder=”What do you need to remember?” 
value=newTitle action=”createTimer” }}
003    </header>
004    <main id=“main” role=”main”>
005        {{outlet}}
006    </main>

Iterate in Handlebars

We’re using Handlebars’ ‘each’ helper, which loops through every timer (by default it iterates over the controller’s contents). The ‘data-template-name’ this time is the same as the main app but has an additional part, ‘/index’. This is tied to the route and every controller has default routes (index, loading, and error). Within the loop we explicitly associate a controller with each item.

001    <script type=”text/x-handlebars” data-template-
name=”timers/index”>
002        <ul id=”timer-list”>
003            {{#each itemController=”timer”}}
004                <!-- next step -->
005            {{/each}}
006        </ul>
007    </script>

Bind attributes

In order to bind an HTML attribute to a property within the controller we use ‘bind-attr’. For each timer item we’ll change the classes on the item depending on two things: if the ‘isCompleted’ property is true or false, then the completed class is added or removed; and if the ‘isEditing’ property is true, then the editing class is added.

001    <li {{bind-attr class=”isCompleted:completed 
isEditing:editing”}}>
002            <!-- next step --> 
003        {{else}}
004            <!-- step 11 -->
005        {{/if}}
006    </li>

Editing view

We can listen to the same property and as well as switching classes, change the markup if a certain condition is met. ‘edit-timer’ is a custom component, which extends a text input. This means that we can add default functionality without repeating ourselves. ‘insert-newLine’ fires when a user presses enter, this will trigger our ‘acceptChanges’ method.

001    {{#if isEditing}}
002        {{edit-timer class=”edit” value=title insert-
newLine=”acceptChanges” placeholder=”Description”}}

Losing focus

Similar to ‘insert-newLine’, ‘focus-out’ is another custom event that Ember provides on input boxes meaning that either we can acceptChanges when a user presses enter or when the field loses focus. The value is what will be shown by default, formattedEnd is a computed property in our controller. It’ll take a timestamp and convert it into something understandable.

001    {{input class=”edit” value=formattedEnd focus-
out=”acceptChanges” insert-newLine=”acceptChanges” 
placeholder=”End: yyyy-mm-dd hh:mm”}}

Actions in templates

This next block resides in the ‘else’ condition if we’re not editing the reminder. We’re performing similar checks to before, the main difference is the ‘action’ word tied to user input through the on keyword (or default is click). Here we’re saying ‘when the user double clicks the label, call ‘editTimer’.

001    {{input type=”checkbox” checked=isCompleted 
class=”toggle”}}
002    <label {{action “editTimer” on=”doubleClick”}}>
{{title}} <span class=”pull-right time-until”>{{from}}
</span></label>
003    <button class=”destroy” {{action “removeTimer”}}>
</button>

Create the application

Now let’s write the Ember app that’ll hook it all up. We create an Ember application with ‘Ember.Application.create’. The second part is needed because our backend uses Mongoose. Mongoose stores IDs prefaced with an underscore, so we’re telling Ember to set the primary key to ‘_id’ instead of ‘id’.

001    //app.js
002    window.Timers = Ember.Application.create();
003    /* map id to _id */
004    Timers.ApplicationSerializer = DS.RESTSerializer.
extend({
005        primaryKey: ‘_id’
006    });

Create a router

In ‘router.js’ we’ll tell Ember what to display at the default route by knowing which template to render. Ember follows the convention over configuration paradigm, which means that naming is significant. For example, templates and controllers can be inferred from the route that you pass. This group of routes inherits from ‘timers’, so ‘timers.active’ and ‘timers.completed’.

001    Timers.Router.map(function() {
002      this.resource(‘timers’, { path: ‘/’ }, function () {
003          this.route(‘active’);
004          this.route(‘completed’);
005       });006    });

Ember Data model

Before we go any further with our routes let’s create a model for our ‘Timer’. This process should look quite familiar as it’s similar to our Mongoose schema. DS (Data Store) is the namespace for Ember Data, which is a separate library to Ember but is the glue for interfacing with our API. We’re telling Ember what type to expect each property to be.

001    Timers.Timer = DS.Model.extend({
002        title: DS.attr(‘string’),
003        isCompleted: DS.attr(‘boolean’),
004        end: DS.attr(‘date’)
005    });

Get the model

We’ve got everything we need to render our reminders out, we just need to tell Ember where to get them from; we do this by specifying the route’s model. The route will then fire off a GET request to ‘/timers’ to get all of our reminders from our JSON API.

001    Timers.TimersRoute = Ember.Route.extend({
002        model: function () {
003            return this.store.find(‘timer’);
004        }
005    });

Default actions

Within our Timer template we specified a custom element, ‘edit-timer’. Ember has lifecycle methods, methods that are called at certain points in a component’s life, from initialisation to destruction. ‘didInsertElement’ is one of those methods; it’s called when the element has been inserted and we want to give that element focus as soon as it is.

001    Timers.EditTimerView = Ember.TextField.extend({
002        didInsertElement: function () {
003            this.$().focus();
004        }
005    });
006    Ember.Handlebars.helper(‘edit-timer’, Timers.
EditTimerView);

Timers array controller

All of our timers are contained within the Timers controller in a file called ‘timers_controller.js’. There are two types of controllers, object and array; this one is an array controller. Array controllers, as the name suggests, are used to containing multiple collections. Controllers contained within array controllers inherit their properties too.

001    Timers.TimersController = Ember.ArrayController.
extend({});

Create a timer

To create a new timer all we have to do is create a new record in our data store. First we pass it what we’re creating (a timer) and then the properties that match up to our model. If anything is passed that doesn’t match the model then the record won’t be saved.

001    createTimer: function() {
002        var title = this.get(‘newTitle’);
003        if (!title.trim()) { return; }
004        var timer = this.store.createRecord(‘timer’, {
005            title: title,
006            isCompleted: false,
007            end: moment().add(‘hours’, 1).toDate()
008        });

Save new timer

Before the timer is saved we set the label to a blank value, restoring it to its default state. We then save the new record, which sends a POST request to our JSON API. Once this is done, the backend creates a new database record and returns the new record with its ID.

001       //previous step
002        this.set(‘newTitle’, ‘’);
003        timer.save();
004    }

Timer controller

Within timer_controller.js we’re going to build our second controller, an Object Controller. Controllers are for defining properties that aren’t persisted, things that are unique to the user’s session while keeping the model purely about data. This allows more specific uses without having to redefine everything on the model. Below is the skeleton of the timer controller.

001    Timers.TimerController = Ember.ObjectController.
extend({
002        isCompleted: function (key, value) {},
003        from: function () {},
004        formattedEnd: function (key, value) {},
005        isEditing: false,
006        actions: {}
007    });

Computed properties

Methods on a controller are computed properties. This means that they’re tied to a property (or properties) specified at the end of the method. Ember can then ensure that computed properties only fire when those properties have finished changing without firing multiple times unnecessarily. We’re using Moment.js to format the date stamp into a readable style.

001    formattedEnd: function (key, value) {
002        var model = this.get(‘model’);
003        if (typeof value === ‘undefined’) {
004                    return moment(model.get(‘end’)).format
(‘YYYY-MM-DD HH:mm’);
005        } else {
006            model.set(‘end’, value);
007            return value;
008        }
009    }.property(‘model.end’),

Continually updating view

The ‘from’ computed property will show the time the timer is set to end in a nice format (eg ‘1 hour from now’) and update this every second. We’re only interested in setting a new value based on the current value, so we’re not passing it a key or value and don’t have to check if we’re using it as a getter or setter.

Set/get isCompleted

We do pass both key and value arguments to the `isCompleted` method. If a value is present then we know that we’re trying to set a new value, if there isn’t one then we know that the application is trying to get the current value. If a new value is set then we save it back to the model, which triggers a PUT request to our API to update that item.

001    isCompleted: function(key, value){
002        var model = this.get(‘model’);
003        if (typeof value === ‘undefined’) {
004            return model.get(‘isCompleted’);
005        } else {
006            model.set(‘isCompleted’, value);
007            model.save();
008            return value;
009        }
010    }.property(‘model.isCompleted’),
011     

Edit timer

Controllers can have actions that are used within templates. Our template calls the ‘editTimer’ method when the user double-clicks on a label. This simply sets the ‘isEditing’ property on the Timer controller to true, which triggers the view to update with the editing view, allowing a timer to be changed.

Save changes

‘acceptChanges’ is called when an input element loses focus or the user presses Enter. If the title is empty then we discard the changes and remove the timer. However, if it does have a title then we take the end date (if there’s no end date our backend defaults to an hour’s time) and save it to our mode, which in turn POSTs it to the backend.

001    acceptChanges: function () {
002        this.set(‘isEditing’, false);
003        if (Ember.isEmpty( this.get(‘model.title’) )) {
004                    this.send(‘removeTimer’);
005        } else {
006                    var end = moment(this.get(‘model’).get
(‘end’)).toDate();
007                    this.get(‘model’).set(‘end’, end);
008                    this.get(‘model’).save();
009        }
010    },
011    

Remove a timer

‘removeTimer’ is the last of our Timer controller’s action methods. It deletes the record using Ember’s ‘deleteRecord’ method removes the item from the controller and save() is smart enough to make a DELETE request to our API, which removes the timer from the database. There iss also ‘destroyRecord’, which persists the deletion immediately if you want a terser deletion.

Reliable time management

Moment.js is a fantastic library for date manipulation but when it comes to user input, the most reliable way to parse a date and time is with the ISO-8601 standard. We’re using a placeholder to guide the user to input the correct format, but a drop-down list might be more robust.

Wrapping up

Ember has a rich API and while initially the learning curve can be daunting, the payback is great. In general, the hardest part can be learning how the constituent parts work together, especially the whole convention over configuration paradigm as sometimes it seems all too magical! Thankfully the documentation and guides (emberjs.com/api) are very comprehensive and well written, so happy hacking!

Improve your web design skills with Web Designer. Download issues direct from GreatDigitalMags.com or buy print issues from ImagineShop

×