Update: This version was written for ember-cli version 0.0.37. Please read my updated tutorial for ember-cli version 0.0.47.
Welcome to Ember CLI! This guide mirrors what is in the official ember.js getting started guide, but walks you through doing it with Ember CLI. Some things will be different than the guide, which is why I wrote this guide to help you through those differences and hopefully you won't struggle as much as I did.
I do recommend reading the getting started guide first since I'm going to leave out some of the details that it covers more throughly.
We will need to install ember-cli. Here are the most basic steps for getting it installed on your system for more details please see the official website.
At your terminal type:
npm install -g ember-cli
And now install Bower:
npm install -g bower
Now navigate to where you want to place your project code and create a new ember app:
ember new todos
Now start the server:
cd todos
ember server
Navigate to http://localhost:4200 to see your new app in action.
There are no differences in this section
Replace the text inside of app/templates/application.hbs with the following:
<section id="todoapp">
  <header id="header">
    <h1>todos</h1>
    <input type="text" id="new-todo" placeholder="What needs to be done?" />
  </header>
  <section id="main">
    <ul id="todo-list">
      <li class="completed">
        <input type="checkbox" class="toggle">
        <label>Learn Ember.js</label><button class="destroy"></button>
      </li>
      <li>
        <input type="checkbox" class="toggle">
        <label>...</label><button class="destroy"></button>
      </li>
      <li>
        <input type="checkbox" class="toggle">
        <label>Profit!</label><button class="destroy"></button>
      </li>
    </ul>
    <input type="checkbox" id="toggle-all">
  </section>
  <footer id="footer">
    <span id="todo-count">
      <strong>2</strong> todos left
    </span>
    <ul id="filters">
      <li>
        <a href="all" class="selected">All</a>
      </li>
      <li>
        <a href="active">Active</a>
      </li>
      <li>
        <a href="completed">Completed</a>
      </li>
    </ul>
    <button id="clear-completed">
      Clear completed (1)
    </button>
  </footer>
</section>
<footer id="info">
  <p>Double-click to edit a todo</p>
</footer>
Replace the contents of app/styles/app.css with this stylesheet.
Inside of the public/ folder create a new folder called assets and place the bg.png file in that folder.
We've already obtained Ember.js and dependencies! Move on to the next section.
Inside app/router.js file add this line to the Router.map body:
this.resource('todos', { path: '/' });
The app/router.js file should look like this now:
import Ember from 'ember';
var Router = Ember.Router.extend({
  location: TodosENV.locationType
});
Router.map(function() {
  this.resource('todos', { path: '/' });
});
export default Router;
Now copy the entire contents of the app/templates/application.hbs file into a new file called app/templates/todos.hbs.
Then replace the entire contents of the app/templates/application.hbs file with:
{{outlet}}
Now go back to your web browser and make sure everything still loaded okay.
Before we create our model we need to edit Brocfile.js so that ember-data will work:
app.import({
  development: 'vendor/ember-data/ember-data.js',
  production:  'vendor/ember-data/ember-data.prod.js'
}, {
  'ember-data': [
    'default'
  ]
});
Create a file at js/models/todo.js and put the following code inside:
import DS from 'ember-data';
var Todo = DS.Model.extend({
  title: DS.attr('string'),
  isCompleted: DS.attr('boolean')
});
export default Todo;
To enable fixtures create the folder app/adapters and then create the file: app/adapters/application.js and place the following code inside:
import DS from 'ember-data';
export default DS.FixtureAdapter.extend();
Now go back to our model app/models/todo.js and the fixture to it so that the file now looks like:
import DS from 'ember-data';
var Todo = DS.Model.extend({
  title: DS.attr('string'),
  isCompleted: DS.attr('boolean')
});
Todo.reopenClass({
  FIXTURES: [
    {
      id: "1",
      title: 'install ember-cli',
      isCompleted: true
    }, {
      id: "2",
      title: 'install additional dependencies',
      isCompleted: true
    }, {
      id: "3",
      title: 'develop amazing things',
      isCompleted: false
    }
]});
export default Todo;
Create a file called app/routes/todos.js and place the following code inside:
import Ember from 'ember';
export default Ember.Route.extend({
  model: function() {
    return this.store.find('todo');
  }
});
Now we need to add some handlebar code to our app/templates/todos.hbs template so that it can dynamically load the contents of our fixture.
<!--- ... additional lines truncated for brevity ... -->
<ul id="todo-list">
  {{ "{{#each" }}}}
    <li>
      <input type="checkbox" class="toggle">
      <label>{{ "{{title" }}}}</label>
      <button class="destroy"></button>
    </li>
  {{ "{{/each" }}}}
</ul>
<!--- ... additional lines truncated for brevity ... -->
Update the <li> tag in app/templates/todos.hbs:
<!--- ... additional lines truncated for brevity ... -->
<li {{bind-attr class="isCompleted:completed"}}>
  <input type="checkbox" class="toggle">
  <label>{{title}}</label>
  <button class="destroy"></button>
</li>
<!--- ... additional lines truncated for brevity ... -->
At the top of app/templates/todos.hbs replace the new todo <input> with an {{ "{{input" }}}} helper:
<!--- ... additional lines truncated for brevity ... -->
<h1>todos</h1>
{{input type="text" id="new-todo" placeholder="What needs to be done?"
          value=newTitle action="createTodo"}}
<!--- ... additional lines truncated for brevity ... -->
Inside of the app/controllers/ folder create a new file called: todos.js and put the following code inside of it:
import Ember from 'ember';
export default Ember.ArrayController.extend({
  actions: {
    createTodo: function() {
      // Get the todo title set by the "New Todo" text field
      var title = this.get('newTitle');
      if (!title.trim()) { return; }
      // Create the new Todo Model
      var todo = this.store.createRecord('todo', {
        title: title,
        isCompleted: false
      });
      // Clear the "New Todo" text field
      this.set('newTitle', '');
      // Save the new model
      todo.save();
    }
  }
});
Inside the app/templates/todos.hbs template replace the <input> tag for the checkbox with:
{{#each}}
  <li {{bind-attr class="isCompleted:completed"}}>
    {{input type="checkbox" checked=isCompleted class="toggle"}}
    <label>{{title}}</label>
    <button class="destroy"></button>
  </li>
{{/each}}
Inside of the app/controllers/ folder create a new file called: todo.js. And place the following code inside:
import Ember from 'ember';
export default Ember.ObjectController.extend({
  isCompleted: function(key, value){
    var model = this.get('model');
    if (value === undefined) {
      // property being used as a getter
      return model.get('isCompleted');
    } else {
      // property being used as a setter
      model.set('isCompleted', value);
      model.save();
      return value;
    }
  }.property('model.isCompleted')
});
Update the static count inside the todo-count span in the app/templates/todos.hbs template:
<!--- ... additional lines truncated for brevity ... -->
<span id="todo-count">
  <strong>{{remaining}}</strong> {{inflection}} left
</span>
<!--- ... additional lines truncated for brevity ... -->
Add this to the app/controllers/todos.js file:
// ... additional lines truncated for brevity ...
actions: {
// ... additional lines truncated for brevity ...
},
remaining: function() {
  return this.filterBy('isCompleted', false).get('length');
}.property('@each.isCompleted'),
inflection: function() {
  var remaining = this.get('remaining');
  return remaining === 1 ? 'todo' : 'todos';
}.property('remaining')
// ... additional lines truncated for brevity ...
Update app/templates/todos.hbs with the following code to enable toggling in and out of edit mode:
<!--- ... additional lines truncated for brevity ... -->
{{#each itemController="todo"}}
  <li {{bind-attr class="isCompleted:completed isEditing:editing"}}>
    {{#if isEditing}}
      <input class="edit">
    {{else}}
      {{input type="checkbox" checked=isCompleted class="toggle"}}
      <label {{action "editTodo" on="doubleClick"}}>{{title}}</label>
      <button class="destroy"></button>
    {{/if}}
  </li>
{{/each}}
<!--- ... additional lines truncated for brevity ... -->
And then update our app/controllers/todo.js controller:
// ... additional lines truncated for brevity ...
export default Ember.ObjectController.extend({
  actions: {
    editTodo: function() {
      this.set('isEditing', true);
    }
  },
  isEditing: false,
  isCompleted: function(key, value){
// ... additional lines truncated for brevity ...
You can now double-click a todo to edit it.
In app/templates/todos.hbs change out the static input tag under if editing:
<!--- ... additional lines truncated for brevity ... -->
{{#if isEditing}}
  {{input class="edit" value=title focus-out="acceptChanges" insert-newline="acceptChanges" autofocus="autofocus"}}
{{else}}
<!--- ... additional lines truncated for brevity ... -->
In app/templates/todos.hbs update the delete button:
<!--- ... additional lines truncated for brevity ... -->
<button {{action "removeTodo"}} class="destroy"></button>
<!--- ... additional lines truncated for brevity ... -->
In app/controllers/todo.js add the removeTodo action:
// ... additional lines truncated for brevity ...
actions: {
  editTodo: function() {
    this.set('isEditing', true);
  },
  acceptChanges: function() {
    this.set('isEditing', false);
    if (Ember.isEmpty(this.get('model.title'))) {
      this.send('removeTodo');
    } else {
      this.get('model').save();
    }
  },
  removeTodo: function() {
    var todo = this.get('model');
    todo.deleteRecord();
    todo.save();
  }
},
// ... additional lines truncated for brevity ...
Now you can delete todos.
Inside of app/templates/todos.hbs move the entire contents of <ul id="todo-list" section into a new template called: app/templates/todos/index.hbs.
Within app/templates/todos.hbs place a Handlebars {{ "{{outlet" }}}} helper where the <ul> was previously:
<!--- ... additional lines truncated for brevity ... -->
<section id="main">
  {{outlet}}
  <input type="checkbox" id="toggle-all">
</section>
<!--- ... additional lines truncated for brevity ... -->
In app/router.js change the todos mapping with an additional empty function parameter so it can accept child routes:
Router.map(function() {
  this.resource('todos', { path: '/' }, function() {
    // additional child routes will go here later
  });
});
Create a new folder called todos inside of the app/routes/ directory.
Create a new file called index.js inside of the app/routes/todos/ directory and add the following code:
import Ember from 'ember';
export default Ember.Route.extend({
  model: function() {
    return this.modelFor('todos');
  }
});
In app/templates/todos.hbs convert the <a> tag for 'Active' todos into a Handlebars {{link-to}} helper:
<!--- ... additional lines truncated for brevity ... -->
<li>
  <a href="all">All</a>
</li>
<li>
  {{#link-to "todos.active" activeClass="selected"}}Active{{/link-to}}
</li>
<li>
  <a href="completed">Completed</a>
</li>
<!--- ... additional lines truncated for brevity ... -->
In app/router.js update the router to recognize this new path:
// ... additional lines truncated for brevity ...
Router.map(function() {
  this.resource('todos', { path: '/' }, function() {
    this.route('active');
  });
});
// ... additional lines truncated for brevity ...
In app/routes/todos/active.js implement a matching route:
import Ember from 'ember';
export default Ember.Route.extend({
  model: function() {
    return this.store.filter('todo', function(todo) {
      return !todo.get('isCompleted');
    });
  },
  renderTemplate: function(controller) {
    this.render('todos/index', { controller: controller} );
  }
});
In app/templates/todos.hbs convert the <a> tag for 'Completed' todos into a Handlebars {{link-to}} helper:
<!--- ... additional lines truncated for brevity ... -->
<li>
  <a href="all">All</a>
</li>
<li>
  {{#link-to "todos.active" activeClass="selected"}}Active{{/link-to}}
</li>
<li>
  {{#link-to "todos.completed" activeClass="selected"}}Completed{{/link-to}}
</li>
<!--- ... additional lines truncated for brevity ... -->
In app/router.js update the router to recognize this new path:
Router.map(function() {
  this.resource('todos', { path: '/' }, function() {
    this.route('active');
    this.route('completed');
  });
});
In app/routes/todos/completed.js implement a matching route:
import Ember from 'ember';
export default Ember.Route.extend({
  model: function() {
    return this.store.filter('todo', function(todo) {
      return todo.get('isCompleted');
    });
  },
  renderTemplate: function(controller) {
    this.render('todos/index', {controller: controller});
  }
});
In app/templates/todos.hbs convert the <a> tag for 'All' todos into a Handlebars {{ "{{link-to" }}}} helper:
<!--- ... additional lines truncated for brevity ... -->
<li>
  {{#link-to "todos.index" activeClass="selected"}}All{{/link-to}}
</li>
<li>
  {{#link-to "todos.active" activeClass="selected"}}Active{{/link-to}}
</li>
<li>
  {{#link-to "todos.completed" activeClass="selected"}}Completed{{/link-to}}
</li>
<!--- ... additional lines truncated for brevity ... -->
In app/templates/todos.hbs update the static <button> for clearing all completed todos:
<!--- ... additional lines truncated for brevity ... -->
{{#if hasCompleted}}
  <button id="clear-completed" {{action "clearCompleted"}}>
    Clear completed ({{completed}})
  </button>
{{/if}}
<!--- ... additional lines truncated for brevity ... -->
In app/controllers/todos.js implement the matching properties and a method that will clear completed todos and persist these changes when the button is clicked:
import Ember from 'ember';
export default Ember.ArrayController.extend({
  actions: {
    clearCompleted: function() {
      var completed = this.filterBy('isCompleted', true);
      completed.invoke('deleteRecord');
      completed.invoke('save');
    },
    // ... additional lines truncated for brevity ...
  },
  hasCompleted: function() {
    return this.get('completed') > 0;
  }.property('completed'),
  completed: function() {
    return this.filterBy('isCompleted', true).get('length');
  }.property('@each.isCompleted'),
  // ... additional lines truncated for brevity ...
In app/templates/todos.hbs replace the static checkbox <input> with an {{ "{{input" }}}}:
<!--- ... additional lines truncated for brevity ... -->
<section id="main">
  {{outlet}}
  {{input type="checkbox" id="toggle-all" checked=allAreDone}}
</section>
<!--- ... additional lines truncated for brevity ... -->
In app/controllers/todos.js implement the matching allAreDone property:
// ... additional lines truncated for brevity ...
allAreDone: function(key, value) {
  return !!this.get('length') && this.isEvery('isCompleted');
}.property('@each.isCompleted')
// ... additional lines truncated for brevity ...
Update the allAreDone property in app/controllers/todos.js to handle both getting and setting behavior:
// ... additional lines truncated for brevity ...
allAreDone: function(key, value) {
  if (value === undefined) {
    return !!this.get('length') && this.everyProperty('isCompleted', true);
  } else {
    this.setEach('isCompleted', value);
    this.invoke('save');
    return value;
  }
}.property('@each.isCompleted')
// ... additional lines truncated for brevity ...
I couldn't figure out how to get this to work with ember-cli, but I will be writing another post that talks about how to connect to a Rails backend for data persistance.
These apps helped me along when I got stuck while writing this tutorial: