TodoMVC-based Getting Started Guide

Hey all,

I’ve been working on an Ember.js Getting Started Guide for the site. I settled on TodoMVC as the sample application for this app. As a super tiny app TodoMVC isn’t a great example of the full power of Ember.js, but I didn’t want to create a brand new application and make people both learn the ins and outs of this app and how Ember.js works.

The full tutorial will contain narrative, links to diffs between steps, and the ability to run each step in a jsfiddle. For now, I want to get feedback on: the tutorial’s steps-as-code and the general TOC

I tried to keep each step small and focused on a single feature. Each step needs to build on skills previously learned. I’d appreciate feedback on how well you think the code sample hits this goal.

7 Likes

@trek.glowacki I just glanced it and looks good. I will go through the complete tutorial and come back with more feedbacks. Just wanted to check, if you can cover relationships in ember-data in this tutorial?

Ideas :

  1. Add comments to TODO items
  2. Add tags to TODO items.

Any example for 1-to-1 or 1-to-many or many-to-many relationship w/ addchild option on parent would help.

If we go beyond the existing TodoMVC feature set, I’d like to just create a brand new application. I suspect Addy and Sindre have plans (http://tastejs.com/) to create a more meaningful application along the line of TodoMVC. If that’s the case, I’d like to get on that band wagon.

1 Like

M2M relationships in Ember data are asked about quite frequently on Stack Overflow. Offering guidance on setting up those relationships would be a good idea. We had that problem ourselves and ended up solving it by using an adapter by @toranb.

For TasteJS: we’re planning on hopefully hammering out a spec for a complex, GitHub API driven application sometime soon. We want it to capture auth, session handling and a number of other features a simple Todo app just doesn’t capture.

@trek.glowacki we’ll be inviting you to take part into the conversations around this in the (hopefully) near future :slight_smile:

4 Likes

Update on this. I tried them. Features beyond Step19 stopped working for me. My stack:

DEBUG: ------------------------------- 
DEBUG: Ember.VERSION : 1.0.0-rc.1
DEBUG: Handlebars.VERSION : 1.0.0-rc.3
DEBUG: jQuery.VERSION : 1.9.1
DEBUG: -------------------------------

I’d need more information to make this actionable. Could you provide a little more context or maybe archive your version of the code and share it with me?

Archive file - http://ge.tt/api/1/files/6qisueb/0/blob?download

Awesome. I can have a look tonight.

it looks like you’re missing

Todos.TodosRoute = Ember.Route.extend({
  model: function () {
    return Todos.Todo.find();
  }
});

from your route at this step. Did you accidentally remove it?

Huh! Yeah, I replaced it with TodosIndexRoute in step18. Its working now.

There are now a few steps with text content https://github.com/emberjs/website/pull/399/files

hey Trek, Any chance you could upload to the “quick-start” code sample on github, the actually libraries you use for dependency in that specific example? I am having a ton of issues trying to get a version of ember to work with the right version of handlebars, getting Assertion errors, and not defined errors all around. It would be great if you could add a libs folder in that project folder for the currently working combination of ember.js handlebars.js and ember-data.js The code i’m refferring to is linked below: https://github.com/emberjs/quickstart-code-sample

Thank you :slight_smile:

For me, everything worked fine until I added the code that’s displayed at the Displaying Model Data step in the Getting Started Guide.

Routing doesn’t seem to work at all, when I add:

Todos.TodosRoute = Ember.Route.extend({});
    model: function () {
        return Todos.Todo.find();
    }
});

The entire UI disappears when I reload the application. Why is this happening?

HELP???


index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Ember.js • TodoMVC</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <!-- template -->
    <script type="text/x-handlebars"  data-template-name="todos">
        <section id="todoapp">
                <!-- Header-->
                <header id="header">
                  <h1>todos</h1>
                  <input type="text" id="new-todo" placeholder="What needs to be done?" />
                </header>

          
                <!-- Main -->
                
                <section id="main">
                  <ul id="todo-list">
                    
                     {{#each controller}}
                      <li>
                        <input type="checkbox" class="toggle">
                        <label>{{title}}</label><button class="destroy"></button>
                      </li>
                    {{/each}}
                     
                  </ul>
                  
                  <input type="checkbox" id="toggle-all">

                </section>
                

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

    </script>     

    <!-- Js scripts -->
    <script src="js/libs/jquery-1.10.2.min.js"></script>
    <script src="js/libs/handlebars.js"></script>
    <script src="js/libs/ember-1.0.0-rc.6.js"></script>
    <script src="js/libs/ember-data.prod.js"></script>

    <!-- application & routes -->
    <script src="js/application.js"></script>
    <script src="js/router.js"></script>
    
    <!-- models -->
    <script src="js/models/store.js"></script>
    <script src="js/models/todo.js"></script>
    <!-- end scripts -->
  </body>
</html>

js/route.js

// Default route using the Router class
Todos.Router.map(function () {
  this.resource('todos', { path: '/' });
});


Todos.TodosRoute = Ember.Route.extend({
  model: function () {
    return Todos.Todo.find();
  }
});

js/application.js

// Create an instance of the application
window.Todos = Ember.Application.create();

js/models/todo.js

// Todo class
Todos.Todo = DS.Model.extend({
  title: DS.attr('string'),
  isCompleted: DS.attr('boolean')
});

// ... additional lines truncated for brevity ...
Todos.Todo.FIXTURES = [
 {
   id: 1,
   title: 'Learn Ember.js today!',
   isCompleted: true
 },
 {
   id: 2,
   title: '...',
   isCompleted: false
 },
 {
   id: 3,
   title: 'Profit!',
   isCompleted: false
 }
];

js/models/store.js

   Todos.Store = DS.Store.extend({
     revision: 12,
     adapter: 'DS.FixtureAdapter'
   });

style.css

html,
body {
	margin: 0;
	padding: 0;
}

button {
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	font-size: 100%;
	vertical-align: baseline;
	font-family: inherit;
	color: inherit;
	-webkit-appearance: none;
	/*-moz-appearance: none;*/
	-ms-appearance: none;
	-o-appearance: none;
	appearance: none;
}

body {
	font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
	line-height: 1.4em;
	/* background: #eaeaea url('bg.png'); */
	background: url('bg.png');
	color: #4d4d4d;
	width: 550px;
	margin: 0 auto;
	-webkit-font-smoothing: antialiased;
	-moz-font-smoothing: antialiased;
	-ms-font-smoothing: antialiased;
	-o-font-smoothing: antialiased;
	font-smoothing: antialiased;
}

#todoapp {
	background: #fff;
	background: rgba(255, 255, 255, 0.9);
	margin: 130px 0 40px 0;
	border: 1px solid #ccc;
	position: relative;
	border-top-left-radius: 2px;
	border-top-right-radius: 2px;
	box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
				0 25px 50px 0 rgba(0, 0, 0, 0.15);
}

#todoapp:before {
	content: '';
	border-left: 1px solid #f5d6d6;
	border-right: 1px solid #f5d6d6;
	width: 2px;
	position: absolute;
	top: 0;
	left: 40px;
	height: 100%;
}

#todoapp input::-webkit-input-placeholder {
	font-style: italic;
}

#todoapp input:-moz-placeholder {
	font-style: italic;
	color: #a9a9a9;
}

#todoapp h1 {
	position: absolute;
	top: -120px;
	width: 100%;
	font-size: 70px;
	font-weight: bold;
	text-align: center;
	color: #b3b3b3;
	color: rgba(255, 255, 255, 0.3);
	text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
	-webkit-text-rendering: optimizeLegibility;
	-moz-text-rendering: optimizeLegibility;
	-ms-text-rendering: optimizeLegibility;
	-o-text-rendering: optimizeLegibility;
	text-rendering: optimizeLegibility;
}

#header {
	padding-top: 15px;
	border-radius: inherit;
}

#header:before {
	content: '';
	position: absolute;
	top: 0;
	right: 0;
	left: 0;
	height: 15px;
	z-index: 2;
	border-bottom: 1px solid #6c615c;
	background: #8d7d77;
	background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
	background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
	background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
	background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
	background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
	background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
	filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
	border-top-left-radius: 1px;
	border-top-right-radius: 1px;
}

#new-todo,
.edit {
	position: relative;
	margin: 0;
	width: 100%;
	font-size: 24px;
	font-family: inherit;
	line-height: 1.4em;
	border: 0;
	outline: none;
	color: inherit;
	padding: 6px;
	border: 1px solid #999;
	box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
	-webkit-box-sizing: border-box;
	-moz-box-sizing: border-box;
	-ms-box-sizing: border-box;
	-o-box-sizing: border-box;
	box-sizing: border-box;
	-webkit-font-smoothing: antialiased;
	-moz-font-smoothing: antialiased;
	-ms-font-smoothing: antialiased;
	-o-font-smoothing: antialiased;
	font-smoothing: antialiased;
}

#new-todo {
	padding: 16px 16px 16px 60px;
	border: none;
	background: rgba(0, 0, 0, 0.02);
	z-index: 2;
	box-shadow: none;
}

#main {
	position: relative;
	z-index: 2;
	border-top: 1px dotted #adadad;
}

label[for='toggle-all'] {
	display: none;
}

#toggle-all {
	position: absolute;
	top: -42px;
	left: -4px;
	width: 40px;
	text-align: center;
	border: none; /* Mobile Safari */
}

#toggle-all:before {
	content: '»';
	font-size: 28px;
	color: #d9d9d9;
	padding: 0 25px 7px;
}

#toggle-all:checked:before {
	color: #737373;
}

#todo-list {
	margin: 0;
	padding: 0;
	list-style: none;
}

#todo-list li {
	position: relative;
	font-size: 24px;
	border-bottom: 1px dotted #ccc;
}

#todo-list li:last-child {
	border-bottom: none;
}

#todo-list li.editing {
	border-bottom: none;
	padding: 0;
}

#todo-list li.editing .edit {
	display: block;
	width: 506px;
	padding: 13px 17px 12px 17px;
	margin: 0 0 0 43px;
}

#todo-list li.editing .view {
	display: none;
}

#todo-list li .toggle {
	text-align: center;
	width: 40px;
	/* auto, since non-WebKit browsers doesn't support input styling */
	height: auto;
	position: absolute;
	top: 0;
	bottom: 0;
	margin: auto 0;
	border: none; /* Mobile Safari */
	-webkit-appearance: none;
	/*-moz-appearance: none;*/
	-ms-appearance: none;
	-o-appearance: none;
	appearance: none;
}

#todo-list li .toggle:after {
	content: '✔';
	line-height: 43px; /* 40 + a couple of pixels visual adjustment */
	font-size: 20px;
	color: #d9d9d9;
	text-shadow: 0 -1px 0 #bfbfbf;
}

#todo-list li .toggle:checked:after {
	color: #85ada7;
	text-shadow: 0 1px 0 #669991;
	bottom: 1px;
	position: relative;
}

#todo-list li label {
	word-break: break-word;
	padding: 15px;
	margin-left: 45px;
	display: block;
	line-height: 1.2;
	-webkit-transition: color 0.4s;
	-moz-transition: color 0.4s;
	-ms-transition: color 0.4s;
	-o-transition: color 0.4s;
	transition: color 0.4s;
}

#todo-list li.completed label {
	color: #a9a9a9;
	text-decoration: line-through;
}

#todo-list li .destroy {
	display: none;
	position: absolute;
	top: 0;
	right: 10px;
	bottom: 0;
	width: 40px;
	height: 40px;
	margin: auto 0;
	font-size: 22px;
	color: #a88a8a;
	-webkit-transition: all 0.2s;
	-moz-transition: all 0.2s;
	-ms-transition: all 0.2s;
	-o-transition: all 0.2s;
	transition: all 0.2s;
}

#todo-list li .destroy:hover {
	text-shadow: 0 0 1px #000,
				 0 0 10px rgba(199, 107, 107, 0.8);
	-webkit-transform: scale(1.3);
	-moz-transform: scale(1.3);
	-ms-transform: scale(1.3);
	-o-transform: scale(1.3);
	transform: scale(1.3);
}

#todo-list li .destroy:after {
	content: '✖';
}

#todo-list li:hover .destroy {
	display: block;
}

#todo-list li .edit {
	display: none;
}

#todo-list li.editing:last-child {
	margin-bottom: -1px;
}

#footer {
	color: #777;
	padding: 0 15px;
	position: absolute;
	right: 0;
	bottom: -31px;
	left: 0;
	height: 20px;
	z-index: 1;
	text-align: center;
}

#footer:before {
	content: '';
	position: absolute;
	right: 0;
	bottom: 31px;
	left: 0;
	height: 50px;
	z-index: -1;
	box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
				0 6px 0 -3px rgba(255, 255, 255, 0.8),
				0 7px 1px -3px rgba(0, 0, 0, 0.3),
				0 43px 0 -6px rgba(255, 255, 255, 0.8),
				0 44px 2px -6px rgba(0, 0, 0, 0.2);
}

#todo-count {
	float: left;
	text-align: left;
}

#filters {
	margin: 0;
	padding: 0;
	list-style: none;
	position: absolute;
	right: 0;
	left: 0;
}

#filters li {
	display: inline;
}

#filters li a {
	color: #83756f;
	margin: 2px;
	text-decoration: none;
}

#filters li a.selected {
	font-weight: bold;
}

#clear-completed {
	float: right;
	position: relative;
	line-height: 20px;
	text-decoration: none;
	background: rgba(0, 0, 0, 0.1);
	font-size: 11px;
	padding: 0 10px;
	border-radius: 3px;
	box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}

#clear-completed:hover {
	background: rgba(0, 0, 0, 0.15);
	box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}

#info {
	margin: 65px auto 0;
	color: #a6a6a6;
	font-size: 12px;
	text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
	text-align: center;
}

#info a {
	color: inherit;
}

/*
	Hack to remove background from Mobile Safari.
	Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
	#toggle-all,
	#todo-list li .toggle {
		background: none;
	}

	#todo-list li .toggle {
		height: 40px;
	}

	#toggle-all {
		top: -56px;
		left: -15px;
		width: 65px;
		height: 41px;
		-webkit-transform: rotate(90deg);
		transform: rotate(90deg);
		-webkit-appearance: none;
		appearance: none;
	}
}

.hidden{
	display:none;
}

Sorry about that. I fixed my issue by downloading and including handlebars-1.0.0-rc.4 from emberjs.com

This Ember noob needs a bit of help. I have built a few basic Ember.js apps. Now I am trying to make the todoMVC app work.

I am using a 64-bit Windows 7 PC and Notepad++ and testing each step using Firefox, Google, and IE. I’m also using the latest Ember Starter Kit (with RC6) and a recent version of Ember-data. But I’ve hit a problem. Each time I reach the step titled “Displaying the Number of Incomplete Todos,” ( http://emberjs.com/guides/getting-started/displaying-the-number-of-incomplete-todos/ ), the counter in the lower left corner (it says “2 items left” in the illustration at page bottom) does NOT update as it is supposed to.

It stays stuck at “2 items left” and shows no sign of life when I click on items in the “ToDo” list. Other aspects of the app are connected to that counter. So the fact that it does not update kills the app’s operation.

I have checked and re-checked the code until my head hurts and have tried twice to build new versions of the app from scratch, very carefully following each step. I have also tried running it on two other Windows machines, with the same discouraging results. Everything works, step by step, until I hit “Displaying the Number of Incomplete Todos,” and there, the counter refuses to budge when I click on items.

Can somebody take a look at the code in that step and in an earlier step or two and see if there might be something obvious that might cause a problem? For example, I’ve seen some vague references on the web to problems involving bindAttr in other apps. And that one is used in the step prior to the step that has stopped me cold. But I’m just guessing. Otherwise, I’m clueless. Has anyone else had this frozen counter problem?

Thanks for any help. I wasted a good holiday “playing” with this.

Sorry that you had issues. Right now the included JSBin on that step isn’t running properly. I’m trying to discover what’s wrong with it. Thanks for brining this to our attention.

It looks like someone updated the version we recommended (RC3) to RC6 but did not update the recommended SHA for EmberData and didn’t test that the application continued to work.

My guess is that the build of ember-data has a breaking change.

The JSBin included with that stop does seem to work properly. (Apparently there was a temporary JSBin bug that prevented it from loading when I last checked.) Have you compared your code to the code provided in the JSBin? Are there any errors in your console locally?

this solve my problem, thank you