
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.
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!
