An Introduction to Ember.js
May 17, 2014Ember.js describes itself as a “framework for creating ambitious web applications.” In the front-end world, it’s often compared to Angular.js or Backbone.js. If you’re interested in Ember, it will help you to realize right away that it’s pretty different from either of those options. I’ll go over a little bit of why it’s different, how to get started, and show a quick sample application using fixture data.
Convention Over Configuration
Ember is a convention-over-configuration, Rails-esque take on the client-side framework. Ember wholly embraces the framework concept. The core team has decided to figure out the most common patterns used when developing web applications, and has created a framework that allows developers to quickly and easily write code using conventions that make sense. Compared to Backbone or Angular, you’ll end up writing much less boilerplate code. A lot of the work is done by Ember behind the scenes; some people like that, some do not — your mileage may vary.
If you want to see the specific differences in a sample application, check out TodoMVC, a collection of implementations of the same “Todo” app in many different libraries/frameworks.
MVC…ish
Ember adheres pretty closely to the Model-View-Controller pattern (though few things are purely “MVC” in the classic sense). I’ve heard Ember described as MVC+R, because the router is a big part of the value Ember provides and is pretty integral to the framework. A typical Ember application will have lots of models, views, controllers, and routes – but also components, helpers, and more. There’s a lot to learn, so let’s get started.
Getting Started
There is an “Ember Way” of doing almost anything when building a web application. The learning curve can be steep at the beginning while you figure out exactly what the “Ember Way” is, but once you’re there, the productivity gains are worth it.
So how do you get there? The official guides are where you should begin, but here’s a list of other helpful resources:
- Ember.js Official Discussion Forum
- Ember.js Stack Overflow tag
- #emberjs on freenode (irc.freenode.net)
- Ember Watch
- Code School - Warming Up with Ember.js
A Sample Application
Let’s walk through a small sample application. I’m not going to go through every step of setting up Ember; this is just intended to give you a look at what Ember code looks like and how certain pieces of the framework work together.
One day, in my entrepreneurial stupor, maybe I decide jukeboxes are broken. I’m going to create the best jukebox of all time in Ember.js. It’s going to be so amazing. Let’s change the world!
Jukebox = Ember.Application.create();
This line just initializes the application. Jukebox
is the name of your application (you’ll use it a lot), and it can be whatever you want it to be. The world is your oyster.
Now that our application has been created, let’s go ahead and define our models. I usually like to do this at this point since it’s the basis for the rest of the application.
So what will they be? Well, jukeboxes have songs, and songs have a name, an artist, and a duration. So we’ll go with that for now.
// Model
Jukebox.Song = DS.Model.extend({
name: DS.attr('string'),
artist: DS.attr('string'),
duration: DS.attr('number')
});
There are a few things happening here. DS
is the namespace for Ember Data. Let’s talk about that for a second.
A Quick Aside About Ember Data
The Ember guides describe Ember Data’s purpose here:
Ember Data is a library that integrates tightly with Ember.js to make it easy to retrieve records from a server, cache them for performance, save updates back to the server, and create new records on the client.
Ember Data comes with several built-in adapters, depending on the data it expects to be returned from the server. You don’t have to use Ember Data with Ember (many people have written custom libraries for this purpose), but it does make it very easy to get started1. I plan on writing more about getting started with Ember Data very soon, so I won’t go into too much detail this time around.
Another Quick Aside — The Ember Inspector
If you’re going to do any development in Ember, you should immediately download the Ember Inspector extension for Chrome (or Firefox, if that’s your thing). It is absolutely invaluable when debugging your code, and can also be a great learning tool — just go to an existing site built on Ember and fire it up, and you’ll be able to see how other Ember users have structured parts of their applications. Okay… back to the jukebox!
Back to the Jukebox
For now, we’ll use Ember Data’s DS.FixtureAdapter
and include some fixture data for our Jukebox.Song
model. Fixture data is just an array of objects.
Jukebox.Song.FIXTURES = [
{ id: 1, name: 'Blue Ridge Mountains', duration: 267, artist: 'Fleet Foxes' },
{ id: 2, name: 'White Winter Hymnal', duration: 149, artist: 'Fleet Foxes' }
];
This looks good, except… we’ve changed our mind about something. It looks like we’ll be repeating ourselves a lot if we have to include the artist name on every song. We also might want to store some additional information about artists. What if we created a Jukebox.Artist
model? Let’s replace the old Jukebox.Song
model definition with new definitions:
// Models
Jukebox.Song = DS.Model.extend({
name: DS.attr('string'),
artist: DS.belongsTo('artist'),
duration: DS.attr('number')
});
Jukebox.Artist = DS.Model.extend({
name: DS.attr('string'),
genre: DS.attr('string'),
songs: DS.hasMany('song')
});
What we’ve done here is create relationships. Ember Data has two types of relationships: DS.hasMany
and DS.belongsTo
. It works exactly how you would expect it to2. Let’s edit our Song fixtures and add new fixture data for Jukebox.Artist
:
Jukebox.Song.FIXTURES = [
{ id: 1, name: 'Blue Ridge Mountains', duration: 267, artist: 1 },
{ id: 2, name: 'White Winter Hymnal', duration: 149, artist: 1 }
];
Jukebox.Artist.FIXTURES = [
{ id: 1, name: 'Fleet Foxes', genre: 'Folk', songs: [1, 2] }
];
One artist, two songs. Good enough for now. Now that the models are taken care of, let’s look at our routes. In most applications, we’ll have a route map (very similar to Rails’ routes.rb
file) where we define our app’s resources and routes. We don’t need it in this application yet, so we’ll skip it. You can learn more about the router from the official Ember guides.
Ember applications come with a built-in ApplicationRoute
and IndexRoute
. We’ll just use IndexRoute
for now.
So what do we need for our jukebox route? For now, we’ll just make it display a list of available songs.
Jukebox.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('song');
}
});
The model
method on a route returns an Object or Promise representing the model you’d like to use for that route. Since we want to display a list of songs on this route’s view, we pass in the song models. this.store.find
is Ember Data-specific, but you could also just return an array of Objects from the model
method.
Now that we have our models, fixtures, and route, we should decide how we want our jukebox to look. Sounds like a job for templates! Ember uses an extended version of Handlebars for its templates. Here’s what the index
template will look like to begin with:
<h3>Available Songs</h3>
<ul>
{{#each}}
<li>{{name}} ({{duration}}) - {{artist.name}}</li>
{{/each}}
</ul>
The each
block loops through the models that are passed in by the route, and for each one displays a list item with that object’s properties. You probably noticed that I am including artist.name
in the list item. Because artist
is a related model (each song
belongsTo an artist
), you can pass in the related model name and display any of its properties as well.
So now we have all of the pieces to display our songs. When we list them out, it looks like this:
Blue Ridge Mountains (267) - Fleet Foxes
White Winter Hymnal (149) - Fleet Foxes
Hmm. That’s okay I guess, but we store duration
as an int (the total number of seconds), and it’s a little strange to have that be shown in the list instead of the more common ‘minutes:seconds’ format. Handlebars lets you define Helpers, which Ember has also extended to let you pass in properties. A helper is just a function that takes zero or more parameters and returns something else (usually a modified version of whatever you are passing in.) Here’s the duration-to-time
helper:
Ember.Handlebars.registerBoundHelper('duration-to-time', function(seconds) {
var minutes = seconds > 60? parseInt(seconds/60, 10) : 0,
leftOverSeconds = seconds > 60? seconds % (minutes * 60) : seconds;
leftOverSeconds = (leftOverSeconds < 10)? '0'+leftOverSeconds : leftOverSeconds;
return minutes + ':' + leftOverSeconds;
});
This isn’t the cleanest code, but it does what we need for now. In our template, we should now replace the {{duration}}
call with {{duration-to-time duration}}
. You can see that we are calling the helper first, then adding a space and passing in duration
as a parameter to the helper. Now our song list looks like this:
Blue Ridge Mountains (4:27) - Fleet Foxes
White Winter Hymnal (2:29) - Fleet Foxes
Awwwww yeah. But this is kind of boring. Just a list of songs? Can’t our jukebox at least let us pick a song and play it? Okay, fine. Let’s add a button next to every song to play it.
<h3>Available Songs</h3>
<ul>
{{#each}}
<li>
{{name}} ({{duration-to-time duration}}) - {{artist.name}} <button {{action 'playSong' this}}>Play</button>
</li>
{{/each}}
</ul>
You’ll might notice that I’ve added an action
block inside of the button
element. This is Ember’s built-in action helper. The action helper is a very big part of Ember — it ties user interaction, like clicking in the views, to the controllers. In this case, I’ve said that this button should call the action playSong
and it should pass in this
, which is just a reference to the current song
model.
Since we are going to call the playSong
action, we should create it somewhere. Enter the mighty Ember.Controller. Until this point, our application has been using the automatically generated IndexController
, which has worked wonderfully, because we haven’t really defined any interactions. But now we need something more. What should the playSong
action do? For now, it’ll just set a property called nowPlaying
to the current song. Here’s what the controller looks like:
Jukebox.IndexController = Ember.ArrayController.extend({
nowPlaying: null,
actions: {
playSong: function(song) {
this.set('nowPlaying', song);
}
}
});
First we initialize nowPlaying to null, then we define an actions
hash with our playSong
action inside of it. If we want to add any other actions (say, deleteSong
), we can also add those here. Since we are passing in the song object via the template, we can set the controller’s nowPlaying
property to just point to the song object.
That’s great, but how do you see what’s playing? We need to update our template. Here’s the new index
template:
Now Playing:
<strong>
{{#if nowPlaying}}
{{nowPlaying.artist.name}} - {{nowPlaying.name}} ({{duration-to-time nowPlaying.duration}})
{{else}}
-
{{/if}}
</strong>
<h3>Available Songs</h3>
<ul>
{{#each}}
<li>{{name}} ({{duration-to-time duration}}) - {{artist.name}} <button {{action 'playSong' this}}>Play</button></li>
{{/each}}
</ul>
We use the Handlebars if
helper to check nowPlaying
, and if it’s not empty, the jukebox shows some information about the song. nowPlaying
is just a song
object, so we have access to the same properties that we have anywhere else where we pass in songs.
Success!
Our jukebox now has a couple songs and lets you click a button to play whichever song you’d like (even if it doesn’t actually play, it’s fun to pretend… right?) You can find the source code and a working example of this on jsbin here. Side note: emberjs.jsbin.com. It will be invaluable to you.
I barely scratched the surface here, and there are many more in-depth Ember topics to discuss, but for now hopefully this gave you a good introduction to Ember. Go ahead and check out the official guides, then get started on your Ember app!
-
As of this writing, Ember Data has not finalized version 1.0, and the core team recently said that a couple API changes will happen before Ember Data hits 1.0. ↩
-
For more details on relationships in Ember Data, check out this section of the official guides. ↩