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 @@ ...@@ -2,70 +2,46 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Knockout.js</title> <title>Knockout.js • TodoMVC</title>
<link rel="stylesheet" href="../../assets/base.css"> <link rel="stylesheet" href="../../assets/base.css">
</head> </head>
<body> <body>
<div id="todoapp"> <section id="todoapp">
<header> <header id="header">
<h1>Todos</h1> <h1>todos</h1>
<input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add" <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> </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"> <input id="toggle-all" type="checkbox" data-bind="checked: allCompleted">
<label for="toggle-all">Mark all as complete</label> <label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-bind="foreach: todos"> <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 }"> <div class="view" data-bind="event: { dblclick: $root.editItem }">
<input class="toggle" type="checkbox" data-bind="checked: done"> <input class="toggle" type="checkbox" data-bind="checked: completed">
<label data-bind="text: content"></label> <label data-bind="text: title"></label>
<a class="destroy" href="#" data-bind="click: $root.remove"></a> <button class="destroy" data-bind="click: $root.remove"></button>
</div> </div>
<input class="edit" type="text" <input class="edit" data-bind="value: title, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }" >
data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"/>
</li> </li>
</ul> </ul>
</section> </section>
<footer data-bind="block: completedCount() || remainingCount()"> <footer id="footer" data-bind="visible: completedCount() || remainingCount()">
<a id="clear-completed" href="#" data-bind="inline: completedCount, click: removeCompleted"> <span id="todo-count">
Clear <span data-bind="text: completedCount"></span> <strong data-bind="text: remainingCount">1</strong>
completed <span data-bind="text: getLabel(completedCount)"></span> <span data-bind="text: getLabel( remainingCount )"></span> left
</a> </span>
<button id="clear-completed" data-bind="visible: completedCount, click: removeCompleted">Clear completed (<span data-bind="text: completedCount"></span>)</button>
<div id="todo-count">
<span data-bind="text: remainingCount"></span>
<span data-bind="text: getLabel(remainingCount)" style="font-weight: normal"></span> left.
</div>
</footer> </footer>
</div> </section>
<div id="instructions" data-bind="visible: todos().length"> <footer id="info">
Double-click to edit a todo. <span data-bind="visible: todos().length">Double-click to edit a todo.</span>
</div> <p>Original Knockout version from <a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a></p>
<div id="credits"> <p>Rewritten to use Knockout 2.0 and standard template by <a href="http://knockmeout.net">Ryan Niemeyer</a></p>
Created by <p>Patches/fixes for cross-browser compat: <a href="http://twitter.com/addyosmani">Addy Osmani</a></p>
<br/> </footer>
<a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a>
<br/> <script src="js/lib/knockout-2.0.0.js" type="text/javascript"></script>
Modified to use knockout.js by <script src="js/app.js" type="text/javascript"></script>
<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> </body>
</html> </html>
\ No newline at end of file
(function () { (function() {
//trim polyfill 'use strict';
if (!String.prototype.trim) { var ENTER_KEY = 13;
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, ''); // 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 = { 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 === ENTER_KEY ) {
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
ko.bindingHandlers.event.init(element, newValueAccessor, allBindingsAccessor, data); 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 = { ko.bindingHandlers.selectAndFocus = {
init:function (element, valueAccessor, allBindingsAccessor) { init: function( element, valueAccessor, allBindingsAccessor ) {
ko.bindingHandlers.hasfocus.init(element, valueAccessor, allBindingsAccessor); ko.bindingHandlers.hasfocus.init( element, valueAccessor, allBindingsAccessor );
ko.utils.registerEventHandler(element, "focus", function () { ko.utils.registerEventHandler( element, 'focus', function() {
element.select(); element.select();
}); } );
}, },
update:function (element, valueAccessor) { update: function( element, valueAccessor ) {
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 );
}
};
//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";
} }
}; };
//represent a single todo item // represent a single todo item
var Todo = function (content, done) { var Todo = function( title, completed ) {
this.content = ko.observable(content); this.title = ko.observable( title );
this.done = ko.observable(done); this.completed = ko.observable( completed );
this.editing = ko.observable(false); this.editing = ko.observable( 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.title, todo.completed );
})); }));
//store the new todo value being entered // store the new todo value being entered
self.current = ko.observable(); self.current = ko.observable();
//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() {
var newTodo, current = self.current().trim(); var current = self.current().trim();
if (current) { if ( current ) {
newTodo = new Todo(current); self.todos.push( new Todo( current ) );
self.todos.push(newTodo); self.current( '' );
self.current("");
} }
}; };
//remove a single todo // remove a single todo
self.remove = function (todo) { self.remove = function( todo ) {
self.todos.remove(todo); self.todos.remove( todo );
}; };
//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.completed();
}); });
}; };
//edit an item // edit an item
self.editItem = function(item) { self.editItem = function( item ) {
item.editing(true); item.editing( true );
}; };
//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) { self.stopEditing = function( item ) {
item.editing(false); item.editing( false );
if (!item.content().trim()) { if ( !item.title().trim() ) {
self.remove(item); self.remove( item );
} }
}; };
//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(), return ko.utils.arrayFilter( self.todos(), function(todo) {
function (todo) { return todo.completed();
return todo.done(); } ).length;
}).length;
}); });
//count of todos that are not complete // count of todos that are not complete
self.remainingCount = ko.computed(function () { self.remainingCount = ko.computed(function() {
return self.todos().length - self.completedCount(); 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({ 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.completed( newValue );
}); });
} }
}); });
//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( ko.computed(function() {
function () { // store a clean copy to local storage, which also creates a dependency on the observableArray and all observables in each item
//get a clean copy of the todos, which also creates a dependency on the observableArray and all observables in each item localStorage.setItem( 'todos-knockout', ko.toJSON( self.todos ) );
var todos = ko.toJS(self.todos);
//store to local storage }).extend({
amplify.store("todos-knockout", todos); throttle: 500
}).extend({ throttle:500 }); //save at most once per second }); // save at most twice per second
}; };
//check local storage for todos // check local storage for todos
var todos = amplify.store("todos-knockout"); var todos = ko.utils.parseJson( localStorage.getItem( 'todos-knockout' ) );
//bind a new instance of our view model to the page // bind a new instance of our view model to the page
ko.applyBindings(new ViewModel(todos || [])); 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