Commit 00435de1 authored by Addy Osmani's avatar Addy Osmani

Merge pull request #352 from petermichaux/gh-pages

Rewrite Maria example to use latest TodoMVC requirements
parents 0d6e84dd 6f5dad67
/*
Aristocrat version 1
Aristocrat version 2
Copyright (c) 2012, Peter Michaux
All rights reserved.
Licensed under the Simplified BSD License.
https://github.com/petermichaux/aristocrat/blob/master/LICENSE
*/
var aristocrat = aristocrat || {};
var aristocrat = {};
(function() {
......@@ -78,7 +78,7 @@ aristocrat.removeClass(document.body, 'king');
var re = getRegExp(className);
while (re.test(el.className)) { // in case multiple occurrences
el.className = el.className.replace(re, ' ');
}
}
};
/**
......
......@@ -4,7 +4,7 @@ Copyright (c) 2012, Peter Michaux
All rights reserved.
Licensed under the Simplified BSD License.
https://github.com/petermichaux/evento/blob/master/LICENSE
*/var evento = evento || {};
*/var evento = {};
/**
@property evento.EventTarget
......@@ -186,7 +186,7 @@ et.dispatchEvent({type:'change', extraData:'abc'});
*/
evento.EventTarget.prototype.dispatchEvent = function(evt) {
// Want to ensure we don't alter the evt object passed in as it
// Want to ensure we don't alter the evt object passed in as it
// may be a bubbling event. So clone it and then setting currentTarget
// won't break some event that is already being dispatched.
evt = create(evt);
......@@ -204,9 +204,9 @@ et.dispatchEvent({type:'change', extraData:'abc'});
//
// Without making a copy, one listener removing
// an already-called listener would result in skipping
// a not-yet-called listener. One listener removing
// a not-yet-called listener. One listener removing
// a not-yet-called listener would result in skipping that
// not-yet-called listner. The worst case scenario
// not-yet-called listner. The worst case scenario
// is a listener adding itself again which would
// create an infinite loop.
//
......@@ -285,14 +285,14 @@ o.dispatchEvent({type:'change'});
(typeof pt[p] === 'function')) {
obj[p] = pt[p];
}
}
}
evento.EventTarget.call(obj);
};
}());
/**
@property evento.addEventListener
@property evento.on
@parameter element {EventTarget} The object you'd like to observe.
......@@ -324,27 +324,27 @@ var o = {
};
// late binding. handleEvent is found when each event is dispatched
evento.addEventListener(document.body, 'click', o);
evento.on(document.body, 'click', o);
// late binding. handleClick is found when each event is dispatched
evento.addEventListener(document.body, 'click', o, 'handleClick');
evento.on(document.body, 'click', o, 'handleClick');
// early binding. The supplied function is bound now
evento.addEventListener(document.body, 'click', o, o.handleClick);
evento.addEventListener(document.body, 'click', o, function(){});
evento.on(document.body, 'click', o, o.handleClick);
evento.on(document.body, 'click', o, function(){});
// supplied function will be called with document.body as this object
evento.addEventListener(document.body, 'click', function(){});
evento.on(document.body, 'click', function(){});
// The following form is supported but is not neccessary given the options
// above and it is recommended you avoid it.
evento.addEventListener(document.body, 'click', this.handleClick, this);
evento.on(document.body, 'click', this.handleClick, this);
*/
/**
@property evento.removeEventListener
@property evento.off
@parameter element {EventTarget} The object you'd like to stop observing.
......@@ -360,32 +360,32 @@ Removes added listener matching the element/type/listener/auxArg combination exa
If this combination is not found there are no errors.
var o = {handleEvent:function(){}, handleClick:function(){}};
evento.removeEventListener(document.body, 'click', o);
evento.removeEventListener(document.body, 'click', o, 'handleClick');
evento.removeEventListener(document.body, 'click', o, fn);
evento.removeEventListener(document.body, 'click', fn);
evento.removeEventListener(document.body, 'click', this.handleClick, this);
evento.off(document.body, 'click', o);
evento.off(document.body, 'click', o, 'handleClick');
evento.off(document.body, 'click', o, fn);
evento.off(document.body, 'click', fn);
evento.off(document.body, 'click', this.handleClick, this);
*/
/**
@property evento.purgeEventListener
@property evento.purge
@parameter listener {EventListener} The listener object that should stop listening.
@description
Removes all registrations of the listener added through evento.addEventListener.
Removes all registrations of the listener added through evento.on.
This purging should be done before your application code looses its last reference
to listener. (This can also be done with more work using evento.removeEventListener for
to listener. (This can also be done with more work using evento.off for
each registeration.) If the listeners are not removed or purged, the listener
will continue to observe the EventTarget and cannot be garbage collected. In an
MVC application this can lead to "zombie views" if the model data cannot be
garbage collected. Event listeners need to be removed from event targets in browsers
with circular reference memory leak problems (i.e. old versions of Internet Explorer.)
The primary motivation for this purge function is to easy cleanup in MVC View destroy
The primary motivation for this purge function is to easy cleanup in MVC View destroy
methods. For example,
var APP_BoxView = function(model, controller) {
......@@ -397,8 +397,8 @@ var APP_BoxView = function(model, controller) {
// implementing the EventTarget interface using listener objects
// and specifying method name using the same subscription interface.
//
evento.addEventListener(this.rootEl, 'click', this, 'handleClick');
evento.addEventListener(this.model, 'change', this, 'handleModelChange');
evento.on(this.rootEl, 'click', this, 'handleClick');
evento.on(this.model, 'change', this, 'handleModelChange');
};
APP_BoxView.prototype.handleClick = function() {
......@@ -415,7 +415,7 @@ APP_BoxView.prototype.destroy = function() {
// to DOM nodes, model objects, or anything else implementing
// the EventTarget interface in one fell swoop.
//
evento.purgeEventListener(this);
evento.purge(this);
};
*/
......@@ -471,7 +471,7 @@ APP_BoxView.prototype.destroy = function() {
return -1;
}
evento.addEventListener = function(element, type, listener, /*optional*/ auxArg) {
evento.on = function(element, type, listener, /*optional*/ auxArg) {
// Want to call createBundle with the same number of arguments
// that were passed to this function. Using apply preserves
// the number of arguments.
......@@ -480,45 +480,45 @@ APP_BoxView.prototype.destroy = function() {
if (indexOfBundle(listener._evento_bundles, bundle) >= 0) {
// do not add the same listener twice
return;
}
}
}
else {
listener._evento_bundles = [];
}
if (typeof bundle.element.addEventListener === 'function') {
bundle.element.addEventListener(bundle.type, bundle.wrappedHandler, false);
bundle.element.addEventListener(bundle.type, bundle.wrappedHandler, false);
}
else if ((typeof bundle.element.attachEvent === 'object') &&
(bundle.element.attachEvent !== null)) {
bundle.element.attachEvent('on'+bundle.type, bundle.wrappedHandler);
}
else {
throw new Error('evento.addEventListener: Supported EventTarget interface not found.');
throw new Error('evento.on: Supported EventTarget interface not found.');
}
listener._evento_bundles.push(bundle);
};
var remove = evento.removeEventListener = function(element, type, listener, /*optional*/ auxArg) {
var remove = evento.off = function(element, type, listener, /*optional*/ auxArg) {
if (listener._evento_bundles) {
var i = indexOfBundle(listener._evento_bundles, createBundle.apply(null, arguments));
if (i >= 0) {
var bundle = listener._evento_bundles[i];
if (typeof bundle.element.removeEventListener === 'function') {
bundle.element.removeEventListener(bundle.type, bundle.wrappedHandler, false);
}
}
else if ((typeof bundle.element.detachEvent === 'object') &&
(bundle.element.detachEvent !== null)) {
bundle.element.detachEvent('on'+bundle.type, bundle.wrappedHandler);
}
}
else {
throw new Error('evento.removeEventListener: Supported EventTarget interface not found.');
}
throw new Error('evento.off: Supported EventTarget interface not found.');
}
listener._evento_bundles.splice(i, 1);
}
}
};
evento.purgeEventListener = function(listener) {
evento.purge = function(listener) {
if (listener._evento_bundles) {
var bundles = listener._evento_bundles.slice(0);
for (var i = 0, ilen = bundles.length; i < ilen; i++) {
......@@ -535,12 +535,13 @@ APP_BoxView.prototype.destroy = function() {
}());
/*
Hijos version 0 - JavaScript classes for building tree structures and the composite design pattern
Hijos version 2
Copyright (c) 2012, Peter Michaux
All rights reserved.
Licensed under the Simplified BSD License.
https://github.com/petermichaux/hijos/blob/master/LICENSE
*/var hijos = hijos || {};
*/
var hijos = {};
/**
@property hijos.Leaf
......@@ -914,12 +915,12 @@ hijos.Node.mixin = function(obj) {
hijos.Node.call(obj);
};
/*
Arbutus version 1
Arbutus version 2
Copyright (c) 2012, Peter Michaux
All rights reserved.
Licensed under the Simplified BSD License.
https://github.com/petermichaux/arbutus/blob/master/LICENSE
*/var arbutus = arbutus || {};
*/var arbutus = {};
(function() {
var trimLeft = /^\s+/,
......@@ -962,7 +963,6 @@ https://github.com/petermichaux/arbutus/blob/master/LICENSE
var parser = doc.createElement('div');
var fragment = doc.createDocumentFragment();
parser.innerHTML = this.before + html + this.after;
// console.log(parser.innerHTML);
var node = this.getFirstResult(parser);
var nextNode;
while (node) {
......@@ -1025,13 +1025,13 @@ don't want to have a loop of thousands with calls to this function.
}());
/*
Grail version 2
Grail version 3
Copyright (c) 2012, Peter Michaux
All rights reserved.
Licensed under the Simplified BSD License.
https://github.com/petermichaux/grail/blob/master/LICENSE
*/
var grail = grail || {};
var grail = {};
(function() {
var trimLeft = /^\s+/;
......@@ -1211,13 +1211,13 @@ The rest of the details are the same as for grail.findAll.
}());
/*
Hormigas version 1
Hormigas version 3
Copyright (c) 2012, Peter Michaux
All rights reserved.
Licensed under the Simplified BSD License.
https://github.com/petermichaux/hormigas/blob/master/LICENSE
*/
var hormigas = hormigas || {};
var hormigas = {};
(function() {
var nextId = 0;
......@@ -1268,6 +1268,25 @@ Harmony Set proposal and the Array.prototype iterators.
/**
@property hormigas.ObjectSet.prototype.isEmpty
@description
Returns true if set is empty. Otherwise returns false.
var alpha = {};
var set = new hormigas.ObjectSet(alpha);
set.isEmpty(); // false
set['delete'](alpha);
set.isEmpty(); // true
*/
hormigas.ObjectSet.prototype.isEmpty = function() {
return this.length < 1;
};
/**
@property hormigas.ObjectSet.prototype.has
@parameter element
......@@ -1656,12 +1675,28 @@ hormigas.ObjectSet.mixin = function(obj) {
hormigas.ObjectSet.call(obj);
};
/*
Maria release candidate 1 - an MVC framework for JavaScript applications
Maria release candidate 5 - an MVC framework for JavaScript applications
Copyright (c) 2012, Peter Michaux
All rights reserved.
Licensed under the Simplified BSD License.
https://github.com/petermichaux/maria/blob/master/LICENSE
*/var maria = maria || {};
*/var maria = {};
// Not all browsers supported by Maria have Object.create
maria.create = (function() {
function F() {}
return function(obj) {
F.prototype = obj;
return new F();
};
}());
maria.borrow = function(sink, source) {
for (var p in source) {
if (Object.prototype.hasOwnProperty.call(source, p)) {
sink[p] = source[p];
}
}
};
// "this" must be a constructor function
// mix the "subclass" function into your constructor function
//
......@@ -1669,10 +1704,13 @@ maria.subclass = function(namespace, name, options) {
options = options || {};
var properties = options.properties;
var SuperConstructor = this;
var Constructor = namespace[name] = function() {
SuperConstructor.apply(this, arguments);
};
var prototype = Constructor.prototype = new SuperConstructor();
var Constructor = namespace[name] =
Object.prototype.hasOwnProperty.call(options, 'constructor') ?
options.constructor :
function() {
SuperConstructor.apply(this, arguments);
};
var prototype = Constructor.prototype = maria.create(SuperConstructor.prototype);
prototype.constructor = Constructor;
if (properties) {
maria.borrow(prototype, properties);
......@@ -1681,18 +1719,17 @@ maria.subclass = function(namespace, name, options) {
SuperConstructor.subclass.apply(this, arguments);
};
};
maria.borrow = function(sink, source) {
for (var p in source) {
if (Object.prototype.hasOwnProperty.call(source, p)) {
sink[p] = source[p];
}
}
maria.on = function() {
evento.on.apply(this, arguments);
};
maria.off = function() {
evento.off.apply(this, arguments);
};
maria.purge = function() {
evento.purge.apply(this, arguments);
};
maria.borrow(maria, evento);
maria.borrow(maria, hijos);
maria.borrow(maria, arbutus);
maria.borrow(maria, grail);
maria.borrow(maria, hormigas);
/**
@property maria.Model
......@@ -1716,7 +1753,7 @@ when a "change" event is dispatched on the model objects.
alert('The model changed!');
}
};
maria.addEventListener(model, 'change', view, 'update');
maria.on(model, 'change', view, 'update');
The model can dispatch a "change" event on itself when the model
changes.
......@@ -1741,7 +1778,7 @@ including that data on the event object.
An event listener can be removed from a model object.
maria.removeEventListener(model, 'change', view, 'update');
maria.off(model, 'change', view, 'update');
A particularly useful pattern is using maria.Model as the "superclass"
of your application's model. The following example shows how this
......@@ -1751,7 +1788,7 @@ See maria.Model.subclass for a more compact way to accomplish the same.
checkit.TodoModel = function() {
maria.Model.apply(this, arguments);
};
checkit.TodoModel.prototype = new maria.Model();
checkit.TodoModel.prototype = maria.create(maria.Model.prototype);
checkit.TodoModel.prototype.constructor = checkit.TodoModel;
checkit.TodoModel.prototype._content = '';
checkit.TodoModel.prototype._isDone = false;
......@@ -1759,7 +1796,7 @@ See maria.Model.subclass for a more compact way to accomplish the same.
return this._content;
};
checkit.TodoModel.prototype.setContent = function(content) {
content = ('' + content).replace(/^\s+|\s+$/g, '');
content = checkit.trim('' + content);
if (this._content !== content) {
this._content = content;
this.dispatchEvent({type: 'change'});
......@@ -1779,14 +1816,6 @@ See maria.Model.subclass for a more compact way to accomplish the same.
this.setDone(!this.isDone());
};
The above TodoModel example does not have an "initialize" method;
however, if some special initialization is requried, maria.Model will
automatically call your "initialize" method.
checkit.TodoModel.prototype.initialize = function() {
alert('Another to-do has been created. You better get busy.');
};
When a model's "destroy" method is called, a "destroy" event is
dispatched and all event listeners who've been added for this event
type will be notified.
......@@ -1796,13 +1825,11 @@ using "addParentEventTarget" and "removeParentEventTarget".)
*/
maria.Model = function() {
maria.EventTarget.call(this);
this.initialize();
evento.EventTarget.call(this);
};
maria.EventTarget.mixin(maria.Model.prototype);
maria.Model.prototype.initialize = function() {};
maria.Model.prototype = maria.create(evento.EventTarget.prototype);
maria.Model.prototype.constructor = maria.Model;
maria.Model.prototype.destroy = function() {
this.dispatchEvent({type: 'destroy'});
......@@ -1829,7 +1856,7 @@ with those elements.
You can create an empty set model object.
var setModel = new maria.SetModel();
var setModel = new maria.SetModel();
What makes a set model object interesting in comparison to a set is
that a set model object is a model object that dispatches "change"
......@@ -1840,7 +1867,7 @@ events when elements are added or deleted from the the set.
alert(setModel.length + ' element(s) in the set.');
}
};
maria.addEventListener(setModel, 'change', view, 'update');
maria.on(setModel, 'change', view, 'update');
You can add elements to the set. Adding an element
that is already in the set has no effect. The add method returns
......@@ -1948,11 +1975,8 @@ to accomplish the same.
checkit.TodosModel = function() {
maria.SetModel.apply(this, arguments);
};
checkit.TodosModel.prototype = new maria.SetModel();
checkit.TodosModel.prototype = maria.create(maria.SetModel.prototype);
checkit.TodosModel.prototype.constructor = checkit.TodosModel;
checkit.TodosModel.prototype.isEmpty = function() {
return this.length === 0;
};
checkit.TodosModel.prototype.getDone = function() {
return this.filter(function(todo) {
return todo.isDone();
......@@ -1989,14 +2013,14 @@ example. This can complement well the flyweight pattern used in a view.
*/
maria.SetModel = function() {
maria.ObjectSet.apply(this, arguments);
hormigas.ObjectSet.apply(this, arguments);
maria.Model.call(this);
};
maria.SetModel.prototype = new maria.Model();
maria.SetModel.prototype = maria.create(maria.Model.prototype);
maria.SetModel.prototype.constructor = maria.SetModel;
maria.ObjectSet.mixin(maria.SetModel.prototype);
hormigas.ObjectSet.mixin(maria.SetModel.prototype);
// Wrap the set mutator methods to dispatch events.
......@@ -2006,11 +2030,11 @@ maria.SetModel.prototype.add = function() {
var added = [];
for (var i = 0, ilen = arguments.length; i < ilen; i++) {
var argument = arguments[i];
if (maria.ObjectSet.prototype.add.call(this, argument)) {
if (hormigas.ObjectSet.prototype.add.call(this, argument)) {
added.push(argument);
if ((typeof argument.addEventListener === 'function') &&
(typeof argument.removeEventListener === 'function')) {
argument.addEventListener('destroy', this);
argument.addEventListener('destroy', this);
}
if ((typeof argument.addParentEventTarget === 'function') &&
// want to know can remove later
......@@ -2032,7 +2056,7 @@ maria.SetModel.prototype['delete'] = function() {
var deleted = [];
for (var i = 0, ilen = arguments.length; i < ilen; i++) {
var argument = arguments[i];
if (maria.ObjectSet.prototype['delete'].call(this, argument)) {
if (hormigas.ObjectSet.prototype['delete'].call(this, argument)) {
deleted.push(argument);
if (typeof argument.removeEventListener === 'function') {
argument.removeEventListener('destroy', this);
......@@ -2051,7 +2075,7 @@ maria.SetModel.prototype['delete'] = function() {
maria.SetModel.prototype.empty = function() {
var deleted = this.toArray();
var result = maria.ObjectSet.prototype.empty.call(this);
var result = hormigas.ObjectSet.prototype.empty.call(this);
if (result) {
for (var i = 0, ilen = deleted.length; i < ilen; i++) {
var element = deleted[i];
......@@ -2140,14 +2164,12 @@ A view has a controller. You can get the current controller.
view.getController();
The view's controller is created lazily the first time the
getController method is called. The view's
getController method is called. The view's
getDefaultControllerConstructor method returns the constructor function
to create the controller object and the getDefaultController actually
calls that constructor. Your application may redefine or override
either of these methods.
A view's initialize method is called when the view is constructed.
A view has a destroy method which should be called before your
application looses its last reference to the view.
......@@ -2183,7 +2205,7 @@ accomplish the same.
myapp.MyView = function() {
maria.View.apply(this, arguments);
};
myapp.MyView.prototype = new maria.View();
myapp.MyView.prototype = maria.create(maria.View.prototype);
myapp.MyView.prototype.constructor = myapp.MyView;
myapp.MyView.prototype.getModelActions = function() {
return {
......@@ -2204,36 +2226,24 @@ accomplish the same.
alert('another method');
};
The above MyView example does not have an "initialize" method;
however, if some special initialization is requried, maria.View
will automatically call your "initialize" method.
myapp.MyView.prototype.initialize = function() {
alert('Another view has been created.');
};
*/
maria.View = function(model, controller) {
maria.Node.call(this);
this.initialize();
hijos.Node.call(this);
this.setModel(model);
this.setController(controller);
};
maria.Node.mixin(maria.View.prototype);
maria.View.prototype.initialize = function() {
// to be overridden by concrete view subclasses
};
maria.View.prototype = maria.create(hijos.Node.prototype);
maria.View.prototype.constructor = maria.View;
maria.View.prototype.destroy = function() {
maria.purgeEventListener(this);
maria.purge(this);
this._model = null;
if (this._controller) {
this._controller.destroy();
this._controller = null;
}
maria.Node.prototype.destroy.call(this);
hijos.Node.prototype.destroy.call(this);
};
maria.View.prototype.update = function() {
......@@ -2279,7 +2289,7 @@ maria.View.prototype._setModelAndController = function(model, controller) {
eventMap = this._lastModelActions;
for (type in eventMap) {
if (Object.prototype.hasOwnProperty.call(eventMap, type)) {
maria.removeEventListener(this._model, type, this, eventMap[type]);
maria.off(this._model, type, this, eventMap[type]);
}
}
delete this._lastModelActions;
......@@ -2288,7 +2298,7 @@ maria.View.prototype._setModelAndController = function(model, controller) {
eventMap = this._lastModelActions = this.getModelActions() || {};
for (type in eventMap) {
if (Object.prototype.hasOwnProperty.call(eventMap, type)) {
maria.addEventListener(model, type, this, eventMap[type]);
maria.on(model, type, this, eventMap[type]);
}
}
}
......@@ -2410,7 +2420,7 @@ the same.
checkit.TodoView = function() {
maria.ElementView.apply(this, arguments);
};
checkit.TodoView.prototype = new maria.ElementView();
checkit.TodoView.prototype = maria.create(maria.ElementView.prototype);
checkit.TodoView.prototype.constructor = checkit.TodoView;
checkit.TodoView.prototype.getDefaultControllerConstructor = function() {
return checkit.TodoController;
......@@ -2445,8 +2455,7 @@ the same.
checkit.TodoView.prototype.buildData = function() {
var model = this.getModel();
var content = model.getContent();
this.find('.todo-content').innerHTML =
content.replace('&', '&amp;').replace('<', '&lt;');
this.find('.todo-content').innerHTML = checkit.escapeHTML(content);
this.find('.check').checked = model.isDone();
aristocrat[model.isDone() ? 'addClass' : 'removeClass'](this.find('.todo'), 'done');
};
......@@ -2478,7 +2487,7 @@ maria.ElementView = function(model, controller, doc) {
this.setDocument(doc);
};
maria.ElementView.prototype = new maria.View();
maria.ElementView.prototype = maria.create(maria.View.prototype);
maria.ElementView.prototype.constructor = maria.ElementView;
maria.ElementView.prototype.getDocument = function() {
......@@ -2513,7 +2522,7 @@ maria.ElementView.prototype.build = function() {
maria.ElementView.prototype.buildTemplate = function() {
// parseHTML returns a DocumentFragment so take firstChild as the rootEl
this._rootEl = maria.parseHTML(this.getTemplate(), this.getDocument()).firstChild;
this._rootEl = arbutus.parseHTML(this.getTemplate(), this.getDocument()).firstChild;
};
(function() {
......@@ -2527,9 +2536,9 @@ maria.ElementView.prototype.buildTemplate = function() {
eventType = matches[1],
selector = matches[2],
methodName = uiActions[key],
elements = maria.findAll(selector, this._rootEl);
elements = this.findAll(selector, this._rootEl);
for (var i = 0, ilen = elements.length; i < ilen; i++) {
maria.addEventListener(elements[i], eventType, this, methodName);
maria.on(elements[i], eventType, this, methodName);
}
}
}
......@@ -2548,10 +2557,6 @@ maria.ElementView.prototype.buildChildViews = function() {
}
};
maria.ElementView.prototype.update = function() {
// to be overridden by concrete ElementView subclasses
};
maria.ElementView.prototype.getContainerEl = function() {
return this.build();
};
......@@ -2571,11 +2576,11 @@ maria.ElementView.prototype.removeChild = function(oldChild) {
};
maria.ElementView.prototype.find = function(selector) {
return maria.find(selector, this.build());
return grail.find(selector, this.build());
};
maria.ElementView.prototype.findAll = function(selector) {
return maria.findAll(selector, this.build());
return grail.findAll(selector, this.build());
};
/**
......@@ -2617,7 +2622,7 @@ maria.SetView.subclass for a more compact way to accomplish the same.
checkit.TodosListView = function() {
maria.SetView.apply(this, arguments);
};
checkit.TodosListView.prototype = new maria.SetView();
checkit.TodosListView.prototype = maria.create(maria.SetView.prototype);
checkit.TodosListView.prototype.constructor = checkit.TodosListView;
checkit.TodosListView.prototype.getTemplate = function() {
return checkit.TodosListTemplate;
......@@ -2631,7 +2636,7 @@ maria.SetView = function() {
maria.ElementView.apply(this, arguments);
};
maria.SetView.prototype = new maria.ElementView();
maria.SetView.prototype = maria.create(maria.ElementView.prototype);
maria.SetView.prototype.constructor = maria.SetView;
maria.SetView.prototype.buildChildViews = function() {
......@@ -2733,7 +2738,7 @@ to accomplish the same.
checkit.TodoController = function() {
maria.Controller.apply(this, arguments);
};
checkit.TodoController.prototype = new maria.Controller();
checkit.TodoController.prototype = maria.create(maria.Controller.prototype);
checkit.TodoController.prototype.constructor = checkit.TodoController;
checkit.TodoController.prototype.onClickCheck = function() {
this.getModel().toggleDone();
......@@ -2743,10 +2748,10 @@ to accomplish the same.
};
checkit.TodoController.prototype.onKeyupInput = function() {
var view = this.getView();
if (/\S/.test(view.getInputValue())) {
view.showToolTip();
} else {
if (checkit.isBlank(view.getInputValue())) {
view.hideToolTip();
} else {
view.showToolTip();
}
};
checkit.TodoController.prototype.onKeypressInput = function(evt) {
......@@ -2759,25 +2764,13 @@ to accomplish the same.
var value = view.getInputValue();
view.hideToolTip();
view.showDisplay();
if (!/^\s*$/.test(value)) {
if (!checkit.isBlank(value)) {
this.getModel().setContent(value);
}
};
The above TodoController example does not have an "initialize" method;
however, if some special initialization is requried, maria.Controller
will automatically call your "initialize" method.
checkit.TodoController.prototype.initialize = function() {
alert('Another to-do controller has been created.');
};
*/
maria.Controller = function() {
this.initialize();
};
maria.Controller.prototype.initialize = function() {};
maria.Controller = function() {};
maria.Controller.prototype.destroy = function() {
this._model = null;
......@@ -2791,6 +2784,9 @@ maria.Controller.prototype.getModel = function() {
return this._model;
};
// setModel is intended to be called *only* by
// the view _setModelAndController method.
// Do otherwise at your own risk.
maria.Controller.prototype.setModel = function(model) {
this._model = model;
};
......@@ -2799,6 +2795,9 @@ maria.Controller.prototype.getView = function() {
return this._view;
};
// setView is intended to be called *only* by
// the view _setModelAndController method.
// Do otherwise at your own risk.
maria.Controller.prototype.setView = function(view) {
this._view = view;
};
......@@ -2822,7 +2821,7 @@ for maria.Model.
return this._content;
},
setContent: function(content) {
content = ('' + content).replace(/^\s+|\s+$/g, '');
content = checkit.trim('' + content);
if (this._content !== content) {
this._content = content;
this.dispatchEvent({type: 'change'});
......@@ -2845,7 +2844,9 @@ for maria.Model.
});
*/
maria.Model.subclass = maria.subclass;
maria.Model.subclass = function() {
maria.subclass.apply(this, arguments);
};
/**
@property maria.SetModel.subclass
......@@ -2860,9 +2861,6 @@ for maria.SetModel.
maria.SetModel.subclass(checkit, 'TodosModel', {
properties: {
isEmpty: function() {
return this.length === 0;
},
getDone: function() {
return this.filter(function(todo) {
return todo.isDone();
......@@ -2894,7 +2892,9 @@ for maria.SetModel.
});
*/
maria.SetModel.subclass = maria.Model.subclass;
maria.SetModel.subclass = function() {
maria.Model.subclass.apply(this, arguments);
};
/**
@property maria.View.subclass
......@@ -2981,8 +2981,7 @@ for maria.ElementView.
buildData: function() {
var model = this.getModel();
var content = model.getContent();
this.find('.todo-content').innerHTML =
content.replace('&', '&amp;').replace('<', '&lt;');
this.find('.todo-content').innerHTML = checkit.escapeHTML(content);
this.find('.check').checked = model.isDone();
aristocrat[model.isDone() ? 'addClass' : 'removeClass'](this.find('.todo'), 'done');
},
......@@ -3012,12 +3011,11 @@ for maria.ElementView.
This subclassing function implements options following the
"convention over configuration" philosophy. The checkit.TodoView will,
by convention, use the checkit.TodoModel, checkit.TodoController
by convention, use the checkit.TodoController
and checkit.TodoTemplate objects. All of these can be configured
explicitely if these conventions do not match your view's needs.
explicitly if these conventions do not match your view's needs.
maria.ElementView.subclass(checkit, 'TodoView', {
modelConstructor : checkit.TodoModel ,
controllerConstructor: checkit.TodoController,
template : checkit.TodoTemplate ,
uiActions: {
......@@ -3028,7 +3026,6 @@ objects in the application's namespace object (i.e. the checkit object
in this example).
maria.ElementView.subclass(checkit, 'TodoView', {
modelConstructorName : 'TodoModel' ,
controllerConstructorName: 'TodoController',
templateName : 'TodoTemplate' ,
uiActions: {
......@@ -3090,7 +3087,6 @@ function equivalent to the more verbose example shown in the
documentation for maria.SetView.
maria.SetView.subclass(checkit, 'TodosListView', {
modelConstructor: checkit.TodosModel,
properties: {
createChildView: function(todoModel) {
return new checkit.TodoView(todoModel);
......@@ -3099,7 +3095,9 @@ documentation for maria.SetView.
});
*/
maria.SetView.subclass = maria.ElementView.subclass;
maria.SetView.subclass = function() {
maria.ElementView.subclass.apply(this, arguments);
};
/**
@property maria.Controller.subclass
......@@ -3122,10 +3120,10 @@ the documentation for maria.Controller.
},
onKeyupInput: function() {
var view = this.getView();
if (/\S/.test(view.getInputValue())) {
view.showToolTip();
} else {
if (checkit.isBlank(view.getInputValue())) {
view.hideToolTip();
} else {
view.showToolTip();
}
},
onKeypressInput: function(evt) {
......@@ -3138,7 +3136,7 @@ the documentation for maria.Controller.
var value = view.getInputValue();
view.hideToolTip();
view.showDisplay();
if (!/^\s*$/.test(value)) {
if (!checkit.isBlank(value)) {
this.getModel().setContent(value);
}
}
......@@ -3146,4 +3144,6 @@ the documentation for maria.Controller.
});
*/
maria.Controller.subclass = maria.subclass;
maria.Controller.subclass = function() {
maria.subclass.apply(this, arguments);
};
ul.completed li.incompleted,
ul.incompleted li.completed {
display: none;
}
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-style: inherit;
font-size: 100%;
font-family: inherit;
vertical-align: baseline;
}
body {
line-height: 1;
color: black;
background: white;
}
ol, ul {
list-style: none;
}
a img {
border: none;
}
html {
background: #eeeeee;
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.4em;
background: #eeeeee;
color: #333333;
}
.todos-app {
width: 480px;
margin: 0 auto 40px;
background: white;
padding: 20px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0;
}
.todos-app h1 {
font-size: 36px;
font-weight: bold;
text-align: center;
padding: 20px 0 30px 0;
line-height: 1;
}
.create-todo {
position: relative;
}
.create-todo input {
width: 466px;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
.create-todo span {
position: absolute;
z-index: 999;
width: 170px;
left: 50%;
margin-left: -85px;
}
.todos-list {
margin-top: 10px;
}
.todos-list li {
padding: 12px 20px 11px 0;
position: relative;
font-size: 24px;
line-height: 1.1em;
border-bottom: 1px solid #cccccc;
}
.todos-list li:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
.todos-list li.editing {
padding: 0;
border-bottom: 0;
}
.todos-list .editing .display,
.todos-list .edit {
display: none;
}
.todos-list .editing .edit {
display: block;
position: relative;
}
.todos-list .editing input {
width: 444px;
font-size: 24px;
font-family: inherit;
margin: 0;
line-height: 1.6em;
border: 0;
outline: none;
padding: 10px 7px 0px 27px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
.todos-list .check {
position: relative;
top: 9px;
margin: 0 10px 0 7px;
float: left;
}
.todos-list .todo-content {
width:400px;
display:block;
}
.todos-list .done .todo-content {
text-decoration: line-through;
color: #777777;
}
.todos-list .todo-destroy {
cursor: pointer;
position: absolute;
right: 5px;
top: 14px;
display:none;
}
.todos-list .todo-hover .todo-destroy {
display:block;
}
.todos-stats {
*zoom: 1;
margin-top: 10px;
color: #777777;
}
.todos-stats:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
.todos-stats .todo-count {
float: left;
}
.todos-stats .todo-count .number {
font-weight: bold;
color: #333333;
}
.todos-stats .todo-clear {
float: right;
}
.todos-stats .todo-clear a {
color: #777777;
font-size: 12px;
}
.todos-stats .todo-clear a:visited {
color: #777777;
}
.todos-stats .todo-clear a:hover {
color: #336699;
}
/*
* François 'cahnory' Germain
*/
.ui-tooltip, .ui-tooltip-top, .ui-tooltip-right, .ui-tooltip-bottom, .ui-tooltip-left {
color:#ffffff;
cursor:normal;
display:-moz-inline-stack;
display:inline-block;
font-size:12px;
font-family:arial;
padding:.5em 1em;
position:relative;
text-align:center;
text-shadow:0 -1px 1px #111111;
-webkit-border-top-left-radius:4px ;
-webkit-border-top-right-radius:4px ;
-webkit-border-bottom-right-radius:4px ;
-webkit-border-bottom-left-radius:4px ;
-khtml-border-top-left-radius:4px ;
-khtml-border-top-right-radius:4px ;
-khtml-border-bottom-right-radius:4px ;
-khtml-border-bottom-left-radius:4px ;
-moz-border-radius-topleft:4px ;
-moz-border-radius-topright:4px ;
-moz-border-radius-bottomright:4px ;
-moz-border-radius-bottomleft:4px ;
border-top-left-radius:4px ;
border-top-right-radius:4px ;
border-bottom-right-radius:4px ;
border-bottom-left-radius:4px ;
-o-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
-moz-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
-khtml-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
-webkit-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444;
background-color:#3b3b3b;
background-image:-moz-linear-gradient(top,#555555,#222222);
background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#555555),color-stop(1,#222222));
filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222);
-ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222);
}
.ui-tooltip:after, .ui-tooltip-top:after, .ui-tooltip-right:after, .ui-tooltip-bottom:after, .ui-tooltip-left:after {
content:"\25B8";
display:block;
font-size:2em;
height:0;
line-height:0;
position:absolute;
}
.ui-tooltip:after, .ui-tooltip-bottom:after {
color:#2a2a2a;
bottom:0;
left:1px;
text-align:center;
text-shadow:1px 0 2px #000000;
-o-transform:rotate(90deg);
-moz-transform:rotate(90deg);
-khtml-transform:rotate(90deg);
-webkit-transform:rotate(90deg);
width:100%;
}
.ui-tooltip-top:after {
bottom:auto;
color:#4f4f4f;
left:-2px;
top:0;
text-align:center;
text-shadow:none;
-o-transform:rotate(-90deg);
-moz-transform:rotate(-90deg);
-khtml-transform:rotate(-90deg);
-webkit-transform:rotate(-90deg);
width:100%;
}
.ui-tooltip-right:after {
color:#222222;
right:-0.375em;
top:50%;
margin-top:-.05em;
text-shadow:0 1px 2px #000000;
-o-transform:rotate(0);
-moz-transform:rotate(0);
-khtml-transform:rotate(0);
-webkit-transform:rotate(0);
}
.ui-tooltip-left:after {
color:#222222;
left:-0.375em;
top:50%;
margin-top:.1em;
text-shadow:0 -1px 2px #000000;
-o-transform:rotate(180deg);
-moz-transform:rotate(180deg);
-khtml-transform:rotate(180deg);
-webkit-transform:rotate(180deg);
}
/*the following changes require some cleanup and integration with the above.**/
/* line 9 */
/* line 17 */
.todos-app {
background: white;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-moz-border-radius-bottomleft: 5px;
-webkit-border-bottom-left-radius: 5px;
-o-border-bottom-left-radius: 5px;
-ms-border-bottom-left-radius: 5px;
-khtml-border-bottom-left-radius: 5px;
border-bottom-left-radius: 5px;
-moz-border-radius-bottomright: 5px;
-webkit-border-bottom-right-radius: 5px;
-o-border-bottom-right-radius: 5px;
-ms-border-bottom-right-radius: 5px;
-khtml-border-bottom-right-radius: 5px;
border-bottom-right-radius: 5px;
}
/* line 24 */
/* line 32 */
.todos-app .content .create-todo {
position: relative;
}
/* line 34 */
.todos-app .content .create-todo input {
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
/* line 47 */
.todos-app .content .create-todo span {
position: absolute;
z-index: 999;
width: 170px;
left: 50%;
margin-left: -85px;
}
/* line 55 */
.todos-app .content ul.todos-list {
margin-top: 10px;
}
/* line 57 */
.todos-app .content ul.todos-list li {
padding: 15px 20px 15px 0;
position: relative;
font-size: 24px;
border-bottom: 1px solid #cccccc;
*zoom: 1;
}
/* line 22, /opt/ree/lib/ruby/gems/1.8/gems/compass-0.10.5/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */
.todos-app .content ul.todos-list li:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
/* line 64 */
.todos-app .content ul.todos-list li.editing {
padding: 0;
border-bottom: 0;
}
/* line 67 */
.todos-app .content ul.todos-list li.editing .todo-input {
display: block;
width: 466px;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
/* line 79 */
.todos-app .content ul.todos-list li.editing .todo-content {
display: none;
}
/* line 81 */
.todos-app .content ul.todos-list li.editing .todo-check {
display: none;
}
/* line 83 */
.todos-app .content ul.todos-list li.editing .todo-destroy {
display: none !important;
}
/* line 85 */
.todos-app .content ul.todos-list li .todo-input {
display: none;
}
/* line 87 */
.todos-app .content ul.todos-list li .todo-check {
position: relative;
top: 6px;
margin: 0 10px 0 7px;
float: left;
}
/* line 93 */
.todos-app .content ul.todos-list li.done .todo-content {
text-decoration: line-through;
color: #777777;
}
/* line 109 */
.todos-app .todos-stats {
*zoom: 1;
margin-top: 10px;
color: #555555;
-moz-border-radius-bottomleft: 5px;
-webkit-border-bottom-left-radius: 5px;
-o-border-bottom-left-radius: 5px;
-ms-border-bottom-left-radius: 5px;
-khtml-border-bottom-left-radius: 5px;
border-bottom-left-radius: 5px;
-moz-border-radius-bottomright: 5px;
-webkit-border-bottom-right-radius: 5px;
-o-border-bottom-right-radius: 5px;
-ms-border-bottom-right-radius: 5px;
-khtml-border-bottom-right-radius: 5px;
border-bottom-right-radius: 5px;
background: #f4fce8;
border-top: 1px solid #ededed;
padding: 0 20px;
line-height: 36px;
}
/* line 22, /opt/ree/lib/ruby/gems/1.8/gems/compass-0.10.5/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */
.todos-app .todos-stats:after {
content: "\0020";
display: block;
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
/* line 118 */
.todos-app .todos-stats .todo-count {
float: left;
}
/* line 120 */
.todos-app .todos-stats .todo-count .number {
font-weight: bold;
color: #555555;
}
/* line 123 */
.todos-app .todos-stats .todo-clear {
float: right;
}
/* line 125 */
.todos-app .todos-stats .todo-clear a {
display: block;
line-height: 20px;
text-decoration: none;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
-o-border-radius: 12px;
-ms-border-radius: 12px;
-khtml-border-radius: 12px;
border-radius: 12px;
background: rgba(0, 0, 0, 0.1);
color: #555555;
font-size: 11px;
margin-top: 8px;
padding: 0 10px 1px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
}
/* line 136 */
.todos-app .todos-stats .todo-clear a:hover, .todos-app .todos-stats .todo-clear a:focus {
background: rgba(0, 0, 0, 0.15);
-moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
}
/* line 139 */
.todos-app .todos-stats .todo-clear a:active {
position: relative;
top: 1px;
}
.todos-app .copyright {
padding:0.5em;
text-align:center;
}
.todos-app ul.todos-toolbar {
list-style-type: none;
margin: 0;
margin-top: 10px;
padding: 0;
}
.todos-app .todos-toolbar li {
margin: 0;
padding: 0;
padding-right: 1em;
cursor: pointer;
display: inline;
font-size: 90%;
text-decoration: underline;
}
<!DOCTYPE html>
<html>
<head>
<title>To-do App</title>
<meta charset="utf-8">
<title>Maria • TodoMVC</title>
<link href="css/todos.css" rel="stylesheet" type="text/css">
<link href="../../../../assets/base.css" rel="stylesheet">
<link href="css/app.css" rel="stylesheet">
</head>
<body>
<noscript>
<p>This application uses JavaScript. Please enable JavaScript in your browser and refresh the page.</p>
</noscript>
<script>
document.write('<p id="loading">loading...</p>');
</script>
<script src="../lib/maria/maria.js"></script>
<script src="../lib/aristocrat/aristocrat.js"></script>
<script src="../../../../assets/director.min.js"></script>
<script src="../lib/maria/maria.js"></script>
<script src="../lib/aristocrat/aristocrat.js"></script>
<script src="js/namespace.js"></script>
<script src="js/models/TodoModel.js"></script>
<script src="js/models/TodosModel.js"></script>
<script src="js/templates/TodosInputTemplate.js"></script>
<script src="js/views/TodosInputView.js"></script>
<script src="js/templates/TodosListTemplate.js"></script>
<script src="js/views/TodosListView.js"></script>
<script src="js/templates/TodosToolbarTemplate.js"></script>
<script src="js/views/TodosToolbarView.js"></script>
<script src="js/controllers/TodosToolbarController.js"></script>
<script src="js/templates/TodosStatsTemplate.js"></script>
<script src="js/views/TodosStatsView.js"></script>
<script src="js/templates/TodosAppTemplate.js"></script>
<script src="js/views/TodosAppView.js"></script>
<script src="js/controllers/TodosInputController.js"></script>
<script src="js/templates/TodoTemplate.js"></script>
<script src="js/views/TodoView.js"></script>
<script src="js/controllers/TodoController.js"></script>
<script src="js/bootstrap.js"></script>
<script src="js/namespace.js"></script>
<script src="js/util.js"></script>
<script src="js/models/TodoModel.js"></script>
<script src="js/models/TodosModel.js"></script>
<script src="js/templates/TodosTemplate.js"></script>
<script src="js/views/TodosView.js"></script>
<script src="js/controllers/TodosController.js"></script>
<script src="js/templates/TodoTemplate.js"></script>
<script src="js/views/TodoView.js"></script>
<script src="js/controllers/TodoController.js"></script>
<script src="js/bootstrap.js"></script>
</body>
</html>
maria.addEventListener(window, 'load', function() {
var loading = document.getElementById('loading');
loading.parentNode.removeChild(loading);
/*jshint strict: false */
/*global maria, Router, checkit */
var model;
if ((typeof localStorage === 'object') && (typeof JSON === 'object')) {
var store = localStorage.getItem('todos-maria');
model = store ? checkit.TodosModel.fromJSON(JSON.parse(store)) :
new checkit.TodosModel();
evento.addEventListener(model, 'change', function() {
localStorage.setItem('todos-maria', JSON.stringify(model.toJSON()));
});
}
else {
model = new checkit.TodosModel();
}
maria.on(window, 'load', function() {
var model;
if ((typeof localStorage === 'object') && (typeof JSON === 'object')) {
var store = localStorage.getItem('todos-maria');
model = store ? checkit.TodosModel.fromJSON(JSON.parse(store)) :
new checkit.TodosModel();
maria.on(model, 'change', function() {
localStorage.setItem('todos-maria', JSON.stringify(model.toJSON()));
});
}
else {
model = new checkit.TodosModel();
}
var view = new checkit.TodosAppView(model);
document.body.appendChild(view.build());
var routes = {
'/': function() {
model.setMode('all');
},
'/active': function() {
model.setMode('incompleted');
},
'/completed': function() {
model.setMode('completed');
}
};
var router = Router(routes);
router.init();
var view = new checkit.TodosAppView(model);
document.body.appendChild(view.build());
});
/*jshint strict: false */
/*global maria, checkit */
maria.Controller.subclass(checkit, 'TodoController', {
properties: {
onMouseoverRoot: function() {
this.getView().showHoverState();
},
onMouseoutRoot: function() {
this.getView().hideHoverState();
},
onClickCheck: function() {
this.getModel().toggleDone();
},
onClickDestroy: function() {
this.getModel().destroy();
},
onDblclickDisplay: function() {
this.getView().showEdit();
},
onKeyupInput: function() {
var view = this.getView();
if (/\S/.test(view.getInputValue())) {
view.showToolTip();
} else {
view.hideToolTip();
}
},
onKeypressInput: function(evt) {
if (evt.keyCode === 13) {
this.onBlurInput();
}
},
onBlurInput: function() {
var view = this.getView();
var value = view.getInputValue();
view.hideToolTip();
view.showDisplay();
if (!/^\s*$/.test(value)) {
this.getModel().setContent(value);
}
}
}
properties: {
onClickDestroy: function() {
this.getModel().destroy();
},
onClickToggle: function() {
this.getModel().toggleCompleted();
},
onDblclickLabel: function() {
this.getView().showEdit();
},
onKeyupEdit: function(evt) {
if (checkit.isEnterKeyCode(evt.keyCode)) {
this.onBlurEdit();
}
},
onBlurEdit: function() {
var model = this.getModel();
var view = this.getView();
var value = view.getInputValue();
view.showDisplay();
if (checkit.isBlank(value)) {
model.destroy();
}
else {
model.setTitle(value);
}
}
}
});
/*jshint strict: false */
/*global maria, checkit */
maria.Controller.subclass(checkit, 'TodosAppController', {
properties: {
onKeyupNewTodo: function(evt) {
if (checkit.isEnterKeyCode(evt.keyCode)) {
var view = this.getView();
var value = view.getInputValue();
if (!checkit.isBlank(value)) {
var todo = new checkit.TodoModel();
todo.setTitle(value);
this.getModel().add(todo);
view.clearInput();
}
}
},
onClickToggleAll: function() {
var model = this.getModel();
if (model.isAllCompleted()) {
model.markAllIncompleted();
}
else {
model.markAllCompleted();
}
},
onClickClearCompleted: function() {
this.getModel().deleteCompleted();
}
}
});
maria.Controller.subclass(checkit, 'TodosInputController', {
properties: {
onFocusInput: function() {
this.onKeyupInput();
},
onBlurInput: function() {
this.getView().hideToolTip();
},
onKeyupInput: function() {
var view = this.getView();
if (/\S/.test(view.getInputValue())) {
view.showToolTip();
} else {
view.hideToolTip();
}
},
onKeypressInput: function(evt) {
if (evt.keyCode != 13) {
return;
}
var view = this.getView();
var value = view.getInputValue();
if (/^\s*$/.test(value)) { // don't create an empty Todo
return;
}
var todo = new checkit.TodoModel();
todo.setContent(value);
this.getModel().add(todo);
view.clearInput();
}
}
});
maria.Controller.subclass(checkit, 'TodosToolbarController', {
properties: {
onClickAllCheckbox: function() {
var model = this.getModel();
if (model.isAllDone()) {
model.markAllUndone();
} else {
model.markAllDone();
}
},
onClickMarkAllDone: function() {
this.getModel().markAllDone();
},
onClickMarkAllUndone: function() {
this.getModel().markAllUndone();
},
onClickDeleteDone: function() {
this.getModel().deleteDone();
}
}
});
/*jshint strict: false */
/*global maria, checkit */
maria.Model.subclass(checkit, 'TodoModel', {
properties: {
_content: '',
_isDone: false,
getContent: function() {
return this._content;
},
setContent: function(content) {
content = ('' + content).replace(/^\s+|\s+$/g, '');
if (this._content !== content) {
this._content = content;
this.dispatchEvent({type: 'change'});
}
},
isDone: function() {
return this._isDone;
},
setDone: function(isDone) {
isDone = !!isDone;
if (this._isDone !== isDone) {
this._isDone = isDone;
this.dispatchEvent({type: 'change'});
}
},
toggleDone: function() {
this.setDone(!this.isDone());
},
toJSON: function() {
return {
content: this._content,
is_done: this._isDone
};
}
}
properties: {
_title: '',
_completed: false,
getTitle: function() {
return this._title;
},
setTitle: function(title) {
title = ('' + title).trim();
if (this._title !== title) {
this._title = title;
this.dispatchEvent({type: 'change'});
}
},
isCompleted: function() {
return this._completed;
},
setCompleted: function(completed) {
completed = !!completed;
if (this._completed !== completed) {
this._completed = completed;
this.dispatchEvent({type: 'change'});
}
},
toggleCompleted: function() {
this.setCompleted(!this.isCompleted());
},
toJSON: function() {
return {
title: this._title,
completed: this._completed
};
}
}
});
checkit.TodoModel.fromJSON = function(todoJSON) {
var model = new checkit.TodoModel();
model._content = todoJSON.content;
model._isDone = todoJSON.is_done;
return model;
var model = new checkit.TodoModel();
model._title = todoJSON.title;
model._completed = todoJSON.completed;
return model;
};
/*jshint strict: false */
/*global maria, checkit */
maria.SetModel.subclass(checkit, 'TodosModel', {
properties: {
isEmpty: function() {
return this.length === 0;
},
getDone: function() {
return this.filter(function(todo) {
return todo.isDone();
});
},
getUndone: function() {
return this.filter(function(todo) {
return !todo.isDone();
});
},
isAllDone: function() {
return this.length > 0 &&
(this.getDone().length === this.length);
},
markAllDone: function() {
this.forEach(function(todo) {
todo.setDone(true);
});
},
markAllUndone: function() {
this.forEach(function(todo) {
todo.setDone(false);
});
},
deleteDone: function() {
this['delete'].apply(this, this.getDone());
},
toJSON: function() {
return this.map(function(todo) {
return todo.toJSON();
});
}
}
properties: {
_mode: 'all',
getPossibleModes: function() {
return ['all', 'incompleted', 'completed'];
},
getMode: function() {
return this._mode;
},
setMode: function(mode) {
if (this.getPossibleModes().some(function(m) {return m === mode;})) {
if (this._mode !== mode) {
this._mode = mode;
this.dispatchEvent({type: 'change'});
}
}
else {
throw new Error('checkit.TodosModel.prototype.setMode: unsupported mode "'+mode+'".');
}
},
getCompleted: function() {
return this.filter(function(todo) {
return todo.isCompleted();
});
},
getIncompleted: function() {
return this.filter(function(todo) {
return !todo.isCompleted();
});
},
isAllCompleted: function() {
return (this.length > 0) && (this.getCompleted().length === this.length);
},
markAllCompleted: function() {
this.forEach(function(todo) {
todo.setCompleted(true);
});
},
markAllIncompleted: function() {
this.forEach(function(todo) {
todo.setCompleted(false);
});
},
deleteCompleted: function() {
this['delete'].apply(this, this.getCompleted());
},
toJSON: function() {
return this.map(function(todo) {
return todo.toJSON();
});
}
}
});
checkit.TodosModel.fromJSON = function(todosJSON) {
var model = new checkit.TodosModel();
for (var i = 0, ilen = todosJSON.length; i < ilen; i++) {
model.add(checkit.TodoModel.fromJSON(todosJSON[i]));
}
return model;
var model = new checkit.TodosModel();
for (var i = 0, ilen = todosJSON.length; i < ilen; i++) {
model.add(checkit.TodoModel.fromJSON(todosJSON[i]));
}
return model;
};
var checkit = checkit || {};
/*jshint unused:false */
var checkit = {};
/*global checkit */
// In a full development environment this template would be expressed
// in a file containing only HTML and be compiled to the following as part
// of the server/build functionality.
......@@ -7,14 +9,11 @@
// included here.
//
checkit.TodoTemplate =
'<li class="todo">' +
'<div class="display">' +
'<input class="check" type="checkbox">' +
'<span class="todo-content"></span>' +
'<span class="todo-destroy">delete</span>' +
'</div>' +
'<div class="edit">' +
'<input class="todo-input" type="text">' +
'<span class="ui-tooltip-top" style="display:none;">Press enter to update this task.</span>' +
'</div>' +
'</li>';
'<li>' +
'<div class="view">' +
'<input class="toggle" type="checkbox">' +
'<label></label>' +
'<button class="destroy"></span>' +
'</div>' +
'<input class="edit">' +
'</li>';
// In a full development environment this template would be expressed
// in a file containing only HTML and be compiled to the following as part
// of the server/build functionality.
//
// Due to the limitations of a simple example that does not require
// any special server environment to try, the manually compiled version is
// included here.
//
checkit.TodosAppTemplate =
'<div class="todos-app">' +
'<h1>Todos</h1>' +
'<div class="content"></div>' +
'<p class="copyright">Another fine widget from AlphaBeta.</p>' +
'</div>';
// In a full development environment this template would be expressed
// in a file containing only HTML and be compiled to the following as part
// of the server/build functionality.
//
// Due to the limitations of a simple example that does not require
// any special server environment to try, the manually compiled version is
// included here.
//
checkit.TodosInputTemplate =
'<div class="create-todo">' +
'<input class="new-todo" placeholder="What needs to be done?" type="text">' +
'<span class="ui-tooltip-top" style="display:none;">Press enter to add this task.</span>' +
'</div>';
// In a full development environment this template would be expressed
// in a file containing only HTML and be compiled to the following as part
// of the server/build functionality.
//
// Due to the limitations of a simple example that does not require
// any special server environment to try, the manually compiled version is
// included here.
//
checkit.TodosListTemplate =
'<ul class="todos-list"></ul>';
// In a full development environment this template would be expressed
// in a file containing only HTML and be compiled to the following as part
// of the server/build functionality.
//
// Due to the limitations of a simple example that does not require
// any special server environment to try, the manually compiled version is
// included here.
//
checkit.TodosStatsTemplate =
'<div class="todos-stats"><strong class="todos-count"></strong> todos in list</div>';
/*global checkit */
// In a full development environment this template would be expressed
// in a file containing only HTML and be compiled to the following as part
// of the server/build functionality.
//
// Due to the limitations of a simple example that does not require
// any special server environment to try, the manually compiled version is
// included here.
//
checkit.TodosAppTemplate =
'<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 completed</label>' +
'<ul id="todo-list"></ul>' +
'</section>' +
'<footer id="footer">' +
'<span id="todo-count"></span>' +
'<ul id="filters">' +
'<li>' +
'<a class="all-filter" href="#/">All</a>' +
'</li>' +
'<li>' +
'<a class="incompleted-filter" href="#/active">Active</a>' +
'</li>' +
'<li>' +
'<a class="completed-filter" href="#/completed">Completed</a>' +
'</li>' +
'</ul>' +
'<button id="clear-completed"></button>' +
'</footer>' +
'</section>' +
'<footer id="info">' +
'<p>Double-click to edit a todo</p>' +
'<p>Inspired by the official <a href="https://github.com/maccman/spine.todos">Spine.Todos</a></p>' +
'<p>Created by <a href="http://github.com/petermichaux">Peter Michaux</a></p>' +
'</footer>';
// In a full development environment this template would be expressed
// in a file containing only HTML and be compiled to the following as part
// of the server/build functionality.
//
// Due to the limitations of a simple example that does not require
// any special server environment to try, the manually compiled version is
// included here.
//
checkit.TodosToolbarTemplate =
'<ul class="todos-toolbar">' +
'<li><input type="checkbox" class="allCheckbox"></li> ' +
'<li class="markallDone">mark all as complete</li> ' +
'<li class="markallUndone">mark all as incomplete</li> ' +
'<li class="deleteComplete">delete complete</li>' +
'</ul>';
/*jshint strict: false */
/*global checkit */
checkit.isBlank = function(str) {
return (/^\s*$/).test(str);
};
checkit.escapeHTML = function(str) {
return str.replace('&', '&amp;').replace('<', '&lt;');
};
checkit.isEnterKeyCode = function(keyCode) {
return keyCode === 13;
};
/*jshint strict: false */
/*global maria, aristocrat, checkit */
maria.ElementView.subclass(checkit, 'TodoView', {
uiActions: {
'mouseover .todo' : 'onMouseoverRoot' ,
'mouseout .todo' : 'onMouseoutRoot' ,
'click .check' : 'onClickCheck' ,
'click .todo-destroy': 'onClickDestroy' ,
'dblclick .todo-content': 'onDblclickDisplay',
'keyup .todo-input' : 'onKeyupInput' ,
'keypress .todo-input' : 'onKeypressInput' ,
'blur .todo-input' : 'onBlurInput'
},
properties: {
buildData: function() {
var model = this.getModel();
var content = model.getContent();
this.find('.todo-content').innerHTML =
content.replace('&', '&amp;').replace('<', '&lt;');
this.find('.check').checked = model.isDone();
aristocrat[model.isDone() ? 'addClass' : 'removeClass'](this.find('.todo'), 'done');
},
update: function() {
this.buildData();
},
showEdit: function() {
var input = this.find('.todo-input');
input.value = this.getModel().getContent();
aristocrat.addClass(this.find('.todo'), 'editing');
input.focus();
},
showDisplay: function() {
aristocrat.removeClass(this.find('.todo'), 'editing');
},
getInputValue: function() {
return this.find('.todo-input').value;
},
showHoverState: function() {
aristocrat.addClass(this.find('.todo'), 'todo-hover');
},
hideHoverState: function() {
aristocrat.removeClass(this.find('.todo'), 'todo-hover');
},
showToolTip: function() {
this.find('.ui-tooltip-top').style.display = 'block';
},
hideToolTip: function() {
this.find('.ui-tooltip-top').style.display = 'none';
}
}
uiActions: {
'click .destroy': 'onClickDestroy' ,
'click .toggle' : 'onClickToggle' ,
'dblclick label' : 'onDblclickLabel',
'keyup .edit' : 'onKeyupEdit' ,
'blur .edit' : 'onBlurEdit'
},
properties: {
buildData: function() {
var model = this.getModel();
var item = this.find('li');
aristocrat.removeClass(item, '(in|)completed');
aristocrat.addClass(item, (model.isCompleted() ? 'completed' : 'incompleted'));
this.find('label').innerHTML = checkit.escapeHTML(model.getTitle());
this.find('.toggle').checked = model.isCompleted();
},
update: function() {
this.buildData();
},
showEdit: function() {
var input = this.find('.edit');
input.value = this.getModel().getTitle();
aristocrat.addClass(this.find('li'), 'editing');
input.focus();
},
showDisplay: function() {
aristocrat.removeClass(this.find('li'), 'editing');
},
getInputValue: function() {
return this.find('.edit').value;
}
}
});
// All the subviews of a TodosAppView will always have
// the same model as the TodosAppView has.
//
maria.ElementView.subclass(checkit, 'TodosAppView', {
properties: {
getContainerEl: function() {
return this.find('.content'); // child views will be appended to this element
},
initialize: function() {
this.appendChild(new checkit.TodosInputView());
this.appendChild(new checkit.TodosToolbarView());
this.appendChild(new checkit.TodosListView());
this.appendChild(new checkit.TodosStatsView());
},
insertBefore: function(newChild, oldChild) {
newChild.setModel(this.getModel());
maria.ElementView.prototype.insertBefore.call(this, newChild, oldChild);
},
setModel: function(model) {
if (model !== this.getModel()) {
maria.ElementView.prototype.setModel.call(this, model);
var childViews = this.childNodes;
for (var i = 0, ilen = childViews.length; i < ilen; i++) {
childViews[i].setModel(model);
}
}
}
}
});
maria.ElementView.subclass(checkit, 'TodosInputView', {
uiActions: {
'focus .new-todo': 'onFocusInput' ,
'blur .new-todo': 'onBlurInput' ,
'keyup .new-todo': 'onKeyupInput' ,
'keypress .new-todo': 'onKeypressInput'
},
properties: {
getInputValue: function() {
return this.find('.new-todo').value;
},
clearInput: function() {
this.find('.new-todo').value = '';
},
showToolTip: function() {
this.find('.ui-tooltip-top').style.display = 'block';
},
hideToolTip: function() {
this.find('.ui-tooltip-top').style.display = 'none';
}
}
});
maria.SetView.subclass(checkit, 'TodosListView', {
properties: {
createChildView: function(todoModel) {
return new checkit.TodoView(todoModel);
}
}
});
maria.ElementView.subclass(checkit, 'TodosStatsView', {
properties: {
buildData: function() {
this.find('.todos-count').innerHTML = this.getModel().length;
},
update: function() {
this.buildData();
}
}
});
maria.ElementView.subclass(checkit, 'TodosToolbarView', {
uiActions: {
'click .allCheckbox' : 'onClickAllCheckbox' ,
'click .markallDone' : 'onClickMarkAllDone' ,
'click .markallUndone' : 'onClickMarkAllUndone',
'click .deleteComplete': 'onClickDeleteDone'
},
properties: {
buildData: function() {
var model = this.getModel();
var checkbox = this.find('.allCheckbox');
checkbox.checked = model.isAllDone();
checkbox.disabled = model.isEmpty();
},
update: function() {
this.buildData();
}
}
});
/*jshint strict: false */
/*global maria, aristocrat, checkit */
maria.SetView.subclass(checkit, 'TodosAppView', {
uiActions: {
'keyup #new-todo' : 'onKeyupNewTodo' ,
'click #toggle-all' : 'onClickToggleAll' ,
'click #clear-completed': 'onClickClearCompleted'
},
properties: {
buildData: function() {
var model = this.getModel();
var length = model.length;
this.find('#main').style.display = (length > 0) ? '' : 'none';
this.find('#footer').style.display = (length > 0) ? '' : 'none';
var checkbox = this.find('#toggle-all');
checkbox.checked = model.isAllCompleted();
checkbox.disabled = model.isEmpty();
var todoList = this.find('#todo-list');
model.getPossibleModes().forEach(function(mode) {
aristocrat.removeClass(todoList, mode);
});
aristocrat.addClass(todoList, model.getMode());
var incompletedLength = model.getIncompleted().length;
this.find('#todo-count').innerHTML =
'<strong>' + incompletedLength + '</strong> ' +
((incompletedLength === 1) ? 'item' : 'items') +
' left';
var selected = this.find('.selected');
if (selected) {
aristocrat.removeClass(selected, 'selected');
}
aristocrat.addClass(this.find('.' + model.getMode() + '-filter'), 'selected');
var completedLength = model.getCompleted().length;
var clearButton = this.find('#clear-completed');
clearButton.style.display = (completedLength > 0) ? '' : 'none';
clearButton.innerHTML = 'Clear completed (' + completedLength + ')';
},
update: function(evt) {
maria.SetView.prototype.update.call(this, evt);
this.buildData();
},
getContainerEl: function() {
// child views will be appended to this element
return this.find('#todo-list');
},
createChildView: function(todoModel) {
return new checkit.TodoView(todoModel);
},
getInputValue: function() {
return this.find('#new-todo').value;
},
clearInput: function() {
this.find('#new-todo').value = '';
}
}
});
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