Ember.js, like Spine and Backbone.js, is a lightweight framework for helping developers build client-side applications in JavaScript using the MVC pattern.
So how is Ember.js different from the others?
While Backbone.js, for example, is a very small and unopinionated foundation upon which you are expected to build your own conventions and additions, Ember.js provides strong conventions and helpful tools for the problems that we think most developers will face when building large-scale applications.
A good way to summarize this is that Ember.js wants to eliminate trivial choices. Good developers love to argue, so when there is no official solution to a problem, every team has to spend time deciding how they should tackle it. Because everyone is doing things a little bit differently, shared knowledge is reduced, help is difficult to obtain, and time is wasted training new developers in your particular dialect.
Some examples of trivial choices that Ember.js decides for you:
What templating language should we use?
Who is responsible for memory management?
How should the application be structured?
How should classes and instances be named?
To be clear, these are all things that can be changed. For example, Ember.js can be used with a different templating language than Handlebars (the default), or none at all. But because there is a default, people tend to stick with it, and there is serious value in having the entire community on the same path.
Ember.js also enhances JavaScript in two ways:
It provides implementations of features that will be in the language, but aren’t yet. Observers and maps are two good examples of this. You can start using these features right away, and Ember.js will automatically use the native implementation for you when it’s available.
It provides shorthand for things that are otherwise laborious to do in vanilla JavaScript. For example, the Ember.js object system allows you to succinctly specify class hierarchies, instead of the ponderous process of manually wiring up object prototypes, as described in Chapter 1.
When it comes to application structure, libraries like Backbone.js give you the primitive types—models and views—but leave it up to you to decide how to wire them together. Ember.js enforces certain patterns through conventions. These conventions serve two purposes:
The code to implement common patterns is extremely concise.
Using objects differently than how they were intended feels comparatively long-winded.
If you are feeling friction when using Ember.js, it usually means you are using the framework in a way that its authors didn’t intend. This is a sign that perhaps you should step back and rethink how you’re approaching the problem!
Many developers appreciate having an opinionated framework that enforces patterns through strong conventions. It means that multiple developers can look at the same problem and have it decompose architecturally in their heads into more-or-less the same solution, which is especially important in larger applications and on teams.
Some developers, however, chafe under these conditions. They want to be able compose the primitives however they’d like, and are willing to take responsibility for the long-term maintenance of the patterns they use.
For small- to medium-scale applications, you should choose whichever feels most appropriate to you. Many developers new to web application development like to cut their teeth on libraries like Spine and Backbone.js, so they fully understand their limitations before graduating to the stronger conventions of Ember.js.
In particular, so-called “island of richness” applications are particularly well-served by Backbone.js and Spine. These are small, interactive apps that are designed to be embedded inside existing content, usually a static page like a news article or blog post.
However, for large applications, especially those whose UI takes over the entire page, you should seriously consider using an opinionated framework like Ember.js. From my experience talking to many web developers, those who think they can get away with small libraries in larger applications end up with custom, buggy, half-implemented versions of more comprehensive frameworks like Ember.js that they alone must maintain.
In this chapter, you’ll learn about a few of the core concepts in Ember.js, and how they work together. Finally, you’ll apply what you’ve learned by building a Todos application.
Every Ember.js app starts by creating an application object:
window.App=Ember.Application.create();
Calling create() on an Ember object returns a new
instance of that class. Here, we’re creating a new instance of
Ember.Application and assigning it to the global
variable App.
The Application object serves a number of
important roles in your application. The most obvious is that it serves as
a namespace, into which all of the other classes you
define will be placed. For example, you might have a controller called
App.UsersController or a model called
App.User.
Less obvious is what the Application is doing for
you behind-the-scenes: creating instances of your controllers, finding
templates in DOM and compiling them, and kicking off routing. While
advanced users can tweak what happens here to their heart’s content, the
default behavior for all of these things should be enough to get you
started.
Another nice feature the Application instance
gives you is sane toString() semantics. As you’re
developing your Ember.js application, try it in the console—it makes
debugging much easier:
user.toString();// => "<App.User:ember123>"
Models are objects that represent persistent data. They are responsible for loading data from your server, making it available to templates, then sending it back to the server if there are any changes to be saved.
Before you begin defining and loading models in your Ember.js app, you must first define a store:
App.Store=DS.Store.extend();
The store is important for several reasons. First, it is responsible for instantiating the records that your application asks for. If you ever ask the store for the same record multiple times, it will always return the same record. This feature, called an identity map, means that different parts of your application don’t get out of sync when displaying the same data.
Second, the store is responsible for communicating the changes you make in your application, such as modifying attributes or creating new records, to the adapter.
The web is full of many different APIs. Some of them transmit JSON over HTTP; others send XML representations of the data. Additionally, newer web browsers support brand new modes of communication (like WebSockets), and can even natively handle binary data.
This means that we can no longer assume that sending JSON to RESTful HTTP endpoints is sufficient for all modern JavaScript web application.
Thankfully, Ember allows you to easily encapsulate how your
application talks to your backend in an object called the
adapter. The default adapter is called
DS.RESTAdapter, and is designed to work with the
conventions of RESTful HTTP APIs like those built using Ruby on
Rails.
If you need something different, you can write a completely custom adapter that uses whatever combination of technologies you’d prefer. The best part is that because the adapter completely encapsulates the details of server communication, changing your server backend does not require any changes to the rest of your application!
To tell the store which adapter to use, just override its
adapter property:
App.Store=DS.Store.extend({adapter:'App.AwesomeWebSocketsAdapter'});
Once you’ve defined your store, it’s time to set up your models. Each model should represent a specific kind of object, and is made up of attributes and relationships.
Start by creating a new subclass of DS.Model.
To tell Ember which properties are attributes that should be fetched
from the server, use the DS.attr helper:
App.User=DS.Model.extend({name:DS.attr('string')});
The argument passed to DS.attr() tells Ember
what type of attribute it is, so that it can be converted into the
correct JavaScript type when the JSON response is received.
Sometimes, a model may have properties that can be derived from other properties. For example, imagine that you are writing a commenting system and want to give users a VIP badge once they’ve posted 100 comments.
There’s no need to save the fact that the user is a VIP to the server, since you’d just be duplicating information (the number of posts) that you already have. Instead, just define a computed property that uses the number of posts to compute whether or not the user is a VIP:
App.User=DS.Model.extend({name:DS.attr('string'),commentCount:DS.attr('number'),isVIP:function(){returnthis.get('commentCount')>100;}.property('commentCount')});
This defines a property called isVIP that is
true when the user has more than 100 comments, or false if they have
fewer than 100.
The arguments passed to the property() function
tell Ember the computed property’s dependent keys.
If one or more of a computed property’s dependent keys changes, Ember
knows that it needs to recalculate the computed property (and update any
templates on screen that may have been using it).
While computed properties really shine when used to build up higher-level properties from primitive attributes in your models, you should note that you can use computed properties everywhere, from your controllers to your views.
Once you’ve defined a model, you can create a new record and initialize its attributes:
varuser=App.User.createRecord({name:"Tyrion Lannister",commentCount:99});
Make sure you access the properties of your models (as well as all
Ember objects) using their get and
set methods:
user.get('name');assertEqual(user.get('name'),"Tyrion Lannister");assertEqual(user.get('isVIP'),false);user.set('commentCount',100);// Now that commentCount has changed, the isVIP// computed property updates automatically.assertEqual(user.get('isVIP'),true);
Using these accessors ensures that templates have a chance to update themselves, computed properties can be recalculated if necessary, and any registered observers can be fired.
You can tell Ember that two models are related to one another by
using the DS.hasMany and
DS.belongsTo helpers. For example, you can describe a
one-to-many relationship between Posts and
Comments:
App.Post=DS.Model.extend({comments:DS.hasMany('App.Comment')});App.Comment=DS.Model.extend({post:DS.belongsTo('App.Post')});
Once you’ve done that, you can treat the relationships like regular properties:
varpost=App.Post.createRecord();varcomment=App.Post.createRecord();// You can set the `belongsTo` sidecomment.set('post',post);// ...or add it to the parent `hasMany` arraypost.get('comments').pushObject(comment);
Whichever side of the relationship you modify, Ember will automatically keep the inverse side up-to-date.
As you can see here, hasMany relationships
behave like arrays. To make changes to them, use Ember’s array mutation
methods to ensure that Ember is notified of the changes you make. For
the full list, see the documentation for Ember.MutableArray
and Ember.MutableEnumerable.
As you’ll remember from Chapter 5, a template allows you to take a fragment of HTML annotated with template variables, combine it with a JavaScript object (called the template context), and produce a string of HTML where the template variables have been replaced with values from the object.
Ember.js uses Handlebars, a logicless JavaScript templating language. Logicless means that, unlike some other templating libraries, you cannot embed arbitrary JavaScript expressions in your template. Instead, you are only allowed to display the properties of the current template context.
While this may seem like a limitation, it is in fact what allows us to implement one of Ember’s coolest features: once you render a template, it automatically stays up-to-date as the underlying template context changes.
That means you never have to write custom render code or add model listeners in your views. In fact, you can usually skip creating views altogether. Just describe the HTML you want in a template, and it is Ember’s responsibility to set up bindings and update the DOM if the underlying object ever changes.
Here’s an example of a simple Handlebars template:
<div class="user-info">
Welcome back, {{firstName}} {{lastName}}!
</div>
Note that simple expressions are wrapped in double curly braces. In
the above example, {{firstName}} means look
up the firstName property from the current context and
put it into the HTML.
There are also block helpers, which look similar to simple
expressions but begin with a hash (#) in the opening
expression and a slash (/) in the closing
expression:
{{#if unreadPosts}}
Unread Posts: {{unreadPosts.length}}
{{/if}}
Block helpers affect the content they enclose. In this example,
information about the number of unread posts is only displayed if the
template context’s unreadPosts property evaluates to a
truthy value.
You can save your templates inside your application’s HTML file, by
wrapping it in a <script> tag:
<script id="user-info-template" type="text/x-handlebars">
<div class="user-info">
Welcome back, {{firstName}} {{lastName}}!
</div>
</script>
Note that the <script> tag includes an
id attribute. You can use this identifier later to tell
Ember.js which template to display. If you have a
<script> tag without an id,
Ember.js assumes that it is your
main template and will render it immediately.
Above, you saw the {{#if}} helper in action.
The {{#if}} helper renders its content only if the
value provided to it evaluates to truthy (that is, it’s not
0, false, null,
undefined, an empty array or an empty string).
You can also provide an {{else}} block in cases
where the value is false:
{{#if isLoaded}}
<h1>{{title}}</h1>
{{else}}
Loading…
{{/if}}
There is an inverse of {{#if}},
{{#unless}}, that only renders its content if the
value provided is falsy:
{{#unless isAdministrator}}
Karma: {{karma}}
{{/unless}
By default, a template’s context is the controller with which it
is paired. You can change the template context inside a block by using
the {{#with}} helper:
<p>Name: {{firstName}} {{lastName}}</p>
{{#with profile}}
<p>Bio: {{bio}}</p>
<p>Address: {{address}}</p>
{{/with}}
Inside the {{#with}} block, the
bio and address properties will be
looked up on the controller’s profile object, instead
of the controller itself.
Finally, the {{#each}} helper allows you to
enumerate an array of objects. Inside the {{#each}}
block, the context is set to each item in the array in turn:
<h1>{{blogTitle}}</h1>
<ul class="posts">
{{#each posts}}
<li>{{title}} by {{author}}</li>
{{/each}}
You can write application-specific helpers for formatting values
to be displayed in your HTML. For example, we often want to properly
pluralize words based on a number associated with them. Imagine we
wanted a {{pluralize}} helper that could be used like
this:
Remaining: {{pluralize remainingItems}}
If the value of remainingItems was
1, it would render:
Remaining: 1 item
Otherwise, it would render:
Remaining: 16 items
If we wanted to customize which word was pluralized, we could pass
the the word option:
Remaining: {{pluralize remainingItems word="post"}}
This would render something like:
Remaining: 16 posts
As it turns out, making helpers like this is extremely easy. The above example can be implemented in only five lines of code:
Ember.Handlebars.registerBoundHelper('pluralize',function(value,options){varword=options.hash.word||"item";if(value===1){return'1 '+word;}returnvalue+' '+word+'s';});
This registers a pluralize helper that receives
the value that you pass to it. Options passed to the helper (like
word="post") can be accessed via the
options.hash object.
Whatever value you return from this helper will be placed into the
template at the appropriate location. If the value changes (in the
examples above, remainingItems), the helper will be
re-run and the DOM will be updated.
In Ember, controllers have two responsibilities:
They respond to actions from templates.
They present models to templates.
In many cases, you will not need a controller at all. Wait until you need to transform or augment the properties of a model, or respond to a user action, to start adding controllers to your application.
When you do need a controller, the first step is to determine what kind of model it will represent.
If it’s not representing a model at all (that is, it only responds
to actions but does not augment or transform a model), define an
Ember.Controller subclass:
App.AuthenticationController=Ember.Controller.extend({logout:function(){// ...put logout code here...}});
You can send actions to a template’s controller using the
{{action}} Handlebars helper:
<button {{action "logout"}}>Log Out</button>
By default, actions are sent when the user clicks on the element
to which you have added the {{action}} helper. You
can change how the action is triggered using the on
attribute:
<button {{action "reload" on="doubleClick"}}>Double-click to reload</a>
You can also use this to trigger an action on a controller when the user submits a form:
<form {{action "submitForm" on="submit"}}>
<label for="name">Name</label>
{{view Ember.TextField valueBinding="name"}}
<button type="submit">Submit</button>
</form>
This would trigger the submitForm action if the
user clicked the Submit button, or if they hit the
enter key while the form’s text field had focus.
Remember that one of the responsibilities of a controller is to present a model to one or more templates. It may augment or transform the properties of a model, in order to make them more appropriate for display to the user. Let’s look at an example to illustrate exactly what this means.
If your controller presents a single model to a template, you
would define a subclass of Ember.ObjectController. In
this example, the PostController represents a single
App.Post model:
// ModelApp.Post=DS.Model.extend({title:DS.attr('string'),comments:DS.hasMany('App.Comment')});// ControllerApp.PostController=Ember.ObjectController.extend();
When the application starts, the router will tell the
PostController to represent a Post
record by setting its model property. (We’ll describe
the router in detail later in this chapter. For now, just know that it
is responsible for connecting controllers to models and templates to
controllers.)
Now, let’s say the template connected to the
PostController asks for its title
property:
<h2>{{title}}</h2>
Here’s where things get really cool.
You probably noticed that we have not defined a
title property on the
PostController. But, because it’s a subclass of
ObjectController, it will automatically look up the
property on its underlying model. As far as the template is concerned,
the controller and the model look exactly the same!
In other words, the ObjectController acts as a
proxy to its model; any request for a given
property returns the value of the property with the same name on the
model.
Sometimes, though, you may store information in a way that is different than how it should be presented. For example, we might store the price of an item as an integer in the database. When we present it to the user, however, we’d like to format it as currency:
varattr=DS.attr;App.Item=DS.Model.extend({name:attr('string'),price:attr('number')});App.ItemController=Ember.ObjectController.extend({price:function(){varprice=this.get('model.price');return"$"+parseFloat(price).toFixed(2);}.property('model.price')});
Then, in our template:
<table>
<tr><th>Item</th><td>{{name}}</td>
<tr><th>Price</th><td>{{price}}</td>
</table>
When we render the template for the user, they’ll see a nicely
formatted price, like $10.50, instead of the integer
1050.
As far as the template is concerned, it’s getting information directly from the model. But we can selectively transform values to make them more appropriate for our templates, without having to put view logic in the model layer.
Besides the ObjectController, Ember has another
model controller: Ember.ArrayController. This is used
to present a collection of models to a template.
Like with ObjectController,
ArrayController presents itself to templates as
though it were the underlying model collection:
App.PostsController=Ember.ArrayController.extend();
<h1>Posts</h1>
{{#each post in controller}}
<h2>{{post.title}}</h2>
{{/each}}
Perhaps the most important use of array controllers is to present aggregate information about the underlying models to the template.
For example, imagine our blog software tracks which posts have already been read by the user. We want to show a count of how many posts the user has left to read, before the supply of new posts is exhausted.
The one-two punch of array controllers and computed properties makes this extremely easy:
App.PostsController=Ember.ArrayController.extend({unreadCount:function(){returnthis.filterProperty('isUnread').get('length');}.property('@each.isUnread')});
While not a lot of code, there is a lot going on here. Let’s break down exactly what this is doing:
First, we define a computed property called
unreadCount.
Inside the computed property:
We use the filterProperty method to get
an array of all of the models in the underlying array whose
isUnread property is true.
We return the length of that array. This becomes the value of the computed property.
Finally, we tell Ember what other properties the value of this
computed property relies on. In this case, we use a special property
called @each. The dependent key
@each.isUnread means recompute
unreadCount every time the
isUnread property of any of the underlying models
is changed.
We can use this synthesized property just like a normal property
in our templates. For example, here’s what the posts
template might look like:
<h1>My Cool Blog</h1>
{{#each post in controller}}
<h2>{{post.title}}</h2>
{{/each}}
<p>Unread Posts: {{unreadCount}}</p>
As posts are added or removed, or the isUnread
property of any of the Post models changes, the
unreadCount value displayed to the user will be
updated automatically. Not too shabby for three lines of code and a
simple Handlebars template.
In Ember, the router is responsible for figuring out what templates should currently be on screen, which controllers the templates should be connected to, and which models those controllers should represent. In many ways, it is responsible for tying together all of the concepts that you’ve learned about so far in this chapter, and telling them all how to work together. If your application were a NASA space project, you could perhaps think of the router as Mission Control.
As the user interacts with your app, the router updates the templates, controllers and models on screen in response to their actions. Because it’s important for users to be able to share what they’re seeing with friends, the router also updates the URL as the UI changes.
Additionally, if the user enters the application via an existing URL, the router is responsible for rebuilding the state of the application.
For example, imagine we’re writing an application for reading blog
posts. If our user visits blog.example.com/, she should
see the list of blog posts available. If she clicks one of the entries
that interests her, two things should happen:
The template for displaying a single blog post should replace the template that displayed a list of posts.
The URL should be updated to reflect what’s currently on screen.
For example, the new URL may become
blog.example.com/post/17.
If our user takes that URL and tweets it to her followers, they should see the same blog post when they click the link.
You can list the routes in your application by calling your
router’s map method.
App.Router.map(function(){this.route("about");this.route("favorites");});
This tells Ember to render the about template
when the user visits /about, and to render the
favorites template when they visit
/favorites. There is also an implicit route from
/ to the index template.
(Note that you do not need to define
App.Router; one is provided for you automatically
when you created your Ember.Application.)
You can configure which URL a particular template gets mapped to
by passing an optional path argument:
App.Router.map(function(){this.route("favorites",{path:'/favs'});});
This would tell Ember to render the favorites
template when the user visits /favs.
As you’ll recall, templates are always bound to a controller which represents a model. So far, we’ve learned how to display a template when a given URL is visited. But how do we tell it what model goes with it?
To do so, we need to first define a route
handler. The route handler is an object that customizes how a
particular route behaves. For example, to specify the model that is
associated with a template, return it from model
hook:
App.IndexRoute=Ember.Route.extend({model:function(){returnApp.Post.find();}});
This tells Ember to render the index template
with the results of App.Post.find() as its
context.
We didn’t need to do any additional configuration to make the route handler start working. Just define the handler with the right name, and Ember will know what to do automatically.
The rules for figuring out the route handler name are easy: it’s the name of the route, capitalized, with “Route” at the end. For example:
App.Router.map(function(){this.route("favorites");this.route("mostPopular");});
These routes would have the following route handlers associated:
App.IndexRoute (the implicit route for
/)
App.FavoritesRoute
App.MostPopularRoute
Most of the routes in your application will usually be associated
with a specific model. For example, you might have a
/posts route, which displays a collection of posts,
and a /comments route, which displays a collection of
comments.
When a route corresponds to a particular model, use the
resource() method instead of
route():
App.Router.map(function(){this.resource('posts');});
Resource routes can contain nested routes. This is useful for filtering a collection of models, for example.
Imagine that in our blog application we wanted to users to be able to filter posts by whether they were unread, or whether the user had favorited them:
App.Router.map(function(){this.resource('posts',function(){this.route('unread');this.route('favorites');});});
The above route definition would create the following URLs, with associated controller, route handler, and template names:
/posts
App.PostsIndexController
App.PostsIndexRoute
posts/index
/posts/unread
App.PostsUnreadController
App.PostsUnreadRoute
posts/unread
/posts/favorites
App.PostsFavoritesController
App.PostsUnreadRoute
posts/favorites
Here is a good rule of thumb for figuring out which kind of route to create:
If it’s a noun, use resource().
If it’s an adjective or an adverb, use
route().
Often, parts of the URL will contain information (typically the
ID) about the model that should be displayed. For example, if you are
viewing the post with ID of 17, the URL might be
/post/17. Obviously, you don’t want to have to
enumerate a different route for each post in your database!
Instead, you can define a route with a dynamic segment. Dynamic segments allow you to match any URL that follows a pattern, and extract information about the model out of it:
App.Router.map(function(){this.resource('post',{path:'/post/:post_id'});});
Note that the path for our post resource
contains a dynamic segment: :post_id. Dynamic
segments should start with a colon (:), contain the
model name, and end with _id.
Instead of having to manually configure which model is associated
with this route by defining a model hook on a route
handler, Ember is smart enough to know that it should look for the
App.Post model with the given ID.
Use the Handlebars {{linkTo}} helper to create
a link that your user can click on to change what appears on screen.
Imagine we’re creating a simple personal website that contains several
different static pages that we want users to be able to switch between.
Our routes look like this:
App.Router.map(function(){this.route('about');this.route('contact');this.route('resume');});
In our about template, we can link to the
contact and resume routes like
so:
Welcome to my personal site!
Please {{#linkTo "resume"}}read my resume{{/linkTo}} or
{{#linkTo "contact"}}contact me!{{/linkTo}}
As the user clicks these links, the old template will be removed and the new one will be put in its place. This happens extremely quickly, because all of the templates have already been loaded and the browser does not need to talk to the server to display the new template.
Stitching together static pages like this is a great way to quickly prototype web applications, while writing practically no code.
What happens if we want to link to a route with a dynamic segment? How do we tell it which model that route should represent?
That turns out to be easy too: just pass the model as an argument
to the {{linkTo}} helper.
For example, imagine we have the following route definitions:
App.Router.map(function(){this.resource('posts');this.resource('post',{path:'/posts/:post_id'});});
The posts template displays a list of the blog
posts the app knows about. Just include the current post as the second
argument to {{linkTo}}, and Ember will take care of
the rest:
<h1>My Cool Blog</h1>
<ul>
{{#each post in controller}}
<li>{{#linkTo "post" post}}{{post.title}}{{/linkTo}}</li>
{{/each}}
</ul>
Now that you’re familiar with the core concepts that make an Ember.js app tick, let’s put them all together to build a simple todo application.
Unlike the previous versions of this application that you’ve built, you won’t be writing a lot of JavaScript. Instead, you’ll describe what you want to happen using templates, and Ember will automatically keep them up-to-date for you.
Let’s start with the minimum needed to get a simple page that loads in all of our dependencies:
<html><head><title>Ember.js Todos</title><linkhref="todos.css"media="all"rel="stylesheet"type="text/css"/><scriptsrc="lib/handlebars-1.0.rc.2.js"></script><scriptsrc="lib/jquery-1.8.3.js"></script><scriptsrc="lib/ember.js"></script><scriptsrc="lib/ember-data.js"></script><scriptsrc="todos.js"></script></head><body></body></html>
Now, let’s move on to todos.js, which contains
all of the JavaScript necessary to run the application.
First, remember that every Ember application starts with an instance
of Ember.Application. Let’s add it to the top of
todos.js now:
App=Ember.Application.create();
This creates a namespace for all of the other classes we’ll define, as well as generating all of the infrastructure necessary to get the app to running.
Next, we’ll tell Ember that we’d like our application to have a data
store by defining a subclass of DS.Store:
App.Store=DS.Store.extend({revision:11,adapter:'DS.FixtureAdapter'});
The revision specifies the current Ember Data API
revision, and the adapter tells the store where to go
to load and save records. In this case, we’re using revision 11 of Ember
Data, and we’re telling the store that it should try to fetch records from
fixture data.
Because Ember Data is frequently being improved, specifying the revision number means that the library authors can alert you when the API changes. This makes it easy to stay up-to-date with improvements as they happen, without having to trudge through mysterious errors.
Now that we have a store, we can describe the models in our
application. Because this is a simple application, we only have one: the
Todo.
App.Todo=DS.Model.extend({isDone:DS.attr('boolean'),content:DS.attr('string')});
This defines a model called App.Todo that has two
attributes:
A Boolean, isDone
A string, content, which contains the
contents of the todo
Now it’s time to tell Ember what template we want to display when
our user loads the application. Using the built-in router, we’ll tell
Ember that it should render the todos template when the
user visits /.
App.Router.map(function(){this.route('todos',{path:'/'});});
If we don’t do anything else, Ember will automatically render the
contents of the todos template as soon as the user
loads the page. However, this wouldn’t really qualify as an
application — it’s just a static page.
In order to make the page dynamic, we need to tell Ember what models
should be displayed by the template. We do that using route handlers. In
this case, because we’re displaying the todos template,
we can customize the App.TodosRoute handler to specify
which model gets rendered:
App.TodosRoute=Ember.Route.extend({model:function(){returnApp.Todo.all();}});
By returning App.Todo.all() from the route
handler’s model hook, we’re telling Ember:
When the user visits our web application at /, find all
of the Todo models you know about and render them using
the todos template.
Finally, we’ll define a controller that is responsible for:
Handling actions sent from the template (for example, the controller is responsible for deleting a todo if the user clicks the X button).
Present aggregate information about the todos to the template (for example, it is the controller’s responsibility to calculate the number of todos remaining).
Here’s the code for the controller:
App.TodosController=Ember.ArrayController.extend({newTodo:function(title,textField){App.Todo.createRecord({content:title});textField.set('value','');},destroyTodo:function(todo){todo.deleteRecord();},destroyCompleted:function(){this.get('completed').invoke('deleteRecord');},remaining:function(){returnthis.get('length')-this.get('completedCount');}.property('length','completedCount'),completed:function(){returnthis.filterProperty('isDone');}.property('@each.isDone'),completedCount:function(){returnthis.get('completed.length');}.property('completed')});
As you can see, there is not much JavaScript code here at all.
It implements three action handlers and three computed properties. In particular, it handles actions sent when:
Creating a new todo.
Destroying a todo.
Destroying all completed todos.
It also implements three computed properties:
remainingThe number of todos remaining.
completedAn array of todos that have been completed.
completedCountThe number of todos that have been completed.
Lastly, let’s switch back to index.html and
implement the template that will render the user interface for our
app.
Remember that Handlebars templates that you keep in your HTML file
need to be wrapped in <script> tags, to prevent
the browser from trying to treat them as HTML. Our
<script> tag will look like this:
<scripttype="text/x-handlebars"data-template-name="todos"></script>
This todos template will be the template for the
entire application. If we were to add features to the app, we would
probably add more templates to go along with this one. But for now, you
can put all of the Handlebars markup inside this one
<script> tag.
Here’s what the entire template looks like, all together:
<div id="todoapp">
<div class="title">
<h1>Todos</h1>
</div>
<div class="content">
<div id="create-todo">
{{view Ember.TextField id="new-todo" action="newTodo"
placeholder="What needs to be done?"}}
</div>
<div id="todos">
<ul id="todo-list">
{{#each todo in controller}}
<li>
<div {{bindAttr class=":todo todo.isDone:done"}}>
<div class="display" title="Double click to edit...">
{{view Ember.Checkbox class="check" checkedBinding="todo.isDone"}}
<div class="todo-content">{{todo.content}}</div>
<span class="todo-destroy" {{action destroyTodo todo}}></span>
</div>
<div class="edit">
<input class="todo-input" type="text" value="${content}" />
</div>
</div>
</li>
{{/each}}
</ul>
</div>
<div id="todo-stats">
{{#if length}}
<span class="todo-count">
<span class="number">{{pluralize remaining}}</span>
</span>
{{/if}}
{{#if length}}
<span class="todo-clear">
<a href="#" {{action destroyCompleted}}>
Clear {{pluralize completedCount word="completed item"}}</span>
</a>
</span>
{{/if}}
</div>
</div>
</div>
There’s a lot going on here, so let’s break it into pieces.
First, we want to create a text field that, when the user hits the enter key, sends an action to the controller telling it to create a new todo:
<div id="create-todo">
{{view Ember.TextField id="new-todo" action="newTodo"
placeholder="What needs to be done?"}}
</div>
Next, we loop over all of the todos that the application knows about
and create an <li> for each:
<ul id="todo-list">
{{#each todo in controller}}
<li>
<!-- todo template here -->
</li>
{{/each}}
</ul>
Inside the {{#each}} block, we put the template
that should be rendered once per todo.
<li>
<div {{bindAttr class=":todo todo.isDone:done"}}>
<div class="display" title="Double click to edit...">
{{view Ember.Checkbox class="check" checkedBinding="todo.isDone"}}
<div class="todo-content">{{todo.content}}</div>
<span class="todo-destroy" {{action destroyTodo todo}}></span>
</div>
<div class="edit">
<input class="todo-input" type="text" value="${content}" />
</div>
</div>
</li>
First, we bind the containing <div>’s
class attribute to a static value, as well as to the
Todo model’s isDone property. If the
model’s isDone is true, the done
class will be added to the HTML element. Otherwise, it will be
removed.