Commit 2dfaa8b3 authored by Cliff Hall's avatar Cliff Hall Committed by Sindre Sorhus

Close GH-280: PureMVC version (this time with routing). Fixes #77

parent 3cdea4c1
## [PureMVC](http://puremvc.github.com/) [JavaScript](https://github.com/PureMVC/puremvc-js-multicore-framework/wiki) Demo - TodoMVC
This demo is a PureMVC port of the [TodoMVC Project](http://todomvc.com), a JavaScript application framework comparison demo. Given a standard html template, css, and a plain vanilla JavaScript reference app (no framework), developers are challenged to produce implementations base upon their favorite framework.
The pseudo-classes are written in PureMVC's optional built-in style.
* [Live Demo](http://darkstar.puremvc.org/content_header.html?url=http://puremvc.org/pages/demos/JS/Demo_JS_TodoMVC/&desc=PureMVC%20JavaScript%20Demo:%20TodoMVC)
* [Discussion](http://forums.puremvc.org/index.php?topic=2049.0)
## Screenshot
![PureMVC JavaScript Demo: TodoMVC](http://puremvc.org/pages/images/screenshots/PureMVC-Shot-JS-TodoMVC.png)
## Status
Production - [Version 1.2](https://github.com/PureMVC/puremvc-js-demo-todomvc/blob/master/VERSION)
## Platforms / Technologies
* [JavaScript](http://en.wikipedia.org/wiki/JavaScript)
* [TodoMVC Project](http://todomvc.com)
## License
* Original TodoMVC Demo - Copyright (c) Addy Osmani & Sindre Sorhus
* TodoMVC port to PureMVC - Copyright (c) 2012 Mike Britton and Cliff Hall
* PureMVC Framework - Copyright(c) 2006-2012 [Futurescale, Inc](http://futurescale.com).
All rights reserved.
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Neither the name of Futurescale, Inc., PureMVC.org, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>PureMVC • TodoMVC</title>
<link rel="stylesheet" href="../../../assets/base.css">
<!--[if IE]>
<script src="assets/ie.js"></script>
<![endif]-->
</head>
<body>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
</header>
<section id="main">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
</ul>
</section>
<footer id="footer">
<span id="todo-count"><strong>1</strong> item left</span>
<ul id="filters">
<li>
<a id="filterAll" class="selected" href="#/">All</a>
</li>
<li>
<a id="filterActive" href="#/active">Active</a>
</li>
<li>
<a id="filterCompleted" href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed">Clear completed (1)</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://puremvc.org">Mike Britton and Cliff Hall for the PureMVC Project</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<!-- TODOMVC PROJECT BASE -->
<script src="../../../assets/base.js"></script>
<!-- FLATIRION DIRECTOR ROUTING LIBRARY -->
<script src="../../../assets/director.min.js"></script>
<!-- PUREMVC LIBRARY -->
<script src="js/lib/puremvc-1.0.1.min.js"></script>
<!-- APPLICATION CONSTANTS -->
<script src="js/AppConstants.js"></script>
<!-- PROXIES -->
<script src="js/model/proxy/TodoProxy.js"></script>
<!-- EVENTS -->
<script src="js/view/event/AppEvents.js"></script>
<!-- VIEW COMPONENTS -->
<script src="js/view/component/TodoForm.js"></script>
<!-- MEDIATORS -->
<script src="js/view/mediator/RoutesMediator.js"></script>
<script src="js/view/mediator/TodoFormMediator.js"></script>
<!-- COMMANDS -->
<script src="js/controller/command/StartupCommand.js"></script>
<script src="js/controller/command/PrepControllerCommand.js"></script>
<script src="js/controller/command/PrepModelCommand.js"></script>
<script src="js/controller/command/PrepViewCommand.js"></script>
<script src="js/controller/command/TodoCommand.js"></script>
<!-- APPLICATION -->
<script src="js/app.js"></script>
<!-- START THE APPLICATION -->
<script>
document.addEventListener('DOMContentLoaded', function()
{
var app = new todomvc.Application();
});
</script>
</body>
</html>
/**
* @author Mike Britton
*
* @class AppConstants
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*
* Define the core and notification constants.
*
* PureMVC JS is multi-core, meaning you may have multiple,
* named and isolated PureMVC cores. This app only has one.
*/
puremvc.define({ name: 'todomvc.AppConstants' },{}, {
// The multiton key for this app's single core
CORE_NAME: 'TodoMVC',
// Notifications
STARTUP: 'startup',
ADD_TODO: 'add_todo',
DELETE_TODO: 'delete_todo',
UPDATE_TODO: 'update_todo',
TOGGLE_TODO_STATUS: 'toggle_todo_status',
REMOVE_TODOS_COMPLETED: 'remove_todos_completed',
FILTER_TODOS: 'filter_todos',
TODOS_FILTERED: 'todos_filtered',
// Filter routes
FILTER_ALL: 'all',
FILTER_ACTIVE: 'active',
FILTER_COMPLETED: 'completed'
}
);
/**
* @author Mike Britton
*
* @class todomvc.Application
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*/
puremvc.define({
name: 'todomvc.Application',
constructor: function() {
// register the startup command and trigger it.
this.facade.registerCommand( todomvc.AppConstants.STARTUP, todomvc.controller.command.StartupCommand );
this.facade.sendNotification( todomvc.AppConstants.STARTUP );
}
},
// INSTANCE MEMBERS
{
// Define the startup notification name
STARTUP: 'startup',
// Get an instance of the PureMVC Facade. This creates the Model, View, and Controller instances.
facade: puremvc.Facade.getInstance( todomvc.AppConstants.CORE_NAME )
}
);
/**
* @author Mike Britton, Cliff Hall
*
* @class PrepControllerCommand
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*/
puremvc.define({
name: 'todomvc.controller.command.PrepControllerCommand',
parent: puremvc.SimpleCommand
},
// INSTANCE MEMBERS
{
/**
* Register Commands with the Controller
* @override
*/
execute: function (note) {
// This registers multiple notes to a single command which performs different logic based on the note name.
// In a more complex app, we'd usually be registering a different command to each notification name.
this.facade.registerCommand( todomvc.AppConstants.ADD_TODO, todomvc.controller.command.TodoCommand );
this.facade.registerCommand( todomvc.AppConstants.REMOVE_TODOS_COMPLETED, todomvc.controller.command.TodoCommand );
this.facade.registerCommand( todomvc.AppConstants.DELETE_TODO, todomvc.controller.command.TodoCommand );
this.facade.registerCommand( todomvc.AppConstants.UPDATE_TODO, todomvc.controller.command.TodoCommand );
this.facade.registerCommand( todomvc.AppConstants.TOGGLE_TODO_STATUS, todomvc.controller.command.TodoCommand );
this.facade.registerCommand( todomvc.AppConstants.FILTER_TODOS, todomvc.controller.command.TodoCommand );
}
}
);
/**
* @author Mike Britton
*
* @class PrepModelCommand
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*/
puremvc.define({
name: 'todomvc.controller.command.PrepModelCommand',
parent: puremvc.SimpleCommand
},
// INSTANCE MEMBERS
{
/**
* Register Proxies with the Model
* @override
*/
execute: function (note) {
this.facade.registerProxy( new todomvc.model.proxy.TodoProxy() );
}
}
);
/**
* @author Mike Britton
*
* @class PrepViewCommand
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*/
puremvc.define ({
name: 'todomvc.controller.command.PrepViewCommand',
parent: puremvc.SimpleCommand
},
// INSTANCE MEMBERS
{
/**
* Register Mediators with the View
* @override
*/
execute: function (note) {
this.facade.registerMediator( new todomvc.view.mediator.TodoFormMediator() );
this.facade.registerMediator( new todomvc.view.mediator.RoutesMediator() );
}
}
);
/**
* @author Mike Britton
*
* @class StartupCommand
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*/
puremvc.define({
name: 'todomvc.controller.command.StartupCommand',
parent: puremvc.MacroCommand
},
// INSTANCE MEMBERS
{
/**
* Add the sub-commands for this MacroCommand
* @override
*/
initializeMacroCommand: function () {
this.addSubCommand( todomvc.controller.command.PrepControllerCommand );
this.addSubCommand( todomvc.controller.command.PrepModelCommand );
this.addSubCommand( todomvc.controller.command.PrepViewCommand );
}
}
);
/**
* @author Mike Britton, Cliff Hall
*
* @class TodoCommand
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*/
puremvc.define ({
name: 'todomvc.controller.command.TodoCommand',
parent: puremvc.SimpleCommand
},
// INSTANCE MEMBERS
{
/**
* Perform business logic (in this case, based on Notification name)
* @override
*/
execute: function ( note ) {
var proxy = this.facade.retrieveProxy( todomvc.model.proxy.TodoProxy.NAME );
switch( note.getName() ) {
case todomvc.AppConstants.ADD_TODO:
proxy.addTodo( note.getBody() );
break;
case todomvc.AppConstants.DELETE_TODO:
proxy.deleteTodo( note.getBody() );
break;
case todomvc.AppConstants.UPDATE_TODO:
proxy.updateTodo( note.getBody() );
break;
case todomvc.AppConstants.TOGGLE_TODO_STATUS:
proxy.toggleCompleteStatus( note.getBody() );
break;
case todomvc.AppConstants.REMOVE_TODOS_COMPLETED:
proxy.removeTodosCompleted();
break;
case todomvc.AppConstants.FILTER_TODOS:
proxy.filterTodos( note.getBody() );
break;
default:
console.log('TodoCommand received an unsupported Notification');
break;
}
}
}
);
This diff is collapsed.
/**
* @author Mike Britton, Cliff Hall
*
* @class TodoProxy
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*
*/
puremvc.define({
name: 'todomvc.model.proxy.TodoProxy',
parent: puremvc.Proxy
},
// INSTANCE MEMBERS
{
todos: [],
stats: {},
filter: todomvc.AppConstants.FILTER_ALL,
LOCAL_STORAGE: 'todos-puremvc',
onRegister: function() {
this.loadData();
},
loadData: function() {
var storageObject;
if ( !localStorage.getItem( this.LOCAL_STORAGE ) ) {
this.saveData();
}
storageObject = JSON.parse( localStorage.getItem( this.LOCAL_STORAGE ) );
this.todos = storageObject.todos;
this.filter = storageObject.filter;
this.computeStats();
},
saveData: function() {
var storageObject = { todos:this.todos, filter:this.filter };
localStorage.setItem( this.LOCAL_STORAGE, JSON.stringify( storageObject ) );
},
computeStats: function() {
this.stats.totalTodo = this.todos.length;
this.stats.todoCompleted = this.getCompletedCount();
this.stats.todoLeft = this.stats.totalTodo - this.stats.todoCompleted;
},
filterTodos: function ( filter ) {
var i;
this.filter = filter;
this.saveData();
i = this.todos.length,
filtered = [];
while ( i-- ) {
if ( filter === todomvc.AppConstants.FILTER_ALL ) {
filtered.push( this.todos[ i ] );
} else if ( this.todos[i].completed === true && filter === todomvc.AppConstants.FILTER_COMPLETED ) {
filtered.push( this.todos[ i ] );
} else if ( this.todos[i].completed === false && filter === todomvc.AppConstants.FILTER_ACTIVE ) {
filtered.push( this.todos[ i ] );
}
}
this.sendNotification( todomvc.AppConstants.TODOS_FILTERED, { todos:filtered, stats:this.stats, filter:this.filter } );
},
todosModified: function() {
this.computeStats();
this.filterTodos( this.filter );
},
removeTodosCompleted: function() {
var i = this.todos.length;
while ( i-- ) {
if ( this.todos[ i ].completed ) {
this.todos.splice( i, 1 );
}
}
this.todosModified();
},
deleteTodo: function( id ) {
var i = this.todos.length;
while ( i-- ) {
if ( this.todos[i].id === id ) {
this.todos.splice(i, 1);
}
}
this.todosModified();
},
toggleCompleteStatus: function( status ) {
var i = this.todos.length;
while ( i-- ) {
this.todos[ i ].completed = status;
}
this.todosModified();
},
updateTodo: function( todo ) {
var i = this.todos.length;
while ( i-- ) {
if ( this.todos[ i ].id === todo.id ) {
this.todos[ i ].title = todo.title;
this.todos[ i ].completed = todo.completed;
}
}
this.todosModified();
},
addTodo: function( newTodo ) {
newTodo.id = this.getUuid();
this.todos.push( newTodo );
this.todosModified();
},
getCompletedCount: function() {
var i = this.todos.length,
completed = 0;
while ( i-- ) {
if ( this.todos[ i ].completed ) {
completed++;
}
}
return completed;
},
getUuid: function() {
var i, random, uuid = '';
for ( i = 0; i < 32; i++ ) {
random = Math.random() * 16 | 0;
if ( i === 8 || i === 12 || i === 16 || i === 20 ) {
uuid += '-';
}
uuid += ( i === 12 ? 4 : (i === 16 ? ( random & 3 | 8 ) : random) ).toString( 16 );
}
return uuid;
}
},
// CLASS MEMBERS
{
NAME: 'TodoProxy'
}
);
/**
* @author Cliff Hall
*
* @class AppEvents
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*/
puremvc.define({ name: 'todomvc.view.event.AppEvents' }, {},
// STATIC MEMBERS
{
// Event name constants
TOGGLE_COMPLETE_ALL: 'toggle_complete_all',
TOGGLE_COMPLETE: 'toggle_complete',
CLEAR_COMPLETED: 'clear_completed',
DELETE_ITEM: 'delete_item',
UPDATE_ITEM: 'update_item',
ADD_ITEM: 'add_item',
// Create event (cross-browser)
createEvent: function( eventName ) {
var event;
if( document.createEvent ) {
event = document.createEvent( 'Events' );
event.initEvent( eventName, false, false );
} else if ( document.createEventObject ) {
event = document.createEventObject();
}
return event;
},
// Add event listener (cross-browser)
addEventListener: function( object, type, listener, useCapture ) {
if ( object.addEventListener ) {
object.addEventListener( type, listener, useCapture );
} else if ( object.attachEvent ) {
object.attachEvent( type, listener );
}
},
// Dispatch event (cross-browser)
dispatchEvent: function( object, event ) {
if ( object.dispatchEvent ) {
object.dispatchEvent( event );
} else if ( object.fireEvent ) {
object.fireEvent( event.type, event );
}
},
}
);
/**
* @author Cliff Hall
*
* @class RoutesMediator
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*/
puremvc.define({
name: 'todomvc.view.mediator.RoutesMediator',
parent: puremvc.Mediator
},
// INSTANCE MEMBERS
{
// the router (Flatirion Director)
router: null,
// setup the routes when mediator is registered
onRegister: function() {
var todoProxy = this.facade.retrieveProxy( todomvc.model.proxy.TodoProxy.NAME ),
defaultRoute = this.getRouteForFilter( todoProxy.filter ),
options = { resource:this, notfound:this.handleFilterAll },
routes = {
'/': this.handleFilterAll,
'/active': this.handleFilterActive,
'/completed': this.handleFilterCompleted
};
this.router = new Router( routes ).configure( options );
this.router.init( defaultRoute );
},
getRouteForFilter: function( filter ) {
var route;
switch (filter) {
case todomvc.AppConstants.FILTER_ALL:
route = '/';
break;
case todomvc.AppConstants.FILTER_ACTIVE:
route = '/active';
break;
case todomvc.AppConstants.FILTER_COMPLETED:
route = '/completed';
break;
}
return route;
},
// route handlers
handleFilterAll: function () {
this.resource.facade.sendNotification( todomvc.AppConstants.FILTER_TODOS, todomvc.AppConstants.FILTER_ALL );
},
handleFilterActive: function () {
this.resource.facade.sendNotification( todomvc.AppConstants.FILTER_TODOS, todomvc.AppConstants.FILTER_ACTIVE );
},
handleFilterCompleted: function () {
this.resource.facade.sendNotification( todomvc.AppConstants.FILTER_TODOS, todomvc.AppConstants.FILTER_COMPLETED );
},
},
// STATIC MEMBERS
{
NAME: 'RoutesMediator'
}
);
/**
* @author Mike Britton
*
* @class TodoFormMediator
* @link https://github.com/PureMVC/puremvc-js-demo-todomvc.git
*/
puremvc.define({
name: 'todomvc.view.mediator.TodoFormMediator',
parent: puremvc.Mediator
},
// INSTANCE MEMBERS
{
// Notifications this mediator is interested in
listNotificationInterests: function() {
return [ todomvc.AppConstants.TODOS_FILTERED ];
},
// Code to be executed when the Mediator instance is registered with the View
onRegister: function() {
this.setViewComponent( new todomvc.view.component.TodoForm );
this.viewComponent.addEventListener( todomvc.view.event.AppEvents.TOGGLE_COMPLETE, this );
this.viewComponent.addEventListener( todomvc.view.event.AppEvents.TOGGLE_COMPLETE_ALL, this );
this.viewComponent.addEventListener( todomvc.view.event.AppEvents.UPDATE_ITEM, this );
this.viewComponent.addEventListener( todomvc.view.event.AppEvents.DELETE_ITEM, this );
this.viewComponent.addEventListener( todomvc.view.event.AppEvents.ADD_ITEM, this );
this.viewComponent.addEventListener( todomvc.view.event.AppEvents.CLEAR_COMPLETED, this );
},
// Handle events from the view component
handleEvent: function ( event ) {
switch( event.type ) {
case todomvc.view.event.AppEvents.TOGGLE_COMPLETE_ALL:
this.sendNotification( todomvc.AppConstants.TOGGLE_TODO_STATUS, event.doToggleComplete );
break;
case todomvc.view.event.AppEvents.DELETE_ITEM:
this.sendNotification( todomvc.AppConstants.DELETE_TODO, event.todoId );
break;
case todomvc.view.event.AppEvents.ADD_ITEM:
this.sendNotification( todomvc.AppConstants.ADD_TODO, event.todo );
break;
case todomvc.view.event.AppEvents.CLEAR_COMPLETED:
this.sendNotification( todomvc.AppConstants.REMOVE_TODOS_COMPLETED );
break;
case todomvc.view.event.AppEvents.TOGGLE_COMPLETE:
case todomvc.view.event.AppEvents.UPDATE_ITEM:
this.sendNotification( todomvc.AppConstants.UPDATE_TODO, event.todo );
break;
}
},
// Handle notifications from other PureMVC actors
handleNotification: function( note ) {
switch ( note.getName() ) {
case todomvc.AppConstants.TODOS_FILTERED:
this.viewComponent.setFilteredTodoList( note.getBody() );
break;
}
},
},
// STATIC MEMBERS
{
NAME: 'TodoFormMediator'
}
);
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