Commit 834b79f6 authored by Addy Osmani's avatar Addy Osmani

Merge pull request #553 from somajs/somajs

soma.js v2 updates
parents 3af9f7bb 10dee133
......@@ -3,7 +3,8 @@
"version": "0.0.0",
"dependencies": {
"todomvc-common": "~0.1.6",
"jquery": "~2.0.0",
"handlebars.js": "~1.0.0-rc.3"
"director": "~1.2.0",
"soma.js": "~2.0.0",
"soma-template": "~0.1.8"
}
}
{
"name": "todomvc-common",
"version": "0.1.7",
"gitHead": "42348a8146fe0be847b93cd98664813fbae62be9",
"_id": "todomvc-common@0.1.7",
"readme": "ERROR: No README.md file found!",
"description": "ERROR: No README.md file found!",
"repository": {
"type": "git",
"url": "git://github.com/tastejs/todomvc-common.git"
}
}
\ No newline at end of file
.data-cloak {
display: none;
}
\ No newline at end of file
<!doctype html>
<html lang="en" data-framework="somajs">
<head>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>soma.js • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
</head>
<body>
<link rel="stylesheet" href="components/todomvc-common/base.css">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<section id="todoapp">
<!-- HEADER VIEW, template: /views/header.js (todo.HeaderView) -->
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
<input id="new-todo" placeholder="What needs to be done?" autofocus data-keypress="add()" data-blur="clear()">
</header>
<section id="main">
<input id="toggle-all" type="checkbox">
<!-- MAIN VIEW, template: /views/main.js (todo.MainView) -->
<section id="main" data-show="{{isVisible}}">
<input id="toggle-all" type="checkbox" data-click="toggleAll()" data-checked="{{allCompleted}}">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul>
<ul id="todo-list" class="data-cloak">
<li data-repeat="todo in items" class="{{completedClass(todo.completed)}} {{todo.editing}}">
<div class="view">
<input class="toggle" type="checkbox" data-checked="{{todo.completed}}" data-click="toggle(todo)">
<label data-dblclick="edit(todo)">{{todo.title}}</label>
<button class="destroy" data-click="remove(todo)"></button>
</div>
<input class="edit" value="{{todo.title}}" data-keypress="update(todo)" data-blur="update(todo)">
</li>
</ul>
</section>
<footer id="footer"></footer>
<!-- FOOTER VIEW, template: /views/footer.js (todo.FooterView) -->
<footer id="footer" class="data-cloak" data-show="{{footerVisible}}">
<span id="todo-count"><strong>{{active}}</strong> {{itemLabel}} left</span>
<ul id="filters">
<li>
<a class="{{highlightFilter('')}}" href="#/">All</a>
</li>
<li>
<a class="{{highlightFilter('active')}}" href="#/active">Active</a>
</li>
<li>
<a class="{{highlightFilter('completed')}}" href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed" data-show="{{clearCompletedVisible()}}" data-click="clearCompleted()">Clear completed ({{completed}})</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://soundstep.com">Romuald Quantin</a></p>
<p>With <a href="http://somajs.github.com/somajs">soma.js</a></p>
<p>Created by <a href="http://soundstep.com">Romuald Quantin</a> (<a href="http://somajs.github.io/somajs/">soma.js</a>)</p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script id="todo-list-template" type="text/x-handlebars-template">
{{#this}}
<li {{#if completed}}class="completed"{{/if}} data-id="{{id}}">
<div class="view">
<input class="toggle" type="checkbox" {{#if completed}}checked{{/if}}>
<label>{{title}}</label>
<button class="destroy"></button>
</div>
<input class="edit" value="{{title}}">
</li>
{{/this}}
</script>
<script id="footer-template" type="text/x-handlebars-template">
<span id="todo-count"><strong>{{active}}</strong> {{itemLabel}} left</span>
<button id="clear-completed">Clear completed ({{completed}})</button>
</script>
<script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/handlebars.js/handlebars.js"></script>
<script src="bower_components/jquery/jquery.js"></script>
<script src="js/lib/soma-native_v1.0.2_min.js"></script>
<script src="js/todos/models/models.js"></script>
<script src="js/todos/views/views.js"></script>
<script src="js/todos/controllers/controllers.js"></script>
<!-- LIBRARIES -->
<script src="components/todomvc-common/base.js"></script>
<script src="components/director/build/director.js"></script>
<script src="components/soma.js/build/soma.js"></script>
<script src="components/soma-template/build/soma-template.js"></script>
<!-- TODO APP -->
<script src="js/models/todos.js"></script>
<script src="js/models/router.js"></script>
<script src="js/views/header.js"></script>
<script src="js/views/main.js"></script>
<script src="js/views/footer.js"></script>
<script src="js/app.js"></script>
</body>
</body>
</html>
var todo = window.todo || {};
/*global soma:false */
(function (todo, soma) {
(function( window ) {
'use strict';
todo.TodoApp = new soma.Application.extend({
init: function() {
this.addModel( todo.TodoModel.NAME, new todo.TodoModel() );
this.addCommand( todo.TodoEvent.RENDER, todo.TodoCommand );
this.addCommand( todo.TodoEvent.CREATE, todo.TodoCommand );
this.addCommand( todo.TodoEvent.DELETE, todo.TodoCommand );
this.addCommand( todo.TodoEvent.TOGGLE, todo.TodoCommand );
this.addCommand( todo.TodoEvent.UPDATE, todo.TodoCommand );
this.addCommand( todo.TodoEvent.TOGGLE_ALL, todo.TodoCommand );
this.addCommand( todo.TodoEvent.CLEAR_COMPLETED, todo.TodoCommand );
this.addView( todo.TodoListView.NAME, new todo.TodoListView( $('#todo-list')[0] ) );
this.addView( todo.FooterView.NAME, new todo.FooterView( $('#footer')[0] ) );
this.addView( todo.TodoInputView.NAME, new todo.TodoInputView( $('#new-todo')[0] ) );
todo.TodoApp = soma.Application.extend({
init: function () {
// mapping rules so the model and router can be injected
this.injector.mapClass('model', todo.Model, true);
this.injector.mapClass('router', todo.Router, true);
// create templates for DOM Elements (optional soma-template plugin)
this.createTemplate(todo.HeaderView, document.getElementById('header'));
this.createTemplate(todo.MainView, document.getElementById('main'));
this.createTemplate(todo.FooterView, document.getElementById('footer'));
},
start: function() {
this.dispatchEvent( new todo.TodoEvent( todo.TodoEvent.RENDER ) );
start: function () {
// dispatch a custom event to render the templates
this.dispatcher.dispatch('render');
}
});
var app = new todo.TodoApp();
// create the application
new todo.TodoApp();
})( window );
})(window.todo = window.todo || {}, soma);
/*global Router:false */
(function (todo, Router) {
'use strict';
todo.Router = function (dispatcher) {
// create the router (director.js)
var router = new Router().init();
// dispatch a custom event to render the template on a route change
router.on(/.*/, function () {
dispatcher.dispatch('render');
});
return {
getRoute: function () {
return router.getRoute()[0];
}
};
};
})(window.todo = window.todo || {}, Router);
(function (todo) {
'use strict';
todo.Model = function () {
var storeKey = 'todos-somajs';
return {
get: function () {
// get the data from the local storage
return JSON.parse(localStorage.getItem(storeKey) || '[]');
},
set: function (items) {
// set the data to the local storage
localStorage.setItem(storeKey, JSON.stringify(items));
},
getActive: function () {
// returns items that are not completed
return this.get().filter(function (item) {
return !item.completed;
}).length;
}
};
};
})(window.todo = window.todo || {});
var todo = window.todo || {};
(function( window ) {
'use strict';
todo.TodoCommand = soma.Command.extend({
execute: function( event ) {
var model = this.getModel( todo.TodoModel.NAME );
switch( event.type ) {
case todo.TodoEvent.RENDER:
this.getView( todo.TodoListView.NAME ).render( model.data, model.getActiveLength() );
this.getView( todo.FooterView.NAME ).render( model.dataFooter );
break;
case todo.TodoEvent.CREATE:
model.addItem( event.params.todoTitle );
break;
case todo.TodoEvent.DELETE:
model.removeItem( event.params.todoId );
break;
case todo.TodoEvent.TOGGLE:
model.toggleItem( event.params.todoId );
break;
case todo.TodoEvent.TOGGLE_ALL:
model.toggleAll( event.params.toggleAll );
break;
case todo.TodoEvent.UPDATE:
model.updateItem( event.params.todoId, event.params.todoTitle );
break;
case todo.TodoEvent.CLEAR_COMPLETED:
model.clearCompleted();
break;
}
}
});
todo.TodoEvent = soma.Event.extend({
constructor: function( type, todoTitle, todoId, toggleAll ) {
return soma.Event.call( this, type, {
todoTitle: todoTitle,
todoId: todoId,
toggleAll: toggleAll
});
}
});
todo.TodoEvent.RENDER = 'TodoEvent.RENDER';
todo.TodoEvent.CREATE = 'TodoEvent.CREATE';
todo.TodoEvent.DELETE = 'TodoEvent.DELETE';
todo.TodoEvent.UPDATE = 'TodoEvent.UPDATE';
todo.TodoEvent.TOGGLE = 'TodoEvent.TOGGLE';
todo.TodoEvent.TOGGLE_ALL = 'TodoEvent.TOGGLE_ALL';
todo.TodoEvent.CLEAR_COMPLETED = 'TodoEvent.CLEAR_COMPLETED';
})( window );
var todo = window.todo || {};
(function( window ) {
'use strict';
todo.TodoModel = new soma.Model.extend({
dataFooter: null,
init: function() {
this.storeKey = 'todos-somajs';
this.data = JSON.parse( this.getStore() ) || [];
this.updateDataFooter();
},
updateDataFooter: function() {
var active = this.getActiveLength();
this.dataFooter = {
active: active,
itemLabel: active === 1 ? 'item' : 'items',
completed: this.data.length - active,
length: this.data.length
};
},
addItem: function( title ) {
this.data.push({
id: this.uuid(),
title: title,
completed: false
});
this.update();
},
removeItem: function( id ) {
this.data.splice( this.getIndexById( id ), 1 );
this.update();
},
toggleItem: function( id ) {
var item = this.data[ this.getIndexById( id ) ];
item.completed = !item.completed;
this.update();
},
updateItem: function( id, title ) {
this.data[ this.getIndexById( id ) ].title = title;
this.update();
},
toggleAll: function( toggleValue ) {
var i;
for ( i = 0; i < this.data.length; i++ ) {
this.data[i].completed = toggleValue;
}
this.update();
},
clearCompleted: function() {
var i = this.data.length;
while ( i-- ) {
if ( this.data[ i ].completed ) {
this.data.splice( i, 1 );
}
}
this.update();
},
getIndexById: function( id ) {
var i;
for ( i = 0; i < this.data.length; i++ ) {
if ( this.data[ i ].id === id ) {
return i;
}
}
return -1;
},
getActiveLength: function() {
var i,
count = 0;
for ( i = 0; i < this.data.length; i++ ) {
if ( !this.data[ i ].completed ) {
count++;
}
}
return count;
},
update: function() {
this.updateDataFooter();
this.setStore( this.data );
this.dispatchEvent( new todo.TodoEvent( todo.TodoEvent.RENDER ) );
},
getStore: function() {
return localStorage.getItem( this.storeKey );
},
setStore: function() {
localStorage.setItem( this.storeKey, JSON.stringify( this.data ) );
},
// https://gist.github.com/1308368
uuid: function(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b;}
});
todo.TodoModel.NAME = 'TodoModel';
})( window );
var todo = window.todo || {};
(function( window ) {
'use strict';
var ENTER_KEY = 13;
todo.TodoListView = soma.View.extend({
template: null,
init: function() {
this.template = Handlebars.compile( $( '#' + this.domElement.id + '-template' ).html() );
$( this.domElement ).on( 'click', '.destroy', this.destroy.bind( this ) );
$( this.domElement ).on( 'click', '.toggle', this.toggle.bind( this ) );
$( this.domElement ).on( 'dblclick', 'label', this.edit );
$( this.domElement ).on( 'blur', '.edit', this.update.bind( this ) );
$( this.domElement ).on( 'keypress', '.edit', this.blurInput );
$('#toggle-all').click( this.toggleAll );
},
render: function( data, activeCount ) {
$(this.domElement).html( this.template( data ) );
$('#toggle-all').prop( 'checked', !activeCount );
$('#main').toggle( !!data.length );
},
destroy: function( event ) {
var id = $(event.target).closest('li').attr('data-id');
this.dispatchEvent( new todo.TodoEvent( todo.TodoEvent.DELETE, null, id ) );
},
toggle: function( event ) {
var id = $(event.target).closest('li').attr('data-id');
this.dispatchEvent( new todo.TodoEvent( todo.TodoEvent.TOGGLE, null, id ) );
},
toggleAll: function() {
this.dispatchEvent( new todo.TodoEvent( todo.TodoEvent.TOGGLE_ALL, null, null, $( this ).prop('checked') ) );
},
edit: function( event ) {
$( this ).closest('li').addClass('editing').find('.edit').focus();
},
update: function( event ) {
var li = $( event.target ).closest('li').removeClass('editing'),
id = li.data('id'),
val = li.find('.edit').val().trim();
if ( val ) {
this.dispatchEvent( new todo.TodoEvent( todo.TodoEvent.UPDATE, val, id ) );
}
else {
this.dispatchEvent( new todo.TodoEvent( todo.TodoEvent.DELETE, null, id ) );
}
},
blurInput: function( event ) {
if ( event.which === ENTER_KEY ) {
event.target.blur();
}
}
});
todo.TodoListView.NAME = 'TodoListView';
todo.TodoInputView = soma.View.extend({
init: function() {
$( this.domElement ).keypress( this.keyPressHandler.bind( this ) );
$( this.domElement ).blur( this.blur );
},
keyPressHandler: function( event ) {
if ( event.which === ENTER_KEY ) {
this.createItem();
}
},
createItem: function() {
var value = this.domElement.value.trim();
if ( value ) {
this.dispatchEvent( new todo.TodoEvent( todo.TodoEvent.CREATE, value ) );
}
this.domElement.value = '';
},
blur: function( event ) {
if ( !this.value.trim() ) {
this.value = '';
}
}
});
todo.TodoInputView.NAME = 'TodoInputView';
todo.FooterView = soma.View.extend({
template: null,
init: function() {
this.template = Handlebars.compile( $( '#' + this.domElement.id + '-template' ).html() );
$( this.domElement ).on( 'click', '#clear-completed', this.clearCompleted.bind( this ) );
},
render: function( data ) {
$( this.domElement ).html( this.template( data ) );
$( this.domElement ).toggle( !!data.length );
$('#clear-completed').toggle( !!data.completed );
},
clearCompleted: function( event ) {
this.dispatchEvent( new todo.TodoEvent( todo.TodoEvent.CLEAR_COMPLETED ) );
}
});
todo.FooterView.NAME = 'FooterView';
})( window );
(function (todo) {
'use strict';
todo.FooterView = function (scope, template, model, router, dispatcher) {
// get data from the injected model
var items = model.get();
// template function: returns a css class for the current filter (all/active/completed)
scope.highlightFilter = function (filter) {
var route = router.getRoute();
return route === filter ? 'selected' : '';
};
// template function: returns the number of completed items
scope.clearCompleted = function () {
items = items.filter(function (item) {
return !item.completed;
});
update();
};
// save the changes to the model and dispatch a custom event to render the templates
function update() {
model.set(items);
dispatcher.dispatch('render');
}
// listen to a custom event to render the footer view
dispatcher.addEventListener('render', function () {
items = model.get();
scope.active = model.getActive();
scope.completed = items.length - scope.active;
scope.itemLabel = scope.active === 1 ? 'item' : 'items';
scope.footerVisible = items.length > 0 ? true : false;
scope.clearCompletedVisible = scope.completed > 0 ? true : false;
template.render();
});
};
})(window.todo = window.todo || {});
(function (todo) {
'use strict';
var ENTER_KEY = 13;
todo.HeaderView = function (scope, template, model, dispatcher) {
// get data from the injected model
var items = model.get();
// template function: add a new item on an enter key press
scope.add = function (event) {
var value = event.currentTarget.value.trim();
if (event.which === ENTER_KEY && value !== '') {
items.push({
title: value,
completed: false
});
event.currentTarget.value = '';
update();
}
};
// template function: remove text from the input (used on blur event)
scope.clear = function (event) {
event.currentTarget.value = '';
};
// save the changes to the model and dispatch a custom event to render the templates
function update() {
model.set(items);
dispatcher.dispatch('render');
}
// listen to a custom event to render the header view
dispatcher.addEventListener('render', function () {
items = model.get();
template.render();
});
};
})(window.todo = window.todo || {});
This diff is collapsed.
......@@ -21,7 +21,6 @@ Get help from other soma.js users:
* [Mailing list on Google Groups](https://groups.google.com/forum/#!forum/somajs)
* [soma.js on Twitter](http://twitter.com/soundstep)
_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)._
......
{
"name": "todomvc-somajs",
"version": "0.0.0",
"dependencies": {
"todomvc-common": "~0.1.6",
"director": "~1.2.0",
"requirejs": "~2.1.5",
"soma.js": "~2.0.0",
"soma-template": "~0.1.8"
}
}
{
"name": "todomvc-common",
"version": "0.1.7",
"gitHead": "42348a8146fe0be847b93cd98664813fbae62be9",
"_id": "todomvc-common@0.1.7",
"readme": "ERROR: No README.md file found!",
"description": "ERROR: No README.md file found!",
"repository": {
"type": "git",
"url": "git://github.com/tastejs/todomvc-common.git"
}
}
\ No newline at end of file
.data-cloak {
display: none;
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
/*global define:false */
(function () {
'use strict';
define(['director'], function (Router) {
var RouterModel = function (dispatcher) {
// create the router (director.js)
var router = new Router().init();
// dispatch a custom event to render the template on a route change
router.on(/.*/, function () {
dispatcher.dispatch('render');
});
return {
getRoute: function () {
return router.getRoute()[0];
}
};
};
return RouterModel;
});
})();
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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