The Ember.js Todos Demo Application: app.js

In the last post, we looked at the Ember demo app, Todos.  We got it installed on our local development environment and had fun creating, completing, and deleting todos.

In the next few posts I’d like to walk through the code.  We’ll start with app/lib/app.js. The Ember app comes to life via instantiating Ember.Application, extending Ember.object(s) (each with methods and properties), creating Ember.Controller(s), and extending Ember.View(s).  In the Todos application, this is all done in app.js.

What follows is a line-by-line walkthrough of the first parts of app.js.

app.js

Leverage jquery and ember.

require('todos/vendor/jquery-1.7.1');
require('todos/vendor/ember-0.9.5');

Require the view template (which we will cover in the next post).

require('todos/templates/main_view');

Create an Ember Application named Todos.

Todos = Ember.Application.create();

Create an Ember Object named Todo.
Todos have two attributes: title and isDone. Each attribute has a default value.

Todos.Todo = Ember.Object.extend({
  title: null,
  isDone: false
});

Create an Ember ArrayController to hold our Todos.  
ArrayControllers handle collection of objects.  Ember knows how to push to, filter, traverse, and perform other enumerable operations on the items in the ArrayController’s content collection.  Yet even more useful and powerful is its binding ability.  You do not need to bind each element of the array to an element in the DOM.  Instead, simply populate the ArrayController’s content property and then wrap the view bindings in #each /each.  We will see this in a future post where we look at the views bindings in detail.  For now, we are just looking at app.js and are about to create our ArrayController with empty content.

Todos.todosController = Ember.ArrayController.create({
  content: [],

Create Todos
When called, createToDo takes a title, creates a Todo setting the title attribute, and then adds our new todo to the content collection via pushObject.

  createTodo: function(title) {
    var todo = Todos.Todo.create({ title: title });
    this.pushObject(todo);
  },

Remove Todos
clearCompletedTodos introduces a couple interesting methods which are available on Ember enumerables such as our todosController.

filterProperty returns only those items in a collection which for which the given attribute (‘isDone’) matches the given value (true).

forEach executes the block for each item.

In this case, each todo for which isDone is true is removed from the content collection (via this.removeObject).

  
  clearCompletedTodos: function() {
    this.filterProperty('isDone', true).forEach(this.removeObject, this);
  },

Define properties and dependencies
Recall that Ember allows you use functions like a property.  Ember functions can be attribute getters, can be setters, and can be “computed properties”.  The “remaining” function is an example of a computed property.

  remaining: function() {
    return this.filterProperty('isDone', false).get('length');
  }.property('@each.isDone'),

This function filters todos where isDone is false, and then returns the length of that filtered collection.

Note the .property call at the end of the function.  This tells Ember to: 1.) treat the function as a property and 2.) optionally create a dependency between this function and other properties.  In the case of todo’s “remaining” function, we can see that the function is to be treated as a property and has a dependency on “@each.isDone”.

@each is a special key which tells Ember.js that the value of “remaining” could change when the array changes or when the isDone property of any of the objects in the contents array changes.  With this dependency marked, Ember automatically observes the collection and its members for changes and automatically recalculates “remaining” upon a change (which then could automatically update views bound to the “remaining” property).

The isEmpty function is in similar form.  It returns the number of items in the content collection. .property(‘length’) tells Ember that isEmpty is dependent upon the collection’s ‘length’.  (i.e. when items are added to or removed from the collection, Ember knows the value of isEmpty changes).

  isEmpty: function() {
    return this.get('length') === 0;
  }.property('length'),

Write getters and setters
allAreDone demonstrates a common pattern in Ember.  This is a getter/setter pattern.  If two values are sent in, it works as a setter; otherwise it acts as a getter (returning the value of the requested attribute).  Note the use of setEach to set the ‘isDone’ property of every item in the collection to value.  Also note the dependency.  allAreDone is dependent on the isDone property of each item in the collection.

  allAreDone: function(key, value) {
    if (arguments.length === 2) {
      this.setEach('isDone', value);

      return value;
    } else {
      return !this.get('isEmpty') && this.everyProperty('isDone', true);
    }
  }.property('@each.isDone')

Close out the Todos.todosController

});

In these 41 lines of code, the Todos application created an Ember Application and defined an Ember Object.  It then created an ArrayController to hold and manage our Todos. It knows how to createToDos, add them and remove them from its content collection, count how many toDos are remaining, answer if it isEmpty, and set all Todos to done or not done. It also knows of dependencies between properties. For example, if any one Todo’s ‘isDone’ property is changes, the application knows that the allAreDone property changed as well.

All of this is great, but even greater when the user can interact with these Todos via views. In the next post, we’ll take a look at the remaining parts of app.js which create a view and tie it to the app.

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s