Commit 91205335 authored by Ryan Niemeyer's avatar Ryan Niemeyer Committed by Sindre Sorhus

Landing pull request 132. Knockout.js updates for the new template and changes...

Landing pull request 132. Knockout.js updates for the new template and changes based on the Idiomatic style guide Fixes #????.

More Details:
 - https://github.com/addyosmani/todomvc/pull/132
parent 6a375dd1
......@@ -2,70 +2,46 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>Knockout.js</title>
<title>Knockout.js • TodoMVC</title>
<link rel="stylesheet" href="../../assets/base.css">
</head>
<body>
<div id="todoapp">
<header>
<h1>Todos</h1>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add"
placeholder="What needs to be done?"/>
placeholder="What needs to be done?" autofocus >
</header>
<section id="main" data-bind="block: todos().length">
<section id="main" data-bind="visible: 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 }">
<li data-bind="css: { completed: completed, editing: editing }">
<div class="view" data-bind="event: { dblclick: $root.editItem }">
<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>
<input class="toggle" type="checkbox" data-bind="checked: completed">
<label data-bind="text: title"></label>
<button class="destroy" data-bind="click: $root.remove"></button>
</div>
<input class="edit" type="text"
data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"/>
<input class="edit" data-bind="value: title, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.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 id="footer" data-bind="visible: completedCount() || remainingCount()">
<span id="todo-count">
<strong data-bind="text: remainingCount">1</strong>
<span data-bind="text: getLabel( remainingCount )"></span> left
</span>
<button id="clear-completed" data-bind="visible: completedCount, click: removeCompleted">Clear completed (<span data-bind="text: completedCount"></span>)</button>
</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>
</section>
<footer id="info">
<span data-bind="visible: todos().length">Double-click to edit a todo.</span>
<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>
</footer>
<script src="js/lib/knockout-2.0.0.js" type="text/javascript"></script>
<script src="js/app.js" type="text/javascript"></script>
</body>
</html>
\ No newline at end of file
(function () {
//trim polyfill
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, '');
(function() {
'use strict';
var ENTER_KEY = 13;
// trim polyfill
if ( !String.prototype.trim ) {
String.prototype.trim = function() {
return this.replace( /^\s+|\s+$/g, '' );
};
}
//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 = {
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) {
if (event.keyCode === 13) {
valueAccessor().call(this, data, event);
// 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 );
}
};
//wrapper to hasfocus that also selects text and applies focus async
// 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);
ko.utils.registerEventHandler(element, "focus", function () {
init: function( element, valueAccessor, allBindingsAccessor ) {
ko.bindingHandlers.hasfocus.init( element, valueAccessor, allBindingsAccessor );
ko.utils.registerEventHandler( element, 'focus', function() {
element.select();
});
} );
},
update:function (element, valueAccessor) {
ko.utils.unwrapObservable(valueAccessor()); //for dependency
//ensure that element is visible before trying to focus
setTimeout(function () {
ko.bindingHandlers.hasfocus.update(element, valueAccessor);
}, 0);
}
};
//alternative to "visible" binding that will specifically set "block" to override what is in css
ko.bindingHandlers.block = {
update:function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
element.style.display = value ? "block" : "none";
}
};
//alternative to "visible" binding that will specifically set "inline" to override what is in css
ko.bindingHandlers.inline = {
update:function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
element.style.display = value ? "inline" : "none";
update: function( element, valueAccessor ) {
ko.utils.unwrapObservable( valueAccessor() ); // for dependency
// ensure that element is visible before trying to focus
setTimeout(function() {
ko.bindingHandlers.hasfocus.update( element, valueAccessor );
}, 0 );
}
};
//represent a single todo item
var Todo = function (content, done) {
this.content = ko.observable(content);
this.done = ko.observable(done);
this.editing = ko.observable(false);
// represent a single todo item
var Todo = function( title, completed ) {
this.title = ko.observable( title );
this.completed = ko.observable( completed );
this.editing = ko.observable( false );
};
//our main view model
var ViewModel = function (todos) {
// 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) {
return new Todo(todo.content, todo.done);
// map array of passed in todos to an observableArray of Todo objects
self.todos = ko.observableArray( ko.utils.arrayMap( todos, function( todo ) {
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();
//add a new todo, when enter key is pressed
self.add = function (data, event) {
var newTodo, current = self.current().trim();
if (current) {
newTodo = new Todo(current);
self.todos.push(newTodo);
self.current("");
// add a new todo, when enter key is pressed
self.add = function() {
var current = self.current().trim();
if ( current ) {
self.todos.push( new Todo( current ) );
self.current( '' );
}
};
//remove a single todo
self.remove = function (todo) {
self.todos.remove(todo);
// remove a single todo
self.remove = function( todo ) {
self.todos.remove( todo );
};
//remove all completed todos
self.removeCompleted = function () {
self.todos.remove(function (todo) {
return todo.done();
// remove all completed todos
self.removeCompleted = function() {
self.todos.remove(function( todo ) {
return todo.completed();
});
};
//edit an item
self.editItem = function(item) {
item.editing(true);
// edit an item
self.editItem = function( item ) {
item.editing( true );
};
//stop editing an item. Remove the item, if it is now empty
self.stopEditing = function(item) {
item.editing(false);
if (!item.content().trim()) {
self.remove(item);
// stop editing an item. Remove the item, if it is now empty
self.stopEditing = function( item ) {
item.editing( false );
if ( !item.title().trim() ) {
self.remove( item );
}
};
//count of all completed todos
self.completedCount = ko.computed(function () {
return ko.utils.arrayFilter(self.todos(),
function (todo) {
return todo.done();
}).length;
// count of all completed todos
self.completedCount = ko.computed(function() {
return ko.utils.arrayFilter( self.todos(), function(todo) {
return todo.completed();
} ).length;
});
//count of todos that are not complete
self.remainingCount = ko.computed(function () {
// count of todos that are not complete
self.remainingCount = ko.computed(function() {
return self.todos().length - self.completedCount();
});
//writeable computed observable to handle marking all complete/incomplete
// 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) {
//set even if value is the same, as subscribers are not notified in that case
todo.done(newValue);
// set all todos to the written value (true/false)
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.completed( newValue );
});
}
});
//helper function to keep expressions out of markup
self.getLabel = function (count) {
return ko.utils.unwrapObservable(count) === 1 ? "item" : "items";
// helper function to keep expressions out of markup
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);
// 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-knockout', ko.toJSON( self.todos ) );
//store to local storage
amplify.store("todos-knockout", todos);
}).extend({ throttle:500 }); //save at most once per second
}).extend({
throttle: 500
}); // save at most twice per second
};
//check local storage for todos
var todos = amplify.store("todos-knockout");
// check local storage for todos
var todos = ko.utils.parseJson( localStorage.getItem( 'todos-knockout' ) );
//bind a new instance of our view model to the page
ko.applyBindings(new ViewModel(todos || []));
// bind a new instance of our view model to the page
ko.applyBindings( new ViewModel( todos || [] ) );
})();
/*!
* Amplify Store - Persistent Client-Side Storage 1.1.0
*
* Copyright 2011 appendTo LLC. (http://appendto.com/team)
* Dual licensed under the MIT or GPL licenses.
* http://appendto.com/open-source-licenses
*
* http://amplifyjs.com
*/
(function(a,b){function e(a,e){c.addType(a,function(f,g,h){var i,j,k,l,m=g,n=(new Date).getTime();if(!f){m={},l=[],k=0;try{f=e.length;while(f=e.key(k++))d.test(f)&&(j=JSON.parse(e.getItem(f)),j.expires&&j.expires<=n?l.push(f):m[f.replace(d,"")]=j.data);while(f=l.pop())e.removeItem(f)}catch(o){}return m}f="__amplify__"+f;if(g===b){i=e.getItem(f),j=i?JSON.parse(i):{expires:-1};if(j.expires&&j.expires<=n)e.removeItem(f);else return j.data}else if(g===null)e.removeItem(f);else{j=JSON.stringify({data:g,expires:h.expires?n+h.expires:null});try{e.setItem(f,j)}catch(o){c[a]();try{e.setItem(f,j)}catch(o){throw c.error()}}}return m})}var c=a.store=function(a,b,d,e){var e=c.type;d&&d.type&&d.type in c.types&&(e=d.type);return c.types[e](a,b,d||{})};c.types={},c.type=null,c.addType=function(a,b){c.type||(c.type=a),c.types[a]=b,c[a]=function(b,d,e){e=e||{},e.type=a;return c(b,d,e)}},c.error=function(){return"amplify.store quota exceeded"};var d=/^__amplify__/;for(var f in{localStorage:1,sessionStorage:1})try{window[f].getItem&&e(f,window[f])}catch(g){}if(window.globalStorage)try{e("globalStorage",window.globalStorage[window.location.hostname]),c.type==="sessionStorage"&&(c.type="globalStorage")}catch(g){}(function(){if(!c.types.localStorage){var a=document.createElement("div"),d="amplify";a.style.display="none",document.getElementsByTagName("head")[0].appendChild(a);try{a.addBehavior("#default#userdata"),a.load(d)}catch(e){a.parentNode.removeChild(a);return}c.addType("userData",function(e,f,g){a.load(d);var h,i,j,k,l,m=f,n=(new Date).getTime();if(!e){m={},l=[],k=0;while(h=a.XMLDocument.documentElement.attributes[k++])i=JSON.parse(h.value),i.expires&&i.expires<=n?l.push(h.name):m[h.name]=i.data;while(e=l.pop())a.removeAttribute(e);a.save(d);return m}e=e.replace(/[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g,"-");if(f===b){h=a.getAttribute(e),i=h?JSON.parse(h):{expires:-1};if(i.expires&&i.expires<=n)a.removeAttribute(e);else return i.data}else f===null?a.removeAttribute(e):(j=a.getAttribute(e),i=JSON.stringify({data:f,expires:g.expires?n+g.expires:null}),a.setAttribute(e,i));try{a.save(d)}catch(o){j===null?a.removeAttribute(e):a.setAttribute(e,j),c.userData();try{a.setAttribute(e,i),a.save(d)}catch(o){j===null?a.removeAttribute(e):a.setAttribute(e,j);throw c.error()}}return m})}})(),function(){function e(a){return a===b?b:JSON.parse(JSON.stringify(a))}var a={},d={};c.addType("memory",function(c,f,g){if(!c)return e(a);if(f===b)return e(a[c]);d[c]&&(clearTimeout(d[c]),delete d[c]);if(f===null){delete a[c];return null}a[c]=f,g.expires&&(d[c]=setTimeout(function(){delete a[c],delete d[c]},g.expires));return f})}()})(this.amplify=this.amplify||{})
\ No newline at end of file
This diff is collapsed.
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