Knockout.js Playground
Experimenting with Knockout.js
by Doug Hill
HTML
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<h1>Another Knockout.js ToDo App</h1>
<div class="app-container">
<button data-bind="click: onAdd">Add another ToDo item</button>
<span class="remaining" data-bind="text: remaining"></span>
<ul class="container" data-bind="foreach: todos">
<li data-bind="visible: editing">
<span class="action"><a class="cancel" href="#" data-bind="click: onCancel">Cancel</a></span>
<span class="action"><a class="update" href="#" data-bind="click: onCommit">Update</a></span>
<input type="text" class="editing" data-bind="value: toDoText" />
</li>
<li data-bind="visible: !editing()">
<span class="action"><a class="remove" href="#" data-bind="click: $parent.onRemove">Remove</a></span>
<span class="action"><a class="edit" href="#" data-bind="click: onEdit">Edit</a></span>
<input type="checkbox" class="complete" data-bind="checked: completed" />
<span class="item" data-bind="css: { complete: completed }, text: toDoText"></span>
</li>
</ul>
</div>
CSS
button {
margin: 10px;
}
h1 {
font: 2.1em sans-serif;
padding: 10px;
margin-bottom: 10px;
}
li {
padding: 10px 20px;
margin: 5px 20px;
border-radius: 5px;
background: #444;
color: white;
font: 1.3em sans-serif;
}
ul {
background: lightgray;
padding: 5px;
}
input.complete {
margin-right: 10px;
}
input.editing {
width: 65%;
}
span.action {
float: right;
margin-left: 10px;
font-size: 0.8em;
}
span.item {
text-shadow: 1px 0 3px black;
}
span.item.complete {
text-decoration: line-through;
}
.app-container{
position: relative;
}
.action a {
text-decoration: none;
color: orange;
}
.action a:hover {
text-decoration: underline;
color: orangered;
}
.remaining {
font: 1.1em sans-serif;
right: 25px;
position: absolute;
margin: 10px;
}
JavaScript
// A ToDo item
function ToDo(item) {
var self = this;
item = item || {};
// Data
self.completed = ko.observable( !! item.completed);
self.editing = ko.observable(false);
self.listText = item.text || 'Edit this ToDo item';
self.tmpText = '';
self.toDoText = ko.computed({
read: function() {
return self.editing() ? self.tmpText : self.listText;
},
write: function(value) {
if (self.editing()) {
self.tmpText = value;
}
}
});
// Operation
self.onEdit = function() {
self.tmpText = self.listText;
self.editing(true);
};
self.onCommit = function() {
self.listText = self.tmpText;
self.editing(false);
};
self.onCancel = function() {
self.editing(false);
};
}
// A ToDo model
function ToDoListModel() {
var self = this;
// Data
self.todos = ko.observableArray([
new ToDo({
text: 'Step one: Create great ToDos App',
completed: true
}),
new ToDo({
text: 'Step two: ???'
}),
new ToDo({
text: 'Step three: Profit'
})
]);
self.remaining = ko.computed(function() {
var arr = self.todos(),
len = arr.length,
i = 0,
open = 0,
txt = '';
for (; i < len; i++) {
if (!arr[i].completed()) open++;
}
if (open === 0) return 'No remaining items';
if (open === 1) return '1 remaining item';
return open + ' remaining items';
});
// Operations
self.onRemove = function(todo) {
self.todos.remove(todo);
};
self.onAdd = function() {
self.todos.push(new ToDo());
};
}
// Create a new model and apply the bindings
var model = new ToDoListModel();
ko.applyBindings(model);