JSFiddle - React, Tailwind, and code Playground
HTML
<script src="http://jshost.googlecode.com/hg/knockout-latest.js"></script>
<script src="http://jshost.googlecode.com/hg/koBindingConventionsPrototype.js"></script>
<div id="todoapp">
<h1>Todos</h1>
<form id="create-todo">
<input placeholder="What needs to be done?" />
</form>
<ul id="todo-list">
<li>
<div class="display">
<input class="check-done" type="checkbox" />
<div class="todo-content"></div>
<span class="todo-destroy"></span>
</div>
<form class="edit">
<input class="edit-content" />
</form>
</li>
</ul>
<div id="todo-stats">
<span class="num-remaining">
<b class="count"></b>
<span class="items"></span> left.
</span>
<span class="todo-clear">
<a href="#">
Clear <b class="number-done"></b>
completed <span class="items"></span>
</a>
</span>
</div>
</div>
<ul id="instructions">
<li>Double-click to edit a todo.</li>
</ul>
CSS
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-weight: inherit;
font-style: inherit;
font-size: 100%;
font-family: inherit;
vertical-align: baseline;
}
body {
line-height: 1;
color: black;
background: white;
}
ol, ul {
list-style: none;
}
a img {
border: none;
}
html {
background: #eeeeee;
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.4em;
background: #eeeeee;
color: #333333;
}
#todoapp {
width: 480px;
margin: 0 auto 40px;
background: white;
padding: 20px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
}
#todoapp h1 {
font-size: 36px;
font-weight: bold;
text-align: center;
padding: 20px 0 30px 0;
line-height: 1;
}
#create-todo {
position: relative;
}
#create-todo input {
width: 466px;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
#create-todo input::-webkit-input-placeholder {
font-style: italic;
}
#create-todo span {
position: absolute;
z-index: 999;
width: 170px;
left: 50%;
margin-left: -85px;
}
#todo-list {
margin-top: 10px;
}
#todo-list li {
...
JavaScript
function Todo (content, done) {
this.content = ko.observable(content);
this.done = ko.observable(done);
this.editing = ko.observable(false);
this.edit = function () { this.editing(true) };
this.save = function () { this.editing(false) };
}
function TodoList (todos) {
this.items = ko.observableArray(todos);
this.itemToAdd = ko.observable('');
this.addItem = function () {
if (this.itemToAdd()) {
this.items.push(new Todo(this.itemToAdd(), false));
this.itemToAdd('');
}
};
this.removeItem = function (item) {
this.items.remove(item);
}
this.done = ko.dependentObservable(function () {
return this.items().filter(function (todo) { return todo.done(); });
}, this);
this.remaining = ko.dependentObservable(function () {
return this.items().filter(function (todo) { return !todo.done(); });
}, this);
this.clearCompleted = function () {
this.items(this.remaining());
};
}
// Get initial state from local storage
var initialTodos = $.map(JSON.parse(localStorage.getItem("Todos") || "[]"), function(obj) {
return new Todo(obj.content, obj.done);
});
var viewModel = new TodoList(initialTodos);
// Set up bindings
ko.bindingConventions({
"#create-todo" : "submit: addItem",
"#create-todo input" : "value: itemToAdd, valueUpdate: 'afterkeydown'",
"#todo-list" : "foreach: items",
"#todo-list li" : "css: { editing: editing, done: done }",
".check-done" : "checked: done",
".todo-content" : "text: content, event: { dblclick: edit }",
"form.edit" : "submit: save",
".edit-content" : "value: content, hasfocus: editing, valueUpdate: 'afterkeydown'",
".num-remaining" : "visible: items().length > 0",
".num-remaining .count" : "text: remaining().length",
".num-remaining .items" : "text: remaining().length == 1 ? 'item' :...