Commit 38b6a009 authored by Kevin Malakoff's avatar Kevin Malakoff Committed by Sindre Sorhus

Close #105: Knockback app update. Fixes #67

parent d1095775
{print} = require 'util'
{spawn} = require 'child_process'
task 'build', 'Build js/ from src/', ->
coffee = spawn 'coffee', ['-c', '-o', 'js', 'src']
coffee.stderr.on 'data', (data) ->
process.stderr.write data.toString()
coffee.stdout.on 'data', (data) ->
print data.toString()
coffee.on 'exit', (code) ->
callback?() if code is 0
task 'watch', 'Watch src/ for changes', ->
coffee = spawn 'coffee', ['-w', '-c', '-o', 'js', 'src']
coffee.stderr.on 'data', (data) ->
process.stderr.write data.toString()
coffee.stdout.on 'data', (data) ->
print data.toString()
\ No newline at end of file
# Knockback.js TodoMVC app
Forked from https://github.com/kmalakoff/knockback-todos
## Getting started
You need [CoffeScript](http://coffeescript.org) to compile if you make changes to the files in the `src` folder.
### Compile
Open Terminal in this folder.
- `cake build` to compile once
- `cake watch` to compile on save
\ No newline at end of file
This diff is collapsed.
<!DOCTYPE html>
<html>
<!-- Support Localization -->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Knockback.js • TodoMVC</title>
<link rel="stylesheet" href="../../assets/base.css">
</head>
<body>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" type="text" data-bind="value: header.title, valueUpdate: 'afterkeydown', event: {keyup: header.onAddTodo}" placeholder="What needs to be done?" autofocus>
</header>
<section id="main" data-bind="block: todos.tasks_exist">
<input id="toggle-all" type="checkbox" data-bind="checked: todos.all_completed">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-bind="foreach: todos.todos">
<li data-bind="css: {completed: completed, editing: editing}, visible: visible">
<div class="view" data-bind="event: {dblclick: onCheckEditBegin}">
<input class="toggle" type="checkbox" data-bind="checked: completed" checked>
<label data-bind="text: title"></label>
<button class="destroy" data-bind="click: onDestroyTodo"></button>
</div>
<input class="edit" type="text" data-bind="value: title, selectAndFocus: editing, event: {blur: onCheckEditEnd, keyup: onCheckEditEnd}">
</li>
</ul>
</section>
<footer id="footer" data-bind="block: todos.tasks_exist">
<span id="todo-count">
<strong data-bind="text: footer.remaining_count"></strong>
<span data-bind="text: footer.remaining_text"></span>
</span>
<ul id="filters">
<li>
<a href="#/" data-bind="css: {selected: settings.list_filter_mode()==''}">All</a>
</li>
<li>
<a href="#/active" data-bind="css: {selected: settings.list_filter_mode()=='active'}">Active</a>
</li>
<li>
<a href="#/completed" data-bind="css: {selected: settings.list_filter_mode()=='completed'}">Completed</a>
</li>
</ul>
<button id="clear-completed" data-bind="text: footer.clear_text, block: footer.clear_text, click: footer.onDestroyCompleted"></button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
<p>Created by <a href="https://github.com/kmalakoff">Kevin Malakoff</a>. <br/>
Please try out the <a href="http://kmalakoff.github.com/knockback-todos/">enhanced version</a> <br/>
with localization, priority colors, and lazy loading <br/>
to see just how dynamic <a href="https://github.com/kmalakoff/knockback">Knockback.js</a> can be!</p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<head>
<title>Knockback Demo: Todos</title>
<!-- Demo Dependencies -->
<script src="libs/json2.js"></script>
<script src="libs/jquery-1.7.min.js"></script>
<script src="libs/jquery.tmpl.min.js"></script>
<script src="js/lib/json2.js"></script>
<script src="../../assets/jquery.min.js"></script>
<!-- Knockback Dependencies -->
<script src="dependencies/underscore.js"></script>
<script src="dependencies/backbone.js"></script>
<script src="dependencies/knockout-2.0.0.js"></script>
<script src="dependencies/knockback.js"></script>
<script src="js/lib/underscore-min.js"></script>
<script src="js/lib/backbone-min.js"></script>
<script src="js/lib/knockout-2.0.0.js"></script>
<script src="js/lib/knockback.min.js"></script>
<!-- The Demo -->
<link href="css/todos.css" media="all" rel="stylesheet" type="text/css"/>
<script src="libs/backbone-localstorage.js"></script>
<script src="todos_classic.js"></script>
</head>
<body>
<!-- More Demo Dependencies -->
<script src="js/lib/backbone-localstorage.js"></script>
<!-- Todo App Interface -->
<div id="todoapp">
<!-- Header Section -->
<div id="todo-header">
<div class="title">
<h1>Todos</h1>
</div>
</div>
<!-- Create Todo Section -->
<div id="todo-create">
<div class="content">
<div id="create-todo">
<input id="new-todo" type="text" placeholder="What needs to be done?" data-bind="value: create.input_text, event: {keyup: create.addTodo}"/>
</div>
</div>
</div>
<!-- Todos List Section -->
<div id="todo-list">
<ul class="todo-list" data-bind="template: {name: 'item-template', foreach: todo_list.todos}"></ul>
</div>
<!-- Stats Section -->
<div id="todo-stats">
<span class="todo-count" data-bind="text: stats.remaining_text"></span>
<span class="todo-clear"> <a href="#" data-bind="text: stats.clear_text, click: stats.onDestroyDone"></a></span>
</div>
<!-- Footer Section -->
<div id="todo-footer">
<ul id="instructions">
<li>Double-click to edit a todo.</li>
</ul>
</div>
</div>
<div id="credits">
Created by
<br />
<a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a>
<br />
<br />
Modified to use <a href="https://github.com/kmalakoff/knockback">Knockback.js</a> by
<br />
<a href="https://github.com/kmalakoff">Kevin Malakoff</a>
<br />
and
<br />
<a href="https://github.com/yuchi">Pier Paolo Ramon</a>
</div>
<!-- Templates -->
<script type="text/x-jquery-tmpl" id="item-template">
<li>
<div class="todo" data-bind="css: {done: done, editing: edit_mode}">
<div class="display">
<input class="check" type="checkbox" data-bind="checked: done" />
<div class="todo-text" data-bind="text: text, dblclick: toggleEditMode"></div>
<div class="todo-destroy" data-bind="click: destroyTodo"></div>
</div>
<div class="edit">
<input class="todo-input" type="text" data-bind="value: text, event: {keyup: onEnterEndEdit}" />
</div>
</div>
</li>
</script>
</body>
<!-- Demo Components -->
<script src="js/models/todo.js"></script>
<script src="js/models/todo_collection.js"></script>
<script src="js/viewmodels/settings.js"></script>
<script src="js/viewmodels/header.js"></script>
<script src="js/viewmodels/todos.js"></script>
<script src="js/viewmodels/footer.js"></script>
<script src="js/routers/app.js"></script>
<!-- The Demo -->
<script src="../../assets/base.js"></script>
<script src="js/app.js"></script>
</body>
</html>
\ No newline at end of file
(function() {
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
$(function() {
var todos;
ko.bindingHandlers.dblclick = {
init: function(element, value_accessor) {
return $(element).dblclick(ko.utils.unwrapObservable(value_accessor()));
}
};
ko.bindingHandlers.block = {
update: function(element, value_accessor) {
return element.style.display = ko.utils.unwrapObservable(value_accessor()) ? 'block' : 'none';
}
};
ko.bindingHandlers.selectAndFocus = {
init: function(element, value_accessor, all_bindings_accessor) {
ko.bindingHandlers.hasfocus.init(element, value_accessor, all_bindings_accessor);
return ko.utils.registerEventHandler(element, 'focus', function() {
return element.select();
});
},
update: function(element, value_accessor) {
ko.utils.unwrapObservable(value_accessor());
return _.defer(__bind(function() {
return ko.bindingHandlers.hasfocus.update(element, value_accessor);
}, this));
}
};
window.app = {
viewmodels: {}
};
app.viewmodels.settings = new SettingsViewModel();
todos = new TodosCollection();
app.viewmodels.header = new HeaderViewModel(todos);
app.viewmodels.todos = new TodosViewModel(todos);
app.viewmodels.footer = new FooterViewModel(todos);
ko.applyBindings(app.viewmodels, $('#todoapp')[0]);
new AppRouter();
Backbone.history.start();
return todos.fetch();
});
}).call(this);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
(function() {
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
};
window.Todo = (function() {
__extends(Todo, Backbone.Model);
function Todo() {
Todo.__super__.constructor.apply(this, arguments);
}
Todo.prototype.completed = function(completed) {
if (arguments.length === 0) {
return !!this.get('completed');
}
return this.save({
completed: completed ? new Date() : null
});
};
return Todo;
})();
}).call(this);
(function() {
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
};
window.TodosCollection = (function() {
__extends(TodosCollection, Backbone.Collection);
function TodosCollection() {
TodosCollection.__super__.constructor.apply(this, arguments);
}
TodosCollection.prototype.localStorage = new Store('todos-knockback');
TodosCollection.prototype.model = Todo;
TodosCollection.prototype.completedCount = function() {
return this.models.reduce((function(prev, cur) {
return prev + (cur.completed() ? 1 : 0);
}), 0);
};
TodosCollection.prototype.remainingCount = function() {
return this.models.length - this.completedCount();
};
TodosCollection.prototype.completeAll = function(completed) {
return this.each(function(todo) {
return todo.completed(completed);
});
};
TodosCollection.prototype.destroyCompleted = function() {
var completed_tasks, model, _i, _len, _results;
completed_tasks = this.filter(function(todo) {
return todo.completed();
});
_results = [];
for (_i = 0, _len = completed_tasks.length; _i < _len; _i++) {
model = completed_tasks[_i];
_results.push(model.destroy());
}
return _results;
};
return TodosCollection;
})();
}).call(this);
(function() {
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
};
window.AppRouter = (function() {
__extends(AppRouter, Backbone.Router);
function AppRouter() {
AppRouter.__super__.constructor.apply(this, arguments);
}
AppRouter.prototype.routes = {
"": "all",
"active": "active",
"completed": "completed"
};
AppRouter.prototype.all = function() {
return app.viewmodels.settings.list_filter_mode('');
};
AppRouter.prototype.active = function() {
return app.viewmodels.settings.list_filter_mode('active');
};
AppRouter.prototype.completed = function() {
return app.viewmodels.settings.list_filter_mode('completed');
};
return AppRouter;
})();
}).call(this);
(function() {
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
window.FooterViewModel = function(todos) {
this.collection_observable = kb.collectionObservable(todos);
this.remaining_count = ko.computed(__bind(function() {
return this.collection_observable.collection().remainingCount();
}, this));
this.remaining_text = ko.computed(__bind(function() {
return "" + (this.collection_observable.collection().remainingCount() === 1 ? 'item' : 'items') + " left";
}, this));
this.clear_text = ko.computed(__bind(function() {
var count;
count = this.collection_observable.collection().completedCount();
if (count) {
return "Clear completed (" + count + ")";
} else {
return '';
}
}, this));
this.onDestroyCompleted = __bind(function() {
return todos.destroyCompleted();
}, this);
return this;
};
}).call(this);
(function() {
var ENTER_KEY;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
ENTER_KEY = 13;
window.HeaderViewModel = function(todos) {
this.title = ko.observable('');
this.onAddTodo = __bind(function(view_model, event) {
if (!$.trim(this.title()) || (event.keyCode !== ENTER_KEY)) {
return true;
}
todos.create({
title: $.trim(this.title())
});
return this.title('');
}, this);
return this;
};
}).call(this);
(function() {
window.SettingsViewModel = function() {
this.list_filter_mode = ko.observable('');
return this;
};
}).call(this);
(function() {
var TodoViewModel;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
TodoViewModel = function(model) {
this.editing = ko.observable(false);
this.completed = kb.observable(model, {
key: 'completed',
write: (function(completed) {
return model.save({
completed: completed
});
})
}, this);
this.visible = ko.computed(__bind(function() {
switch (app.viewmodels.settings.list_filter_mode()) {
case 'active':
return !this.completed();
case 'completed':
return this.completed();
default:
return true;
}
}, this));
this.title = kb.observable(model, {
key: 'title',
write: (__bind(function(title) {
if ($.trim(title)) {
model.save({
title: $.trim(title)
});
} else {
_.defer(function() {
return model.destroy();
});
}
return this.editing(false);
}, this))
}, this);
this.onDestroyTodo = __bind(function() {
return model.destroy();
}, this);
this.onCheckEditBegin = __bind(function() {
if (!this.editing() && !this.completed()) {
this.editing(true);
return $('.todo-input').focus();
}
}, this);
this.onCheckEditEnd = __bind(function(view_model, event) {
if ((event.keyCode === 13) || (event.type === 'blur')) {
$('.todo-input').blur();
return this.editing(false);
}
}, this);
return this;
};
window.TodosViewModel = function(todos) {
this.todos = ko.observableArray([]);
this.collection_observable = kb.collectionObservable(todos, this.todos, {
view_model: TodoViewModel
});
this.tasks_exist = ko.computed(__bind(function() {
return this.collection_observable().length;
}, this));
this.all_completed = ko.computed({
read: __bind(function() {
return !this.collection_observable.collection().remainingCount();
}, this),
write: __bind(function(completed) {
return this.collection_observable.collection().completeAll(completed);
}, this)
});
return this;
};
}).call(this);
/*
* Globalize Culture fr-FR
*
* http://github.com/jquery/globalize
*
* Copyright Software Freedom Conservancy, Inc.
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* This file was generated by the Globalize Culture Generator
* Translation: bugs found in this file need to be fixed in the generator
*/
(function( window, undefined ) {
var Globalize;
if ( typeof require !== "undefined"
&& typeof exports !== "undefined"
&& typeof module !== "undefined" ) {
// Assume CommonJS
Globalize = require( "globalize" );
} else {
// Global variable
Globalize = window.Globalize;
}
Globalize.addCultureInfo( "fr-FR", "default", {
name: "fr-FR",
englishName: "French (France)",
nativeName: "français (France)",
language: "fr",
numberFormat: {
",": " ",
".": ",",
NaN: "Non Numérique",
negativeInfinity: "-Infini",
positiveInfinity: "+Infini",
percent: {
",": " ",
".": ","
},
currency: {
pattern: ["-n $","n $"],
",": " ",
".": ",",
symbol: ""
}
},
calendars: {
standard: {
firstDay: 1,
days: {
names: ["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],
namesAbbr: ["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],
namesShort: ["di","lu","ma","me","je","ve","sa"]
},
months: {
names: ["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre",""],
namesAbbr: ["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc.",""]
},
AM: null,
PM: null,
eras: [{"name":"ap. J.-C.","start":null,"offset":0}],
patterns: {
d: "dd/MM/yyyy",
D: "dddd d MMMM yyyy",
t: "HH:mm",
T: "HH:mm:ss",
f: "dddd d MMMM yyyy HH:mm",
F: "dddd d MMMM yyyy HH:mm:ss",
M: "d MMMM",
Y: "MMMM yyyy"
}
}
}
});
}( this ));
/*
* Globalize Culture it-IT
*
* http://github.com/jquery/globalize
*
* Copyright Software Freedom Conservancy, Inc.
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* This file was generated by the Globalize Culture Generator
* Translation: bugs found in this file need to be fixed in the generator
*/
(function( window, undefined ) {
var Globalize;
if ( typeof require !== "undefined"
&& typeof exports !== "undefined"
&& typeof module !== "undefined" ) {
// Assume CommonJS
Globalize = require( "globalize" );
} else {
// Global variable
Globalize = window.Globalize;
}
Globalize.addCultureInfo( "it-IT", "default", {
name: "it-IT",
englishName: "Italian (Italy)",
nativeName: "italiano (Italia)",
language: "it",
numberFormat: {
",": ".",
".": ",",
NaN: "Non un numero reale",
negativeInfinity: "-Infinito",
positiveInfinity: "+Infinito",
percent: {
pattern: ["-n%","n%"],
",": ".",
".": ","
},
currency: {
pattern: ["-$ n","$ n"],
",": ".",
".": ",",
symbol: ""
}
},
calendars: {
standard: {
firstDay: 1,
days: {
names: ["domenica","lunedì","martedì","mercoledì","giovedì","venerdì","sabato"],
namesAbbr: ["dom","lun","mar","mer","gio","ven","sab"],
namesShort: ["do","lu","ma","me","gi","ve","sa"]
},
months: {
names: ["gennaio","febbraio","marzo","aprile","maggio","giugno","luglio","agosto","settembre","ottobre","novembre","dicembre",""],
namesAbbr: ["gen","feb","mar","apr","mag","giu","lug","ago","set","ott","nov","dic",""]
},
AM: null,
PM: null,
eras: [{"name":"d.C.","start":null,"offset":0}],
patterns: {
d: "dd/MM/yyyy",
D: "dddd d MMMM yyyy",
t: "HH:mm",
T: "HH:mm:ss",
f: "dddd d MMMM yyyy HH:mm",
F: "dddd d MMMM yyyy HH:mm:ss",
M: "dd MMMM",
Y: "MMMM yyyy"
}
}
}
});
}( this ));
This diff is collapsed.
/*
* jQuery Templates Plugin 1.0.0pre
* http://github.com/jquery/jquery-tmpl
* Requires jQuery 1.4.2
*
* Copyright Software Freedom Conservancy, Inc.
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function(a){var r=a.fn.domManip,d="_tmplitem",q=/^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,b={},f={},e,p={key:0,data:{}},i=0,c=0,l=[];function g(g,d,h,e){var c={data:e||(e===0||e===false)?e:d?d.data:{},_wrap:d?d._wrap:null,tmpl:null,parent:d||null,nodes:[],calls:u,nest:w,wrap:x,html:v,update:t};g&&a.extend(c,g,{nodes:[],parent:d});if(h){c.tmpl=h;c._ctnt=c._ctnt||c.tmpl(a,c);c.key=++i;(l.length?f:b)[i]=c}return c}a.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(f,d){a.fn[f]=function(n){var g=[],i=a(n),k,h,m,l,j=this.length===1&&this[0].parentNode;e=b||{};if(j&&j.nodeType===11&&j.childNodes.length===1&&i.length===1){i[d](this[0]);g=this}else{for(h=0,m=i.length;h<m;h++){c=h;k=(h>0?this.clone(true):this).get();a(i[h])[d](k);g=g.concat(k)}c=0;g=this.pushStack(g,f,i.selector)}l=e;e=null;a.tmpl.complete(l);return g}});a.fn.extend({tmpl:function(d,c,b){return a.tmpl(this[0],d,c,b)},tmplItem:function(){return a.tmplItem(this[0])},template:function(b){return a.template(b,this[0])},domManip:function(d,m,k){if(d[0]&&a.isArray(d[0])){var g=a.makeArray(arguments),h=d[0],j=h.length,i=0,f;while(i<j&&!(f=a.data(h[i++],"tmplItem")));if(f&&c)g[2]=function(b){a.tmpl.afterManip(this,b,k)};r.apply(this,g)}else r.apply(this,arguments);c=0;!e&&a.tmpl.complete(b);return this}});a.extend({tmpl:function(d,h,e,c){var i,k=!c;if(k){c=p;d=a.template[d]||a.template(null,d);f={}}else if(!d){d=c.tmpl;b[c.key]=c;c.nodes=[];c.wrapped&&n(c,c.wrapped);return a(j(c,null,c.tmpl(a,c)))}if(!d)return[];if(typeof h==="function")h=h.call(c||{});e&&e.wrapped&&n(e,e.wrapped);i=a.isArray(h)?a.map(h,function(a){return a?g(e,c,d,a):null}):[g(e,c,d,h)];return k?a(j(c,null,i)):i},tmplItem:function(b){var c;if(b instanceof a)b=b[0];while(b&&b.nodeType===1&&!(c=a.data(b,"tmplItem"))&&(b=b.parentNode));return c||p},template:function(c,b){if(b){if(typeof b==="string")b=o(b);else if(b instanceof a)b=b[0]||{};if(b.nodeType)b=a.data(b,"tmpl")||a.data(b,"tmpl",o(b.innerHTML));return typeof c==="string"?(a.template[c]=b):b}return c?typeof c!=="string"?a.template(null,c):a.template[c]||a.template(null,q.test(c)?c:a(c)):null},encode:function(a){return(""+a).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;")}});a.extend(a.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){__=__.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(__,$1,$2);__=[];",close:"call=$item.calls();__=call._.concat($item.wrap(call,__));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){__.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){__.push($.encode($1a));}"},"!":{open:""}},complete:function(){b={}},afterManip:function(f,b,d){var e=b.nodeType===11?a.makeArray(b.childNodes):b.nodeType===1?[b]:[];d.call(f,b);m(e);c++}});function j(e,g,f){var b,c=f?a.map(f,function(a){return typeof a==="string"?e.key?a.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+d+'="'+e.key+'" $2'):a:j(a,e,a._ctnt)}):e;if(g)return c;c=c.join("");c.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,c,e,d){b=a(e).get();m(b);if(c)b=k(c).concat(b);if(d)b=b.concat(k(d))});return b?b:k(c)}function k(c){var b=document.createElement("div");b.innerHTML=c;return a.makeArray(b.childNodes)}function o(b){return new Function("jQuery","$item","var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('"+a.trim(b).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,function(m,l,k,g,b,c,d){var j=a.tmpl.tag[k],i,e,f;if(!j)throw"Unknown template tag: "+k;i=j._default||[];if(c&&!/\w$/.test(b)){b+=c;c=""}if(b){b=h(b);d=d?","+h(d)+")":c?")":"";e=c?b.indexOf(".")>-1?b+h(c):"("+b+").call($item"+d:b;f=c?e:"(typeof("+b+")==='function'?("+b+").call($item):("+b+"))"}else f=e=i.$1||"null";g=h(g);return"');"+j[l?"close":"open"].split("$notnull_1").join(b?"typeof("+b+")!=='undefined' && ("+b+")!=null":"true").split("$1a").join(f).split("$1").join(e).split("$2").join(g||i.$2||"")+"__.push('"})+"');}return __;")}function n(c,b){c._wrap=j(c,true,a.isArray(b)?b:[q.test(b)?b:a(b).html()]).join("")}function h(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function s(b){var a=document.createElement("div");a.appendChild(b.cloneNode(true));return a.innerHTML}function m(o){var n="_"+c,k,j,l={},e,p,h;for(e=0,p=o.length;e<p;e++){if((k=o[e]).nodeType!==1)continue;j=k.getElementsByTagName("*");for(h=j.length-1;h>=0;h--)m(j[h]);m(k)}function m(j){var p,h=j,k,e,m;if(m=j.getAttribute(d)){while(h.parentNode&&(h=h.parentNode).nodeType===1&&!(p=h.getAttribute(d)));if(p!==m){h=h.parentNode?h.nodeType===11?0:h.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[h]||f[h]);e.key=++i;b[i]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;h=a.data(j.parentNode,"tmplItem");h=h?h.key:0}if(e){k=e;while(k&&k.key!=h){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery);
\ No newline at end of file
$ ->
# Add custom handlers to Knockout.js - adapted from Knockout.js Todos app: https://github.com/ashish01/knockoutjs-todos
ko.bindingHandlers.dblclick =
init: (element, value_accessor) -> $(element).dblclick(ko.utils.unwrapObservable(value_accessor()))
ko.bindingHandlers.block =
update: (element, value_accessor) -> element.style.display = if ko.utils.unwrapObservable(value_accessor()) then 'block' else 'none'
ko.bindingHandlers.selectAndFocus =
init: (element, value_accessor, all_bindings_accessor) ->
ko.bindingHandlers.hasfocus.init(element, value_accessor, all_bindings_accessor)
ko.utils.registerEventHandler(element, 'focus', -> element.select())
update: (element, value_accessor) ->
ko.utils.unwrapObservable(value_accessor()) # create dependency
_.defer(=>ko.bindingHandlers.hasfocus.update(element, value_accessor))
# Create and bind the app viewmodels
window.app = {viewmodels: {}}
app.viewmodels.settings = new SettingsViewModel()
todos = new TodosCollection()
app.viewmodels.header = new HeaderViewModel(todos)
app.viewmodels.todos = new TodosViewModel(todos)
app.viewmodels.footer = new FooterViewModel(todos)
ko.applyBindings(app.viewmodels, $('#todoapp')[0])
# Start the app routing
new AppRouter()
Backbone.history.start()
# Load the todos
todos.fetch()
# kb.vmRelease(app.viewmodels) # Destroy when finished with the view model
class window.Todo extends Backbone.Model
completed: (completed) ->
return !!@get('completed') if arguments.length == 0
@save({completed: if completed then new Date() else null})
class window.TodosCollection extends Backbone.Collection
localStorage: new Store('todos-knockback') # Save all of the todos under the `"todos-knockback"` namespace.
model: Todo
completedCount: -> @models.reduce(((prev,cur)-> return prev + if cur.completed() then 1 else 0), 0)
remainingCount: -> @models.length - @completedCount()
completeAll: (completed) -> @each((todo) -> todo.completed(completed))
destroyCompleted: ->
completed_tasks = @filter((todo) -> return todo.completed())
model.destroy() for model in completed_tasks
class window.AppRouter extends Backbone.Router
routes:
"": "all"
"active": "active"
"completed": "completed"
all: -> app.viewmodels.settings.list_filter_mode('')
active: -> app.viewmodels.settings.list_filter_mode('active')
completed: -> app.viewmodels.settings.list_filter_mode('completed')
\ No newline at end of file
window.FooterViewModel = (todos) ->
@collection_observable = kb.collectionObservable(todos)
@remaining_count = ko.computed(=> return @collection_observable.collection().remainingCount())
@remaining_text = ko.computed(=> return "#{if @collection_observable.collection().remainingCount() == 1 then 'item' else 'items'} left")
@clear_text = ko.computed(=>
count = @collection_observable.collection().completedCount()
return if count then "Clear completed (#{count})" else ''
)
@onDestroyCompleted = => todos.destroyCompleted()
@
\ No newline at end of file
ENTER_KEY = 13
window.HeaderViewModel = (todos) ->
@title = ko.observable('')
@onAddTodo = (view_model, event) =>
return true if not $.trim(@title()) or (event.keyCode != ENTER_KEY)
# Create task and reset UI
todos.create({title: $.trim(@title())})
@title('')
@
\ No newline at end of file
window.SettingsViewModel = ->
@list_filter_mode = ko.observable('')
@
TodoViewModel = (model) ->
# Task UI state
@editing = ko.observable(false)
@completed = kb.observable(model, {key: 'completed', write: ((completed) -> model.save(completed: completed)) }, @)
@visible = ko.computed(=>
switch app.viewmodels.settings.list_filter_mode()
when 'active' then return not @completed()
when 'completed' then return @completed()
else return true
)
@title = kb.observable(model, {
key: 'title'
write: ((title) =>
if $.trim(title) then model.save(title: $.trim(title)) else _.defer(->model.destroy())
@editing(false)
)
}, @)
@onDestroyTodo = => model.destroy()
@onCheckEditBegin = => (@editing(true); $('.todo-input').focus()) if not @editing() and not @completed()
@onCheckEditEnd = (view_model, event) => ($('.todo-input').blur(); @editing(false)) if (event.keyCode == 13) or (event.type == 'blur')
@
window.TodosViewModel = (todos) ->
@todos = ko.observableArray([])
@collection_observable = kb.collectionObservable(todos, @todos, view_model: TodoViewModel)
@tasks_exist = ko.computed(=> @collection_observable().length)
@all_completed = ko.computed(
read: => return not @collection_observable.collection().remainingCount()
write: (completed) => @collection_observable.collection().completeAll(completed)
)
@
\ No newline at end of file
###
knockback-todos.js
(c) 2011 Kevin Malakoff.
Knockback-Todos is freely distributable under the MIT license.
See the following for full license details:
https:#github.com/kmalakoff/knockback-todos/blob/master/LICENSE
###
$(document).ready(->
# add a doubleclick and placeholder handlers to KO
ko.bindingHandlers.dblclick =
init: (element, value_accessor, all_bindings_accessor, view_model) ->
$(element).dblclick(ko.utils.unwrapObservable(value_accessor()))
###################################
# Model: http://en.wikipedia.org/wiki/Model_view_controller
# ORM: http://en.wikipedia.org/wiki/Object-relational_mapping
###################################
# Todos
class TodoList extends Backbone.Collection
localStorage: new Store("kb_todos") # Save all of the todo items under the `"kb_todos"` namespace.
doneCount: -> @models.reduce(((prev,cur)-> return prev + if !!cur.get('done') then 1 else 0), 0)
remainingCount: -> @models.length - @doneCount()
allDone: -> return @filter((todo) -> return !!todo.get('done'))
todos = new TodoList()
todos.fetch()
###################################
# MVVM: http://en.wikipedia.org/wiki/Model_View_ViewModel
###################################
CreateTodoViewModel = ->
@input_text = ko.observable('')
@addTodo = (view_model, event) ->
text = @create.input_text()
return true if (!text || event.keyCode != 13)
todos.create({text: text})
@create.input_text('')
@
TodoViewModel = (model) ->
@text = kb.observable(model, {key: 'text', write: ((text) -> model.save({text: text}))}, this)
@edit_mode = ko.observable(false)
@toggleEditMode = (event) => @edit_mode(!@edit_mode()) if not @done()
@onEnterEndEdit = (event) => @edit_mode(false) if (event.keyCode == 13)
@done = kb.observable(model, {key: 'done', write: ((done) -> model.save({done: done})) }, this)
@destroyTodo = => model.destroy()
@
TodoListViewModel = (todos) ->
@todos = ko.observableArray([])
@collection_observable = kb.collectionObservable(todos, @todos, { view_model: TodoViewModel })
@
# Stats Footer
StatsViewModel = (todos) ->
@collection_observable = kb.collectionObservable(todos)
@remaining_text = ko.dependentObservable(=>
count = @collection_observable.collection().remainingCount(); return '' if not count
return "#{count} #{if count == 1 then 'item' else 'items'} remaining."
)
@clear_text = ko.dependentObservable(=>
count = @collection_observable.collection().doneCount(); return '' if not count
return "Clear #{count} completed #{if count == 1 then 'item' else 'items'}."
)
@onDestroyDone = => model.destroy() for model in todos.allDone()
@
app_view_model =
create: new CreateTodoViewModel()
todo_list: new TodoListViewModel(todos)
stats: new StatsViewModel(todos)
ko.applyBindings(app_view_model, $('#todoapp')[0])
# Destroy when finished with the view model
# kb.vmRelease(app_view_model)
)
\ No newline at end of file
/*
knockback-todos.js
(c) 2011 Kevin Malakoff.
Knockback-Todos is freely distributable under the MIT license.
See the following for full license details:
https:#github.com/kmalakoff/knockback-todos/blob/master/LICENSE
*/
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
}, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
$(document).ready(function() {
var CreateTodoViewModel, StatsViewModel, TodoList, TodoListViewModel, TodoViewModel, app_view_model, todos;
ko.bindingHandlers.dblclick = {
init: function(element, value_accessor, all_bindings_accessor, view_model) {
return $(element).dblclick(ko.utils.unwrapObservable(value_accessor()));
}
};
TodoList = (function() {
__extends(TodoList, Backbone.Collection);
function TodoList() {
TodoList.__super__.constructor.apply(this, arguments);
}
TodoList.prototype.localStorage = new Store("kb_todos");
TodoList.prototype.doneCount = function() {
return this.models.reduce((function(prev, cur) {
return prev + (!!cur.get('done') ? 1 : 0);
}), 0);
};
TodoList.prototype.remainingCount = function() {
return this.models.length - this.doneCount();
};
TodoList.prototype.allDone = function() {
return this.filter(function(todo) {
return !!todo.get('done');
});
};
return TodoList;
})();
todos = new TodoList();
todos.fetch();
CreateTodoViewModel = function() {
this.input_text = ko.observable('');
this.addTodo = function(view_model, event) {
var text;
text = this.create.input_text();
if (!text || event.keyCode !== 13) {
return true;
}
todos.create({
text: text
});
return this.create.input_text('');
};
return this;
};
TodoViewModel = function(model) {
this.text = kb.observable(model, {
key: 'text',
write: (function(text) {
return model.save({
text: text
});
})
}, this);
this.edit_mode = ko.observable(false);
this.toggleEditMode = __bind(function(event) {
if (!this.done()) {
return this.edit_mode(!this.edit_mode());
}
}, this);
this.onEnterEndEdit = __bind(function(event) {
if (event.keyCode === 13) {
return this.edit_mode(false);
}
}, this);
this.done = kb.observable(model, {
key: 'done',
write: (function(done) {
return model.save({
done: done
});
})
}, this);
this.destroyTodo = __bind(function() {
return model.destroy();
}, this);
return this;
};
TodoListViewModel = function(todos) {
this.todos = ko.observableArray([]);
this.collection_observable = kb.collectionObservable(todos, this.todos, {
view_model: TodoViewModel
});
return this;
};
StatsViewModel = function(todos) {
this.collection_observable = kb.collectionObservable(todos);
this.remaining_text = ko.dependentObservable(__bind(function() {
var count;
count = this.collection_observable.collection().remainingCount();
if (!count) {
return '';
}
return "" + count + " " + (count === 1 ? 'item' : 'items') + " remaining.";
}, this));
this.clear_text = ko.dependentObservable(__bind(function() {
var count;
count = this.collection_observable.collection().doneCount();
if (!count) {
return '';
}
return "Clear " + count + " completed " + (count === 1 ? 'item' : 'items') + ".";
}, this));
this.onDestroyDone = __bind(function() {
var model, _i, _len, _ref, _results;
_ref = todos.allDone();
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
model = _ref[_i];
_results.push(model.destroy());
}
return _results;
}, this);
return this;
};
app_view_model = {
create: new CreateTodoViewModel(),
todo_list: new TodoListViewModel(todos),
stats: new StatsViewModel(todos)
};
return ko.applyBindings(app_view_model, $('#todoapp')[0]);
});
\ No newline at end of file
......@@ -17,7 +17,7 @@
placeholder="What needs to be done?" autofocus>
</header>
<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: completeAll">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-bind="foreach: todos">
<li data-bind="css: { completed: completed, editing: editing }">
......
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