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

cleanup for issue #88.

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