Commit 2a30629f authored by Kevin Malakoff's avatar Kevin Malakoff Committed by Sindre Sorhus

Close GH-291: Updated to Latest Knockback and simplified the implementation (take 2).

parent 1bb1340f
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
task 'build', 'Build js/ from src/', -> task 'build', 'Build js/ from src/', ->
coffee = spawn 'coffee', ['-c', '-o', 'js', 'src'] coffee = spawn 'coffee', ['-c', '-o', 'js', 'src']
coffee.stderr.on 'data', (data) -> coffee.stderr.on 'data', (data) ->
process.stderr.write data.toString() message = data.toString()
if message.search('is now called') < 0
process.stderr.write message
coffee.stdout.on 'data', (data) -> coffee.stdout.on 'data', (data) ->
print data.toString() print data.toString()
coffee.on 'exit', (code) -> coffee.on 'exit', (code) ->
...@@ -15,4 +17,4 @@ task 'watch', 'Watch src/ for changes', -> ...@@ -15,4 +17,4 @@ task 'watch', 'Watch src/ for changes', ->
coffee.stderr.on 'data', (data) -> coffee.stderr.on 'data', (data) ->
process.stderr.write data.toString() process.stderr.write data.toString()
coffee.stdout.on 'data', (data) -> coffee.stdout.on 'data', (data) ->
print data.toString() print data.toString()
\ No newline at end of file
...@@ -10,68 +10,61 @@ ...@@ -10,68 +10,61 @@
<![endif]--> <![endif]-->
</head> </head>
<body> <body>
<section id="todoapp"> <section id="todoapp" kb-inject="AppViewModel">
<header id="header"> <header id="header">
<h1>todos</h1> <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> <input id="new-todo" type="text" data-bind="value: title, valueUpdate: 'afterkeydown', event: {keyup: onAddTodo}" placeholder="What needs to be done?" autofocus>
</header> </header>
<section id="main" data-bind="block: todos.tasks_exist"> <section id="main" data-bind="block: tasks_exist">
<input id="toggle-all" type="checkbox" data-bind="checked: todos.all_completed"> <input id="toggle-all" type="checkbox" data-bind="checked: all_completed">
<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.todos"> <ul id="todo-list" data-bind="foreach: todos">
<li data-bind="css: {completed: completed, editing: editing}, visible: visible"> <li data-bind="css: {completed: completed, editing: editing}">
<div class="view"> <div class="view" data-bind="event: {dblclick: onCheckEditBegin}">
<input class="toggle" type="checkbox" data-bind="checked: completed" checked> <input class="toggle" type="checkbox" data-bind="checked: completed" checked>
<label data-bind="text: title, event: {dblclick: onCheckEditBegin}"></label> <label data-bind="text: title"></label>
<button class="destroy" data-bind="click: onDestroyTodo"></button> <button class="destroy" data-bind="click: onDestroyTodo"></button>
</div> </div>
<input class="edit" type="text" data-bind="value: title, selectAndFocus: editing, event: {blur: onCheckEditEnd, keyup: onCheckEditEnd}"> <input class="edit" type="text" data-bind="value: title, selectAndFocus: editing, event: {blur: onCheckEditEnd, keyup: onCheckEditEnd}">
</li> </li>
</ul> </ul>
</section> </section>
<footer id="footer" data-bind="block: todos.tasks_exist"> <footer id="footer" data-bind="block: tasks_exist">
<span id="todo-count" data-bind="html: footer.remaining_text"></span> <span id="todo-count" data-bind="html: loc.remaining_message"></span>
<ul id="filters"> <ul id="filters">
<li> <li>
<a href="#/" data-bind="css: {selected: settings.list_filter_mode()==''}">All</a> <a href="#/" data-bind="css: {selected: list_filter_mode()==''}">All</a>
</li> </li>
<li> <li>
<a href="#/active" data-bind="css: {selected: settings.list_filter_mode()=='active'}">Active</a> <a href="#/active" data-bind="css: {selected: list_filter_mode()=='active'}">Active</a>
</li> </li>
<li> <li>
<a href="#/completed" data-bind="css: {selected: settings.list_filter_mode()=='completed'}">Completed</a> <a href="#/completed" data-bind="css: {selected: list_filter_mode()=='completed'}">Completed</a>
</li> </li>
</ul> </ul>
<button id="clear-completed" data-bind="text: footer.clear_text, block: footer.clear_text, click: footer.onDestroyCompleted"></button> <button id="clear-completed" data-bind="text: loc.clear_message, block: loc.clear_message, click: onDestroyCompleted"></button>
</footer> </footer>
</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>Created by <a href="https://github.com/kmalakoff">Kevin Malakoff</a>. <br/> <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/> Please try out the <a href="http://kmalakoff.github.com/knockback-todos-app/">enhanced version</a> <br/>
with localization, priority colors, and lazy loading <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> 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> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer> </footer>
<!-- Demo Dependencies -->
<!-- App Dependencies -->
<script src="../../assets/base.js"></script> <script src="../../assets/base.js"></script>
<script src="../../assets/jquery.min.js"></script> <script src="../../assets/jquery.min.js"></script>
<!-- Knockback Dependencies --> <script src="js/lib/knockback-full-stack-0.16.7.min.js"></script>
<script src="../../assets/lodash.min.js"></script>
<script src="js/lib/backbone-min.js"></script>
<script src="js/lib/knockout-2.1.0.js"></script>
<script src="js/lib/knockback.min.js"></script>
<!-- More Demo Dependencies -->
<script src="js/lib/backbone.localStorage-min.js"></script> <script src="js/lib/backbone.localStorage-min.js"></script>
<!-- Demo Components -->
<!-- App and Components -->
<script src="js/lib/knockout-extended-bindings.js"></script>
<script src="js/models/todo.js"></script> <script src="js/models/todo.js"></script>
<script src="js/models/todo_collection.js"></script> <script src="js/models/todo_collection.js"></script>
<script src="js/viewmodels/settings.js"></script> <script src="js/viewmodels/todo.js"></script>
<script src="js/viewmodels/header.js"></script> <script src="js/viewmodels/app.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="js/app.js"></script>
</body> </body>
</html> </html>
\ No newline at end of file
// Generated by CoffeeScript 1.3.3
(function() {
$(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.focus();
});
},
update: function(element, value_accessor) {
var _this = this;
ko.utils.unwrapObservable(value_accessor());
return _.defer(function() {
return ko.bindingHandlers.hasfocus.update(element, value_accessor);
});
}
};
window.app = {
viewmodels: {}
};
app.viewmodels.settings = new SettingsViewModel();
todos = new TodoCollection();
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.
/** /**
* Backbone localStorage Adapter * Backbone localStorage Adapter
* https://github.com/jeromegn/Backbone.localStorage * https://github.com/jeromegn/Backbone.localStorage
*/(function(){function a(){return((1+Math.random())*65536|0).toString(16).substring(1)}function b(){return a()+a()+"-"+a()+"-"+a()+"-"+a()+"-"+a()+a()+a()}Backbone.LocalStorage=window.Store=function(a){this.name=a;var b=this.localStorage().getItem(this.name);this.records=b&&b.split(",")||[]},_.extend(Backbone.LocalStorage.prototype,{save:function(){this.localStorage().setItem(this.name,this.records.join(","))},create:function(a){return a.id||(a.id=b(),a.set(a.idAttribute,a.id)),this.localStorage().setItem(this.name+"-"+a.id,JSON.stringify(a)),this.records.push(a.id.toString()),this.save(),a},update:function(a){return this.localStorage().setItem(this.name+"-"+a.id,JSON.stringify(a)),_.include(this.records,a.id.toString())||this.records.push(a.id.toString()),this.save(),a},find:function(a){return JSON.parse(this.localStorage().getItem(this.name+"-"+a.id))},findAll:function(){return _(this.records).chain().map(function(a){return JSON.parse(this.localStorage().getItem(this.name+"-"+a))},this).compact().value()},destroy:function(a){return this.localStorage().removeItem(this.name+"-"+a.id),this.records=_.reject(this.records,function(b){return b==a.id.toString()}),this.save(),a},localStorage:function(){return localStorage}}),Backbone.LocalStorage.sync=window.Store.sync=Backbone.localSync=function(a,b,c,d){var e=b.localStorage||b.collection.localStorage;typeof c=="function"&&(c={success:c,error:d});var f;switch(a){case"read":f=b.id!=undefined?e.find(b):e.findAll();break;case"create":f=e.create(b);break;case"update":f=e.update(b);break;case"delete":f=e.destroy(b)}f?c.success(f):c.error("Record not found")},Backbone.ajaxSync=Backbone.sync,Backbone.getSyncMethod=function(a){return a.localStorage||a.collection&&a.collection.localStorage?Backbone.localSync:Backbone.ajaxSync},Backbone.sync=function(a,b,c,d){return Backbone.getSyncMethod(b).apply(this,[a,b,c,d])}})(); */(function(){function a(){return((1+Math.random())*65536|0).toString(16).substring(1)}function b(){return a()+a()+"-"+a()+"-"+a()+"-"+a()+"-"+a()+a()+a()}Backbone.LocalStorage=window.Store=function(a){this.name=a;var b=this.localStorage().getItem(this.name);this.records=b&&b.split(",")||[]},_.extend(Backbone.LocalStorage.prototype,{save:function(){this.localStorage().setItem(this.name,this.records.join(","))},create:function(a){return a.id||(a.id=b(),a.set(a.idAttribute,a.id)),this.localStorage().setItem(this.name+"-"+a.id,JSON.stringify(a)),this.records.push(a.id.toString()),this.save(),a},update:function(a){return this.localStorage().setItem(this.name+"-"+a.id,JSON.stringify(a)),_.include(this.records,a.id.toString())||this.records.push(a.id.toString()),this.save(),a},find:function(a){return JSON.parse(this.localStorage().getItem(this.name+"-"+a.id))},findAll:function(){return _(this.records).chain().map(function(a){return JSON.parse(this.localStorage().getItem(this.name+"-"+a))},this).compact().value()},destroy:function(a){return this.localStorage().removeItem(this.name+"-"+a.id),this.records=_.reject(this.records,function(b){return b==a.id.toString()}),this.save(),a},localStorage:function(){return localStorage}}),Backbone.LocalStorage.sync=window.Store.sync=Backbone.localSync=function(a,b,c,d){var e=b.localStorage||b.collection.localStorage;typeof c=="function"&&(c={success:c,error:d});var f;switch(a){case"read":f=b.id!=undefined?e.find(b):e.findAll();break;case"create":f=e.create(b);break;case"update":f=e.update(b);break;case"delete":f=e.destroy(b)}f?c.success(f):c.error("Record not found")},Backbone.ajaxSync=Backbone.sync,Backbone.getSyncMethod=function(a){return a.localStorage||a.collection&&a.collection.localStorage?Backbone.localSync:Backbone.ajaxSync},Backbone.sync=function(a,b,c,d){return Backbone.getSyncMethod(b).apply(this,[a,b,c,d])}})();
\ No newline at end of file
// Generated by CoffeeScript 1.3.3
(function() {
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(function() {
return ko.bindingHandlers.hasfocus.update(element, value_accessor);
});
}
};
}).call(this);
...@@ -32,16 +32,14 @@ ...@@ -32,16 +32,14 @@
}; };
TodoCollection.prototype.destroyCompleted = function() { TodoCollection.prototype.destroyCompleted = function() {
var completed_tasks, model, _i, _len, _results; var completed_tasks, model, _i, _len;
completed_tasks = this.filter(function(todo) { completed_tasks = this.filter(function(todo) {
return todo.completed(); return todo.completed();
}); });
_results = [];
for (_i = 0, _len = completed_tasks.length; _i < _len; _i++) { for (_i = 0, _len = completed_tasks.length; _i < _len; _i++) {
model = completed_tasks[_i]; model = completed_tasks[_i];
_results.push(model.destroy()); model.destroy();
} }
return _results;
}; };
return TodoCollection; return TodoCollection;
......
// Generated by CoffeeScript 1.3.3
(function() {
var __hasProp = {}.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(_super) {
__extends(AppRouter, _super);
function AppRouter() {
return 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;
})(Backbone.Router);
}).call(this);
// Generated by CoffeeScript 1.3.3
(function() {
var ENTER_KEY;
ENTER_KEY = 13;
window.AppViewModel = function() {
var filter_fn, router,
_this = this;
this.collections = {
todos: new TodoCollection()
};
this.collections.todos.fetch();
this.list_filter_mode = ko.observable('');
filter_fn = ko.computed(function() {
switch (_this.list_filter_mode()) {
case 'active':
return function(model) {
return model.completed();
};
case 'completed':
return function(model) {
return !model.completed();
};
default:
return function() {
return false;
};
}
});
this.todos = kb.collectionObservable(this.collections.todos, {
view_model: TodoViewModel,
filters: filter_fn
});
this.todos_changed = kb.triggeredObservable(this.collections.todos, 'change add remove');
this.tasks_exist = ko.computed(function() {
_this.todos_changed();
return !!_this.collections.todos.length;
});
this.title = ko.observable('');
this.onAddTodo = function(view_model, event) {
if (!$.trim(_this.title()) || (event.keyCode !== ENTER_KEY)) {
return true;
}
_this.collections.todos.create({
title: $.trim(_this.title())
});
return _this.title('');
};
this.remaining_count = ko.computed(function() {
_this.todos_changed();
return _this.collections.todos.remainingCount();
});
this.completed_count = ko.computed(function() {
_this.todos_changed();
return _this.collections.todos.completedCount();
});
this.all_completed = ko.computed({
read: function() {
return !_this.remaining_count();
},
write: function(completed) {
return _this.collections.todos.completeAll(completed);
}
});
this.onDestroyCompleted = function() {
return _this.collections.todos.destroyCompleted();
};
this.loc = {
remaining_message: ko.computed(function() {
return "<strong>" + (_this.remaining_count()) + "</strong> " + (_this.remaining_count() === 1 ? 'item' : 'items') + " left";
}),
clear_message: ko.computed(function() {
var count;
if ((count = _this.completed_count())) {
return "Clear completed (" + count + ")";
} else {
return '';
}
})
};
router = new Backbone.Router;
router.route('', null, function() {
return _this.list_filter_mode('');
});
router.route('active', null, function() {
return _this.list_filter_mode('active');
});
router.route('completed', null, function() {
return _this.list_filter_mode('completed');
});
Backbone.history.start();
};
}).call(this);
// Generated by CoffeeScript 1.3.3
(function() {
window.FooterViewModel = function(todos) {
var _this = this;
this.todos = kb.collectionObservable(todos);
this.todos.collection().bind('change', function() {
return _this.todos.valueHasMutated();
});
this.remaining_text = ko.computed(function() {
return "<strong>" + (_this.todos.collection().remainingCount()) + "</strong> " + (_this.todos.collection().remainingCount() === 1 ? 'item' : 'items') + " left";
});
this.clear_text = ko.computed(function() {
var count;
count = _this.todos.collection().completedCount();
if (count) {
return "Clear completed (" + count + ")";
} else {
return '';
}
});
this.onDestroyCompleted = function() {
return todos.destroyCompleted();
};
return this;
};
}).call(this);
// Generated by CoffeeScript 1.3.3
(function() {
var ENTER_KEY;
ENTER_KEY = 13;
window.HeaderViewModel = function(todos) {
var _this = this;
this.title = ko.observable('');
this.onAddTodo = function(view_model, event) {
if (!$.trim(_this.title()) || (event.keyCode !== ENTER_KEY)) {
return true;
}
todos.create({
title: $.trim(_this.title())
});
return _this.title('');
};
return this;
};
}).call(this);
// Generated by CoffeeScript 1.3.3
(function() {
window.SettingsViewModel = function() {
this.list_filter_mode = ko.observable('');
return this;
};
}).call(this);
// Generated by CoffeeScript 1.3.3 // Generated by CoffeeScript 1.3.3
(function() { (function() {
var TodoViewModel;
TodoViewModel = function(model) { window.TodoViewModel = function(model) {
var _this = this; var _this = this;
this.editing = ko.observable(false); this.editing = ko.observable(false);
this.completed = kb.observable(model, { this.completed = kb.observable(model, {
...@@ -14,16 +13,6 @@ ...@@ -14,16 +13,6 @@
return model.completed(completed); return model.completed(completed);
}) })
}, this); }, this);
this.visible = ko.computed(function() {
switch (app.viewmodels.settings.list_filter_mode()) {
case 'active':
return !_this.completed();
case 'completed':
return _this.completed();
default:
return true;
}
});
this.title = kb.observable(model, { this.title = kb.observable(model, {
key: 'title', key: 'title',
write: (function(title) { write: (function(title) {
...@@ -44,7 +33,8 @@ ...@@ -44,7 +33,8 @@
}; };
this.onCheckEditBegin = function() { this.onCheckEditBegin = function() {
if (!_this.editing() && !_this.completed()) { if (!_this.editing() && !_this.completed()) {
return _this.editing(true); _this.editing(true);
return $('.todo-input').focus();
} }
}; };
this.onCheckEditEnd = function(view_model, event) { this.onCheckEditEnd = function(view_model, event) {
...@@ -53,29 +43,6 @@ ...@@ -53,29 +43,6 @@
return _this.editing(false); return _this.editing(false);
} }
}; };
return this;
};
window.TodosViewModel = function(todos) {
var _this = this;
this.todos = kb.collectionObservable(todos, {
view_model: TodoViewModel
});
this.todos.collection().bind('change', function() {
return _this.todos.valueHasMutated();
});
this.tasks_exist = ko.computed(function() {
return _this.todos().length;
});
this.all_completed = ko.computed({
read: function() {
return !_this.todos.collection().remainingCount();
},
write: function(completed) {
return _this.todos.collection().completeAll(completed);
}
});
return this;
}; };
}).call(this); }).call(this);
# Knockback.js • [TodoMVC](http://todomvc.com) # Knockback.js • [TodoMVC](http://todomvc.com)
Forked from https://github.com/kmalakoff/knockback-todos
## Getting started ## Getting started
[CoffeeScript](http://coffeescript.org) is required to compile this application if you make changes to the files in the `src` folder. You need [CoffeScript](http://coffeescript.org) to compile if you make changes to the files in the `src` folder.
## Compile ## Compile
...@@ -14,4 +11,4 @@ Open Terminal in this folder. ...@@ -14,4 +11,4 @@ Open Terminal in this folder.
- `cake build` to compile once - `cake build` to compile once
- `cake watch` to compile on save - `cake watch` to compile on save
\ 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.focus())
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 TodoCollection()
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
# 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))
\ No newline at end of file
class window.Todo extends Backbone.Model class window.Todo extends Backbone.Model
completed: (completed) -> completed: (completed) ->
return !!@get('completed') if arguments.length == 0 return !!@get('completed') if arguments.length is 0 # getter
@save({completed: if completed then new Date() else null})
@save({completed: if completed then new Date() else null}) # setter
\ No newline at end of file
class window.TodoCollection extends Backbone.Collection class window.TodoCollection extends Backbone.Collection
localStorage: new Store('todos-knockback') # Save all of the todos under the `"todos-knockback"` namespace. localStorage: new Store('todos-knockback') # Save all of the todos under the "todos-knockback" namespace.
model: Todo model: Todo
completedCount: -> @models.reduce(((prev,cur)-> return prev + if cur.completed() then 1 else 0), 0) completedCount: -> @models.reduce(((prev,cur)-> return prev + if cur.completed() then 1 else 0), 0)
...@@ -10,3 +10,4 @@ class window.TodoCollection extends Backbone.Collection ...@@ -10,3 +10,4 @@ class window.TodoCollection extends Backbone.Collection
destroyCompleted: -> destroyCompleted: ->
completed_tasks = @filter((todo) -> return todo.completed()) completed_tasks = @filter((todo) -> return todo.completed())
model.destroy() for model in completed_tasks model.destroy() for model in completed_tasks
return
\ No newline at end of file
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')
ENTER_KEY = 13
window.AppViewModel = ->
#############################
# Shared
#############################
# collections
@collections =
todos: new TodoCollection()
@collections.todos.fetch()
# shared observables
@list_filter_mode = ko.observable('')
filter_fn = ko.computed(=>
switch @list_filter_mode()
when 'active' then return (model) -> return model.completed()
when 'completed' then return (model) -> return not model.completed()
else return -> return false
)
@todos = kb.collectionObservable(@collections.todos, {view_model: TodoViewModel, filters: filter_fn})
@todos_changed = kb.triggeredObservable(@collections.todos, 'change add remove')
@tasks_exist = ko.computed(=> @todos_changed(); return !!@collections.todos.length)
#############################
# Header Section
#############################
@title = ko.observable('')
@onAddTodo = (view_model, event) =>
return true if not $.trim(@title()) or (event.keyCode != ENTER_KEY)
# Create task and reset UI
@collections.todos.create({title: $.trim(@title())})
@title('')
#############################
# Main Section
#############################
@remaining_count = ko.computed(=> @todos_changed(); return @collections.todos.remainingCount())
@completed_count = ko.computed(=> @todos_changed(); return @collections.todos.completedCount())
@all_completed = ko.computed(
read: => return not @remaining_count()
write: (completed) => @collections.todos.completeAll(completed)
)
#############################
# Footer Section
#############################
@onDestroyCompleted = =>
@collections.todos.destroyCompleted()
#############################
# Localization
#############################
@loc =
remaining_message: ko.computed(=> return "<strong>#{@remaining_count()}</strong> #{if @remaining_count() == 1 then 'item' else 'items'} left")
clear_message: ko.computed(=> return if (count = @completed_count()) then "Clear completed (#{count})" else '')
#############################
# Routing
#############################
router = new Backbone.Router
router.route('', null, => @list_filter_mode(''))
router.route('active', null, => @list_filter_mode('active'))
router.route('completed', null, => @list_filter_mode('completed'))
Backbone.history.start()
return
\ No newline at end of file
window.FooterViewModel = (todos) ->
@todos = kb.collectionObservable(todos)
@todos.collection().bind('change', => @todos.valueHasMutated()) # get notified of changes to any models
@remaining_text = ko.computed(=> return "<strong>#{@todos.collection().remainingCount()}</strong> #{if @todos.collection().remainingCount() == 1 then 'item' else 'items'} left")
@clear_text = ko.computed(=>
count = @todos.collection().completedCount()
return if count then "Clear completed (#{count})" else ''
)
@onDestroyCompleted = => todos.destroyCompleted()
@
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('')
@
window.SettingsViewModel = ->
@list_filter_mode = ko.observable('')
@
TodoViewModel = (model) -> window.TodoViewModel = (model) ->
# Task UI state
@editing = ko.observable(false) @editing = ko.observable(false)
@completed = kb.observable(model, {key: 'completed', read: (-> return model.completed()), write: ((completed) -> model.completed(completed)) }, @) @completed = kb.observable(model, {key: 'completed', read: (-> return model.completed()), write: ((completed) -> model.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, { @title = kb.observable(model, {
key: 'title' key: 'title'
...@@ -19,18 +12,14 @@ TodoViewModel = (model) -> ...@@ -19,18 +12,14 @@ TodoViewModel = (model) ->
@onDestroyTodo = => model.destroy() @onDestroyTodo = => model.destroy()
@onCheckEditBegin = => @editing(true) if not @editing() and not @completed() @onCheckEditBegin = =>
@onCheckEditEnd = (view_model, event) => ($('.todo-input').blur(); @editing(false)) if (event.keyCode == 13) or (event.type == 'blur') if not @editing() and not @completed()
@ @editing(true)
$('.todo-input').focus()
window.TodosViewModel = (todos) -> @onCheckEditEnd = (view_model, event) =>
@todos = kb.collectionObservable(todos, {view_model: TodoViewModel}) if (event.keyCode == 13) or (event.type == 'blur')
@todos.collection().bind('change', => @todos.valueHasMutated()) # get notified of changes to any models $('.todo-input').blur()
@editing(false)
@tasks_exist = ko.computed(=> @todos().length)
@all_completed = ko.computed( return
read: => return not @todos.collection().remainingCount() \ No newline at end of file
write: (completed) => @todos.collection().completeAll(completed)
)
@
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