Navigation Menu

Skip to content

Commit

Permalink
Merge pull request tastejs#210 from cujojs/cujo-pull
Browse files Browse the repository at this point in the history
Cujo todo implementation
  • Loading branch information
addyosmani committed Jul 6, 2012
2 parents 21c9a3f + 6c7fbb9 commit 66dd8d2
Show file tree
Hide file tree
Showing 499 changed files with 78,522 additions and 0 deletions.
36 changes: 36 additions & 0 deletions labs/architecture-examples/cujo/README.md
@@ -0,0 +1,36 @@
# Cujojs TodoMVC

[Cujojs](http://cujojs.com) is an *architectural framework* for building highly modular, scalable, maintainable applications in Javascript. It provides architectural plumbing, such as modules (AMD and CommonJS), declarative application composition, declarative connections, and aspect oriented programming.

It is not a typical MV\* framework, although it does provide MV\* building blocks, such as templating and data binding.

## Highlights:

Some things we feel are interesting about cujojs's TodoMVC as compared to other implementations:

* Application composition is separate from application logic
* Code is *highly* modular and organized into components, each consisting of
one or more of the following:
* Composition specification (a.k.a. "wire spec")
* JavaScript controller module
* helper modules
* localization bundle (strings.js)
* HTML template (template.html)
* CSS file, typically unthemed (structure.css)
* HTML templates are clean and simple, editable by mere mortals.
* OOCSS is used for visual state changes
* zero direct style manipulation
* drastically simplifies HTML templates
* JavaScript environment is shimmed, rather than abstracted
* code to modern standards, not to abstraction layers
* All strings are easily internationalized
* Application code has no explicit dependencies on:
* DOMReady - the application lifecycle, even DOMReady, is managed
transparently. Things that can happen before DOMReady, do.
Things that can't, don't.
* DOM Query engine
* DOM Event library

## Credit

TodoMVC Template Created by [Sindre Sorhus](http://sindresorhus.com)
7 changes: 7 additions & 0 deletions labs/architecture-examples/cujo/TODO.md
@@ -0,0 +1,7 @@
TODO before release
---

* implement filters (using routing or another method)
* build javascript using cram.js
* use curl's preloads feature rather than .next() in run.js
* use a theme.css file
130 changes: 130 additions & 0 deletions labs/architecture-examples/cujo/app/controller.js
@@ -0,0 +1,130 @@
define(function () {
"use strict";

var updateRemainingCount, textProp;

updateRemainingCount = normalizeTextProp;

return {
/**
* Create a new todo
* @injected
* @param todo {Object} data used to create new todo
* @param todo.text {String} text of the todo
*/
createTodo: function(todo) {},

/**
* Remove an existing todo
* @injected
* @param todo {Object} existing todo, or object with same identifier, to remove
*/
removeTodo: function(todo) {},

/**
* Update an existing todo
* @injected
* @param todo {Object} updated todo
*/
updateTodo: function(todo) {},

/**
* Start inline editing a todo
* @param node {Node} Dom node of the todo
*/
beginEditTodo: function(node) {
this.querySelector('.edit', node).select();
},

/**
* Finish editing a todo
* @param todo {Object} todo to finish editing and save changes
*/
endEditTodo: function(todo) {
// As per application spec, todos edited to have empty
// text should be removed.
if (/\S/.test(todo.text)) {
this.updateTodo(todo);
} else {
this.removeTodo(todo);
}
},

/**
* Remove all completed todos
*/
removeCompleted: function() {
var todos = this.todos;

todos.forEach(function(todo) {
if(todo.complete) todos.remove(todo);
});
},

/**
* Check/uncheck all todos
*/
toggleAll: function() {
var todos, complete;

todos = this.todos;
complete = this.masterCheckbox.checked;

todos.forEach(function(todo) {
todo.complete = complete;
todos.update(todo);
});
},

/**
* Update the remaining and completed counts, and update
* the check/uncheck all checkbox if all todos have become
* checked or unchecked.
*/
updateCount: function() {
var total, checked;

total = checked = 0;

this.todos.forEach(function(todo) {
total++;
if(todo.complete) checked++;
});

this.masterCheckbox.checked = total > 0 && checked === total;

this.updateTotalCount(total);
this.updateCompletedCount(checked);

this.updateRemainingCount(total - checked);
},

updateTotalCount: function(total) {},

updateCompletedCount: function(completed) {
this.countNode.innerHTML = completed;
},

updateRemainingCount: function (remaining) {
updateRemainingCount(this.remainingNodes, remaining);
}

};

/**
* Self-optimizing function to set the text of a node
*/
function normalizeTextProp () {
// sniff for proper textContent property
textProp = 'textContent' in document.documentElement ? 'textContent' : 'innerText';
// resume normally
(updateRemainingCount = setTextProp).apply(this, arguments);
}

function setTextProp (nodes, value) {
for (var i = 0; i < nodes.length; i++) {
nodes[i][textProp] = '' + value;
}
}

});
14 changes: 14 additions & 0 deletions labs/architecture-examples/cujo/app/controls/strings.js
@@ -0,0 +1,14 @@
define({
// TODO: Deal with singular vs. plural
itemsLeft: {
zero: '\\o/ no todo items!!!',
one: 'only one item left!',
many: 'ugh, <strong></strong> items left'
},
filter: {
all: 'All',
active: 'Active',
completed: 'Completed'
},
clearCompleted: 'Clear completed'
});
40 changes: 40 additions & 0 deletions labs/architecture-examples/cujo/app/controls/structure.css
@@ -0,0 +1,40 @@
.remaining-count-zero,
.remaining-count-one {
display: none;
}

.remaining-zero .remaining-count-zero {
display: inline;
}

.remaining-one .remaining-count-one {
display: inline;
}

.remaining-zero .remaining-count-many,
.remaining-one .remaining-count-many {
display: none;
}

#clear-completed {
opacity: 1;
/* TODO: this is theme/skin. Move to a theme file */
-webkit-transition: all .1s ease;
}
.completed-zero #clear-completed {
opacity: 0;
}

#footer {
display: none;
}

.todos-one #footer,
.todos-many #footer {
display: block;
}

/* TODO: Reinstate once we add routing */
#filters {
display: none;
}
21 changes: 21 additions & 0 deletions labs/architecture-examples/cujo/app/controls/template.html
@@ -0,0 +1,21 @@
<footer id="footer">
<!-- This should be `0 items left` by default -->
<span id="todo-count">
<span class="remaining-count-zero">${itemsLeft.zero}</span>
<span class="remaining-count-one">${itemsLeft.one}</span>
<span class="remaining-count-many">${itemsLeft.many}</span>
</span>
<!-- Remove this if you don't implement routing -->
<ul id="filters">
<li>
<a class="selected" href="#/">${filter.all}</a>
</li>
<li>
<a href="#/active">${filter.active}</a>
</li>
<li>
<a href="#/completed">${filter.completed}</a>
</li>
</ul>
<button id="clear-completed">${clearCompleted} (<span class="count">1</span>)</button>
</footer>
10 changes: 10 additions & 0 deletions labs/architecture-examples/cujo/app/create/cleanTodo.js
@@ -0,0 +1,10 @@
define(function() {

return function(todo) {
todo.text = todo.text && todo.text.trim() || '';
todo.complete = !!todo.complete;

return todo;
}

});
25 changes: 25 additions & 0 deletions labs/architecture-examples/cujo/app/create/generateMetadata.js
@@ -0,0 +1,25 @@
define(function() {

/**
* Since we're using a datastore (localStorage) that doesn't generate ids and such
* for us, this transform generates a GUID id and a dateCreated. It can be
* injected into a pipeline for creating new todos.
*/
return function generateMetadata(item) {
item.id = guidLike();
item.dateCreated = new Date().getTime();

return item;
};

// GUID-like generation, not actually a GUID, tho, from:
// http://stackoverflow.com/questions/7940616/what-makes-this-pseudo-guid-generator-better-than-math-random
function s4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
}

function guidLike() {
return (s4()+s4()+"-"+s4()+"-"+s4()+"-"+s4()+"-"+s4()+s4()+s4());
}

});
6 changes: 6 additions & 0 deletions labs/architecture-examples/cujo/app/create/strings.js
@@ -0,0 +1,6 @@
define({
title: 'todos',
todo: {
placeholder: 'What needs to be done?'
}
});
6 changes: 6 additions & 0 deletions labs/architecture-examples/cujo/app/create/template.html
@@ -0,0 +1,6 @@
<header id="header">
<h1>${title}</h1>
<form>
<input id="new-todo" name="text" placeholder="${todo.placeholder}" autofocus>
</form>
</header>
18 changes: 18 additions & 0 deletions labs/architecture-examples/cujo/app/create/validateTodo.js
@@ -0,0 +1,18 @@
define(function() {

/**
* Validate a todo
*/
return function validateTodo(todo) {
var valid, result;

// Must be a valid object, and have a text property that is non-empty
valid = todo && 'text' in todo && todo.text.trim();
result = { valid: !!valid };

if(!valid) result.errors = [{ property: 'text', message: 'missing' }];

return result;
}

});
6 changes: 6 additions & 0 deletions labs/architecture-examples/cujo/app/footer/strings.js
@@ -0,0 +1,6 @@
define({
edit: 'Double-click to edit a todo',
templateBy: 'Template by',
createdBy: 'Created by',
partOf: 'Part of'
});
7 changes: 7 additions & 0 deletions labs/architecture-examples/cujo/app/footer/template.html
@@ -0,0 +1,7 @@
<footer id="info">
<p>${edit}</p>
<p>${templateBy} <a href="http://github.com/sindresorhus">Sindre Sorhus</a></p>
<!-- Change this out with your name and url ↓ -->
<p>${createdBy} <a href="http://cujojs.com">cujojs</a></p>
<p>${partOf} <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
14 changes: 14 additions & 0 deletions labs/architecture-examples/cujo/app/list/setCompletedClass.js
@@ -0,0 +1,14 @@
define(function() {

/**
* Custom data linking handler for setting the "completed" class.
* The intent here is just to show that you can implement custom
* handlers for data/dom linking to do anything that isn't provided
* by default.
*/
return function(node, data, info) {
// Simple-minded implementation just to show custom data linking handler
node.className = data[info.prop] ? 'completed' : '';
};

});
3 changes: 3 additions & 0 deletions labs/architecture-examples/cujo/app/list/strings.js
@@ -0,0 +1,3 @@
define({
markAll: 'Mark all as complete'
});
6 changes: 6 additions & 0 deletions labs/architecture-examples/cujo/app/list/structure.css
@@ -0,0 +1,6 @@
#toggle-all {
display: none;
}
.todos-one #toggle-all, .todos-many #toggle-all {
display: block;
}
16 changes: 16 additions & 0 deletions labs/architecture-examples/cujo/app/list/template.html
@@ -0,0 +1,16 @@
<section id="main">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">${markAll}</label>
<ul id="todo-list">
<!-- These are here just to show the structure of the list items -->
<!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
<li>
<div class="view">
<input class="toggle" type="checkbox">
<label></label>
<button class="destroy"></button>
</div>
<input class="edit" value="">
</li>
</ul>
</section>

0 comments on commit 66dd8d2

Please sign in to comment.