Commit a8ebd557 authored by Sindre Sorhus's avatar Sindre Sorhus

Merge pull request #728 from bitovi/canjs-2.0

CanJS 2.0 architecture example
parents 20d346f5 1bc79b0a
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
"name": "todomvc-canjs", "name": "todomvc-canjs",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"jquery": "~1.9.1", "jquery": "~2.0.0",
"canjs": "~1.1.5", "canjs": "~2.0.0",
"canjs-localstorage": "~0.1.0", "canjs-localstorage": "~0.2.0",
"todomvc-common": "~0.1.6" "todomvc-common": "~0.1.6"
} }
} }
can.Model('can.Model.LocalStorage', { (function(define) {
// Implement local storage handling if (typeof define == "undefined") {
localStore : function (cb) { define = function(deps, fn) {
var name = this.name, can.Model.LocalStorage = fn(can.Model);
data = JSON.parse(window.localStorage[name] || (window.localStorage[name] = '[]')), }
res = cb.call(this, data); }
if (res !== false) {
can.each(data, function (todo) {
delete todo.editing;
});
window.localStorage[name] = JSON.stringify(data);
}
},
findAll : function (params) { define(['can/model'], function(Model) {
var def = new can.Deferred(); return Model.extend({
this.localStore(function (todos) { // Implement local storage handling
var instances = [], localStore: function(cb) {
self = this; var name = this.name,
can.each(todos, function (todo) { data = JSON.parse(window.localStorage[name] || (window.localStorage[name] = '[]')),
instances.push(new self(todo)); res = cb.call(this, data);
}); if (res !== false) {
def.resolve({data : instances}); can.each(data, function(todo) {
}); delete todo.editing;
return def; });
}, window.localStorage[name] = JSON.stringify(data);
}
},
destroy : function (id) { findAll: function(params) {
var def = new can.Deferred(); var def = new can.Deferred();
this.localStore(function (todos) { this.localStore(function(todos) {
for (var i = 0; i < todos.length; i++) { var instances = [],
if (todos[i].id === id) { self = this;
todos.splice(i, 1); can.each(todos, function(todo) {
break; instances.push(new self(todo));
} });
} def.resolve({data: instances});
def.resolve({}); });
}); return def;
return def; },
},
create : function (attrs) { destroy: function(id) {
var def = new can.Deferred(); var def = new can.Deferred();
this.localStore(function (todos) { this.localStore(function(todos) {
attrs.id = attrs.id || parseInt(100000 * Math.random(), 10); for (var i = 0; i < todos.length; i++) {
todos.push(attrs); if (todos[i].id === id) {
}); todos.splice(i, 1);
def.resolve({id : attrs.id}); break;
return def; }
}, }
def.resolve({});
});
return def;
},
update : function (id, attrs) { create: function(attrs) {
var def = new can.Deferred(), todo; var def = new can.Deferred();
this.localStore(function (todos) { this.localStore(function(todos) {
for (var i = 0; i < todos.length; i++) { attrs.id = attrs.id || parseInt(100000 * Math.random(), 10);
if (todos[i].id === id) { todos.push(attrs);
todo = todos[i]; });
break; def.resolve({id: attrs.id});
} return def;
} },
can.extend(todo, attrs);
}); update: function(id, attrs) {
def.resolve({}); var def = new can.Deferred(), todo;
return def; this.localStore(function(todos) {
} for (var i = 0; i < todos.length; i++) {
}, {}); if (todos[i].id === id) {
\ No newline at end of file todo = todos[i];
break;
}
}
can.extend(todo, attrs);
});
def.resolve({});
return def;
}
}, {});
});
})(window.define);
...@@ -14,13 +14,51 @@ ...@@ -14,13 +14,51 @@
<p>Written by <a href="http://bitovi.com">Bitovi</a></p> <p>Written by <a href="http://bitovi.com">Bitovi</a></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>
<script src="bower_components/jquery/jquery.js"></script> <script type="text/mustache" id="app-template">
<todo-app>
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus="" can-enter="createTodo">
</header>
<section id="main" class="{{^if todos.length}}hidden{{/if}}">
<input id="toggle-all" type="checkbox" {{#if todos.allComplete}}checked="checked"{{/if}} can-click="toggleAll">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
{{#each displayList}}
<li class="todo{{#if complete}} completed{{/if}}{{#if editing}} editing{{/if}}">
<div class="view">
<input class="toggle" type="checkbox" can-value="complete">
<label can-dblclick="edit">{{text}}</label>
<button class="destroy" can-click="destroy"></button>
</div>
<input class="edit" type="text" value="{{text}}" can-blur="updateTodo"
can-keyup="cancelEditing" can-enter="updateTodo">
</li>
{{/each}}
</ul>
</section>
<footer id="footer" class="{{^if todos.length}}hidden{{/if}}">
<span id="todo-count">
<strong>{{todos.remaining}}</strong> {{plural "item" todos.remaining}} left
</span>
<ul id="filters">
<li>{{{link "All" undefined}}}</li>
<li>{{{link "Active" "active"}}}</li>
<li>{{{link "Completed" "completed"}}}</li>
</ul>
<button id="clear-completed" class="{{^if todos.completed.length}}hidden{{/if}}" can-click="clearCompleted">
Clear completed ({{todos.completed.length}})
</button>
</footer>
</todo-app>
</script>
<script src="bower_components/todomvc-common/base.js"></script> <script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/canjs/can.jquery.js"></script> <script src="bower_components/canjs/can.jquery.js"></script>
<script src="bower_components/canjs-localstorage/can.localstorage.js"></script> <script src="bower_components/canjs-localstorage/can.localstorage.js"></script>
<script src="js/lib/can.mustache.min.js"></script>
<script src="js/models/todo.js"></script> <script src="js/models/todo.js"></script>
<script src="js/todos/todos.js"></script> <script src="js/components/todo-app.js"></script>
<script src="js/app.js"></script> <script src="js/app.js"></script>
</body> </body>
</html> </html>
/*global $ Todos Models Mustache can*/ /* global $, can */
(function () { (function () {
'use strict'; 'use strict';
$(function () { $(function () {
// Set up a route that maps to the `filter` attribute // Set up a route that maps to the `filter` attribute
can.route(':filter'); can.route(':filter');
// Delay routing until we initialized everything
can.route.ready(false);
// View helper for pluralizing strings
Mustache.registerHelper('plural', function (str, count) {
return str + (count !== 1 ? 's' : '');
});
// Initialize the app // Render #app-template
Models.Todo.findAll({}, function (todos) { $('#todoapp').html(can.view('app-template', {}));
new Todos('#todoapp', {
todos: todos,
state: can.route,
view : 'views/todos.mustache'
});
});
// Now we can start routing // Start the router
can.route.ready(true); can.route.ready();
}); });
})(); })();
/* global can */
(function (namespace) {
'use strict';
var ESCAPE_KEY = 27;
can.Component.extend({
// Create this component on a tag like `<todo-app>`.
tag: 'todo-app',
scope: {
// Store the Todo model in the scope
Todo: namespace.Models.Todo,
// A list of all Todos retrieved from LocalStorage
todos: new namespace.Models.Todo.List({}),
// Edit a Todo
edit: function (todo, el) {
todo.attr('editing', true);
el.parents('.todo').find('.edit').focus();
},
cancelEditing: function (todo, el, ev) {
if (ev.which === ESCAPE_KEY) {
el.val(todo.attr('text'));
todo.attr('editing', false);
}
},
// Returns a list of Todos filtered based on the route
displayList: function () {
var filter = can.route.attr('filter');
return this.todos.filter(function (todo) {
if (filter === 'completed') {
return todo.attr('complete');
}
if (filter === 'active') {
return !todo.attr('complete');
}
return true;
});
},
updateTodo: function (todo, el) {
var value = can.trim(el.val());
if (value === '') {
todo.destroy();
} else {
todo.attr({
editing: false,
text: value
});
}
},
createTodo: function (context, el) {
var value = can.trim(el.val());
var TodoModel = this.Todo;
if (value !== '') {
new TodoModel({
text: value,
complete: false
}).save();
can.route.removeAttr('filter');
el.val('');
}
},
toggleAll: function (scope, el) {
var toggle = el.prop('checked');
this.attr('todos').each(function (todo) {
todo.attr('complete', toggle);
});
},
clearCompleted: function () {
this.attr('todos').completed().forEach(function (todo) {
todo.destroy();
});
}
},
events: {
// When a new Todo has been created, add it to the todo list
'{Todo} created': function (Construct, ev, todo) {
this.scope.attr('todos').push(todo);
}
},
helpers: {
link: function (name, filter) {
var data = filter ? { filter: filter } : {};
return can.route.link(name, data, {
className: can.route.attr('filter') === filter ? 'selected' : ''
});
},
plural: function (singular, num) {
return num() === 1 ? singular : singular + 's';
}
}
});
})(this);
(function(i,m,l){i.view.ext=".mustache";var n=function(a){return i.isFunction(a.attr)&&a.constructor&&!!a.constructor.canMakeObserve},o=function(a){return a&&a.splice&&"number"==typeof a.length},h=function(a){if(this.constructor!=h){var c=new h(a);return function(a){return c.render(a)}}"function"==typeof a?this.template={fn:a}:(i.extend(this,a),this.template=this.scanner.scan(this.text,this.name))};i.Mustache=m.Mustache=h;h.prototype.render=function(a){a=a||{};return this.template.fn.call(a,a,{_data:a})};
i.extend(h.prototype,{scanner:new i.view.Scanner({text:{start:"var ___c0nt3xt = []; ___c0nt3xt.___st4ck = true;var ___st4ck = function(context, self) {var s;if (arguments.length == 1 && context) {s = !context.___st4ck ? [context] : context;} else {s = context && context.___st4ck ? context.concat([self]) : ___st4ck(context).concat([self]);}return (s.___st4ck = true) && s;};"},tokens:[["returnLeft","{{{","{{[{&]"],["commentFull","{{!}}","^[\\s\\t]*{{!.+?}}\\n"],["commentLeft","{{!","(\\n[\\s\\t]*{{!|{{!)"],
["escapeFull","{{}}","(^[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}$)",function(a){return{before:/^\n.+?\n$/.test(a)?"\n":"",content:a.match(/\{\{(.+?)\}\}/)[1]||""}}],["escapeLeft","{{"],["returnRight","}}}"],["right","}}"]],helpers:[{name:/^>[\s|\w]\w*/,fn:function(a){return"can.view.render('"+i.trim(a.replace(/^>\s?/,""))+"', ___st4ck(___c0nt3xt,this).pop())"}},{name:/^\s?data\s/,fn:function(a){return"can.proxy(function(__){can.data(can.$(__),'"+a.replace(/(^\s?data\s)|(["'])/g,
"")+"', this.pop()); }, ___st4ck(___c0nt3xt,this))"}},{name:/^.*$/,fn:function(a,c){var b=!1,d=[],a=i.trim(a);if(a.length&&(b=a.match(/^([#^/]|else$)/))){b=b[0];switch(b){case "#":case "^":d.push(c.insert+"can.view.txt(0,'"+c.tagName+"',"+c.status+",this,function(){ return ");break;case "/":return{raw:'return ___v1ew.join("");}}])}));'}}a=a.substring(1)}if("else"!=b){var j=[],f=0,g=!1,h,e;(i.trim(a)+" ").replace(/((([^\s]+?=)?('.*?'|".*?"))|.*?)\s/g,function(a,b){j.push(b)});for(d.push("can.Mustache.txt(___st4ck(___c0nt3xt,this),"+
(b?'"'+b+'"':"null")+",");h=j[f];f++)f&&d.push(","),f&&(e=h.match(/^(('.*?'|".*?"|[0-9.]+|true|false)|((.+?)=(('.*?'|".*?"|[0-9.]+|true|false)|(.+))))$/))?e[2]?d.push(e[0]):(g||(g=!0,d.push("{___h4sh:{")),d.push(e[4],":",e[6]?e[6]:'can.Mustache.get("'+e[5].replace(/"/g,'\\"')+'",___st4ck(___c0nt3xt,this))'),f==j.length-1&&d.push("}}")):d.push('can.Mustache.get("'+h.replace(/"/g,'\\"')+'",___st4ck(___c0nt3xt,this)'+(0==f&&1<j.length?",true":"")+")")}b&&"else"!=b&&d.push(",[{_:function(){");switch(b){case "#":d.push('return ___v1ew.join("");}},{fn:function(___c0nt3xt){var ___v1ew = [];');
break;case "else":case "^":d.push('return ___v1ew.join("");}},{inverse:function(___c0nt3xt){var ___v1ew = [];');break;default:d.push(");")}d=d.join("");return b?{raw:d}:d}}]})});for(var m=i.view.Scanner.prototype.helpers,p=0;p<m.length;p++)h.prototype.scanner.helpers.unshift(m[p]);h.registerHelper=function(a,c){this._helpers.push({name:a,fn:c})};h.getHelper=function(a){for(var c=0,b;b=this._helpers[c];c++)if(b.name==a)return b;return null};h.txt=function(a,c,b){var d=Array.prototype.slice.call(arguments,
3),j=i.extend.apply(i,[{fn:function(){},inverse:function(){}}].concat(c?d.pop():[])),f=d.length?d:[b],g=!0,k=[],e;if(c)for(e=0;e<f.length;e++)g=o(f[e])?"#"==c?g&&!!f[e].length:"^"==c?g&&!f[e].length:g:"#"==c?g&&!!f[e]:"^"==c?g&&!f[e]:g;if(f=h.getHelper(b)||i.isFunction(b)&&{fn:b}){a=a.___st4ck&&a[a.length-1]||a;j={fn:i.proxy(j.fn,a),inverse:i.proxy(j.inverse,a)};if((g=d[d.length-1])&&g.___h4sh)j.hash=d.pop().___h4sh;d.push(j);return f.fn.apply(a,d)||""}if(g)switch(c){case "#":if(o(b)){for(e=0;e<b.length;e++)k.push(j.fn.call(b[e]||
{},a)||"");return k.join("")}return j.fn.call(b||{},a)||"";case "^":return j.inverse.call(b||{},a)||"";default:return""+(b!==l?b:"")}return""};h.get=function(a,c,b){var d=a.split("."),j=c[c.length-1],f=c[c.length-2],g,k,e;if(/^\.|this$/.test(a)){if(/^object|undefined$/.test(typeof f)){for(;b=c.pop();)if("undefined"!==typeof b)return b;return""}return f||""}if(!b)for(f=c.length-1;0<=f;f--){b=c[f];if(b!==l)for(e=0;e<d.length;e++)if("undefined"!=typeof b[d[e]])g=b,b=b[k=d[e]];else{n(b)?(g=b,k=d[e]):
g=b=l;break}if(b!==l){if(i.isFunction(g[k]))return g[k]();if(n(g))return g.attr(k);n(b)&&o(b)&&b.attr("length");return b}}return j!==l&&i.isFunction(j[a])?j[a]:h.getHelper(a)?a:""};h._helpers=[{name:"if",fn:function(a,c){return a?c.fn(this):c.inverse(this)}},{name:"unless",fn:function(a,c){if(!a)return c.fn(this)}},{name:"each",fn:function(a,c){if(a&&a.length){for(var b=[],d=0;d<a.length;d++)b.push(c.fn(a[d]));return b.join("")}}},{name:"with",fn:function(a,c){if(a)return c.fn(a)}}];i.view.register({suffix:"mustache",
contentType:"x-mustache-template",script:function(a,c){return"can.Mustache(function(_CONTEXT,_VIEW) { "+(new h({text:c,name:a})).template.out+" })"},renderer:function(a,c){return h({text:c,name:a})}})})(can,this);
/*global can */ /*global can */
(function (namespace, undefined) { (function (namespace) {
'use strict'; 'use strict';
// Basic Todo entry model // Basic Todo entry model
// { text: 'todo', complete: false } var Todo = can.Model.LocalStorage.extend({
var Todo = can.Model.LocalStorage({
storageName: 'todos-canjs' storageName: 'todos-canjs'
}, { }, {
// Returns if this instance matches a given filter init: function () {
// (currently `active` and `complete`) // Autosave when changing the text or completing the todo
matches : function () { this.on('change', function (ev, prop) {
var filter = can.route.attr('filter'); if (prop === 'text' || prop === 'complete') {
return !filter || (filter === 'active' && !this.attr('complete')) || ev.target.save();
(filter === 'completed' && this.attr('complete')); }
});
} }
}); });
// List for Todos // List for Todos
Todo.List = can.Model.List({ Todo.List = Todo.List.extend({
completed: function () { filter: function (check) {
var completed = 0; var list = [];
this.each(function (todo) { this.each(function (todo) {
completed += todo.attr('complete') ? 1 : 0; if (check(todo)) {
list.push(todo);
}
}); });
return completed; return list;
},
completed: function () {
return this.filter(function (todo) {
return todo.attr('complete');
});
}, },
remaining: function () { remaining: function () {
return this.attr('length') - this.completed(); return this.attr('length') - this.completed().length;
}, },
allComplete: function () { allComplete: function () {
return this.attr('length') === this.completed(); return this.attr('length') === this.completed().length;
} }
}); });
......
/*global can Models*/
(function (namespace, undefined) {
'use strict';
var ENTER_KEY = 13;
var Todos = can.Control({
// Default options
defaults : {
view : 'views/todos.ejs'
}
}, {
// Initialize the Todos list
init: function () {
// Render the Todos
this.element.append(can.view(this.options.view, this.options));
},
// Listen for when a new Todo has been entered
'#new-todo keyup': function (el, e) {
var value = can.trim(el.val());
if (e.keyCode === ENTER_KEY && value !== '') {
new Models.Todo({
text : value,
complete : false
}).save(function () {
el.val('');
});
}
},
// Handle a newly created Todo
'{Models.Todo} created': function (list, e, item) {
this.options.todos.push(item);
// Reset the filter so that you always see your new todo
this.options.state.attr('filter', '');
},
// Listener for when the route changes
'{state} change' : function () {
// Remove the `selected` class from the old link and add it to the link for the current location hash
this.element.find('#filters').find('a').removeClass('selected')
.end().find('[href="' + window.location.hash + '"]').addClass('selected');
},
// Listen for editing a Todo
'.todo dblclick': function (el) {
el.data('todo').attr('editing', true).save(function () {
el.children('.edit').focus();
});
},
// Update a todo
updateTodo: function (el) {
var value = can.trim(el.val()),
todo = el.closest('.todo').data('todo');
// If we don't have a todo we don't need to do anything
if (!todo) {
return;
}
if (value === '') {
todo.destroy();
} else {
todo.attr({
editing : false,
text : value
}).save();
}
},
// Listen for an edited Todo
'.todo .edit keyup': function (el, e) {
if (e.keyCode === ENTER_KEY) {
this.updateTodo(el);
}
},
'.todo .edit focusout' : 'updateTodo',
// Listen for the toggled completion of a Todo
'.todo .toggle click': function (el) {
el.closest('.todo').data('todo')
.attr('complete', el.is(':checked'))
.save();
},
// Listen for a removed Todo
'.todo .destroy click': function (el) {
el.closest('.todo').data('todo').destroy();
},
// Listen for toggle all completed Todos
'#toggle-all click': function (el) {
var toggle = el.prop('checked');
can.each(this.options.todos, function (todo) {
todo.attr('complete', toggle).save();
});
},
// Listen for removing all completed Todos
'#clear-completed click': function () {
for (var i = this.options.todos.length - 1, todo; i > -1 && (todo = this.options.todos[i]); i--) {
if (todo.attr('complete')) {
todo.destroy();
}
}
}
});
namespace.Todos = Todos;
})(this);
...@@ -4,34 +4,35 @@ ...@@ -4,34 +4,35 @@
> _[CanJS - canjs.com](http://canjs.com)_ > _[CanJS - canjs.com](http://canjs.com)_
## Learning CanJS ## Learning CanJS
The [CanJS website](http://canjs.com) is a great resource for getting started. The [CanJS website](http://canjs.com) is a great resource for getting started.
Here are some links you may find helpful: Here are some links you may find helpful:
* [Documentation](http://donejs.com/docs.html#!canjs) * [CanJS guides documentation](http://canjs.com/guides/index.html)
* [Why CanJS](http://canjs.com/#why_canjs) * [API documentation](http://canjs.com/docs/index.html)
* [Applications built with CanJS](http://canjs.com/#examples)
* [Blog](http://bitovi.com/blog/tag/canjs) * [Blog](http://bitovi.com/blog/tag/canjs)
* [Getting started video](http://www.youtube.com/watch?v=GdT4Oq6ZQ68) * [Getting started video](http://www.youtube.com/watch?v=GdT4Oq6ZQ68)
Articles and guides from the community:
* [Diving into CanJS](http://net.tutsplus.com/tutorials/javascript-ajax/diving-into-canjs)
Get help from other CanJS users: Get help from other CanJS users:
* [CanJS on StackOverflow](http://stackoverflow.com/questions/tagged/canjs) * [CanJS on StackOverflow](http://stackoverflow.com/questions/tagged/canjs)
* [CanJS Forums](http://forum.javascriptmvc.com/#Forum/canjs) * [CanJS Forums](http://forum.javascriptmvc.com/#Forum/canjs)
* [CanJS on Twitter](http://twitter.com/canjs) * [CanJS on Twitter](http://twitter.com/canjs)
* [#canjs](http://webchat.freenode.net/?channels=canjs) IRC channel on Freenode
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ _If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
## Implementation ## Implementation
The CanJS TodoMVC example uses [can.Component](http://canjs.com/guides/Components.html) introduced in CanJS 2.0.
can.Component supports declarative view bindings using Mustache/Handlebars as the template syntax.
Version 2 is mostly backwards compatible with previous 1.1.x version. For alternative architecture examples have a look at
the [TodoMVC 1.2.0 CanJS example](https://github.com/tastejs/todomvc/tree/1.2.0/architecture-examples/canjs).
### CanJS and JavaScriptMVC ### CanJS and JavaScriptMVC
CanJS is the extracted, more modern and more library-like MVC parts of [JavaScriptMVC](http://javascriptmvc.com), formerly known as jQueryMX. CanJS is the extracted, more modern and more library-like MVC parts of [JavaScriptMVC](http://javascriptmvc.com), formerly known as jQueryMX.
...@@ -45,20 +46,3 @@ Additionally, JavaScriptMVC contains: ...@@ -45,20 +46,3 @@ Additionally, JavaScriptMVC contains:
- [StealJS](http://javascriptmvc.com/docs.html#!stealjs) - A JavaScript package manager - [StealJS](http://javascriptmvc.com/docs.html#!stealjs) - A JavaScript package manager
- [DocumentJS](http://javascriptmvc.com/docs.html#!DocumentJS) - A documentation engine - [DocumentJS](http://javascriptmvc.com/docs.html#!DocumentJS) - A documentation engine
- [FuncUnit](http://funcunit.com) - jQuery style functional unit testing - [FuncUnit](http://funcunit.com) - jQuery style functional unit testing
### View engines
CanJS supports both live binding [EJS](http://canjs.us/#can_ejs) and [Mustache/Handlebars](http://canjs.us/#can_mustache)
templates. By default the Mustache view will be used but an EJS example is available as well.
You can easily change it by modifying the `view` option in the `js/app.js` file:
```js
Models.Todo.findAll({}, function (todos) {
new Todos('#todoapp', {
todos: todos,
state: can.route,
view: 'views/todos.ejs'
});
});
```
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
</header>
<section id="main" class="<%= todos.attr("length") === 0 ? "hidden" : "" %>">
<input id="toggle-all" type="checkbox" <%= todos.allComplete() ? "checked" : "" %>>
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<% todos.each(function( todo ) { %>
<li class="todo
<%= todo.matches(state.attr('filter')) ? '' : 'hidden' %>
<%= todo.attr('complete') ? 'completed' : '' %>
<%= todo.attr('editing') ? 'editing' : '' %>"
<%= (el)-> el.data('todo', todo) %>>
<div class="view">
<input class="toggle" type="checkbox" <%= todo.attr('complete') ? 'checked' : '' %>>
<label><%= todo.attr('text') %></label>
<button class="destroy"></button>
</div>
<input class="edit" value="<%= todo.attr('text') %>">
</li>
<% }) %>
</ul>
</section>
<footer id="footer" class="<%= todos.attr('length') === 0 ? 'hidden' : '' %>">
<span id="todo-count">
<strong><%= todos.remaining() %></strong>
item<%= todos.remaining() == 1 ? "" : "s" %> left
</span>
<ul id="filters">
<li><a class="selected" href="#!">All</a></li>
<li><a href="#!active">Active</a></li>
<li><a href="#!completed">Completed</a></li>
</ul>
<button id="clear-completed" class="<%= todos.completed() === 0 ? 'hidden' : '' %>">
Clear completed (<%= todos.completed() %>)
</button>
</footer>
<header id="header">
<h1>todos</h1>
<input id="new-todo" {{ (el) -> el.val('').focus() }} placeholder="What needs to be done?" autofocus="">
</header>
<section id="main" class="{{^todos}}hidden{{/todos}}">
<input id="toggle-all" type="checkbox" {{#todos.allComplete}}checked="checked"{{/todos.allComplete}}>
<label for="toggle-all" >Mark all as complete</label>
<ul id="todo-list">
{{#todos}}
<li class="todo {{^matches}}hidden{{/matches}} {{#complete}}completed{{/complete}} {{#editing}}editing{{/editing}}" {{data 'todo'}}>
<div class="view">
<input class="toggle" type="checkbox" {{#complete}}checked="checked"{{/complete}}>
<label>{{text}}</label>
<button class="destroy"></button>
</div>
<input class="edit" type="text" value="{{text}}">
</li>
{{/todos}}
</ul>
</section>
<footer id="footer" class="{{^todos}}hidden{{/todos}}">
<span id="todo-count">
<strong>{{todos.remaining}}</strong> {{plural "item" todos.remaining}} left
</span>
<ul id="filters">
<li>
<a class="selected" href="#!">All</a>
</li>
<li>
<a href="#!active">Active</a>
</li>
<li>
<a href="#!completed">Completed</a>
</li>
</ul>
<button id="clear-completed" class="{{^todos.completed}}hidden{{/todos.completed}}">
Clear completed ({{todos.completed}})
</button>
</footer>
...@@ -311,10 +311,10 @@ ...@@ -311,10 +311,10 @@
"heading": "Official Resources", "heading": "Official Resources",
"links": [{ "links": [{
"name": "Documentation", "name": "Documentation",
"url": "http://donejs.com/docs.html#!canjs" "url": "http://canjs.com/docs/index.html"
}, { }, {
"name": "Why CanJS", "name": "Getting started",
"url": "http://canjs.com/#why_canjs" "url": "http://canjs.com/guides/Tutorial.html"
}, { }, {
"name": "Applications built with CanJS", "name": "Applications built with CanJS",
"url": "http://canjs.com/#examples" "url": "http://canjs.com/#examples"
...@@ -342,6 +342,9 @@ ...@@ -342,6 +342,9 @@
}, { }, {
"name": "CanJS on Twitter", "name": "CanJS on Twitter",
"url": "http://twitter.com/canjs" "url": "http://twitter.com/canjs"
}, {
"name": "#canjs IRC",
"url": "http://webchat.freenode.net/?channels=canjs"
}] }]
}] }]
}, },
......
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