Commit 43032485 authored by Sindre Sorhus's avatar Sindre Sorhus

Merge pull request #722 from passy/knockout-upgrade

Knockout upgrade & escape fix
parents 4072b6e5 233db94c
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"todomvc-common": "~0.1.4", "todomvc-common": "~0.1.4",
"knockout.js": "~2.2.0rc", "knockout.js": "~3.0.0",
"director": "~1.1.10" "director": "~1.2.0"
} }
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<html lang="en" data-framework="knockoutjs"> <html lang="en" data-framework="knockoutjs">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"> <meta content="IE=edge" http-equiv="X-UA-Compatible">
<title>Knockout.js • TodoMVC</title> <title>Knockout.js • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> <link rel="stylesheet" href="bower_components/todomvc-common/base.css">
</head> </head>
...@@ -22,14 +22,14 @@ ...@@ -22,14 +22,14 @@
<label data-bind="text: title, event: { dblclick: $root.editItem }"></label> <label data-bind="text: title, event: { dblclick: $root.editItem }"></label>
<button class="destroy" data-bind="click: $root.remove"></button> <button class="destroy" data-bind="click: $root.remove"></button>
</div> </div>
<input class="edit" data-bind="value: title, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"> <input class="edit" data-bind="value: title, valueUpdate: 'afterkeydown', enterKey: $root.saveEditing, escapeKey: $root.cancelEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }">
</li> </li>
</ul> </ul>
</section> </section>
<footer id="footer" data-bind="visible: completedCount() || remainingCount()"> <footer id="footer" data-bind="visible: completedCount() || remainingCount()">
<span id="todo-count"> <span id="todo-count">
<strong data-bind="text: remainingCount">0</strong> <strong data-bind="text: remainingCount">0</strong>
<span data-bind="text: getLabel( remainingCount )"></span> left <span data-bind="text: getLabel(remainingCount)"></span> left
</span> </span>
<ul id="filters"> <ul id="filters">
<li> <li>
...@@ -49,13 +49,11 @@ ...@@ -49,13 +49,11 @@
</section> </section>
<footer id="info"> <footer id="info">
<p>Double-click to edit a todo</p> <p>Double-click to edit a todo</p>
<p>Original Knockout version from <a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a></p> <p>Written by <a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a> and <a href="http://knockmeout.net">Ryan Niemeyer</a></p>
<p>Rewritten to use Knockout 2.0 and standard template by <a href="http://knockmeout.net">Ryan Niemeyer</a></p>
<p>Patches/fixes for cross-browser compat: <a href="http://twitter.com/addyosmani">Addy Osmani</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer> </footer>
<script src="bower_components/todomvc-common/base.js"></script> <script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/knockout.js/knockout.js"></script> <script src="bower_components/knockout.js/knockout.debug.js"></script>
<script src="bower_components/director/build/director.js"></script> <script src="bower_components/director/build/director.js"></script>
<script src="js/app.js"></script> <script src="js/app.js"></script>
</body> </body>
......
/*global ko Router */ /*global ko, Router */
(function () { (function () {
'use strict'; 'use strict';
var ENTER_KEY = 13; var ENTER_KEY = 13;
var ESCAPE_KEY = 27;
// A factory function we can use to create binding handlers for specific
// keycodes.
function keyhandlerBindingFactory(keyCode) {
return {
init: function (element, valueAccessor, allBindingsAccessor, data, bindingContext) {
var wrappedHandler, newValueAccessor;
// wrap the handler with a check for the enter key
wrappedHandler = function (data, event) {
if (event.keyCode === keyCode) {
valueAccessor().call(this, data, event);
}
};
// a custom binding to handle the enter key (could go in a separate library) // create a valueAccessor with the options that we would want to pass to the event binding
ko.bindingHandlers.enterKey = { newValueAccessor = function () {
init: function (element, valueAccessor, allBindingsAccessor, data) { return {
var wrappedHandler, newValueAccessor; keyup: wrappedHandler
};
// wrap the handler with a check for the enter key
wrappedHandler = function (data, event) {
if (event.keyCode === ENTER_KEY) {
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
}; };
};
// call the real event binding's init function // call the real event binding's init function
ko.bindingHandlers.event.init(element, newValueAccessor, allBindingsAccessor, data); ko.bindingHandlers.event.init(element, newValueAccessor, allBindingsAccessor, data, bindingContext);
} }
}; };
}
// wrapper to hasfocus that also selects text and applies focus async // a custom binding to handle the enter key
ko.bindingHandlers.enterKey = keyhandlerBindingFactory(ENTER_KEY);
// another custom binding, this time to handle the escape key
ko.bindingHandlers.escapeKey = keyhandlerBindingFactory(ESCAPE_KEY);
// wrapper to hasFocus that also selects text and applies focus async
ko.bindingHandlers.selectAndFocus = { ko.bindingHandlers.selectAndFocus = {
init: function (element, valueAccessor, allBindingsAccessor) { init: function (element, valueAccessor, allBindingsAccessor, bindingContext) {
ko.bindingHandlers.hasfocus.init(element, valueAccessor, allBindingsAccessor); ko.bindingHandlers.hasFocus.init(element, valueAccessor, allBindingsAccessor, bindingContext);
ko.utils.registerEventHandler(element, 'focus', function () { ko.utils.registerEventHandler(element, 'focus', function () {
element.focus(); element.focus();
}); });
...@@ -40,7 +50,7 @@ ...@@ -40,7 +50,7 @@
ko.utils.unwrapObservable(valueAccessor()); // for dependency ko.utils.unwrapObservable(valueAccessor()); // for dependency
// ensure that element is visible before trying to focus // ensure that element is visible before trying to focus
setTimeout(function () { setTimeout(function () {
ko.bindingHandlers.hasfocus.update(element, valueAccessor); ko.bindingHandlers.hasFocus.update(element, valueAccessor);
}, 0); }, 0);
} }
}; };
...@@ -54,61 +64,60 @@ ...@@ -54,61 +64,60 @@
// our main view model // 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 // map array of passed in todos to an observableArray of Todo objects
self.todos = ko.observableArray(ko.utils.arrayMap(todos, function (todo) { this.todos = ko.observableArray(todos.map(function (todo) {
return new Todo(todo.title, todo.completed); return new Todo(todo.title, todo.completed);
})); }));
// store the new todo value being entered // store the new todo value being entered
self.current = ko.observable(); this.current = ko.observable();
self.showMode = ko.observable('all'); this.showMode = ko.observable('all');
self.filteredTodos = ko.computed(function () { this.filteredTodos = ko.computed(function () {
switch (self.showMode()) { switch (this.showMode()) {
case 'active': case 'active':
return self.todos().filter(function (todo) { return this.todos().filter(function (todo) {
return !todo.completed(); return !todo.completed();
}); });
case 'completed': case 'completed':
return self.todos().filter(function (todo) { return this.todos().filter(function (todo) {
return todo.completed(); return todo.completed();
}); });
default: default:
return self.todos(); return this.todos();
} }
}); }.bind(this));
// add a new todo, when enter key is pressed // add a new todo, when enter key is pressed
self.add = function () { this.add = function () {
var current = self.current().trim(); var current = this.current().trim();
if (current) { if (current) {
self.todos.push(new Todo(current)); this.todos.push(new Todo(current));
self.current(''); this.current('');
} }
}; }.bind(this);
// remove a single todo // remove a single todo
self.remove = function (todo) { this.remove = function (todo) {
self.todos.remove(todo); this.todos.remove(todo);
}; }.bind(this);
// remove all completed todos // remove all completed todos
self.removeCompleted = function () { this.removeCompleted = function () {
self.todos.remove(function (todo) { this.todos.remove(function (todo) {
return todo.completed(); return todo.completed();
}); });
}; }.bind(this);
// edit an item // edit an item
self.editItem = function (item) { this.editItem = function (item) {
item.editing(true); item.editing(true);
}; item.previousTitle = item.title();
}.bind(this);
// stop editing an item. Remove the item, if it is now empty // stop editing an item. Remove the item, if it is now empty
self.stopEditing = function (item) { this.saveEditing = function (item) {
item.editing(false); item.editing(false);
var title = item.title(); var title = item.title();
...@@ -122,47 +131,53 @@ ...@@ -122,47 +131,53 @@
} }
if (!trimmedTitle) { if (!trimmedTitle) {
self.remove(item); this.remove(item);
} }
}; }.bind(this);
// cancel editing an item and revert to the previous content
this.cancelEditing = function (item) {
item.editing(false);
item.title(item.previousTitle);
}.bind(this);
// count of all completed todos // count of all completed todos
self.completedCount = ko.computed(function () { this.completedCount = ko.computed(function () {
return ko.utils.arrayFilter(self.todos(), function (todo) { return this.todos().filter(function (todo) {
return todo.completed(); return todo.completed();
}).length; }).length;
}); }.bind(this));
// count of todos that are not complete // count of todos that are not complete
self.remainingCount = ko.computed(function () { this.remainingCount = ko.computed(function () {
return self.todos().length - self.completedCount(); return this.todos().length - this.completedCount();
}); }.bind(this));
// writeable computed observable to handle marking all complete/incomplete // writeable computed observable to handle marking all complete/incomplete
self.allCompleted = ko.computed({ this.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 !this.remainingCount();
}, }.bind(this),
// 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) { this.todos().forEach(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.completed(newValue); todo.completed(newValue);
}); });
} }.bind(this)
}); });
// helper function to keep expressions out of markup // helper function to keep expressions out of markup
self.getLabel = function (count) { this.getLabel = function (count) {
return ko.utils.unwrapObservable(count) === 1 ? 'item' : 'items'; return ko.utils.unwrapObservable(count) === 1 ? 'item' : 'items';
}; }.bind(this);
// 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(function () {
// store a clean copy to local storage, which also creates a dependency on the observableArray and all observables in each item // store a clean copy to local storage, which also creates a dependency on the observableArray and all observables in each item
localStorage.setItem('todos-knockoutjs', ko.toJSON(self.todos)); localStorage.setItem('todos-knockoutjs', ko.toJSON(this.todos));
}).extend({ }.bind(this)).extend({
throttle: 500 throttle: 500
}); // save at most twice per second }); // save at most twice per second
}; };
...@@ -176,5 +191,5 @@ ...@@ -176,5 +191,5 @@
// set up filter routing // set up filter routing
/*jshint newcap:false */ /*jshint newcap:false */
Router({'/:filter': viewModel.showMode}).init(); Router({ '/:filter': viewModel.showMode }).init();
})(); }());
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