Commit 3a6db4da authored by Addy Osmani's avatar Addy Osmani

Merge pull request #88 from rniemeyer/knockout-template-update

Update Knockout.js sample to conform to template for Issue #68
parents 2e76a44a 73ebf519
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-weight: inherit;
font-style: inherit;
font-size: 100%;
font-family: inherit;
vertical-align: baseline;
}
body {
line-height: 1;
color: black;
background: white;
}
ol, ul {
list-style: none;
}
a img {
border: none;
}
html {
background: #eeeeee;
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.4em;
background: #eeeeee;
color: #333333;
}
#todoapp {
width: 480px;
margin: 0 auto 40px;
background: white;
padding: 20px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
-moz-border-radius: 0 0 5px 5px;
-o-border-radius: 0 0 5px 5px;
-webkit-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
#todoapp h1 {
font-size: 36px;
font-weight: bold;
text-align: center;
padding: 20px 0 30px 0;
line-height: 1;
}
#create-todo {
position: relative;
}
#create-todo input {
width: 466px;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
#create-todo input::-webkit-input-placeholder {
font-style: italic;
}
#create-todo span {
position: absolute;
z-index: 999;
width: 170px;
left: 50%;
margin-left: -85px;
}
#todo-list {
margin-top: 10px;
}
#todo-list li {
padding: 12px 20px 11px 0;
position: relative;
font-size: 24px;
line-height: 1.1em;
border-bottom: 1px solid #cccccc;
}
#todo-list li:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
#todo-list li.editing {
padding: 0;
border-bottom: 0;
}
#todo-list .editing .display,
#todo-list .edit {
display: none;
}
#todo-list .editing .edit {
display: block;
}
#todo-list .editing input {
width: 444px;
font-size: 24px;
font-family: inherit;
margin: 0;
line-height: 1.6em;
border: 0;
outline: none;
padding: 10px 7px 0px 27px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
#todo-list .check {
position: relative;
top: 9px;
margin: 0 10px 0 7px;
float: left;
}
#todo-list .done .todo-content {
text-decoration: line-through;
color: #777777;
}
#todo-list .todo-destroy {
position: absolute;
right: 5px;
top: 14px;
display: none;
cursor: pointer;
width: 20px;
height: 20px;
background: url(destroy.png) no-repeat 0 0;
}
#todo-list li:hover .todo-destroy {
display: block;
}
#todo-list .todo-destroy:hover {
background-position: 0 -20px;
}
#todo-stats {
*zoom: 1;
margin-top: 10px;
color: #777777;
}
#todo-stats:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
#todo-stats .todo-count {
float: left;
}
#todo-stats .todo-count .number {
font-weight: bold;
color: #333333;
}
#todo-stats .todo-clear {
float: right;
}
#todo-stats .todo-clear a {
color: #777777;
font-size: 12px;
}
/* these two rules are overridden below. find ".todo-clear a" */
#todo-stats .todo-clear a:visited {
color: #777777;
}
#todo-stats .todo-clear a:hover, #todo-stats .todo-clear a:focus {
color: #336699;
}
#instructions {
width: 520px;
margin: 10px auto;
color: #777777;
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
text-align: center;
}
#instructions a {
color: #336699;
}
#credits {
width: 520px;
margin: 30px auto;
color: #999;
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
text-align: center;
}
#credits a {
color: #888;
}
/*
* François 'cahnory' Germain
*/
.ui-tooltip, .ui-tooltip-top, .ui-tooltip-right, .ui-tooltip-bottom, .ui-tooltip-left {
color:#ffffff;
cursor:normal;
display:-moz-inline-stack;
display:inline-block;
font-size:12px;
font-family:arial;
padding:.5em 1em;
position:relative;
text-align:center;
text-shadow:0 -1px 1px #111111;
-webkit-border-top-left-radius:4px ;
-webkit-border-top-right-radius:4px ;
-webkit-border-bottom-right-radius:4px ;
-webkit-border-bottom-left-radius:4px ;
-khtml-border-top-left-radius:4px ;
-khtml-border-top-right-radius:4px ;
-khtml-border-bottom-right-radius:4px ;
-khtml-border-bottom-left-radius:4px ;
-moz-border-radius-topleft:4px ;
-moz-border-radius-topright:4px ;
-moz-border-radius-bottomright:4px ;
-moz-border-radius-bottomleft:4px ;
border-top-left-radius:4px ;
border-top-right-radius:4px ;
border-bottom-right-radius:4px ;
border-bottom-left-radius:4px ;
-o-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
-moz-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
-khtml-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
-webkit-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
background-color:#3b3b3b;
background-image:-moz-linear-gradient(top,#555555,#222222);
background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#555555),color-stop(1,#222222));
filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222);
-ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222);
}
.ui-tooltip:after, .ui-tooltip-top:after, .ui-tooltip-right:after, .ui-tooltip-bottom:after, .ui-tooltip-left:after {
content:"\25B8";
display:block;
font-size:2em;
height:0;
line-height:0;
position:absolute;
}
.ui-tooltip:after, .ui-tooltip-bottom:after {
color:#2a2a2a;
bottom:0;
left:1px;
text-align:center;
text-shadow:1px 0 2px #000000;
-o-transform:rotate(90deg);
-moz-transform:rotate(90deg);
-khtml-transform:rotate(90deg);
-webkit-transform:rotate(90deg);
width:100%;
}
.ui-tooltip-top:after {
bottom:auto;
color:#4f4f4f;
left:-2px;
top:0;
text-align:center;
text-shadow:none;
-o-transform:rotate(-90deg);
-moz-transform:rotate(-90deg);
-khtml-transform:rotate(-90deg);
-webkit-transform:rotate(-90deg);
width:100%;
}
.ui-tooltip-right:after {
color:#222222;
right:-0.375em;
top:50%;
margin-top:-.05em;
text-shadow:0 1px 2px #000000;
-o-transform:rotate(0);
-moz-transform:rotate(0);
-khtml-transform:rotate(0);
-webkit-transform:rotate(0);
}
.ui-tooltip-left:after {
color:#222222;
left:-0.375em;
top:50%;
margin-top:.1em;
text-shadow:0 -1px 2px #000000;
-o-transform:rotate(180deg);
-moz-transform:rotate(180deg);
-khtml-transform:rotate(180deg);
-webkit-transform:rotate(180deg);
}
/* new additions - cleanup required*/
/* line 109 */
#todoapp #todo-stats {
*zoom: 1;
margin-top: 10px;
color: #555555;
-moz-border-radius-bottomleft: 5px;
-webkit-border-bottom-left-radius: 5px;
-o-border-bottom-left-radius: 5px;
-ms-border-bottom-left-radius: 5px;
-khtml-border-bottom-left-radius: 5px;
border-bottom-left-radius: 5px;
-moz-border-radius-bottomright: 5px;
-webkit-border-bottom-right-radius: 5px;
-o-border-bottom-right-radius: 5px;
-ms-border-bottom-right-radius: 5px;
-khtml-border-bottom-right-radius: 5px;
border-bottom-right-radius: 5px;
background: #f4fce8;
border-top: 1px solid #ededed;
padding: 0 20px;
line-height: 36px;
}
/* line 22, /opt/ree/lib/ruby/gems/1.8/gems/compass-0.10.5/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */
#todoapp #todo-stats:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
/* line 118 */
#todoapp #todo-stats .todo-count {
float: left;
}
/* line 120 */
#todoapp #todo-stats .todo-count .number {
font-weight: bold;
color: #555555;
}
/* line 123 */
#todoapp #todo-stats .todo-clear {
float: right;
}
/* line 125 */
#todoapp #todo-stats .todo-clear a {
display: block;
line-height: 20px;
text-decoration: none;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
-o-border-radius: 12px;
-ms-border-radius: 12px;
-khtml-border-radius: 12px;
border-radius: 12px;
background: rgba(0, 0, 0, 0.1);
color: #555555;
font-size: 11px;
margin-top: 8px;
padding: 0 10px 1px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
}
/* line 136 */
#todoapp #todo-stats .todo-clear a:hover, #todoapp #todo-stats .todo-clear a:focus {
background: rgba(0, 0, 0, 0.15);
-moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
}
/* line 139 */
#todoapp #todo-stats .todo-clear a:active {
position: relative;
top: 1px;
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml"> <html lang="en">
<head> <head>
<meta charset="utf-8">
<title>Knockout.js</title> <title>Knockout.js</title>
<link href="css/todos.css" media="all" rel="stylesheet" type="text/css" /> <link rel="stylesheet" href="../../assets/base.css">
</head> </head>
<body> <body>
<div id="todoapp"> <div id="todoapp">
<div class="title"> <header>
<h1>Todos</h1> <h1>Todos</h1>
</div> <input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add"
<div class="content"> placeholder="What needs to be done?"/>
<div id="create-todo"> </header>
<input id="new-todo" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add" placeholder="What needs to be done?" /> <section id="main" data-bind="block: todos().length">
<span class="ui-tooltip-top" data-bind="visible: showTooltip" style="display: none;">Press Enter to save this task</span> <input id="toggle-all" type="checkbox" data-bind="checked: allCompleted">
</div> <label for="toggle-all">Mark all as complete</label>
<div id="todos">
<div data-bind="visible: todos().length">
<input id="check-all" class="check" type="checkbox" data-bind="checked: allCompleted" />
<label for="check-all">Mark all as complete</label>
</div>
<ul id="todo-list" data-bind="foreach: todos"> <ul id="todo-list" data-bind="foreach: todos">
<li data-bind="css: { editing: editing }"> <li data-bind="css: { done: done, editing: editing }">
<div class="todo" data-bind="css: { done : done }"> <div class="view" data-bind="event: { dblclick: $root.editItem }">
<div class="display"> <input class="toggle" type="checkbox" data-bind="checked: done">
<input class="check" type="checkbox" data-bind="checked: done" /> <label data-bind="text: content"></label>
<div class="todo-content" data-bind="text: content, event: { dblclick: edit }" style="cursor: pointer;"></div> <a class="destroy" href="#" data-bind="click: $root.remove"></a>
<span class="todo-destroy" data-bind="click: $root.remove"></span>
</div>
<div class="edit">
<input class="todo-input" data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: stopEditing, event: { blur: stopEditing }"/>
</div>
</div> </div>
<input class="edit" type="text"
data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"/>
</li> </li>
</ul> </ul>
</div> </section>
<div id="todo-stats"> <footer data-bind="block: completedCount() || remainingCount()">
<span class="todo-count" data-bind="visible: remainingCount"> <a id="clear-completed" href="#" data-bind="inline: completedCount, click: removeCompleted">
<span class="number" data-bind="text: remainingCount"></span> Clear <span data-bind="text: completedCount"></span>
<span class="word" data-bind="text: getLabel(remainingCount)"></span> left. completed <span data-bind="text: getLabel(completedCount)"></span>
</span>
<span class="todo-clear" data-bind="visible: completedCount">
<a href="#" data-bind="click: removeCompleted">
Clear <span class="number-done" data-bind="text: completedCount"></span>
completed <span class="word-done" data-bind="text: getLabel(completedCount)"></span>
</a> </a>
</span>
</div> <div id="todo-count">
</div> <span data-bind="text: remainingCount"></span>
<span data-bind="text: getLabel(remainingCount)" style="font-weight: normal"></span> left.
</div> </div>
<ul id="instructions"> </footer>
<li data-bind="visible: todos().length">Double-click to edit a todo.</li> </div>
</ul> <div id="instructions" data-bind="visible: todos().length">
<div id="credits"> Double-click to edit a todo.
</div>
<div id="credits">
Created by Created by
<br /> <br/>
<a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a> <a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a>
<br /> <br/>
Modified to use knockout.js by Modified to use knockout.js by
<br /> <br/>
<a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a> <a href="https://github.com/ashish01/knockoutjs-todos">Ashish Sharma</a>
<br/> <br/>
Updated to use knockout.js 2.0 by Updated to use knockout.js 2.0 by
<br/> <br/>
<a href="http://knockmeout.net">Ryan Niemeyer</a> <a href="http://knockmeout.net">Ryan Niemeyer</a>
<br /> <br/>
Patches/fixes for cross-browser compat: Patches/fixes for cross-browser compat:
<br /> <br/>
<a href="http://twitter.com/addyosmani">Addy Osmani</a> <a href="http://twitter.com/addyosmani">Addy Osmani</a>
</div> </div>
<!-- Knockout has no direct dependencies -->
<!-- Knockout has no direct dependencies --> <script src="js/libs/knockout-2.0.0.js" type="text/javascript"></script>
<script src="js/knockout-2.0.0.js" type="text/javascript"></script> <!-- needed to support JSON.stringify in older browsers (for local storage) -->
<!-- needed to support JSON.stringify in older browsers (for local storage) --> <script src="js/libs/json2.js" type="text/javascript"></script>
<script src="js/json2.js" type="text/javascript"></script> <!-- used for local storage -->
<!-- used for local storage --> <script src="js/libs/amplify.store.min.js" type="text/javascript"></script>
<script src="js/amplify.store.min.js" type="text/javascript"></script> <!-- our app code -->
<!-- our app code --> <script src="js/app.js" type="text/javascript"></script>
<script src="js/todos.js" type="text/javascript"></script>
</body> </body>
</html> </html>
\ No newline at end of file
(function() { (function () {
//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 === 13) {
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
...@@ -21,6 +28,38 @@ ...@@ -21,6 +28,38 @@
} }
}; };
//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 () {
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";
}
};
//represent a single todo item //represent a single todo item
var Todo = function (content, done) { var Todo = function (content, done) {
...@@ -29,18 +68,11 @@ ...@@ -29,18 +68,11 @@
this.editing = ko.observable(false); this.editing = ko.observable(false);
}; };
//can place methods on prototype, as there can be many todos
ko.utils.extend(Todo.prototype, {
edit: function() { this.editing(true); },
stopEditing: function() { this.editing(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.content, todo.done);
})); }));
...@@ -49,9 +81,12 @@ ...@@ -49,9 +81,12 @@
//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 (data, event) {
var newTodo = new Todo(self.current()); var newTodo, current = self.current().trim();
if (current) {
newTodo = new Todo(current);
self.todos.push(newTodo); self.todos.push(newTodo);
self.current(""); self.current("");
}
}; };
//remove a single todo //remove a single todo
...@@ -61,14 +96,28 @@ ...@@ -61,14 +96,28 @@
//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.done();
}); });
}; };
//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);
}
};
//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(), function(todo) { return ko.utils.arrayFilter(self.todos(),
function (todo) {
return todo.done(); return todo.done();
}).length; }).length;
}); });
...@@ -81,51 +130,32 @@ ...@@ -81,51 +130,32 @@
//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.done(newValue);
}); });
} }
}); });
//track whether the tooltip should be shown
self.showTooltip = ko.observable(false);
self.showTooltip.setTrue = function() { self.showTooltip(true); }; //avoid an anonymous function each time
//watch the current value
self.current.subscribe(function(newValue) {
//if the value was just updated, then the tooltip should not be shown
self.showTooltip(false);
//clear the current timer, as it is actively being updated
if (self.showTooltip.timer) {
clearTimeout(self.showTooltip.timer);
}
//if there is a value and then show the tooltip after 1 second
if (newValue) {
self.showTooltip.timer = setTimeout(self.showTooltip.setTrue, 1000);
}
});
//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(function() { ko.computed(
function () {
//get a clean copy of the todos, 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
var todos = ko.toJS(self.todos); var todos = ko.toJS(self.todos);
//store to local storage //store to local storage
amplify.store("todos-knockout", todos); amplify.store("todos-knockout", todos);
}).extend({ throttle: 1000 }); //save at most once per second }).extend({ throttle:500 }); //save at most once per second
}; };
//check local storage for todos //check local storage for todos
......
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