Commit fdd26a69 authored by Ralf S. Engelschall's avatar Ralf S. Engelschall Committed by Pascal Hartig

provide ComponentJS TodoMVC Example implementation

parent d1d06d2d
......@@ -78,6 +78,9 @@
<li class="labs">
<a href="labs/architecture-examples/cujo/index.html" data-source="http://cujojs.com" data-content="cujoJS 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.">cujoJS</a>
</li>
<li class="labs">
<a href="labs/architecture-examples/componentjs/index.html" data-source="http://componentjs.com" data-content="ComponentJS is a stand-alone MPL-licensed Open Source library for JavaScript, providing a powerful run-time Component System for hierarchically structuring the User-Interface (UI) dialogs of complex HTML5-based Rich Clients (aka Single-Page-Apps) — under maximum applied Separation of Concerns (SoC) architecture principle, through optional Model, View and Controller component roles, with sophisticated hierarchical Event, Service, Hook, Model, Socket and Property mechanisms, and fully independent and agnostic of the particular UI widget toolkit.">ComponentJS</a>
</li>
<li class="routing labs">
<a href="labs/architecture-examples/dermis/" data-source="https://github.com/wearefractal/dermis" data-content="dermis is a tiny framework that provides models, collections, controllers, and data-binding out of the box. dermis is designed to be the simplest possible solution for creating complex applications">dermis</a>
</li>
......
/* global cs, UUIDjs, _ */
(function () {
'use strict';
// data model: Todo List entity
cs.ns('app.dm').TodoList = cs.clazz({
dynamics: {
items: []
},
cons: function (obj) {
_.assign(this, _.pick(obj, function (val, key) { return _.has(this, key); }, this));
},
protos: {
itemAdd: function (item) { this.items.push(item); },
itemDel: function (item) { this.items = _.without(this.items, item); },
itemById: function (id) { return _.find(this.items, { id: id }); }
}
});
// data model: Todo Item entity
cs.ns('app.dm').TodoItem = cs.clazz({
dynamics: {
id: '0',
title: '',
completed: false
},
cons: function (obj) {
this.id = UUIDjs.create(1).hex;
_.assign(this, _.pick(obj, function (val, key) { return _.has(this, key); }, this));
}
});
}());
/* global cs, app, _ */
(function () {
'use strict';
// service tier
cs.ns('app').sv = new cs.clazz({
dynamics: {
todoList: null,
storageId: 'todos-componentjs'
},
protos: {
todo: function () {
return this.todoList;
},
load: function () {
this.todoList = new app.dm.TodoList();
if (_.has(localStorage, this.storageId)) {
var obj = JSON.parse(localStorage[this.storageId]);
this.todoList.items = _.map(obj.items, function (item) {
return new app.dm.TodoItem(item);
});
}
},
save: function () {
localStorage[this.storageId] = JSON.stringify(this.todoList);
}
}
})();
}());
<!-- view mask of "main" UI composite -->
<markup id="main">
<div class="main">
<section class="main__todo">
</section>
<footer class="main__info">
<p>Double-click to edit a todo</p>
<p>Written by <a href="http://engelschall.com">Ralf S. Engelschall</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</div>
</markup>
.main {
font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.4em;
font-smoothing: antialiased;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
}
.main__todo {
color: #4d4d4d;
width: 550px;
margin: 0 auto;
}
.main__info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
.main__info a {
color: inherit;
}
/* global cs, app, Router, $, _ */
(function () {
'use strict';
// component of the 'main' UI composite
cs.ns('app.ui.composite').main = cs.clazz({
mixin: [cs.marker.controller, cs.marker.view],
protos: {
create: function () {
// create todo widget components and auto-increase their state with us
cs(this).create('todo-model/todo-view', app.ui.widget.todo.model, app.ui.widget.todo.view);
cs(this).property('ComponentJS:state-auto-increase', true);
},
prepare: function () {
var self = this;
var todoModel = cs(self, '//todo-model');
// two-way bind URL route to presentation model
var router = new Router({
'/': function () { todoFilterSelect('all'); },
'/active': function () { todoFilterSelect('active'); },
'/completed': function () { todoFilterSelect('completed'); }
});
var todoFilterSelect = function (filter) {
todoModel.value('state:status-filter-selected', filter);
todoModel.value('cmd:item-list-updated', true);
};
todoModel.observe({ name: 'event:status-filter-select', func: function (ev, value) {
var route = '/' + value;
if (route === '/all') {
route = '/';
}
if (router.getRoute() !== route) {
window.location.hash = '#' + route;
router.setRoute(route);
}
}});
router.init();
// transfer business model into presentation model
var bm2pm = function () {
var pmItems = [];
var bmTodoList = app.sv.todo();
_.forEach(bmTodoList.items, function (bmTodoItem) {
pmItems.push({
id: bmTodoItem.id,
title: bmTodoItem.title,
completed: bmTodoItem.completed,
editing: false
});
});
todoModel.value('data:item-list', pmItems);
todoModel.value('cmd:item-list-updated', true);
};
// react on item CRUD model event value changes
todoModel.observe({ name: 'event:new-item-create', func: function (/* ev, value */) {
var text = todoModel.value('data:new-item-text');
todoModel.value('data:new-item-text', '');
var todoList = app.sv.todo();
var todoItem = new app.dm.TodoItem({ title: text });
todoList.itemAdd(todoItem);
app.sv.save();
bm2pm();
}});
todoModel.observe({ name: 'event:item-list-item-modified', func: function (ev, item) {
var todoList = app.sv.todo();
var todoItem = todoList.itemById(item.id);
todoItem.title = item.title;
todoItem.completed = item.completed;
app.sv.save();
bm2pm();
}});
todoModel.observe({ name: 'event:item-list-item-removed', func: function (ev, item) {
var todoList = app.sv.todo();
var todoItem = todoList.itemById(item.id);
todoList.itemDel(todoItem);
app.sv.save();
bm2pm();
}});
// initially load business model and trigger transfer into presentation model
app.sv.load();
bm2pm();
},
render: function () {
// render view mask
var ui = $.markup('main');
cs(this).plug(ui);
cs(this).socket({ ctx: $('.main__todo', ui), spool: 'materialized' });
}
}
});
}());
html,
body {
margin: 0;
padding: 0;
background: #eaeaea url(../bower_components/todomvc-common/bg.png);
}
/* global cs, app, $, _ */
(function () {
'use strict';
// component of the root UI composite
cs.ns('app.ui.composite').root = cs.clazz({
mixin: [cs.marker.controller, cs.marker.view],
protos: {
create: function () {
// create main composite component and auto-increase its state with us
cs(this).create('main', app.ui.composite.main);
cs(this).property('ComponentJS:state-auto-increase', true);
},
prepare: function () {
// await the readiness of the DOM
if (_.isObject(document)) {
var self = this;
cs(self).guard('render', 1);
$(document).ready(function () {
// load all markup code
$.markup.load(function () {
cs(self).guard('render', -1);
});
});
}
},
render: function () {
// place a socket onto the DOM body element
cs(this).socket({ ctx: $('body'), spool: 'materialized' });
},
release: function () {
// destroy socket onto DOM body element
cs(this).unspool('materialized');
},
cleanup: function () {
// destroy main composite component
cs(this, 'main').destroy();
}
}
});
}());
/* global cs */
(function () {
'use strict';
// some UI constants
cs.ns('app.ui').constants = {
// DOM event key code
KEY_ENTER: 13,
KEY_ESCAPE: 27
};
}());
<!-- view mask of "todo" UI widget -->
<markup id="todo">
<div class="todo">
<header class="todo__header">
<h1 class="todo__h1">todos</h1>
<input class="todo__new todo__input" placeholder="What needs to be done?" autofocus="autofocus">
</header>
<section class="todo__main">
<input class="todo__toggle-all todo__input" type="checkbox">
<label class="todo__toggle-label" for="todo__toggle-all">Mark all as complete</label>
<ul class="todo__list">
<markup id="item">
<li class="todo__item {% if completed %}completed{% endif %}" data-id="{{id}}">
<div class="todo__item--view view">
<input class="todo__toggle toggle todo__input" type="checkbox"
{% if completed %}checked="checked"{% endif %}>
<label class="todo__label">{{title}}</label>
<button class="todo__destroy destroy todo__button">
</div>
<input class="todo__item--edit edit todo__input" value="{{title}}">
</li>
</markup>
</ul>
</section>
<footer class="todo__footer">
<span class="todo__count">
<strong data-bind="data:status-items-remaining"></strong>
<span data-bind="data:status-items-remaining-unit"></span> left
</span>
<ul class="todo__filters" data-bind="state:status-filter-selected">
<li><a href="#" data-tag="all">All</a></li>
<li><a href="#" data-tag="active">Active</a></li>
<li><a href="#" data-tag="completed">Completed</a></li>
</ul>
<button class="todo__completed todo__button">
Clear completed (<span data-bind="data:status-items-completed"></span>)
</button>
</footer>
</div>
</markup>
/* global cs, _ */
(function () {
'use strict';
// model component of the 'todo' UI widget
cs.ns('app.ui.widget.todo').model = cs.clazz({
mixin: [cs.marker.model],
protos: {
create: function () {
// define presentation model
cs(this).model({
'data:item-list': { value: [], valid: '[{ id: string, title: string, completed: boolean, editing: boolean }*]' },
'cmd:item-list-updated': { value: false, valid: 'boolean', autoreset: true },
'state:all-item-selected': { value: false, valid: 'boolean' },
'event:all-item-select': { value: false, valid: 'boolean', autoreset: true },
'data:new-item-text': { value: '', valid: 'string', store: true },
'event:new-item-create': { value: false, valid: 'boolean', autoreset: true },
'event:item-list-item-modified': { value: null, valid: 'object', autoreset: true },
'event:item-list-item-removed': { value: null, valid: 'object', autoreset: true },
'data:status-items-remaining': { value: 0, valid: 'number' },
'data:status-items-remaining-unit': { value: '', valid: 'string' },
'state:status-filter-selected': { value: 'all', valid: 'string', store: true },
'event:status-filter-select': { value: '', valid: 'string', autoreset: true },
'data:status-items-completed': { value: 0, valid: 'number' },
'event:status-clear-select': { value: false, valid: 'boolean', autoreset: true }
});
},
prepare: function () {
var self = this;
// presentation logic: determine singular/plural of remaining items unit
cs(self).observe({
name: 'data:status-items-remaining',
touch: true,
func: function (ev, value) {
cs(self).value('data:status-items-remaining-unit',
value !== 1 ? 'items' : 'item');
}
});
// presentation logic: determine number of completed and remaining items
cs(self).observe({
name: 'cmd:item-list-updated',
touch: true,
func: function (/* ev, value */) {
var items = cs(self).value('data:item-list');
var completed = _.filter(items, 'completed').length;
var remaining = items.length - completed;
cs(self).value('data:status-items-completed', completed);
cs(self).value('data:status-items-remaining', remaining);
if (remaining === 0 && completed > 0) {
cs(self).value('state:all-item-selected', true);
}
else if (remaining > 0) {
cs(self).value('state:all-item-selected', false);
}
}
});
// presentation logic: implement 'all item selected'
cs(self).observe({
name: 'event:all-item-select',
func: function (ev, value) {
var items = cs(self).value('data:item-list');
var modified = false;
_.forEach(items, function (item) {
if (item.completed !== value) {
item.completed = value;
cs(self).value('event:item-list-item-modified', item, true);
modified = true;
}
});
if (modified) {
cs(self).value('cmd:item-list-updated', true, true);
}
}
});
}
}
});
}());
.todo {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
.todo:before {
content: "";
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
.todo__button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
.todo__button,
.todo__input[type="checkbox"] {
outline: none;
}
.todo__input::-webkit-input-placeholder {
font-style: italic;
}
.todo__input::-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
.todo__h1 {
position: absolute;
top: -120px;
width: 100%;
font-size: 70px;
font-weight: bold;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.todo__header {
padding-top: 15px;
border-radius: inherit;
}
.todo__header:before {
content: "";
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
.todo__footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
z-index: 1;
text-align: center;
}
.todo__footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo__main.hidden {
display: none;
}
.todo__footer.hidden {
display: none;
}
.todo__count {
float: left;
text-align: left;
}
.todo__filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.todo__filters li {
display: inline;
}
.todo__filters li a {
color: #83756f;
margin: 2px;
text-decoration: none;
}
.todo__filters li a.selected {
font-weight: bold;
}
.todo__completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}
.todo__completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
.todo__new,
.todo__item--edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
.todo__new {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
.todo__main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
.todo__toggle-label {
display: none;
}
.todo__toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
/* Mobile Safari */
border: none;
}
.todo__toggle-all:before {
content: "»";
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
.todo__toggle-all:checked:before {
color: #737373;
}
.todo__list {
margin: 0;
padding: 0;
list-style: none;
}
.todo__item {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
.todo__item:last-child {
border-bottom: none;
}
.todo__item.editing {
border-bottom: none;
padding: 0;
}
.todo__item.editing .todo__item--edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
.todo__item.editing .todo__item--view {
display: none;
}
.todo__toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
/* Mobile Safari */
border: none;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
.todo__toggle:after {
content: '✔';
/* 40 + a couple of pixels visual adjustment */
line-height: 43px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
.todo__toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
.todo__label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
transition: color 0.4s;
}
.todo__item.completed .todo__label {
color: #a9a9a9;
text-decoration: line-through;
}
.todo__destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
transition: all 0.2s;
}
.todo__destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-ms-transform: scale(1.3);
transform: scale(1.3);
}
.todo__destroy:after {
content: '✖';
}
.todo__item:hover .todo__destroy {
display: block;
}
.todo__item--edit {
display: none;
}
.todo__item.editing:last-child {
margin-bottom: -1px;
}
/* Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera */
@media screen and (-webkit-min-device-pixel-ratio:0) {
.todo__toggle-all,
.todo__toggle {
background: none;
}
.todo__toggle {
height: 40px;
}
.todo__toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
/* global cs, app, $, _ */
(function () {
'use strict';
// view component of the 'todo' UI widget
cs.ns('app.ui.widget.todo').view = cs.clazz({
mixin: [cs.marker.view],
protos: {
render: function () {
// render outer view mask
var self = this;
var ui = $.markup('todo');
cs(self).plug(ui);
// two-way bind the 'all items' selection checkbox
$('.todo__toggle-all', ui).change(function (/* ev */) {
cs(self).value('event:all-item-select', $('.todo__toggle-all', ui).prop('checked'), true);
});
cs(self).observe({
name: 'state:all-item-selected',
spool: 'materialized',
func: function (ev, value) { $('.todo__toggle-all', ui).prop('checked', value); }
});
// two-way bind the 'new item' text input field
$('.todo__new', ui).keyup(function (/* ev */) {
cs(self).value('data:new-item-text', $('.todo__new', ui).val());
}).change(function (/* ev */) {
var value = $('.todo__new', ui).val().trim();
if (value !== '') {
cs(self).value('data:new-item-text', value);
cs(self).value('event:new-item-create', true, true);
}
});
cs(self).observe({
name: 'data:new-item-text',
touch: true,
spool: 'materialized',
func: function (ev, value) { $('.todo__new', ui).val(value); }
});
// two-way bind the list of items
cs(self).observe({
name: 'cmd:item-list-updated',
spool: 'materialized',
touch: true,
func: function (/* ev, value */) {
var items = cs(self).value('data:item-list');
var filter = cs(self).value('state:status-filter-selected');
// render item markup for all non-filtered items
$('.todo__list', ui).html('');
for (var i = 0; i < items.length; i++) {
if (filter === 'all' ||
(filter === 'active' && !items[i].completed) ||
(filter === 'completed' && items[i].completed)) {
var item = $.markup('todo/item', items[i]);
$('.todo__list', ui).append(item);
}
}
// show/hide the footer accordingly
if (items.length === 0) {
$('.todo__main', ui).addClass('hidden');
$('.todo__footer', ui).addClass('hidden');
}
else {
$('.todo__main', ui).removeClass('hidden');
$('.todo__footer', ui).removeClass('hidden');
}
// one-way bind double-click interaction onto all items to start editing mode
$('.todo__item--view .todo__label', ui).bind('dblclick', function (ev) {
var title = $(ev.target).text();
var parent = $(ev.target).parent().parent();
parent.addClass('editing');
$('.edit', parent).val(title).focus();
});
// one-way bind key-press and field blur interactions to leave editing mode
var blur = function (el, takeTitle) {
var id = $(el).parent().data('id') + '';
$(el).parent().removeClass('editing');
if (takeTitle) {
var items = cs(self).value('data:item-list');
var item = _.find(items, { id: id });
var title = $(el).val().trim();
if (title === '') {
cs(self).value('data:item-list', _.without(items, item));
cs(self).value('event:item-list-item-removed', item, true);
} else {
item.title = title;
cs(self).value('event:item-list-item-modified', item);
}
cs(self).value('cmd:item-list-updated', true, true);
}
};
$('.todo__item--edit', ui).keyup(function (ev) {
if (ev.which === app.ui.constants.KEY_ENTER) {
blur(ev.target, true);
} else if (ev.which === app.ui.constants.KEY_ESCAPE) {
blur(ev.target, false);
}
}).blur(function (ev) {
if ($(ev.target).parent().hasClass('editing')) {
blur(ev.target, true);
}
});
// one-way bind click interaction to toggle item completion
$('.todo__toggle', ui).click(function (ev) {
var id = $(ev.target).parent().parent().data('id') + '';
var items = cs(self).value('data:item-list');
var item = _.find(items, { id: id });
item.completed = !item.completed;
cs(self).value('event:item-list-item-modified', item, true);
cs(self).value('cmd:item-list-updated', true, true);
});
// one-way bind click interaction to remove item
$('.todo__destroy', ui).click(function (ev) {
var id = $(ev.target).parent().parent().data('id') + '';
var items = cs(self).value('data:item-list');
var item = _.find(items, { id: id });
cs(self).value('data:item-list', _.without(items, item));
cs(self).value('event:item-list-item-removed', item, true);
cs(self).value('cmd:item-list-updated', true, true);
});
}
});
// one-way bind status remaining items label
cs(self).observe({
name: 'data:status-items-remaining',
spool: 'materialized',
touch: true,
func: function (ev, value) {
$('*[data-bind=\'data:status-items-remaining\']', ui).text(value);
$('*[data-bind=\'data:status-items-remaining-unit\']', ui)
.text(parseInt(value) === 1 ? 'item' : 'items');
}
});
// two-way bind status filter buttons
cs(self).observe({
name: 'state:status-filter-selected',
spool: 'materialized',
touch: true,
func: function (ev, value) {
var a = $('*[data-bind=\'state:status-filter-selected\'] > li > a', ui);
a.removeClass('selected');
a.filter('*[data-tag=\'' + value + '\']').addClass('selected');
}
});
$('*[data-bind=\'state:status-filter-selected\'] > li > a', ui).click(function (ev) {
cs(self).value('event:status-filter-select', $(ev.target).data('tag'), true);
cs(self).value('cmd:item-list-updated', true, true);
return false;
});
// two-way bind status clear item button
cs(self).observe({
name: 'data:status-items-completed',
spool: 'materialized',
touch: true,
func: function (ev, value) {
if (value > 0) {
$('.todo__completed', ui).css('display', 'block');
$('*[data-bind=\'data:status-items-completed\']', ui).text(value);
}
else {
$('.todo__completed', ui).css('display', 'none');
}
}
});
$('.todo__completed', ui).click(function (/* ev */) {
var items = cs(self).value('data:item-list');
_.forEach(items, function (item) {
if (item.completed) {
cs(self).value('event:item-list-item-removed', item, true);
}
});
cs(self).value('data:item-list', _.filter(items, function (item) {
return !item.completed;
}));
cs(self).value('cmd:item-list-updated', true, true);
});
}
}
});
}());
/* global ComponentJS, cs, app, _ */
(function () {
'use strict';
// application bootstrap class
ComponentJS.symbol('cs');
cs.ns('app').boot = cs.clazz({
statics: {
init: function () {
// bootstrap ComponentJS framework
cs.bootstrap();
cs.debug(0);
},
main: function () {
// fire up the component tree
cs.create('/ui', app.ui.composite.root);
cs('/ui').state(_.isObject(document) ? 'visible' : 'prepared');
}
}
});
}());
{
"name": "todomvc-componentjs",
"dependencies": {
"componentjs": "1.0.1",
"jquery": "2.0.3",
"jquery-markup": "1.0.26",
"nunjucks": "1.0.0",
"director": "1.2.0",
"lodash": "2.3.0",
"uuid-js": "0.7.5",
"todomvc-common": "0.1.9"
}
}
/*
** ComponentJS -- Component System for JavaScript <http://componentjs.com>
** Copyright (c) 2009-2013 Ralf S. Engelschall <http://engelschall.com>
**
** This Source Code Form is subject to the terms of the Mozilla Public
** License, v. 2.0. If a copy of the MPL was not distributed with this
** file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* This is a small ComponentJS plugin which adds some ExtJS-specific
* functionality to the otherwise fully UI toolkit independent ComponentJS
* core framework.
*/
/* global ComponentJS:false */
/* jshint unused:false */
ComponentJS.plugin("extjs", function (_cs, $cs, GLOBAL) {
/*
* SPECIALIZED EXTJS SOCKET SUPPORT
*/
/* define the extra trait for components */
var trait = $cs.trait({
protos: {
socket: function () {
/* determine parameters */
var params = $cs.params("socket", arguments, {
name: { def: "default" },
scope: { def: null },
ctx: { pos: 0, req: true },
plug: { pos: 1, def: null }, /* removed "req: true" */
unplug: { pos: 2, def: null }, /* removed "req: true" */
spool: { def: null },
type: { def: "default" } /* added */
});
/* create pass-through information */
var arg = _cs.extend({}, params);
delete arg.type;
/* optionally change behaviour */
if (params.type === "extjs") {
/* provide specialized ExtJS socket functionality */
arg.plug = function (el, comp) { this.add(el); };
arg.unplug = function (el, comp) { this.remove(el); };
}
/* pass-through execution to original/base method */
return this.base(arg);
}
}
});
/* mixin this trait to all components */
_cs.latch("ComponentJS:bootstrap:comp:mixin", function (mixins) {
mixins.push(trait);
});
});
/*
** ComponentJS -- Component System for JavaScript <http://componentjs.com>
** Copyright (c) 2009-2013 Ralf S. Engelschall <http://engelschall.com>
**
** This Source Code Form is subject to the terms of the Mozilla Public
** License, v. 2.0. If a copy of the MPL was not distributed with this
** file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* This is a small ComponentJS plugin which adds some jQuery-specific
* functionality to the otherwise fully UI toolkit independent ComponentJS
* core framework.
*/
/* global ComponentJS:false */
/* global jQuery:false */
/* jshint unused:false */
ComponentJS.plugin("jquery", function (_cs, $cs, GLOBAL) {
/*
* SPECIALIZED JQUERY SOCKET SUPPORT
*/
/* define the extra trait for components */
var trait = $cs.trait({
protos: {
socket: function () {
/* determine parameters */
var params = $cs.params("socket", arguments, {
name: { def: "default" },
scope: { def: null },
ctx: { pos: 0, req: true },
plug: { pos: 1, def: null }, /* removed "req: true" */
unplug: { pos: 2, def: null }, /* removed "req: true" */
spool: { def: null },
type: { def: "default" } /* added */
});
/* create pass-through information */
var arg = _cs.extend({}, params);
delete arg.type;
/* optionally change behaviour */
if ( /* explicitly requested */
params.type === "jquery" ||
/* implicitly detected */
( params.type === "default" &&
typeof params.ctx.jquery === "string" &&
params.ctx.jquery.match(/^[0-9]+(?:\.[0-9]+)+$/) ) ) {
/* provide specialized jQuery socket functionality */
arg.plug = function (el, comp) { jQuery(this).append(el); };
arg.unplug = function (el, comp) { jQuery(el).remove(); };
}
/* pass-through execution to original/base method */
return this.base(arg);
}
}
});
/* mixin this trait to all components */
_cs.latch("ComponentJS:bootstrap:comp:mixin", function (mixins) {
mixins.push(trait);
});
});
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eaeaea url('bg.png');
color: #4d4d4d;
width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
button,
input[type="checkbox"] {
outline: none;
}
#todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todoapp input::-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
#todoapp h1 {
position: absolute;
top: -120px;
width: 100%;
font-size: 70px;
font-weight: bold;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
#main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
label[for='toggle-all'] {
display: none;
}
#toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
/* Mobile Safari */
border: none;
}
#toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
#toggle-all:checked:before {
color: #737373;
}
#todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
/* Mobile Safari */
border: none;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#todo-list li .toggle:after {
content: '✔';
/* 40 + a couple of pixels visual adjustment */
line-height: 43px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
#todo-list li .toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
#todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
transition: color 0.4s;
}
#todo-list li.completed label {
color: #a9a9a9;
text-decoration: line-through;
}
#todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
transition: all 0.2s;
}
#todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-ms-transform: scale(1.3);
transform: scale(1.3);
}
#todo-list li .destroy:after {
content: '✖';
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
z-index: 1;
text-align: center;
}
#footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
float: left;
text-align: left;
}
#filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
#filters li {
display: inline;
}
#filters li a {
color: #83756f;
margin: 2px;
text-decoration: none;
}
#filters li a.selected {
font-weight: bold;
}
#clear-completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
#info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
#info a {
color: inherit;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
height: 40px;
}
#toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
.hidden {
display: none;
}
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #C5C5C5;
border-bottom: 1px dashed #F7F7F7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
-webkit-transition-property: left;
transition-property: left;
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
.learn-bar > .learn {
left: 8px;
}
.learn-bar #todoapp {
width: 550px;
margin: 130px auto 40px auto;
}
}
(function () {
'use strict';
// Underscore's Template Module
// Courtesy of underscorejs.org
var _ = (function (_) {
_.defaults = function (object) {
if (!object) {
return object;
}
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
var iterable = arguments[argsIndex];
if (iterable) {
for (var key in iterable) {
if (object[key] == null) {
object[key] = iterable[key];
}
}
}
}
return object;
}
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /(.)^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
var render;
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
}
if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
return match;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n";
try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
return template;
};
return _;
})({});
if (location.hostname === 'todomvc.com') {
window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
}
function redirect() {
if (location.hostname === 'tastejs.github.io') {
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
}
}
function findRoot() {
var base;
[/labs/, /\w*-examples/].forEach(function (href) {
var match = location.href.match(href);
if (!base && match) {
base = location.href.indexOf(match);
}
});
return location.href.substr(0, base);
}
function getFile(file, callback) {
if (!location.host) {
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
}
var xhr = new XMLHttpRequest();
xhr.open('GET', findRoot() + file, true);
xhr.send();
xhr.onload = function () {
if (xhr.status === 200 && callback) {
callback(xhr.responseText);
}
};
}
function Learn(learnJSON, config) {
if (!(this instanceof Learn)) {
return new Learn(learnJSON, config);
}
var template, framework;
if (typeof learnJSON !== 'object') {
try {
learnJSON = JSON.parse(learnJSON);
} catch (e) {
return;
}
}
if (config) {
template = config.template;
framework = config.framework;
}
if (!template && learnJSON.templates) {
template = learnJSON.templates.todomvc;
}
if (!framework && document.querySelector('[data-framework]')) {
framework = document.querySelector('[data-framework]').getAttribute('data-framework');
}
if (template && learnJSON[framework]) {
this.frameworkJSON = learnJSON[framework];
this.template = template;
this.append();
}
}
Learn.prototype.append = function () {
var aside = document.createElement('aside');
aside.innerHTML = _.template(this.template, this.frameworkJSON);
aside.className = 'learn';
// Localize demo links
var demoLinks = aside.querySelectorAll('.demo-link');
Array.prototype.forEach.call(demoLinks, function (demoLink) {
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
});
document.body.className = (document.body.className + ' learn-bar').trim();
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
};
redirect();
getFile('learn.json', Learn);
})();
/*
* UUID-js: A js library to generate and parse UUIDs, TimeUUIDs and generate
* TimeUUID based on dates for range selections.
* @see http://www.ietf.org/rfc/rfc4122.txt
**/
function UUIDjs() {
}
UUIDjs.maxFromBits = function(bits) {
return Math.pow(2, bits);
};
UUIDjs.limitUI04 = UUIDjs.maxFromBits(4);
UUIDjs.limitUI06 = UUIDjs.maxFromBits(6);
UUIDjs.limitUI08 = UUIDjs.maxFromBits(8);
UUIDjs.limitUI12 = UUIDjs.maxFromBits(12);
UUIDjs.limitUI14 = UUIDjs.maxFromBits(14);
UUIDjs.limitUI16 = UUIDjs.maxFromBits(16);
UUIDjs.limitUI32 = UUIDjs.maxFromBits(32);
UUIDjs.limitUI40 = UUIDjs.maxFromBits(40);
UUIDjs.limitUI48 = UUIDjs.maxFromBits(48);
UUIDjs.randomUI04 = function() {
return Math.round(Math.random() * UUIDjs.limitUI04);
};
UUIDjs.randomUI06 = function() {
return Math.round(Math.random() * UUIDjs.limitUI06);
};
UUIDjs.randomUI08 = function() {
return Math.round(Math.random() * UUIDjs.limitUI08);
};
UUIDjs.randomUI12 = function() {
return Math.round(Math.random() * UUIDjs.limitUI12);
};
UUIDjs.randomUI14 = function() {
return Math.round(Math.random() * UUIDjs.limitUI14);
};
UUIDjs.randomUI16 = function() {
return Math.round(Math.random() * UUIDjs.limitUI16);
};
UUIDjs.randomUI32 = function() {
return Math.round(Math.random() * UUIDjs.limitUI32);
};
UUIDjs.randomUI40 = function() {
return (0 | Math.random() * (1 << 30)) + (0 | Math.random() * (1 << 40 - 30)) * (1 << 30);
};
UUIDjs.randomUI48 = function() {
return (0 | Math.random() * (1 << 30)) + (0 | Math.random() * (1 << 48 - 30)) * (1 << 30);
};
UUIDjs.paddedString = function(string, length, z) {
string = String(string);
z = (!z) ? '0' : z;
var i = length - string.length;
for (i; i > 0; i >>>= 1, z += z) {
if (i & 1) {
string = z + string;
}
}
return string;
};
UUIDjs.prototype.fromParts = function(timeLow, timeMid, timeHiAndVersion, clockSeqHiAndReserved, clockSeqLow, node) {
this.version = (timeHiAndVersion >> 12) & 0xF;
this.hex = UUIDjs.paddedString(timeLow.toString(16), 8)
+ '-'
+ UUIDjs.paddedString(timeMid.toString(16), 4)
+ '-'
+ UUIDjs.paddedString(timeHiAndVersion.toString(16), 4)
+ '-'
+ UUIDjs.paddedString(clockSeqHiAndReserved.toString(16), 2)
+ UUIDjs.paddedString(clockSeqLow.toString(16), 2)
+ '-'
+ UUIDjs.paddedString(node.toString(16), 12);
return this;
};
UUIDjs.prototype.toString = function() {
return this.hex;
};
UUIDjs.prototype.toURN = function() {
return 'urn:uuid:' + this.hex;
};
UUIDjs.prototype.toBytes = function() {
var parts = this.hex.split('-');
var ints = [];
var intPos = 0;
var i = 0;
for (i; i < parts.length; i++) {
var j = 0;
for (j; j < parts[i].length; j+=2) {
ints[intPos++] = parseInt(parts[i].substr(j, 2), 16);
}
}
return ints;
};
UUIDjs.prototype.equals = function(uuid) {
if (!(uuid instanceof UUID)) {
return false;
}
if (this.hex !== uuid.hex) {
return false;
}
return true;
};
UUIDjs.getTimeFieldValues = function(time) {
var ts = time - Date.UTC(1582, 9, 15);
var hm = ((ts / 0x100000000) * 10000) & 0xFFFFFFF;
return { low: ((ts & 0xFFFFFFF) * 10000) % 0x100000000,
mid: hm & 0xFFFF, hi: hm >>> 16, timestamp: ts };
};
UUIDjs._create4 = function() {
return new UUIDjs().fromParts(
UUIDjs.randomUI32(),
UUIDjs.randomUI16(),
0x4000 | UUIDjs.randomUI12(),
0x80 | UUIDjs.randomUI06(),
UUIDjs.randomUI08(),
UUIDjs.randomUI48()
);
};
UUIDjs._create1 = function() {
var now = new Date().getTime();
var sequence = UUIDjs.randomUI14();
var node = (UUIDjs.randomUI08() | 1) * 0x10000000000 + UUIDjs.randomUI40();
var tick = UUIDjs.randomUI04();
var timestamp = 0;
var timestampRatio = 1/4;
if (now !== timestamp) {
if (now < timestamp) {
sequence++;
}
timestamp = now;
tick = UUIDjs.randomUI04();
} else if (Math.random() < timestampRatio && tick < 9984) {
tick += 1 + UUIDjs.randomUI04();
} else {
sequence++;
}
var tf = UUIDjs.getTimeFieldValues(timestamp);
var tl = tf.low + tick;
var thav = (tf.hi & 0xFFF) | 0x1000;
sequence &= 0x3FFF;
var cshar = (sequence >>> 8) | 0x80;
var csl = sequence & 0xFF;
return new UUIDjs().fromParts(tl, tf.mid, thav, cshar, csl, node);
};
UUIDjs.create = function(version) {
version = version || 4;
return this['_create' + version]();
};
UUIDjs.fromTime = function(time, last) {
last = (!last) ? false : last;
var tf = UUIDjs.getTimeFieldValues(time);
var tl = tf.low;
var thav = (tf.hi & 0xFFF) | 0x1000; // set version '0001'
if (last === false) {
return new UUIDjs().fromParts(tl, tf.mid, thav, 0, 0, 0);
}
return new UUIDjs().fromParts(tl, tf.mid, thav, 0x80 | UUIDjs.limitUI06, UUIDjs.limitUI08 - 1, UUIDjs.limitUI48 - 1);
};
UUIDjs.firstFromTime = function(time) {
return UUIDjs.fromTime(time, false);
};
UUIDjs.lastFromTime = function(time) {
return UUIDjs.fromTime(time, true);
};
UUIDjs.fromURN = function(strId) {
var r, p = /^(?:urn:uuid:|\{)?([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{2})([0-9a-f]{2})-([0-9a-f]{12})(?:\})?$/i;
if ((r === p.exec(strId))) {
return new UUIDjs().fromParts(parseInt(r[1], 16), parseInt(r[2], 16),
parseInt(r[3], 16), parseInt(r[4], 16),
parseInt(r[5], 16), parseInt(r[6], 16));
}
return null;
};
UUIDjs.fromBytes = function(ints) {
var str = '';
var pos = 0;
var parts = [4, 2, 2, 2, 6];
var i = 0;
if (ints.length < 5) {
return null;
}
for (i; i < parts.length; i++) {
var j = 0;
for (j; j < parts[i]; j++) {
var octet = ints[pos++].toString(16);
if (octet.length === 1) {
octet = '0' + octet;
}
str += octet;
}
if (parts[i] !== 6) {
str += '-';
}
}
return UUIDjs.fromURN(str);
};
UUIDjs.fromBinary = function(binary) {
var ints = [];
var i = 0;
for (i; i < binary.length; i++) {
ints[i] = binary.charCodeAt(i);
if (ints[i] > 255 || ints[i] < 0) {
throw new Error('Unexpected byte in binary data.');
}
}
return UUIDjs.fromBytes(ints);
};
<!doctype html>
<html lang="en" data-framework="componentjs">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>ComponentJS • TodoMVC</title>
<!-- load third-party libraries -->
<script src="bower_components/lodash/dist/lodash.js"></script>
<script src="bower_components/uuid-js/lib/uuid.js"></script>
<script src="bower_components/nunjucks/browser/nunjucks.js"></script>
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/jquery-markup/jquery.markup.js"></script>
<script src="bower_components/director/build/director.js"></script>
<script src="bower_components/componentjs/component.js"></script>
<script src="bower_components/componentjs/component.plugin.jquery.js"></script>
<!-- load and call application initialization code -->
<script src="app/app.js"></script>
<script type="text/javascript">app.boot.init()</script>
<!-- load the service and datamodel parts -->
<script src="app/app-dm.js"></script>
<script src="app/app-sv.js"></script>
<!-- load the root UI component -->
<script src="app/app-ui-constants.js"></script>
<script src="app/app-ui-composite-root.js"></script>
<link href="app/app-ui-composite-root-style.css" rel="stylesheet" type="text/css">
<!-- load the main UI component -->
<script src="app/app-ui-composite-main.js"></script>
<link href="app/app-ui-composite-main-mask.html" rel="markup" type="text/x-markup-nunjucks">
<link href="app/app-ui-composite-main-style.css" rel="stylesheet" type="text/css">
<!-- load the todo UI component -->
<script src="app/app-ui-widget-todo-view.js"></script>
<script src="app/app-ui-widget-todo-model.js"></script>
<link href="app/app-ui-widget-todo-mask.html" rel="markup" type="text/x-markup-nunjucks">
<link href="app/app-ui-widget-todo-style.css" rel="stylesheet" type="text/css">
<!-- load required todomvc.com integration functions -->
<link href="bower_components/todomvc-common/base.css" rel="stylesheet" type="text/css">
<script src="bower_components/todomvc-common/base.js"></script>
<!-- call application main code -->
<script type="text/javascript">app.boot.main()</script>
</head>
<body>
</body>
</html>
{
"maxerr": 200,
"bitwise": true,
"camelcase": false,
"curly": false,
"eqeqeq": true,
"forin": false,
"immed": true,
"latedef": true,
"newcap": false,
"noarg": false,
"noempty": false,
"nonew": true,
"plusplus": false,
"quotmark": "double",
"regexp": false,
"undef": true,
"unused": true,
"strict": false,
"trailing": true,
"maxparams": 9,
"maxdepth": 7,
"maxstatements": 150,
"maxlen": 200,
"loopfunc": true,
"-W014": false,
"-W033": false,
"globals": {
"ComponentJS": false,
"cs": false,
"app": false,
"_": false,
"$": false,
"localStorage": false,
"document": false,
"window": false,
"Router": false,
"UUIDjs": false
}
}
# ComponentJS TodoMVC Example
## About ComponentJS
> ComponentJS is a stand-alone MPL-licensed Open Source library for
JavaScript, providing a powerful Component System for hierarchically
structuring the User-Interface (UI) dialogs of complex HTML5-based Rich
Clients (aka Single-Page-Apps) — under maximum applied Separation
of Concerns (SoC) architecture principle, through optional Model,
View and Controller component roles, with sophisticated hierarchical
Event, Service, Hook, Model, Socket and Property mechanisms, and fully
independent and agnostic of the particular UI widget toolkit.
> _[ComponentJS &mdash; componentjs.com](http://componentjs.com)_
## Third-Party Libraries
This ComponentJS TodoMVC Example uses the following libraries and frameworks
(which are all installed through Bower):
- [ComponentJS](http://componentjs.com/) 1.0.1<br/>
The MVC framework.
- [jQuery](http://jquery.com/) 2.0.3<br/>
The DOM manipulation and eventing library.
- [jQuery-Markup](http://plugins.jquery.com/markup/) 1.0.26<br/>
The view mask template integration library.
- [Nunjucks](http://jlongster.github.io/nunjucks/) 1.0.0<br/>
The view mask template engine library.
- [Flatiron Director](https://github.com/flatiron/director) 1.2.0<br/>
The URL routing library.
- [UUID.js](https://github.com/aurigadl/uuid-js) 0.7.5<br/>
The UUID generation library.
- [Lo-Dash](http://lodash.com/) 2.3.0<br/>
The collection utility library.
- [todomvc-common](https://github.com/tastejs/todomvc-common) 0.1.9<br/>
The background image and TodoMVC.com integration code.
## Hints about the ComponentJS TodoMVC Example
This ComponentJS TodoMVC Example tries to
closely follow the official [TodoMVC App Specification](https://github.com/tastejs/todomvc/blob/gh-pages/app-spec.md)
as long as there are no conflicting ComponentJS best practices.
The known resolved conflicts were:
- **Component Reusability and CSS Usage**:
TodoMVC `todo-common` provides a `base.css` which was not
directly used within this ComponentJS TodoMVC Example. There
are two reasons for this:
- **Single vs. Multiple Files**:
The `base.css` provides all styles of the TodoMVC application
in one single file, while in ComponentJS-based applications
the styles are local to the components which create the
corresponding DOM elements. In ComponentJS TodoMVC Example
we have three such components (`root`, `main` and `todo`)
and hence the `base.css` was split into three parts accordingly, too.
- **Unique Ids vs. Class Selectors**:
The styling in `base.css` is mainly based on unique identifiers (`#foo`)
instead of classes (`.foo`). This is a big "no-go" for UI
approaches like ComponentJS where UI widgets (here the `todo`
UI component) are fully reusable and are potentially rendered
multiple times into the same DOM tree. For the particular
TodoMVC use case this does not happen, but the ComponentJS
TodoMVC Example should have been strictly the way things
are done in ComponentJS applications. As a result, all CSS
selectors of `base.css` were converted from unique identifiers to
[BEM](http://bem.info/method/definitions/)-like classes.
- **Source File Grouping**:
TodoMVC recommends to group all sources files according to
technical classifications. ComponentJS-based applications usually
use a domain-specific classification to group files, i.e., the UI is
split into domain-specific components and each component is fully
self-contained. This means that each component consists of its own
JavaScript code, its own style, its own mask, etc. As the TodoMVC use
case is a trivial one, in the ComponentJS TodoMVC example you see this
through the common filename prefixes only. In a real-world ComponentJS
application one would see this also through the directory tree.
- **URL Routing**:
The TodoMVC application speciification just requires that an URL
based routing exists. In order to avoid extra code, one could have
implemented this by using FlatIron Director directly within the `todo`
component and especially just use direct hyperlinks in the view mask.
While sufficient and perhaps acceptable for a trivial use case like
the TodoMVC, it is not for a larger application. There the URL routing
should be done only by a component which has the whole UI as the scope
(the `root` and `main` components but not the `todo` widget) and there
should be no direct hyperlinks within a single component (as it is not
allowd to control the URL of the whole apllication). We use Flatiron
Director in the `main` component and perform a two-way binding into
the `todo` component.
- **LocalStorage**:
The TodoMVC application speciification just requires that the
todo list is persistend in the the HTML5 `localStorage`. In
order to avoid extra code, one could have implemented this by
allowing ComponentJS to implicitly persist the Todo list items into
`localStorage` from within the `todo` widget with the help of the
`component.plugin.localstorage.js` plugin. While sufficient and
perhaps acceptable for a trivial use case like the TodoMVC, it is not
for a larger application. There the Todo items would come from an
underlying service tier and its UI-independent Business Model (while
the UI widget uses a so-called Presentation Model). We decided to
already use this strict separation between Presentation and Business
model for the trivial TodoMVC use case, even if it increases the total
amount of required code, of course.
## Learning ComponentJS
The [ComponentJS website](http://componentjs.com) is a great resource for getting started.
Here are some links you may find helpful:
* [Features](http://componentjs.com/features.html)
* [Demo](http://componentjs.com/demo.html)
* [API Reference](http://componentjs.com/api/api.screen.html)
* [Tutorial](http://componentjs.com/tutorial.html)
For more details about the TodoMVC initiative and the idea behind the TodoMVC applications see:
* [TodoMVC Initiative](https://todomvc.com/)
* [App Specification](https://github.com/tastejs/todomvc/blob/gh-pages/app-spec.md)
......@@ -443,6 +443,40 @@
}]
}]
},
"componentjs": {
"name": "ComponentJS",
"description": "ComponentJS is a stand-alone MPL-licensed Open Source library for JavaScript, providing a powerful run-time Component System for hierarchically structuring the User-Interface (UI) dialogs of complex HTML5-based Rich Clients (aka Single-Page-Apps) — under maximum applied Separation of Concerns (SoC) architecture principle, through optional Model, View and Controller component roles, with sophisticated hierarchical Event, Service, Hook, Model, Socket and Property mechanisms, and fully independent and agnostic of the particular UI widget toolkit.",
"homepage": "componentjs.com/",
"source_path": [{
"name": "Architecture Example",
"url": "labs/architecture-examples/componentjs"
}],
"link_groups": [{
"heading": "Official Resources",
"links": [{
"name": "Overview Video",
"url": "http://www.youtube.com/watch?v=gtz7PCMxzVA"
}, {
"name": "Demo",
"url": "http://componentjs.com/demo.html"
}, {
"name": "Tutorial",
"url": "http://componentjs.com/tutorial.html"
}, {
"name": "API Reference",
"url": "http://componentjs.com/api.html"
}]
}, {
"heading": "Community",
"links": [{
"name": "ComponentJS on GitHub",
"url": "https://github.com/rse/componentjs"
}, {
"name": "ComponentJS on Twitter",
"url": "http://twitter.com/componentjs"
}]
}]
},
"cujo": {
"name": "cujoJS",
"description": "cujo is an architectural toolkit for next generation JavaScript applications. It encourages highly modular development, declarative application assembly, and embraces the asynchronous nature of JavaScript and its fusion of object-oriented and functional programming styles.",
......
......@@ -81,6 +81,7 @@ We also have a number of in-progress applications in Labs:
- [Enyo + Backbone.js](http://enyojs.com/)
- [SAPUI5](http://scn.sap.com/community/developer-center/front-end)
- [Lavaca](http://getlavaca.com) + [RequireJS](http://requirejs.org) (using AMD)
- [ComponentJS](http://componentjs.com)
## Live demos
......
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