Knockout.js Todo Example
HTML
<script src="http://knockoutjs.com/downloads/knockout-2.3.0.js"></script>
<div id="todoapp">
<header>
<h1>Todos by Knockout.js</h1>
<input id="new-todo" type="text" placeholder="What needs to be done?" data-bind="value:inputTitle,event: { keyup: createOnEnter}" />
</header>
<section id="main" style="display: block;">
<div data-bind="visible:todos().length>0">
<input id="toggle-all" type="checkbox" data-bind="checked:markAll" />
<label for="toggle-all">Mark all as complete</label>
</div>
<ul id="todo-list" data-bind="template:{ name:'item-template',foreach: todos}">
</ul>
</section>
<footer style="display: block;">
<div data-bind="visible:todos().length>0">
<div class="todo-count"><b data-bind="text:todos().length"></b> items left</div>
<!-- ko if: doneTodos() > 0 -->
<a id="clear-completed" data-bind="click:clear">
Clear <span data-bind="html:countDoneText(true)"></span>.
</a>
<!-- /ko -->
<br style="clear:both" />
</div>
</footer>
</div>
<script type="text/template" id="item-template">
<li data-bind="event:{ dblclick :$root.toggleEditMode},css : {done:done() }">
<div class="view">
<input class="toggle" type="checkbox" data-bind="checked:done" />
<label data-bind="text:title"></label>
<a class="destroy"></a>
</div>
<input class="edit" type="text" data-bind="value:title,event: { keyup: $root.editOnEnter}" />
</li>
</script>
CSS
body {
font-size: 14px;
background: #eeeeee;
color: #333333;
width: 520px;
margin: 0 auto;
}
#todoapp {
background: #fff;
padding: 20px;
margin-bottom: 40px;
}
#todoapp h1 {
font-size: 36px;
text-align: center;
}
#todoapp input[type="text"] {
width: 466px;
font-size: 24px;
line-height: 1.4em;
padding: 6px;
}
#main {
display: none;
}
#todo-list {
margin: 10px 0;
padding: 0;
list-style: none;
}
#todo-list li {
padding: 18px 20px 18px 0;
position: relative;
font-size: 24px;
border-bottom: 1px solid #cccccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li .edit {
display: none;
}
#todo-list li.editing {
border-bottom: 1px solid #778899;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li.editing .edit {
display: block;
width: 444px;
padding: 13px 15px 14px 20px;
margin: 0;
}
#todo-list li.done label {
color: #777777;
text-decoration: line-through;
}
#todo-list .destroy {
position: absolute;
right: 5px;
top: 20px;
display: none;
cursor: pointer;
width: 20px;
height: 20px;
}
#todoapp footer {
display: none;
margin: 0 -20px -20px -20px;
overflow: hidden;
color: #555555;
background: #f4fce8;
border-top: 1px solid #ededed;
padding: 0 20px;
line-height: 37px;
}
.todo-count {
float: left;
}
.todo-count .count {
font-weight: bold;
}
#clear-completed {
float: right;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
color: #555555;
font-size: 11px;
margin-top: 8px;
margin-bottom: 8px;
padding: 0 10px 1px;
cursor: pointer;
}
JavaScript
$(function() {
var Todo = function(title, done, order, callback) {
var self = this;
self.title = ko.observable(title);
self.done = ko.observable(done);
self.order = order;
self.updateCallback = ko.computed(function() {
callback(self);
return true;
});
}
var viewModel = function() {
var self = this;
self.todos = ko.observableArray([]);
self.inputTitle = ko.observable("");
self.doneTodos = ko.observable(0);
self.markAll = ko.observable(false);
self.addOne = function() {
var order = self.todos().length;
var t = new Todo(self.inputTitle(), false, order, self.countUpdate);
self.todos.push(t);
};
self.createOnEnter = function(item, event) {
if (event.keyCode == 13 && self.inputTitle()) {
self.addOne();
self.inputTitle("");
} else {
return true;
};
}
self.toggleEditMode = function(item, event) {
$(event.target).closest('li').toggleClass('editing');
}
self.editOnEnter = function(item, event) {
if (event.keyCode == 13 && item.title) {
item.updateCallback();
self.toggleEditMode(item, event);
} else {
return true;
};
}
self.markAll.subscribe(function(newValue) {
ko.utils.arrayForEach(self.todos(), function(item) {
return item.done(newValue);
});
});
self.countUpdate = function(item) {
var doneArray = ko.utils.arrayFilter(self.todos(), function(it) {
return it.done();
});
self.doneTodos(doneArray.length);
return true;
};
self.countDoneText = function(bool) {
var cntAll = self.todos().length;
var cnt = (bool ? self.doneTodos() : cntAll - self.doneTodos());
var text = "<span class='count'>" + cnt.toString() + "</span>";
text += (bool ? " completed" : " remaining");
text += (self.doneTodos() > 1 ? " items" : " item");
return text;
}
self.clear = function()...