Skip to content

Commit 3a6db4d

Browse files
committed
Merge pull request tastejs#88 from rniemeyer/knockout-template-update
Update Knockout.js sample to conform to template for Issue tastejs#68
2 parents 2e76a44 + 73ebf51 commit 3a6db4d

File tree

9 files changed

+229
-609
lines changed

9 files changed

+229
-609
lines changed
-555 Bytes
Binary file not shown.

architecture-examples/knockoutjs/css/todos.css

Lines changed: 0 additions & 400 deletions
This file was deleted.
Lines changed: 63 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,71 @@
1-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2-
<html xmlns="http://www.w3.org/1999/xhtml">
1+
<!doctype html>
2+
<html lang="en">
33
<head>
4+
<meta charset="utf-8">
45
<title>Knockout.js</title>
5-
<link href="css/todos.css" media="all" rel="stylesheet" type="text/css" />
6+
<link rel="stylesheet" href="../../assets/base.css">
67
</head>
78
<body>
8-
<div id="todoapp">
9-
<div class="title">
10-
<h1>Todos</h1>
11-
</div>
12-
<div class="content">
13-
<div id="create-todo">
14-
<input id="new-todo" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add" placeholder="What needs to be done?" />
15-
<span class="ui-tooltip-top" data-bind="visible: showTooltip" style="display: none;">Press Enter to save this task</span>
16-
</div>
17-
<div id="todos">
18-
<div data-bind="visible: todos().length">
19-
<input id="check-all" class="check" type="checkbox" data-bind="checked: allCompleted" />
20-
<label for="check-all">Mark all as complete</label>
9+
<div id="todoapp">
10+
<header>
11+
<h1>Todos</h1>
12+
<input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add"
13+
placeholder="What needs to be done?"/>
14+
</header>
15+
<section id="main" data-bind="block: todos().length">
16+
<input id="toggle-all" type="checkbox" data-bind="checked: allCompleted">
17+
<label for="toggle-all">Mark all as complete</label>
18+
<ul id="todo-list" data-bind="foreach: todos">
19+
<li data-bind="css: { done: done, editing: editing }">
20+
<div class="view" data-bind="event: { dblclick: $root.editItem }">
21+
<input class="toggle" type="checkbox" data-bind="checked: done">
22+
<label data-bind="text: content"></label>
23+
<a class="destroy" href="#" data-bind="click: $root.remove"></a>
2124
</div>
22-
<ul id="todo-list" data-bind="foreach: todos">
23-
<li data-bind="css: { editing: editing }">
24-
<div class="todo" data-bind="css: { done : done }">
25-
<div class="display">
26-
<input class="check" type="checkbox" data-bind="checked: done" />
27-
<div class="todo-content" data-bind="text: content, event: { dblclick: edit }" style="cursor: pointer;"></div>
28-
<span class="todo-destroy" data-bind="click: $root.remove"></span>
29-
</div>
30-
<div class="edit">
31-
<input class="todo-input" data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: stopEditing, event: { blur: stopEditing }"/>
32-
</div>
33-
</div>
34-
</li>
35-
</ul>
36-
</div>
37-
<div id="todo-stats">
38-
<span class="todo-count" data-bind="visible: remainingCount">
39-
<span class="number" data-bind="text: remainingCount"></span>
40-
<span class="word" data-bind="text: getLabel(remainingCount)"></span> left.
41-
</span>
42-
<span class="todo-clear" data-bind="visible: completedCount">
43-
<a href="#" data-bind="click: removeCompleted">
44-
Clear <span class="number-done" data-bind="text: completedCount"></span>
45-
completed <span class="word-done" data-bind="text: getLabel(completedCount)"></span>
46-
</a>
47-
</span>
48-
</div>
49-
</div>
50-
</div>
51-
<ul id="instructions">
52-
<li data-bind="visible: todos().length">Double-click to edit a todo.</li>
53-
</ul>
54-
<div id="credits">
55-
Created by
56-
<br />
57-
<a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a>
58-
<br />
59-
Modified to use knockout.js by
60-
<br />
61-
<a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a>
62-
<br/>
63-
Updated to use knockout.js 2.0 by
64-
<br/>
65-
<a href="http://knockmeout.net">Ryan Niemeyer</a>
66-
<br />
67-
Patches/fixes for cross-browser compat:
68-
<br />
69-
<a href="http://twitter.com/addyosmani">Addy Osmani</a>
70-
</div>
25+
<input class="edit" type="text"
26+
data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"/>
27+
</li>
28+
</ul>
29+
</section>
30+
<footer data-bind="block: completedCount() || remainingCount()">
31+
<a id="clear-completed" href="#" data-bind="inline: completedCount, click: removeCompleted">
32+
Clear <span data-bind="text: completedCount"></span>
33+
completed <span data-bind="text: getLabel(completedCount)"></span>
34+
</a>
7135

72-
<!-- Knockout has no direct dependencies -->
73-
<script src="js/knockout-2.0.0.js" type="text/javascript"></script>
74-
<!-- needed to support JSON.stringify in older browsers (for local storage) -->
75-
<script src="js/json2.js" type="text/javascript"></script>
76-
<!-- used for local storage -->
77-
<script src="js/amplify.store.min.js" type="text/javascript"></script>
78-
<!-- our app code -->
79-
<script src="js/todos.js" type="text/javascript"></script>
36+
<div id="todo-count">
37+
<span data-bind="text: remainingCount"></span>
38+
<span data-bind="text: getLabel(remainingCount)" style="font-weight: normal"></span> left.
39+
</div>
40+
</footer>
41+
</div>
42+
<div id="instructions" data-bind="visible: todos().length">
43+
Double-click to edit a todo.
44+
</div>
45+
<div id="credits">
46+
Created by
47+
<br/>
48+
<a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a>
49+
<br/>
50+
Modified to use knockout.js by
51+
<br/>
52+
<a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a>
53+
<br/>
54+
Updated to use knockout.js 2.0 by
55+
<br/>
56+
<a href="http://knockmeout.net">Ryan Niemeyer</a>
57+
<br/>
58+
Patches/fixes for cross-browser compat:
59+
<br/>
60+
<a href="http://twitter.com/addyosmani">Addy Osmani</a>
61+
</div>
62+
<!-- Knockout has no direct dependencies -->
63+
<script src="js/libs/knockout-2.0.0.js" type="text/javascript"></script>
64+
<!-- needed to support JSON.stringify in older browsers (for local storage) -->
65+
<script src="js/libs/json2.js" type="text/javascript"></script>
66+
<!-- used for local storage -->
67+
<script src="js/libs/amplify.store.min.js" type="text/javascript"></script>
68+
<!-- our app code -->
69+
<script src="js/app.js" type="text/javascript"></script>
8070
</body>
8171
</html>
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
(function () {
2+
//trim polyfill
3+
if (!String.prototype.trim) {
4+
String.prototype.trim = function () {
5+
return this.replace(/^\s+|\s+$/g, '');
6+
};
7+
}
8+
9+
//a custom binding to handle the enter key (could go in a separate library)
10+
ko.bindingHandlers.enterKey = {
11+
init:function (element, valueAccessor, allBindingsAccessor, data) {
12+
var wrappedHandler, newValueAccessor;
13+
14+
//wrap the handler with a check for the enter key
15+
wrappedHandler = function (data, event) {
16+
if (event.keyCode === 13) {
17+
valueAccessor().call(this, data, event);
18+
}
19+
};
20+
21+
//create a valueAccessor with the options that we would want to pass to the event binding
22+
newValueAccessor = function () {
23+
return { keyup:wrappedHandler };
24+
};
25+
26+
//call the real event binding's init function
27+
ko.bindingHandlers.event.init(element, newValueAccessor, allBindingsAccessor, data);
28+
}
29+
};
30+
31+
//wrapper to hasfocus that also selects text and applies focus async
32+
ko.bindingHandlers.selectAndFocus = {
33+
init:function (element, valueAccessor, allBindingsAccessor) {
34+
ko.bindingHandlers.hasfocus.init(element, valueAccessor, allBindingsAccessor);
35+
ko.utils.registerEventHandler(element, "focus", function () {
36+
element.select();
37+
});
38+
},
39+
update:function (element, valueAccessor) {
40+
ko.utils.unwrapObservable(valueAccessor()); //for dependency
41+
//ensure that element is visible before trying to focus
42+
setTimeout(function () {
43+
ko.bindingHandlers.hasfocus.update(element, valueAccessor);
44+
}, 0);
45+
}
46+
};
47+
48+
//alternative to "visible" binding that will specifically set "block" to override what is in css
49+
ko.bindingHandlers.block = {
50+
update:function (element, valueAccessor) {
51+
var value = ko.utils.unwrapObservable(valueAccessor());
52+
element.style.display = value ? "block" : "none";
53+
}
54+
};
55+
56+
//alternative to "visible" binding that will specifically set "inline" to override what is in css
57+
ko.bindingHandlers.inline = {
58+
update:function (element, valueAccessor) {
59+
var value = ko.utils.unwrapObservable(valueAccessor());
60+
element.style.display = value ? "inline" : "none";
61+
}
62+
};
63+
64+
//represent a single todo item
65+
var Todo = function (content, done) {
66+
this.content = ko.observable(content);
67+
this.done = ko.observable(done);
68+
this.editing = ko.observable(false);
69+
};
70+
71+
//our main view model
72+
var ViewModel = function (todos) {
73+
var self = this;
74+
//map array of passed in todos to an observableArray of Todo objects
75+
self.todos = ko.observableArray(ko.utils.arrayMap(todos, function (todo) {
76+
return new Todo(todo.content, todo.done);
77+
}));
78+
79+
//store the new todo value being entered
80+
self.current = ko.observable();
81+
82+
//add a new todo, when enter key is pressed
83+
self.add = function (data, event) {
84+
var newTodo, current = self.current().trim();
85+
if (current) {
86+
newTodo = new Todo(current);
87+
self.todos.push(newTodo);
88+
self.current("");
89+
}
90+
};
91+
92+
//remove a single todo
93+
self.remove = function (todo) {
94+
self.todos.remove(todo);
95+
};
96+
97+
//remove all completed todos
98+
self.removeCompleted = function () {
99+
self.todos.remove(function (todo) {
100+
return todo.done();
101+
});
102+
};
103+
104+
//edit an item
105+
self.editItem = function(item) {
106+
item.editing(true);
107+
};
108+
109+
//stop editing an item. Remove the item, if it is now empty
110+
self.stopEditing = function(item) {
111+
item.editing(false);
112+
if (!item.content().trim()) {
113+
self.remove(item);
114+
}
115+
};
116+
117+
//count of all completed todos
118+
self.completedCount = ko.computed(function () {
119+
return ko.utils.arrayFilter(self.todos(),
120+
function (todo) {
121+
return todo.done();
122+
}).length;
123+
});
124+
125+
//count of todos that are not complete
126+
self.remainingCount = ko.computed(function () {
127+
return self.todos().length - self.completedCount();
128+
});
129+
130+
//writeable computed observable to handle marking all complete/incomplete
131+
self.allCompleted = ko.computed({
132+
//always return true/false based on the done flag of all todos
133+
read:function () {
134+
return !self.remainingCount();
135+
},
136+
//set all todos to the written value (true/false)
137+
write:function (newValue) {
138+
ko.utils.arrayForEach(self.todos(), function (todo) {
139+
//set even if value is the same, as subscribers are not notified in that case
140+
todo.done(newValue);
141+
});
142+
}
143+
});
144+
145+
//helper function to keep expressions out of markup
146+
self.getLabel = function (count) {
147+
return ko.utils.unwrapObservable(count) === 1 ? "item" : "items";
148+
};
149+
150+
//internal computed observable that fires whenever anything changes in our todos
151+
ko.computed(
152+
function () {
153+
//get a clean copy of the todos, which also creates a dependency on the observableArray and all observables in each item
154+
var todos = ko.toJS(self.todos);
155+
156+
//store to local storage
157+
amplify.store("todos-knockout", todos);
158+
}).extend({ throttle:500 }); //save at most once per second
159+
};
160+
161+
//check local storage for todos
162+
var todos = amplify.store("todos-knockout");
163+
164+
//bind a new instance of our view model to the page
165+
ko.applyBindings(new ViewModel(todos || []));
166+
})();

0 commit comments

Comments
 (0)