Commit 5945668e authored by Rhsy's avatar Rhsy Committed by Sindre Sorhus

Close GH-286: update PlastronJS example.

parent d075d120
......@@ -4,6 +4,13 @@
<meta charset="utf-8">
<title>PlastronJS • TodoMVC</title>
<link rel="stylesheet" href="../../../assets/base.css">
<style type="text/css">
#filters.none li.none,
#filters.active li.active,
#filters.completed li.completed {
font-weight: bold;
}
</style>
<!--[if IE]>
<script src="../../../assets/ie.js"></script>
<![endif]-->
......@@ -21,14 +28,14 @@
</section>
<footer id="footer">
<span id="todo-count"><strong>0</strong> item left</span>
<ul id="filters">
<li>
<a class="selected" href="#/">All</a>
<ul id="filters" class="none">
<li class="none">
<a href="#/">All</a>
</li>
<li>
<li class="active">
<a href="#/active">Active</a>
</li>
<li>
<li class="completed">
<a href="#/completed">Completed</a>
</li>
</ul>
......
goog.require('goog.dom');
goog.require('goog.dom.classes');
goog.require('mvc.Collection');
goog.require('mvc.Router');
goog.require('todomvc.listcontrol');
goog.require('todomvc.listmodel');
......@@ -10,38 +7,22 @@ var todolist = new todomvc.listmodel();
// Create the control for the collection.
var todolistControl = new todomvc.listcontrol( todolist );
// HTML already there so use decorate.
todolistControl.decorate( goog.dom.getElement('todoapp') );
// Setup router
var router = new mvc.Router();
/**
* toggles selected class on filters list
*
* @param {string} chosenFilter selected filter by name.
*/
var toggleFilters = function( chosenFilter ) {
var filters = goog.dom.getElementsByTagNameAndClass( 'A', undefined,
goog.dom.getElement('filters') );
goog.array.forEach( filters, function( filter ) {
goog.dom.classes.enable( filter, 'selected',
goog.dom.getTextContent( filter ) === chosenFilter );
});
};
router.route( '/', function() {
todolistControl.setFilter( todomvc.listcontrol.Filter.ALL );
toggleFilters('All');
router.route( '{/}', function() {
todolist.set( 'filter', 'none' );
});
router.route( '/active', function() {
todolistControl.setFilter( todomvc.listcontrol.Filter.ACTIVE );
toggleFilters('Active');
todolist.set( 'filter', 'active' );
});
router.route( '/completed', function() {
todolistControl.setFilter( todomvc.listcontrol.Filter.COMPLETED );
toggleFilters('Completed');
todolist.set( 'filter', 'completed' );
});
goog.provide('todomvc.listcontrol');
goog.require('goog.dom');
goog.require('goog.events.KeyCodes');
goog.require('goog.string');
goog.require('mvc.Control');
......@@ -17,27 +16,10 @@ goog.require('todomvc.todocontrol');
*/
todomvc.listcontrol = function( list ) {
goog.base( this, list );
this.filter_ = todomvc.listcontrol.Filter.ALL;
};
goog.inherits( todomvc.listcontrol, mvc.Control );
/**
* @enum {Function}
*/
todomvc.listcontrol.Filter = {
ALL: function() {
return true
},
ACTIVE: function( model ) {
return !model.get('completed')
},
COMPLETED: function( model ) {
return model.get('completed')
}
};
/**
* setup for event listeners.
*
......@@ -48,126 +30,80 @@ todomvc.listcontrol.prototype.enterDocument = function() {
var list = /** @type {Object} */(this.getModel());
// Create new model from text box
var input = this.getEls('input')[0];
this.on( goog.events.EventType.KEYUP, function( e ) {
// handle new note entry
this.on( goog.events.EventType.KEYUP, this.handleNewInput, '.todo-entry' );
// On return get trimmed text
if ( e.keyCode !== goog.events.KeyCodes.ENTER ) {
return;
}
var text = goog.string.trim( input.value );
if ( !text) {
return;
}
// Create new model
list.newModel({
'title': text
// update complete button based on completed
this.autobind('#clear-completed', {
template: 'Clear completed ({$completed})',
noClick: true, // click should not set completed
show: true // hide when completed == false
});
input.value = '';
}, 'todo-entry' );
// Clear completed
this.click(function( e ) {
goog.array.forEach( list.get('completed'), function( model ) {
this.click( function() {
goog.array.forEach( list.getModels( 'completed' ),
function( model ) {
model.dispose();
});
}, 'clear-completed' );
}, '.clear-completed' );
// Toggle completed
this.click(function( e ) {
var checked = e.target.checked;
goog.array.forEach( list.getModels(), function( model ) {
model.set( 'completed', checked );
// when to check the check all
this.autobind('.toggle-all', {
reqs: 'allDone'
});
}, 'toggle-all' );
// Refresh the view on changes that effect the models order
this.anyModelChange(function() {
this.refresh();
list.save();
}, this );
// Toggle footer and main body
this.modelChange(function() {
this.showMainFooter( !!list.getLength() );
}, this );
this.showMainFooter( !!list.getLength() );
// change classes of ULs based on filter
this.autobind( 'ul', {
reqs: 'filter',
reqClass: ['active', 'completed', 'none']
} );
// Update counts
this.bind( 'completed', function( completedModels ) {
// Update "left" count
soy.renderElement(goog.dom.getElement('todo-count'),
todomvc.templates.itemsLeft, {
left: list.getLength() - completedModels.length
// update the count based on active
this.autobind('#todo-count', {
template: todomvc.templates.itemsLeft,
reqs: 'active'
});
// Update clear button
var clearButton = goog.dom.getElement('clear-completed');
goog.dom.setTextContent( clearButton,
'Clear completed (' + completedModels.length + ')' );
goog.style.showElement( clearButton, completedModels.length );
// Update checkbox
var checkBox = this.getEls('.toggle-all')[0];
checkBox.checked = completedModels.length === list.getLength();
// show or hide based on the totals
this.autobind(['#main', 'footer'], {
show: 'total',
noClick: true
});
// Get the saved todos
list.fetch();
};
// autolists on modelChange and return refresh function
var refresh = this.autolist( todomvc.todocontrol,
goog.dom.getElement('todo-list') ).fire;
/**
* show or hide the footer.
*
* @param {boolean=} opt_hide whether to hide the footer.
*/
todomvc.listcontrol.prototype.showMainFooter = function( opt_hide ) {
var main = goog.dom.getElement('main');
var footer = goog.dom.getElementsByTagNameAndClass('footer')[0];
// if filter changes refresh view
this.bind( 'filter', refresh );
goog.style.showElement( main, opt_hide );
goog.style.showElement( footer, opt_hide );
// if anything changes save models and refresh view
this.anyModelChange( refresh );
};
/**
* sets the function to determine which children are returned by the control.
*
* @param {Function} filter to decide models returned.
* adds the input as a new item
*/
todomvc.listcontrol.prototype.setFilter = function( filter ) {
this.filter_ = filter;
this.refresh();
};
todomvc.listcontrol.prototype.handleNewInput = function( e ) {
var input = e.target;
// On return get trimmed text
if ( e.keyCode !== goog.events.KeyCodes.ENTER ) {
return;
}
/**
* refreshes the view of the childen.
*/
todomvc.listcontrol.prototype.refresh = function() {
var text = goog.string.trim( input.value );
if ( !text ) {
return;
}
// Dispose and remove all the children.
this.forEachChild(function( child ) {
child.dispose();
// Create new model
this.getModel().newModel({
'title': text
});
this.removeChildren( true );
// Create new controls for the models
goog.array.forEach( this.getModel().getModels(this.filter_),
function( model ) {
var newModelControl = new todomvc.todocontrol( model );
this.addChild( newModelControl );
newModelControl.render( goog.dom.getElement('todo-list') );
}, this );
input.value = '';
};
......@@ -15,7 +15,6 @@ goog.require('todomvc.templates');
*/
todomvc.todocontrol = function( model ) {
goog.base( this, model );
};
goog.inherits( todomvc.todocontrol, mvc.Control );
......@@ -27,10 +26,7 @@ goog.inherits( todomvc.todocontrol, mvc.Control );
* @inheritDoc
*/
todomvc.todocontrol.prototype.createDom = function() {
var el = soy.renderAsFragment( todomvc.templates.todoItem, {
model: this.getModel().toJson()
}, null );
var el = soy.renderAsElement( todomvc.templates.todoItem, null, null );
this.setElementInternal(/** @type {Element} */(el));
};
......@@ -45,31 +41,35 @@ todomvc.todocontrol.prototype.enterDocument = function() {
var model = this.getModel();
// Toggle complete
this.click(function( e ) {
model.set( 'completed', e.target.checked );
}, 'toggle' );
this.autobind('.toggle', '{$completed}');
// Delete the model
this.click(function( e ) {
model.dispose();
}, 'destroy' );
}, '.destroy' );
// keep label inline with title
this.autobind( 'label', '{$title}')
var inputEl = this.getEls('.edit')[0];
// Dblclick to edit
this.on( goog.events.EventType.DBLCLICK, function( e ) {
goog.dom.classes.add( this.getElement(), 'editing' );
inputEl.value = model.get('title');
inputEl.focus();
}, 'view' );
}, '.view' );
// Save on edit
// blur on enter
this.on( goog.events.EventType.KEYUP, function( e ) {
if ( e.keyCode === goog.events.KeyCodes.ENTER ) {
model.set( 'title', inputEl.value );
e.target.blur();
}
}, 'edit' );
});
// finish editing on blur
this.on( goog.events.EventType.BLUR, function( e ) {
model.set( 'title', inputEl.value );
}, 'edit' );
goog.dom.classes.remove( this.getElement(), 'editing' );
});
// bind the title and the edit input
this.autobind('.edit', '{$title}');
};
PlastronJS @ 3a58c34c
Subproject commit 3a58c34c673ae0c5ba60eda5727f0d8332cb7fcf
......@@ -12,13 +12,37 @@ goog.require('todomvc.todomodel');
todomvc.listmodel = function() {
var todosSchema = {
// number of completed
'completed': {
get: function() {
return this.getModels(function( mod ) {
return mod.get('completed');
return this.getModels( 'completed' ).length;
},
models: true
},
'allDone': {
get: function() {
return this.getLength() == this.getModels( 'completed' ).length;
},
set: function( done ) {
goog.array.forEach( this.getModels( 'none' ), function( model ) {
model.set( 'completed', done );
});
},
models: true
},
// number of active models
'active': {
get: function() {
return this.getLength() - this.getModels( 'completed' ).length;
},
models: true
},
// the total
'total': {
get: function() {
return this.getLength();
},
models: true
}
};
......@@ -28,15 +52,45 @@ todomvc.listmodel = function() {
'schema': todosSchema,
'modelType': todomvc.todomodel
});
// fetch from localstorage
this.fetch();
// save on any changes
this.anyModelChange( this.save );
};
goog.inherits( todomvc.listmodel, mvc.Collection );
todomvc.listmodel.Filter = {
'none': function() {
return true
},
'active': function( model ) {
return !model.get('completed')
},
'completed': function( model ) {
return model.get('completed')
}
};
/**
* return models based on current filter or filter given
*
* @inheritDoc
*/
todomvc.listmodel.prototype.getModels = function(opt_filter) {
return goog.base(this, 'getModels',
todomvc.listmodel.Filter[ opt_filter || this.get( 'filter' ) ] );
};
/**
* @return {Object} todos as json.
*/
todomvc.listmodel.prototype.toJson = function() {
return goog.array.map( this.getModels(), function( mod ) {
return goog.array.map( this.getModels( 'none' ), function( mod ) {
return mod.toJson();
});
};
......@@ -13,6 +13,7 @@ goog.require('mvc.Model.ValidateError');
todomvc.todomodel = function( opt_options ) {
goog.base( this, opt_options );
// title must have a length, also format to remove spaces
this.setter( 'title', function( title ) {
var updated = goog.string.trim( title );
......@@ -23,6 +24,7 @@ todomvc.todomodel = function( opt_options ) {
return updated;
});
// when a note title is no longer valid then remove it
this.errorHandler(function() {
this.dispose();
});
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -5,19 +5,5 @@
"output-wrapper": "(function(){%output%})();",
"mode": "ADVANCED",
"level": "VERBOSE",
"output-file": "js/compiled.js",
"define": {
"goog.LOCALE": "en_GB"
},
"checks": {
"checkRegExp": "WARNING",
"checkTypes": "WARNING",
"checkVars": "WARNING",
"deprecated": "WARNING",
"fileoverviewTags": "WARNING",
"invalidCasts": "WARNING",
"missingProperties": "WARNING",
"nonStandardJsDocs": "WARNING",
"undefinedVars": "WARNING"
}
"output-file": "js/compiled.js"
}
{namespace todomvc.templates}
/**
* @param model
*/
{template .todoItem}
<li {if $model['completed']}class="completed"{/if}>
<li>
<div class="view">
<input class="toggle" type="checkbox" {if $model['completed']}checked{/if}>
<label>{$model['title']}</label>
<input class="toggle" type="checkbox">
<label></label>
<button class="destroy"></button>
</div>
<input class="edit">
<input class="edit" type="text">
</li>
{/template}
/**
* @param left
* use template for the 's' logic.
* Used in autobind so gets data under model namespace.
*
* @param model
*/
{template .itemsLeft}
<strong>{$left}</strong> item{if $left != 1}s{/if} left
<strong>{$model['active']}</strong> item{if $model['active'] != 1}s{/if} left
{/template}
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