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 @@
"version": "0.0.0",
"dependencies": {
"todomvc-common": "~0.1.4",
"knockout.js": "~2.2.0rc",
"director": "~1.1.10"
"knockout.js": "~3.0.0",
"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 @@
<html lang="en" data-framework="knockoutjs">
<head>
<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>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
</head>
......@@ -22,14 +22,14 @@
<label data-bind="text: title, event: { dblclick: $root.editItem }"></label>
<button class="destroy" data-bind="click: $root.remove"></button>
</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>
</ul>
</section>
<footer id="footer" data-bind="visible: completedCount() || remainingCount()">
<span id="todo-count">
<strong data-bind="text: remainingCount">0</strong>
<span data-bind="text: getLabel( remainingCount )"></span> left
<span data-bind="text: getLabel(remainingCount)"></span> left
</span>
<ul id="filters">
<li>
......@@ -49,13 +49,11 @@
</section>
<footer id="info">
<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>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>Written by <a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a> and <a href="http://knockmeout.net">Ryan Niemeyer</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<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="js/app.js"></script>
</body>
......
/*global ko Router */
/*global ko, Router */
(function () {
'use strict';
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)
ko.bindingHandlers.enterKey = {
init: function (element, valueAccessor, allBindingsAccessor, data) {
var wrappedHandler, newValueAccessor;
// 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
// 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
ko.bindingHandlers.event.init(element, newValueAccessor, allBindingsAccessor, data);
}
};
// call the real event binding's init function
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 = {
init: function (element, valueAccessor, allBindingsAccessor) {
ko.bindingHandlers.hasfocus.init(element, valueAccessor, allBindingsAccessor);
init: function (element, valueAccessor, allBindingsAccessor, bindingContext) {
ko.bindingHandlers.hasFocus.init(element, valueAccessor, allBindingsAccessor, bindingContext);
ko.utils.registerEventHandler(element, 'focus', function () {
element.focus();
});
......@@ -40,7 +50,7 @@
ko.utils.unwrapObservable(valueAccessor()); // for dependency
// ensure that element is visible before trying to focus
setTimeout(function () {
ko.bindingHandlers.hasfocus.update(element, valueAccessor);
ko.bindingHandlers.hasFocus.update(element, valueAccessor);
}, 0);
}
};
......@@ -54,61 +64,60 @@
// our main view model
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) {
this.todos = ko.observableArray(todos.map(function (todo) {
return new Todo(todo.title, todo.completed);
}));
// 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 () {
switch (self.showMode()) {
this.filteredTodos = ko.computed(function () {
switch (this.showMode()) {
case 'active':
return self.todos().filter(function (todo) {
return this.todos().filter(function (todo) {
return !todo.completed();
});
case 'completed':
return self.todos().filter(function (todo) {
return this.todos().filter(function (todo) {
return todo.completed();
});
default:
return self.todos();
return this.todos();
}
});
}.bind(this));
// add a new todo, when enter key is pressed
self.add = function () {
var current = self.current().trim();
this.add = function () {
var current = this.current().trim();
if (current) {
self.todos.push(new Todo(current));
self.current('');
this.todos.push(new Todo(current));
this.current('');
}
};
}.bind(this);
// remove a single todo
self.remove = function (todo) {
self.todos.remove(todo);
};
this.remove = function (todo) {
this.todos.remove(todo);
}.bind(this);
// remove all completed todos
self.removeCompleted = function () {
self.todos.remove(function (todo) {
this.removeCompleted = function () {
this.todos.remove(function (todo) {
return todo.completed();
});
};
}.bind(this);
// edit an item
self.editItem = function (item) {
this.editItem = function (item) {
item.editing(true);
};
item.previousTitle = item.title();
}.bind(this);
// stop editing an item. Remove the item, if it is now empty
self.stopEditing = function (item) {
this.saveEditing = function (item) {
item.editing(false);
var title = item.title();
......@@ -122,47 +131,53 @@
}
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
self.completedCount = ko.computed(function () {
return ko.utils.arrayFilter(self.todos(), function (todo) {
this.completedCount = ko.computed(function () {
return this.todos().filter(function (todo) {
return todo.completed();
}).length;
});
}.bind(this));
// count of todos that are not complete
self.remainingCount = ko.computed(function () {
return self.todos().length - self.completedCount();
});
this.remainingCount = ko.computed(function () {
return this.todos().length - this.completedCount();
}.bind(this));
// 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
read: function () {
return !self.remainingCount();
},
return !this.remainingCount();
}.bind(this),
// set all todos to the written value (true/false)
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
todo.completed(newValue);
});
}
}.bind(this)
});
// helper function to keep expressions out of markup
self.getLabel = function (count) {
this.getLabel = function (count) {
return ko.utils.unwrapObservable(count) === 1 ? 'item' : 'items';
};
}.bind(this);
// internal computed observable that fires whenever anything changes in our todos
ko.computed(function () {
// 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));
}).extend({
localStorage.setItem('todos-knockoutjs', ko.toJSON(this.todos));
}.bind(this)).extend({
throttle: 500
}); // save at most twice per second
};
......@@ -176,5 +191,5 @@
// set up filter routing
/*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