Commit 6bfa658c authored by Ryan Niemeyer's avatar Ryan Niemeyer

cleanup for issue #88.

parent b525101e
......@@ -6,63 +6,66 @@
<link rel="stylesheet" href="../../assets/base.css">
</head>
<body>
<div id="todoapp">
<header>
<h1>Todos</h1>
<input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add" placeholder="What needs to be done?" />
</header>
<section id="main" data-bind="block: todos().length">
<input id="toggle-all" type="checkbox" data-bind="checked: allCompleted">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-bind="foreach: todos">
<li data-bind="css: { done: done, editing: editing }" >
<div class="view">
<input class="toggle" type="checkbox" data-bind="checked: done">
<label data-bind="text: content, event: { dblclick: edit }"></label>
<a class="destroy" href="#" data-bind="click: $root.remove"></a>
</div>
<input class="edit" type="text" data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: stopEditing, hasfocus: editing, event: { blur: stopEditing }" />
</li>
</ul>
</section>
<footer data-bind="block: completedCount() || remainingCount()">
<a id="clear-completed" href="#" data-bind="inline: completedCount, click: removeCompleted">
Clear <span data-bind="text: completedCount"></span>
completed <span data-bind="text: getLabel(completedCount)"></span>
</a>
<div id="todo-count" data-bind="inline: remainingCount">
<span data-bind="text: remainingCount"></span>
<span data-bind="text: getLabel(remainingCount)" style="font-weight: normal"></span> left.
</div>
</footer>
</div>
<div id="instructions" data-bind="visible: todos().length">
Double-click to edit a todo.
</div>
<div id="credits">
Created by
<br />
<a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a>
<br />
Modified to use knockout.js by
<br />
<a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a>
<br/>
Updated to use knockout.js 2.0 by
<br/>
<a href="http://knockmeout.net">Ryan Niemeyer</a>
<br />
Patches/fixes for cross-browser compat:
<br />
<a href="http://twitter.com/addyosmani">Addy Osmani</a>
</div>
<!-- Knockout has no direct dependencies -->
<script src="js/libs/knockout-2.0.0.js" type="text/javascript"></script>
<!-- needed to support JSON.stringify in older browsers (for local storage) -->
<script src="js/libs/json2.js" type="text/javascript"></script>
<!-- used for local storage -->
<script src="js/libs/amplify.store.min.js" type="text/javascript"></script>
<!-- our app code -->
<script src="js/app.js" type="text/javascript"></script>
<div id="todoapp">
<header>
<h1>Todos</h1>
<input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add"
placeholder="What needs to be done?"/>
</header>
<section id="main" data-bind="block: todos().length">
<input id="toggle-all" type="checkbox" data-bind="checked: allCompleted">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-bind="foreach: todos">
<li data-bind="css: { done: done, editing: editing }">
<div class="view" data-bind="event: { dblclick: edit }">
<input class="toggle" type="checkbox" data-bind="checked: done">
<label data-bind="text: content"></label>
<a class="destroy" href="#" data-bind="click: $root.remove"></a>
</div>
<input class="edit" type="text"
data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: stopEditing, hasfocus: editing, select: true, event: { blur: stopEditing }"/>
</li>
</ul>
</section>
<footer data-bind="block: completedCount() || remainingCount()">
<a id="clear-completed" href="#" data-bind="inline: completedCount, click: removeCompleted">
Clear <span data-bind="text: completedCount"></span>
completed <span data-bind="text: getLabel(completedCount)"></span>
</a>
<div id="todo-count">
<span data-bind="text: remainingCount"></span>
<span data-bind="text: getLabel(remainingCount)" style="font-weight: normal"></span> left.
</div>
</footer>
</div>
<div id="instructions" data-bind="visible: todos().length">
Double-click to edit a todo.
</div>
<div id="credits">
Created by
<br/>
<a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a>
<br/>
Modified to use knockout.js by
<br/>
<a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a>
<br/>
Updated to use knockout.js 2.0 by
<br/>
<a href="http://knockmeout.net">Ryan Niemeyer</a>
<br/>
Patches/fixes for cross-browser compat:
<br/>
<a href="http://twitter.com/addyosmani">Addy Osmani</a>
</div>
<!-- Knockout has no direct dependencies -->
<script src="js/libs/knockout-2.0.0.js" type="text/javascript"></script>
<!-- needed to support JSON.stringify in older browsers (for local storage) -->
<script src="js/libs/json2.js" type="text/javascript"></script>
<!-- used for local storage -->
<script src="js/libs/amplify.store.min.js" type="text/javascript"></script>
<!-- our app code -->
<script src="js/app.js" type="text/javascript"></script>
</body>
</html>
\ No newline at end of file
(function() {
(function () {
//a custom binding to handle the enter key (could go in a separate library)
ko.bindingHandlers.enterKey = {
init: function(element, valueAccessor, allBindingsAccessor, data) {
init:function (element, valueAccessor, allBindingsAccessor, data) {
var wrappedHandler, newValueAccessor;
//wrap the handler with a check for the enter key
wrappedHandler = function(data, event) {
wrappedHandler = function (data, event) {
if (event.keyCode === 13) {
valueAccessor().call(this, data, event);
}
};
//create a valueAccessor with the options that we would want to pass to the event binding
newValueAccessor = function() {
return { keyup: wrappedHandler };
newValueAccessor = function () {
return { keyup:wrappedHandler };
};
//call the real event binding's init function
......@@ -21,9 +21,20 @@
}
};
//select text when element is focused
ko.bindingHandlers.select = {
init:function (element) {
var handler = function () {
element.select();
};
ko.utils.registerEventHandler(element, "focus", handler);
}
};
//alternative to "visible" binding that will specifically set "block" to override what is in css
ko.bindingHandlers.block = {
update: function (element, valueAccessor) {
update:function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
element.style.display = value ? "block" : "none";
}
......@@ -31,13 +42,12 @@
//alternative to "visible" binding that will specifically set "inline" to override what is in css
ko.bindingHandlers.inline = {
update: function (element, valueAccessor) {
update:function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
element.style.display = value ? "inline" : "none";
}
};
//represent a single todo item
var Todo = function (content, done) {
this.content = ko.observable(content);
......@@ -47,16 +57,19 @@
//can place methods on prototype, as there can be many todos
ko.utils.extend(Todo.prototype, {
edit: function() { this.editing(true); },
stopEditing: function() { this.editing(false); }
edit:function () {
this.editing(true);
},
stopEditing:function () {
this.editing(false);
}
});
//our main view model
var ViewModel = function(todos) {
var ViewModel = function (todos) {
var self = this;
//map array of passed in todos to an observableArray of Todo objects
self.todos = ko.observableArray(ko.utils.arrayMap(todos, function(todo) {
self.todos = ko.observableArray(ko.utils.arrayMap(todos, function (todo) {
return new Todo(todo.content, todo.done);
}));
......@@ -65,9 +78,12 @@
//add a new todo, when enter key is pressed
self.add = function (data, event) {
var newTodo = new Todo(self.current());
self.todos.push(newTodo);
self.current("");
var newTodo, current = (self.current() || "").replace(/^(\s|\u00A0)+|(\s|\u00A0)+$/g, "");
if (current) {
newTodo = new Todo(current);
self.todos.push(newTodo);
self.current("");
}
};
//remove a single todo
......@@ -77,16 +93,17 @@
//remove all completed todos
self.removeCompleted = function () {
self.todos.remove(function(todo) {
self.todos.remove(function (todo) {
return todo.done();
});
};
//count of all completed todos
self.completedCount = ko.computed(function () {
return ko.utils.arrayFilter(self.todos(), function(todo) {
return todo.done();
}).length;
return ko.utils.arrayFilter(self.todos(),
function (todo) {
return todo.done();
}).length;
});
//count of todos that are not complete
......@@ -97,12 +114,12 @@
//writeable computed observable to handle marking all complete/incomplete
self.allCompleted = ko.computed({
//always return true/false based on the done flag of all todos
read: function() {
read:function () {
return !self.remainingCount();
},
//set all todos to the written value (true/false)
write: function(newValue) {
ko.utils.arrayForEach(self.todos(), function(todo) {
write:function (newValue) {
ko.utils.arrayForEach(self.todos(), function (todo) {
//set even if value is the same, as subscribers are not notified in that case
todo.done(newValue);
});
......@@ -110,18 +127,19 @@
});
//helper function to keep expressions out of markup
self.getLabel = function(count) {
self.getLabel = function (count) {
return ko.utils.unwrapObservable(count) === 1 ? "item" : "items";
};
//internal computed observable that fires whenever anything changes in our todos
ko.computed(function() {
//get a clean copy of the todos, which also creates a dependency on the observableArray and all observables in each item
var todos = ko.toJS(self.todos);
//store to local storage
amplify.store("todos-knockout", todos);
}).extend({ throttle: 1000 }); //save at most once per second
ko.computed(
function () {
//get a clean copy of the todos, which also creates a dependency on the observableArray and all observables in each item
var todos = ko.toJS(self.todos);
//store to local storage
amplify.store("todos-knockout", todos);
}).extend({ throttle:1000 }); //save at most once per second
};
//check local storage for todos
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment