Commit 5cc9baa7 authored by addyosmani's avatar addyosmani

Adding initial Maria.js implementation.

parent 4743337f
This diff is collapsed.
<!DOCTYPE html>
<html>
<head>
<title>To-do App</title>
<link href="css/todos.css" rel="stylesheet" type="text/css">
</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/evento/evento.js"></script>
<script src="lib/hijos/hijos.js"></script>
<script src="lib/arbutus/arbutus.js"></script>
<script src="lib/grail/grail.js"></script>
<script src="lib/hormigas/hormigas.js"></script>
<script src="src/namespace.js"></script>
<script src="src/borrow.js"></script>
<script src="src/borrowEvento.js"></script>
<script src="src/borrowHijos.js"></script>
<script src="src/borrowArbutus.js"></script>
<script src="src/borrowGrail.js"></script>
<script src="src/borrowHormigas.js"></script>
<script src="src/subclass.js"></script>
<script src="src/Model.js"></script>
<script src="src/SetModel.js"></script>
<script src="src/View.js"></script>
<script src="src/ElementView.js"></script>
<script src="src/SetView.js"></script>
<script src="src/Controller.js"></script>
<script src="src/Model.subclass.js"></script>
<script src="src/SetModel.subclass.js"></script>
<script src="src/View.subclass.js"></script>
<script src="src/ElementView.subclass.js"></script>
<script src="src/SetView.subclass.js"></script>
<script src="src/Controller.subclass.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>
</body>
</html>
maria.addEventListener(window, 'load', function() {
var loading = document.getElementById('loading');
loading.parentNode.removeChild(loading);
if ((typeof localStorage === 'object') && (typeof JSON === 'object')) {
var store = localStorage.getItem('todos-maria');
var model = store ? checkit.TodosModel.fromJSON(JSON.parse(store)) :
new checkit.TodosModel();
evento.addEventListener(model, 'change', function() {
localStorage.setItem('todos-maria', JSON.stringify(model.toJSON()));
});
}
var app = new checkit.TodosAppView(model);
document.body.appendChild(app.getRootEl());
});
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);
}
}
}
});
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();
}
}
});
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
};
}
}
});
checkit.TodoModel.fromJSON = function(todoJSON) {
var model = new checkit.TodoModel();
model._content = todoJSON.content;
model._isDone = todoJSON.is_done;
return model;
};
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();
});
}
}
});
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;
};
// 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.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>';
// 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>';
\ No newline at end of file
// 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>';
// 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>';
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: {
update: 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.getRootEl(), 'done');
},
showEdit: function() {
var input = this.find('.todo-input');
input.value = this.getModel().getContent();
aristocrat.addClass(this.getRootEl(), 'editing');
input.select();
},
showDisplay: function() {
aristocrat.removeClass(this.getRootEl(), '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';
}
}
});
// 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() {
var model = this.getModel();
if (!model) {
model = new checkit.TodosModel();
this.setModel(model);
}
this.appendChild(new checkit.TodosInputView(model));
this.appendChild(new checkit.TodosToolbarView(model));
this.appendChild(new checkit.TodosListView(model));
this.appendChild(new checkit.TodosStatsView(model));
},
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', {
modelConstructor: checkit.TodosModel,
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', {
modelConstructor: checkit.TodosModel,
properties: {
createChildView: function(todoModel) {
return new checkit.TodoView(todoModel);
}
}
});
maria.ElementView.subclass(checkit, 'TodosStatsView', {
modelConstructor: checkit.TodosModel,
properties: {
update: function() {
this.find('.todos-count').innerHTML = this.getModel().length;
}
}
});
maria.ElementView.subclass(checkit, 'TodosToolbarView', {
modelConstructor: checkit.TodosModel,
uiActions: {
'click .allCheckbox' : 'onClickAllCheckbox' ,
'click .markallDone' : 'onClickMarkAllDone' ,
'click .markallUndone' : 'onClickMarkAllUndone',
'click .deleteComplete': 'onClickDeleteDone'
},
properties: {
update: function() {
var model = this.getModel();
var checkbox = this.find('.allCheckbox');
checkbox.checked = model.isAllDone();
checkbox.disabled = model.isEmpty();
}
}
});
/*
Arbutus version 1
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||{};(function(){var trimLeft=/^\s+/,trimRight=/\s+$/;function trim(str){return str.replace(trimLeft,'').replace(trimRight,'');}
function getFirstChild(element){return element.firstChild;}
function getFirstGrandChild(element){return element.firstChild.firstChild;}
function getSecondGrandChild(element){return element.firstChild.firstChild.nextSibling;}
function getFirstGreatGrandChild(element){return element.firstChild.firstChild.firstChild;}
function getFirstGreatGreatGrandChild(element){return element.firstChild.firstChild.firstChild.firstChild;}
function Parser(before,after,getFirstResult){if(before){this.before=before;}
if(after){this.after=after;}
if(getFirstResult){this.getFirstResult=getFirstResult;}};Parser.prototype={before:'',after:'',parse:function(html,doc){var parser=doc.createElement('div');var fragment=doc.createDocumentFragment();parser.innerHTML=this.before+html+this.after;var node=this.getFirstResult(parser);var nextNode;while(node){nextNode=node.nextSibling;fragment.appendChild(node);node=nextNode;}
return fragment;},getFirstResult:getFirstChild};var parsers={'td':new Parser('<table><tbody><tr>','</tr></tbody></table>',getFirstGreatGreatGrandChild),'tr':new Parser('<table><tbody>','</tbody></table>',getFirstGreatGrandChild),'tbody':new Parser('<table>','</table>',getFirstGrandChild),'col':new Parser('<table><colgroup>','</colgroup></table>',getFirstGreatGrandChild),'option':new Parser('<select><option>a</option>','</select>',getSecondGrandChild)};parsers.th=parsers.td;parsers.thead=parsers.tbody;parsers.tfoot=parsers.tbody;parsers.caption=parsers.tbody;parsers.colgroup=parsers.tbody;var tagRegExp=/^<([a-z]+)/i;arbutus.parseHTML=function(html,doc){html=trim(html);var matches=html.match(tagRegExp),parser=(matches&&parsers[matches[1].toLowerCase()])||Parser.prototype;return parser.parse(html,doc||document);};}());
\ No newline at end of file
/*
Arbutus version 1
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 || {};
(function() {
var trimLeft = /^\s+/,
trimRight = /\s+$/;
function trim(str) {
return str.replace(trimLeft, '').replace(trimRight, '');
}
function getFirstChild(element) {
return element.firstChild;
}
function getFirstGrandChild(element) {
return element.firstChild.firstChild;
}
function getSecondGrandChild(element) {
return element.firstChild.firstChild.nextSibling;
}
function getFirstGreatGrandChild(element) {
return element.firstChild.firstChild.firstChild;
}
function getFirstGreatGreatGrandChild(element) {
return element.firstChild.firstChild.firstChild.firstChild;
}
function Parser(before, after, getFirstResult) {
if (before) {
this.before = before;
}
if (after) {
this.after = after;
}
if (getFirstResult) {
this.getFirstResult = getFirstResult;
}
};
Parser.prototype = {
before: '',
after: '',
parse: function(html, doc) {
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) {
nextNode = node.nextSibling;
fragment.appendChild(node);
node = nextNode;
}
return fragment;
},
getFirstResult: getFirstChild
};
var parsers = {
'td': new Parser('<table><tbody><tr>', '</tr></tbody></table>', getFirstGreatGreatGrandChild),
'tr': new Parser('<table><tbody>', '</tbody></table>', getFirstGreatGrandChild),
'tbody': new Parser('<table>', '</table>', getFirstGrandChild),
'col': new Parser('<table><colgroup>', '</colgroup></table>', getFirstGreatGrandChild),
// Without the option in the next line, the parsed option will always be selected.
'option': new Parser('<select><option>a</option>', '</select>', getSecondGrandChild)
};
parsers.th = parsers.td;
parsers.thead = parsers.tbody;
parsers.tfoot = parsers.tbody;
parsers.caption = parsers.tbody;
parsers.colgroup = parsers.tbody;
var tagRegExp = /^<([a-z]+)/i; // first group must be tag name
/**
@property arbutus.parseHTML
@parameter html {string} The string of HTML to be parsed.
@parameter doc {Document} Optional document object to create the new DOM nodes.
@return {DocumentFragment}
@description
The html string will be trimmed.
Returns a document fragment that has the children defined by the html string.
var fragment = arbutus.parseHTML('<p>alpha beta</p>');
document.body.appendChild(fragment);
Note that a call to this function is relatively expensive and you probably
don't want to have a loop of thousands with calls to this function.
*/
arbutus.parseHTML = function(html, doc) {
// IE will trim when setting innerHTML so unify for all browsers
html = trim(html);
var matches = html.match(tagRegExp),
parser = (matches && parsers[matches[1].toLowerCase()]) ||
Parser.prototype;
return parser.parse(html, doc || document);
};
}());
/*
Aristocrat version 1
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 || {};
(function() {
var regExpCache = {};
function getRegExp(className) {
return Object.prototype.hasOwnProperty.call(regExpCache, className) ?
regExpCache[className] :
(regExpCache[className] = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)'));
}
/**
@property aristocrat.hasClass
@parameter element {Element} The DOM element to test.
@parameter className {string} The class name to test for on element.
@returns {boolean}
@description
Tests if element has className in the element.className property.
aristocrat.hasClass(document.body, 'king');
*/
var hasClass = aristocrat.hasClass = function(el, className) {
return getRegExp(className).test(el.className);
};
/**
@property aristocrat.addClass
@parameter element {Element} The DOM element to test.
@parameter className {string} The class name to add to element.
@description
Add className to element.className if className is not already in element.className.
aristocrat.addClass(document.body, 'king');
*/
var addClass = aristocrat.addClass = function(el, className) {
if (!hasClass(el, className)) {
el.className = el.className + ' ' + className;
}
};
/**
@property aristocrat.removeClass
@parameter element {Element} The DOM element to test.
@parameter className {string} The class name to remove from element.
@description
Removes all occurrences of className in element.className.
aristocrat.removeClass(document.body, 'king');
*/
var removeClass = aristocrat.removeClass = function(el, className) {
var re = getRegExp(className);
while (re.test(el.className)) { // in case multiple occurrences
el.className = el.className.replace(re, ' ');
}
};
/**
@property aristocrat.toggleClass
@parameter element {Element} The DOM element to test.
@parameter className {string} The class name to toggle on element.
@description
If element.className has className then className is removed. Otherwise
className is added.
aristocrat.toggleClass(document.body, 'king');
*/
aristocrat.toggleClass = function(el, className) {
if (hasClass(el, className)) {
removeClass(el, className);
}
else {
addClass(el, className);
}
};
}());
This diff is collapsed.
This diff is collapsed.
/*
Evento version 0 - JavaScript libraries for working with the observer pattern
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||{};evento.EventTarget=function(){};(function(){function hasOwnProperty(o,p){return Object.prototype.hasOwnProperty.call(o,p);}
var create=(function(){function F(){}
return function(o){F.prototype=o;return new F();};}());evento.EventTarget.prototype.addEventListener=function(type,listener){hasOwnProperty(this,'_evento_listeners')||(this._evento_listeners={});hasOwnProperty(this._evento_listeners,type)||(this._evento_listeners[type]=[]);var listeners=this._evento_listeners[type];for(var i=0,ilen=listeners.length;i<ilen;i++){if(listeners[i]===listener){return;}}
listeners.push(listener);};evento.EventTarget.prototype.removeEventListener=function(type,listener){if(hasOwnProperty(this,'_evento_listeners')&&hasOwnProperty(this._evento_listeners,type)){var listeners=this._evento_listeners[type];for(var i=0,ilen=listeners.length;i<ilen;i++){if(listeners[i]===listener){listeners.splice(i,1);return;}}}};evento.EventTarget.prototype.addParentEventTarget=function(parent){if(typeof parent.dispatchEvent!=='function'){throw new TypeError('evento.EventTarget.prototype.addParentEventTarget: Parents must have dispatchEvent method.');}
hasOwnProperty(this,'_evento_parents')||(this._evento_parents=[]);var parents=this._evento_parents;for(var i=0,ilen=parents.length;i<ilen;i++){if(parents[i]===parent){return;}}
parents.push(parent);};evento.EventTarget.prototype.removeParentEventTarget=function(parent){if(hasOwnProperty(this,'_evento_parents')){var parents=this._evento_parents;for(var i=0,ilen=parents.length;i<ilen;i++){if(parents[i]===parent){parents.splice(i,1);return;}}}};evento.EventTarget.prototype.dispatchEvent=function(evt){evt=create(evt);('target'in evt)||(evt.target=this);evt.currentTarget=this;evt._propagationStopped=('bubbles'in evt)?!evt.bubbles:false;evt.stopPropagation=function(){evt._propagationStopped=true;};if(hasOwnProperty(this,'_evento_listeners')&&hasOwnProperty(this._evento_listeners,evt.type)){var listeners=this._evento_listeners[evt.type].slice(0);for(var i=0,ilen=listeners.length;i<ilen;i++){var listener=listeners[i];if(typeof listener==='function'){listener.call(this,evt);}
else{listener.handleEvent(evt);}}}
if(hasOwnProperty(this,'_evento_parents')&&!evt._propagationStopped){for(var i=0,ilen=this._evento_parents.length;i<ilen;i++){this._evento_parents[i].dispatchEvent(evt);}}};evento.EventTarget.call(evento.EventTarget.prototype);evento.EventTarget.mixin=function(obj){var pt=evento.EventTarget.prototype;for(var p in pt){if(hasOwnProperty(pt,p)&&(typeof pt[p]==='function')){obj[p]=pt[p];}}
evento.EventTarget.call(obj);};}());(function(){function createBundle(element,type,listener,auxArg){var bundle={element:element,type:type,listener:listener};if(arguments.length>3){bundle.auxArg=auxArg;}
if(typeof listener==='function'){var thisObj=arguments.length>3?auxArg:element;bundle.wrappedHandler=function(evt){listener.call(thisObj,evt);};}
else if(typeof auxArg==='function'){bundle.wrappedHandler=function(evt){auxArg.call(listener,evt);};}
else{var methodName=arguments.length>3?auxArg:'handleEvent';bundle.wrappedHandler=function(evt){listener[methodName](evt);};}
return bundle;}
function bundlesAreEqual(a,b){return(a.element===b.element)&&(a.type===b.type)&&(a.listener===b.listener)&&((!a.hasOwnProperty('auxArg')&&!b.hasOwnProperty('auxArg'))||(a.hasOwnProperty('auxArg')&&b.hasOwnProperty('auxArg')&&(a.auxArg===b.auxArg)));}
function indexOfBundle(bundles,bundle){for(var i=0,ilen=bundles.length;i<ilen;i++){if(bundlesAreEqual(bundles[i],bundle)){return i;}}
return-1;}
evento.addEventListener=function(element,type,listener,auxArg){var bundle=createBundle.apply(null,arguments);if(listener._evento_bundles){if(indexOfBundle(listener._evento_bundles,bundle)>=0){return;}}
else{listener._evento_bundles=[];}
if(typeof bundle.element.addEventListener==='function'){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.');}
listener._evento_bundles.push(bundle);};var remove=evento.removeEventListener=function(element,type,listener,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.');}
listener._evento_bundles.splice(i,1);}}};evento.purgeEventListener=function(listener){if(listener._evento_bundles){var bundles=listener._evento_bundles.slice(0);for(var i=0,ilen=bundles.length;i<ilen;i++){var bundle=bundles[i];if(bundle.hasOwnProperty('auxArg')){remove(bundle.element,bundle.type,bundle.listener,bundle.auxArg);}
else{remove(bundle.element,bundle.type,bundle.listener);}}}};}());
\ No newline at end of file
This diff is collapsed.
/*
Grail version 2
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||{};(function(){var trimLeft=/^\s+/;var trimRight=/\s+$/;var idRegExp=/^#(\S+)$/;var tagClassRegExp=/^([\w-]+)?(?:\.([\w-]+))?$/;function trim(str){return str.replace(trimLeft,'').replace(trimRight,'');}
function isHostMethod(obj,prop){return(typeof obj[prop]==='function')||((typeof obj[prop]==='object')&&(obj[prop]!==null));}
function findById(id,root){return(root.id===id)?root:(isHostMethod(root,'getElementById'))?root.getElementById(id):(isHostMethod(root,'querySelector'))?root.querySelector('#'+id):firstInDOM(root,function(node){return node.id===id;});}
function getTagNameClassNameMatcher(tagName,className){tagName=tagName?tagName.toUpperCase():'*';if(className){var regExp=new RegExp('(?:^|\\s+)'+className+'(?:\\s+|$)');}
return function(element){return(((tagName==='*')||(element.tagName&&(element.tagName.toUpperCase()===tagName)))&&((!className)||regExp.test(element.className)));}}
function filterDOM(node,func){var results=[];function walk(node){if(func(node)){results.push(node);}
node=node.firstChild;while(node){walk(node);node=node.nextSibling;}}
walk(node);return results;}
function firstInDOM(node,func){function walk(node){if(func(node)){return node;}
node=node.firstChild;while(node){var result=walk(node);if(result){return result;}
node=node.nextSibling;}}
return walk(node);}
grail.findAll=function(selector,root){selector=trim(selector);root=root||document;var matches;if(matches=selector.match(idRegExp)){var el=findById(matches[1],root);return el?[el]:[];}
else if(matches=selector.match(tagClassRegExp)){var tagNameClassNameMatcher=getTagNameClassNameMatcher(matches[1],matches[2]);if(isHostMethod(root,'querySelectorAll')){var elements;var results=[];if(tagNameClassNameMatcher(root)){results.push(root);}
elements=root.querySelectorAll(selector);for(var i=0,ilen=elements.length;i<ilen;i++){results.push(elements[i]);}
return results;}
else{return filterDOM(root,tagNameClassNameMatcher);}}
else{throw new Error('grail.findAll: Unsupported selector "'+selector+'".');}};grail.find=function(selector,root){selector=trim(selector);root=root||document;var matches;if(matches=selector.match(idRegExp)){return findById(matches[1],root);}
else if(matches=selector.match(tagClassRegExp)){var tagNameClassNameMatcher=getTagNameClassNameMatcher(matches[1],matches[2]);if(isHostMethod(root,'querySelector')){return tagNameClassNameMatcher(root)?root:root.querySelector(selector);}
else{return firstInDOM(root,tagNameClassNameMatcher);}}
else{throw new Error('grail.find: Unsupported selector "'+selector+'".');}};}());
\ No newline at end of file
/*
Grail version 2
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 || {};
(function() {
var trimLeft = /^\s+/;
var trimRight = /\s+$/;
// group 1 must be the id
var idRegExp = /^#(\S+)$/;
// group 1 must be the tagName and group 2 must be the className
var tagClassRegExp = /^([\w-]+)?(?:\.([\w-]+))?$/;
function trim(str) {
return str.replace(trimLeft, '').replace(trimRight, '');
}
function isHostMethod(obj, prop) {
return (typeof obj[prop] === 'function') ||
((typeof obj[prop] === 'object') && (obj[prop] !== null)); // Internet Explorer
}
function findById(id, root) {
return (root.id === id) ?
root :
(isHostMethod(root, 'getElementById')) ?
root.getElementById(id) :
(isHostMethod(root, 'querySelector')) ?
root.querySelector('#' + id) :
firstInDOM(root, function(node) {return node.id === id;});
}
function getTagNameClassNameMatcher(tagName, className) {
tagName = tagName ? tagName.toUpperCase() : '*';
if (className) {
var regExp = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
}
return function(element) {
return (((tagName === '*') ||
(element.tagName && (element.tagName.toUpperCase() === tagName))) &&
((!className) ||
regExp.test(element.className)));
}
}
function filterDOM(node, func) {
var results = [];
function walk(node) {
if (func(node)) {
results.push(node);
}
node = node.firstChild;
while (node) {
walk(node);
node = node.nextSibling;
}
}
walk(node);
return results;
}
function firstInDOM(node, func) {
function walk(node) {
if (func(node)) {
return node;
}
node = node.firstChild;
while (node) {
var result = walk(node);
if (result) {
return result;
}
node = node.nextSibling;
}
}
return walk(node);
}
/**
@property grail.findAll
@parameter selector {string} The CSS selector for the search.
@parameter root {Document|Element} Optional element to use as the search start point.
@description
Search for all elements matching the CSS selector. Returns an array of the elements.
Acceptable simple selectors are of the following forms only.
div
#alpha
.beta
div.gamma
In the case of a #myId selector, the returned array will always have
zero or one elements. It is more likely that you want to call grail.find when
using an id selector.
If the root element is supplied it is used as the starting point for the search.
The root element will be in the results if it matches the selector.
If the root element is not supplied then the current document is used
as the search starting point.
grail.findAll('#alpha');
grail.findAll('div.gamma', document.body);
*/
grail.findAll = function(selector, root) {
selector = trim(selector);
root = root || document;
var matches;
if (matches = selector.match(idRegExp)) {
var el = findById(matches[1], root);
return el ? [el] : [];
}
else if (matches = selector.match(tagClassRegExp)) {
var tagNameClassNameMatcher = getTagNameClassNameMatcher(matches[1], matches[2]);
if (isHostMethod(root, 'querySelectorAll')) {
var elements;
var results = [];
if (tagNameClassNameMatcher(root)) {
results.push(root);
}
elements = root.querySelectorAll(selector);
for (var i = 0, ilen = elements.length; i < ilen; i++) {
results.push(elements[i]);
}
return results;
}
else {
return filterDOM(root, tagNameClassNameMatcher);
}
}
else {
throw new Error('grail.findAll: Unsupported selector "'+selector+'".');
}
};
/**
@property grail.find
@parameter selector {string} The CSS selector for the search.
@parameter root {Document|Element} Optional element to use as the search start point.
@description
Search for the first element matching the CSS selector. If the element is
found then it is returned. If no matching element is found then
null or undefined is returned.
The rest of the details are the same as for grail.findAll.
*/
grail.find = function(selector, root) {
selector = trim(selector);
root = root || document;
var matches;
if (matches = selector.match(idRegExp)) {
return findById(matches[1], root);
}
else if (matches = selector.match(tagClassRegExp)) {
var tagNameClassNameMatcher = getTagNameClassNameMatcher(matches[1], matches[2]);
if (isHostMethod(root, 'querySelector')) {
return tagNameClassNameMatcher(root) ? root : root.querySelector(selector);
}
else {
return firstInDOM(root, tagNameClassNameMatcher);
}
}
else {
throw new Error('grail.find: Unsupported selector "'+selector+'".');
}
};
}());
/*
Hijos version 0 - JavaScript classes for building tree structures and the composite design pattern
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||{};hijos.Leaf=function(){this.parentNode=null;this.previousSibling=null;this.nextSibling=null;};hijos.Leaf.prototype.destroy=function(){this.parentNode=null;this.previousSibling=null;this.nextSibling=null;};hijos.Leaf.call(hijos.Leaf.prototype);hijos.Leaf.mixin=function(obj){obj.destroy=hijos.Leaf.prototype.destroy;hijos.Leaf.call(obj);};hijos.Node=function(){hijos.Leaf.call(this);this.childNodes=[];this.firstChild=null;this.lastChild=null;};hijos.Leaf.mixin(hijos.Node.prototype);hijos.Node.prototype.destroy=function(){var children=this.childNodes.slice(0);for(var i=0,ilen=children.length;i<ilen;i++){children[i].destroy();}
hijos.Leaf.prototype.destroy.call(this);this.childNodes=null;this.firstChild=null;this.lastChild=null;};hijos.Node.prototype.hasChildNodes=function(){return this.childNodes.length>0;};hijos.Node.prototype.insertBefore=function(newChild,oldChild){if(arguments.length<2){throw new Error('hijos.Node.prototype.insertBefore: not enough arguments.');}
if(oldChild===undefined){oldChild=null;}
if((newChild===oldChild)||(oldChild&&(oldChild.previousSibling===newChild))||((oldChild===null)&&this.lastChild===newChild)){return;}
var node=this;while(node){if(node===newChild){throw new Error('hijos.Node.prototype.insertBefore: Node cannot be inserted at the specified point in the hierarchy.');}
node=node.parentNode;}
var children=this.childNodes;var indexForNewChild;if(oldChild===null){indexForNewChild=children.length;}
else{for(var i=0,ilen=children.length;i<ilen;i++){if(children[i]===oldChild){indexForNewChild=i;break;}}
if(typeof indexForNewChild!=='number'){throw new Error('hijos.Node.prototype.insertBefore: Node was not found.');}}
var parent=newChild.parentNode;if(parent){parent.removeChild(newChild);}
children.splice(indexForNewChild,0,newChild);this.firstChild=children[0];this.lastChild=children[children.length-1];newChild.parentNode=this;var previousSibling=newChild.previousSibling=(children[indexForNewChild-1]||null);if(previousSibling){previousSibling.nextSibling=newChild;}
var nextSibling=newChild.nextSibling=(children[indexForNewChild+1]||null);if(nextSibling){nextSibling.previousSibling=newChild;}};hijos.Node.prototype.appendChild=function(newChild){if(arguments.length<1){throw new Error('hijos.Node.prototype.appendChild: not enough arguments.');}
this.insertBefore(newChild,null);};hijos.Node.prototype.replaceChild=function(newChild,oldChild){if(arguments.length<2){throw new Error('hijos.Node.prototype.replaceChild: not enough arguments.');}
if(!oldChild){throw new Error('hijos.Node.prototype.replaceChild: oldChild must not be falsy.');}
if(newChild===oldChild){return;}
this.insertBefore(newChild,oldChild);this.removeChild(oldChild);};hijos.Node.prototype.removeChild=function(oldChild){if(arguments.length<1){throw new Error('hijos.Node.prototype.removeChild: not enough arguments.');}
var children=this.childNodes;for(var i=0,ilen=children.length;i<ilen;i++){if(children[i]===oldChild){var previousSibling=children[i-1];if(previousSibling){previousSibling.nextSibling=oldChild.nextSibling;}
var nextSibling=children[i+1];if(nextSibling){nextSibling.previousSibling=oldChild.previousSibling;}
oldChild.parentNode=null;oldChild.previousSibling=null;oldChild.nextSibling=null;children.splice(i,1);this.firstChild=children[0]||null;this.lastChild=children[children.length-1]||null;return;}}
throw new Error('hijos.Node.prototype.removeChild: node not found.');};hijos.Node.call(hijos.Node.prototype);hijos.Node.mixin=function(obj){for(var p in hijos.Node.prototype){if(Object.prototype.hasOwnProperty.call(hijos.Node.prototype,p)&&(typeof hijos.Node.prototype[p]==='function')){obj[p]=hijos.Node.prototype[p];}}
hijos.Node.call(obj);};
\ No newline at end of file
This diff is collapsed.
/*
Hormigas version 1
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||{};(function(){var nextId=0;function getId(){return nextId++;}
function initSet(set){set._hormigas_ObjectSet_elements={};set.length=0;}
hormigas.ObjectSet=function(){initSet(this);for(var i=0,ilen=arguments.length;i<ilen;i++){this.add(arguments[i]);}};hormigas.ObjectSet.prototype.has=function(element){return Object.prototype.hasOwnProperty.call(element,'_hormigas_ObjectSet_id')&&Object.prototype.hasOwnProperty.call(this._hormigas_ObjectSet_elements,element._hormigas_ObjectSet_id);};hormigas.ObjectSet.prototype.add=function(element){if(this.has(element)){return false;}
else{var id;if(!Object.prototype.hasOwnProperty.call(element,'_hormigas_ObjectSet_id')){element._hormigas_ObjectSet_id=getId();}
this._hormigas_ObjectSet_elements[element._hormigas_ObjectSet_id]=element;this.length++;return true;}};hormigas.ObjectSet.prototype['delete']=function(element){if(this.has(element)){delete this._hormigas_ObjectSet_elements[element._hormigas_ObjectSet_id];this.length--;return true;}
else{return false;}};hormigas.ObjectSet.prototype.empty=function(){if(this.length>0){initSet(this);return true;}
else{return false;}};hormigas.ObjectSet.prototype.toArray=function(){var elements=[];for(var p in this._hormigas_ObjectSet_elements){if(Object.prototype.hasOwnProperty.call(this._hormigas_ObjectSet_elements,p)){elements.push(this._hormigas_ObjectSet_elements[p]);}}
return elements;};hormigas.ObjectSet.prototype.forEach=function(callbackfn){var thisArg=arguments[1];for(var p in this._hormigas_ObjectSet_elements){if(Object.prototype.hasOwnProperty.call(this._hormigas_ObjectSet_elements,p)){callbackfn.call(thisArg,this._hormigas_ObjectSet_elements[p],this);}}};hormigas.ObjectSet.prototype.every=function(callbackfn){var thisArg=arguments[1];for(var p in this._hormigas_ObjectSet_elements){if(Object.prototype.hasOwnProperty.call(this._hormigas_ObjectSet_elements,p)&&!callbackfn.call(thisArg,this._hormigas_ObjectSet_elements[p],this)){return false;}}
return true;};hormigas.ObjectSet.prototype.some=function(callbackfn){var thisArg=arguments[1];for(var p in this._hormigas_ObjectSet_elements){if(Object.prototype.hasOwnProperty.call(this._hormigas_ObjectSet_elements,p)&&callbackfn.call(thisArg,this._hormigas_ObjectSet_elements[p],this)){return true;}}
return false;};hormigas.ObjectSet.prototype.reduce=function(callbackfn){var elements=this.toArray();var i=0;var ilen=elements.length;var accumulator;if(arguments.length>1){accumulator=arguments[1];}
else if(ilen<1){throw new TypeError('reduce of empty set with no initial value');}
else{i=1;accumulator=elements[0];}
while(i<ilen){accumulator=callbackfn.call(undefined,accumulator,elements[i],this);i++;}
return accumulator;};hormigas.ObjectSet.prototype.map=function(callbackfn){var thisArg=arguments[1];var result=[];for(var p in this._hormigas_ObjectSet_elements){if(Object.prototype.hasOwnProperty.call(this._hormigas_ObjectSet_elements,p)){result.push(callbackfn.call(thisArg,this._hormigas_ObjectSet_elements[p],this));}}
return result;};hormigas.ObjectSet.prototype.filter=function(callbackfn){var thisArg=arguments[1];var result=[];for(var p in this._hormigas_ObjectSet_elements){if(Object.prototype.hasOwnProperty.call(this._hormigas_ObjectSet_elements,p)){var element=this._hormigas_ObjectSet_elements[p];if(callbackfn.call(thisArg,element,this)){result.push(element);}}}
return result;};}());hormigas.ObjectSet.call(hormigas.ObjectSet.prototype);hormigas.ObjectSet.mixin=function(obj){for(var p in hormigas.ObjectSet.prototype){if(Object.prototype.hasOwnProperty.call(hormigas.ObjectSet.prototype,p)&&(typeof hormigas.ObjectSet.prototype[p]==='function')){obj[p]=hormigas.ObjectSet.prototype[p];}}
hormigas.ObjectSet.call(obj);};
\ No newline at end of file
This diff is collapsed.
maria.Controller = function() {
this.initialize();
};
maria.Controller.prototype.initialize = function() {};
maria.Controller.prototype.destroy = function() {
this._model = null;
if (this._view) {
this._view.setController(null);
this._view = null;
}
};
maria.Controller.prototype.getModel = function() {
return this._model;
};
maria.Controller.prototype.setModel = function(model) {
this._model = model;
};
maria.Controller.prototype.getView = function() {
return this._view;
};
maria.Controller.prototype.setView = function(view) {
this._view = view;
};
maria.Controller.subclass = maria.subclass;
maria.ElementView = function(model, controller, doc) {
this._doc = doc || document;
maria.View.call(this, model, controller);
};
maria.ElementView.prototype = new maria.View();
maria.ElementView.prototype.constructor = maria.ElementView;
maria.ElementView.prototype.getDocument = function() {
return this._doc;
};
maria.ElementView.prototype.getTemplate = function() {
return '<div></div>';
};
maria.ElementView.prototype.getUIActions = function() {
return {};
};
(function() {
var actionRegExp = /^(\S+)\s*(.*)$/;
maria.ElementView.prototype.getRootEl = function() {
if (!this._rootEl) {
// parseHTML returns a DocumentFragment so take firstChild as the rootEl
var rootEl = this._rootEl = maria.parseHTML(this.getTemplate(), this._doc).firstChild;
var uiActions = this.getUIActions();
for (var key in uiActions) {
if (Object.prototype.hasOwnProperty.call(uiActions, key)) {
var matches = key.match(actionRegExp),
eventType = matches[1],
selector = matches[2],
methodName = uiActions[key],
elements = maria.findAll(selector, this._rootEl);
for (var i = 0, ilen = elements.length; i < ilen; i++) {
evento.addEventListener(elements[i], eventType, this, methodName);
}
}
}
var childViews = this.childNodes;
for (var i = 0, ilen = childViews.length; i < ilen; i++) {
this.getContainerEl().appendChild(childViews[i].getRootEl());
}
this.update();
}
return this._rootEl;
};
}());
maria.ElementView.prototype.getContainerEl = function() {
return this.getRootEl();
};
maria.ElementView.prototype.insertBefore = function(newChild, oldChild) {
maria.View.prototype.insertBefore.call(this, newChild, oldChild);
if (this._rootEl) {
this.getContainerEl().insertBefore(newChild.getRootEl(), oldChild ? oldChild.getRootEl() : null);
}
};
maria.ElementView.prototype.removeChild = function(oldChild) {
maria.View.prototype.removeChild.call(this, oldChild);
if (this._rootEl) {
this.getContainerEl().removeChild(oldChild.getRootEl());
}
};
maria.ElementView.prototype.find = function(selector) {
return maria.find(selector, this.getRootEl());
};
maria.ElementView.prototype.findAll = function(selector) {
return maria.findAll(selector, this.getRootEl());
};
maria.ElementView.subclass = function(namespace, name, options) {
options = options || {};
var template = options.template;
var templateName = options.templateName || name.replace(/(View|)$/, 'Template');
var uiActions = options.uiActions;
var properties = options.properties || (options.properties = {});
if (!Object.prototype.hasOwnProperty.call(properties, 'getTemplate')) {
if (template) {
properties.getTemplate = function() {
return template;
};
}
else if (templateName) {
properties.getTemplate = function() {
return namespace[templateName];
};
}
}
if (uiActions) {
if (!Object.prototype.hasOwnProperty.call(properties, 'getUIActions')) {
properties.getUIActions = function() {
return uiActions;
};
}
for (var key in uiActions) {
if (Object.prototype.hasOwnProperty.call(uiActions, key)) {
var methodName = uiActions[key];
if (!Object.prototype.hasOwnProperty.call(properties, methodName)) {
(function(methodName) {
properties[methodName] = function(evt) {
this.getController()[methodName](evt);
};
}(methodName));
}
}
}
}
maria.View.subclass.call(this, namespace, name, options);
};
/**
@property maria.Model
@description
A constructor function to create new model objects.
var model = new maria.Model();
The most interesting feature of model objects is that they are event
targets and so can be observed by any event listeners. Other model
objects, view objects, or any other interested objects can observe by
being added as event listeners.
For example, the following view object's "update" method will be called
when a "change" event is dispatched on the model objects.
var view = {
update: function(evt) {
alert('The model changed!');
}
};
maria.addEventListener(model, 'change', view, 'update');
The model can dispatch a "change" event on itself when the model
changes.
model.setContent = function(content) {
this._content = content;
model.dispatchEvent({type: 'change'});
};
If desired, a model can push additional data to its observers by
including that data on the event object.
model.setContent = function(content) {
var previousContent = this._content;
this._content = content;
model.dispatchEvent({
type: 'change',
previousContent: previousContent,
content: content
});
};
An event listener can be removed from a model object.
maria.removeEventListener(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
can be done at a low level for a to-do application.
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.constructor = checkit.TodoModel;
checkit.TodoModel.prototype._content = '';
checkit.TodoModel.prototype._isDone = false;
checkit.TodoModel.prototype.getContent = function() {
return this._content;
};
checkit.TodoModel.prototype.setContent = function(content) {
content = ('' + content).replace(/^\s+|\s+$/g, '');
if (this._content !== content) {
this._content = content;
this.dispatchEvent({type: 'change'});
}
};
checkit.TodoModel.prototype.isDone = function() {
return this._isDone;
};
checkit.TodoModel.prototype.setDone = function(isDone) {
isDone = !!isDone;
if (this._isDone !== isDone) {
this._isDone = isDone;
this.dispatchEvent({type: 'change'});
}
};
checkit.TodoModel.prototype.toggleDone = function() {
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.
(See evento.EventTarget for advanced information about event bubbling
using "addParentEventTarget" and "removeParentEventTarget".)
*/
maria.Model = function() {
maria.EventTarget.call(this);
this.initialize();
};
maria.EventTarget.mixin(maria.Model.prototype);
maria.Model.prototype.initialize = function() {};
maria.Model.prototype.destroy = function() {
this.dispatchEvent({type: 'destroy'});
};
/**
@property maria.Model.subclass
@description
A function that makes subclassing maria.Model more compact.
The following example creates a checkit.TodoModel constructor function
equivalent to the more verbose example shown in the documentation
for maria.Model.
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());
}
}
});
*/
maria.Model.subclass = maria.subclass;
/**
@property maria.SetModel
@description
A constructor function to create new set model objects. A set model
object is a collection of elements. An element can only be included
once in a set model object.
The constructor takes multiple arguments and populates the set model
with those elements.
var alpha = new maria.Model();
alpha.name = 'Alpha';
var beta = new maria.Model();
beta.name = 'Beta';
var setModel = new maria.SetModel(alpha, beta);
You can create an empty set model object.
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"
events when elements are added or deleted from the the set.
var view = {
update: function(evt) {
alert(setModel.length + ' element(s) in the set.');
}
};
maria.addEventListener(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
true if the element is added to the set.
setModel.add(alpha); // returns true, alpha was added
setModel.add(alpha); // returns false, alpha not added again
The add method accepts multiple objects and only dispatches
a single "change" event if any of the arguments are added to
the set model object.
setModel.add(alpha, beta); // returns true, beta was added
When elements are added to the set model object, all "change" event
listeners are passed an event object with an addedTargets property
which has an array containing all elements added to the set model
object.
You can check if an element is in the set.
setModel.has(alpha); // returns true, alpha was added above
You can get the number of elements in the set.
setModel.length; // returns 2
An element can be deleted from the set. Removing it multiple times
has no effect. The delete method returns true if the element is
deleted from the set.
setModel['delete'](alpha); // returns true, alpha was deleted
setModel['delete'](alpha); // returns false, alpha wasn't in set
The delete method accepts multiple objects.
setModel['delete'](alpha, beta); // returns true, beta was removed
When elements are deleted from the set model object, all "change" event
listeners are passed an event object with a deletedTargets property
which is an array containing all elements deleted from the set model
object.
Note that delete is a keyword in JavaScript and to keep old browsers
happy you need to write setModel['delete']. You can write
setModel.delete if old browsers are not supported by your application.
You can empty a set in one call. The method returns true if any
elements are removed from the set model object.
setModel.empty(); // returns false, alpha and beta removed above.
If the call to empty does delete elements from the set, all "change"
event listeners are passed an event object with deletedTargets just
as for the delete method.
Another interesting feature of a set model object is that it observes
its elements (for all elements that implement the event target
interface) and when an element dispatches a "destroy" event then
the element is automatically removed from the set model object. The
set model object then dispatches a "change" event with a deletedTargets
property just as for the delete method.
A set model object has some other handy methods.
setModel.add(alpha, beta);
setModel.toArray(); // returns [alpha, beta] or [beta, alpha]
setModel.forEach(function(element) {
alert(element.name);
});
setModel.every(function(element) {
return element.name.length > 4;
}); // returns false
setModel.some(function(element) {
return element.name.length > 4;
}); // returns true
setModel.reduce(function(accumulator, element) {
return accumulator + element.name.length;
}, 0); // returns 9
setModel.map(function(element) {
return element.name.length;
}); // returns [4, 5] or [5, 4]
setModel.filter(function(element) {
return element.name.length > 4;
}); // returns [alpha]
The order of the elements returned by toArray and the order of
iteration of the other methods is undefined as a set is an unordered
collection. Do not depend on any ordering that the current
implementation uses.
A particularly useful pattern is using maria.SetModel as the
"superclass" of your application's collection model. The following
example shows how this can be done at a low level for a to-do
application. See maria.SetModel.subclass for a more compact way
to accomplish the same.
checkit.TodosModel = function() {
maria.SetModel.apply(this, arguments);
};
checkit.TodosModel.prototype = new maria.SetModel();
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();
});
};
checkit.TodosModel.prototype.getUndone = function() {
return this.filter(function(todo) {
return !todo.isDone();
});
};
checkit.TodosModel.prototype.isAllDone = function() {
return this.length > 0 &&
(this.getDone().length === this.length);
};
checkit.TodosModel.prototype.markAllDone = function() {
this.forEach(function(todo) {
todo.setDone(true);
});
};
checkit.TodosModel.prototype.markAllUndone = function() {
this.forEach(function(todo) {
todo.setDone(false);
});
};
checkit.TodosModel.prototype.deleteDone = function() {
this['delete'].apply(this, this.getDone());
};
Another feature of set model objects is that events dispatched on
elements of the set model object bubble up and are dispatched on the
set model object. This makes it possible to observe only the set model
object and still know when elements in the set are changing, for
example. This can complement well the flyweight pattern used in a view.
*/
maria.SetModel = function() {
maria.ObjectSet.apply(this, arguments);
maria.Model.call(this);
};
maria.SetModel.prototype = new maria.Model();
maria.SetModel.prototype.constructor = maria.SetModel;
maria.ObjectSet.mixin(maria.SetModel.prototype);
// Wrap the set mutator methods to dispatch events.
// takes multiple arguments so that only one event will be fired
//
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)) {
added.push(argument);
if ((typeof argument.addEventListener === 'function') &&
(typeof argument.removeEventListener === 'function')) {
argument.addEventListener('destroy', this);
}
if ((typeof argument.addParentEventTarget === 'function') &&
// want to know can remove later
(typeof argument.removeParentEventTarget === 'function')) {
argument.addParentEventTarget(this);
}
}
}
var modified = added.length > 0;
if (modified) {
this.dispatchEvent({type: 'change', addedTargets: added, deletedTargets: []});
}
return modified;
};
// takes multiple arguments so that only one event will be fired
//
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)) {
deleted.push(argument);
if (typeof argument.removeEventListener === 'function') {
argument.removeEventListener('destroy', this);
}
if (typeof argument.removeParentEventTarget === 'function') {
argument.removeParentEventTarget(this);
}
}
}
var modified = deleted.length > 0;
if (modified) {
this.dispatchEvent({type: 'change', addedTargets: [], deletedTargets: deleted});
}
return modified;
};
maria.SetModel.prototype.empty = function() {
var deleted = this.toArray();
var result = maria.ObjectSet.prototype.empty.call(this);
if (result) {
for (var i = 0, ilen = deleted.length; i < ilen; i++) {
var element = deleted[i];
if (typeof element.removeEventListener === 'function') {
element.removeEventListener('destroy', this);
}
if (typeof element.removeParentEventTarget === 'function') {
element.removeParentEventTarget(this);
}
}
this.dispatchEvent({type: 'change', addedTargets: [], deletedTargets: deleted});
}
return result;
};
maria.SetModel.prototype.handleEvent = function(ev) {
// If it is a destroy event being dispatched on the
// destroyed element then we want to remove it from
// this set.
if ((ev.type === 'destroy') &&
(ev.currentTarget === ev.target)) {
this['delete'](ev.target);
}
};
/**
@property maria.SetModel.subclass
@description
A function that makes subclassing maria.SetModel more compact.
The following example creates a checkit.TodosModel constructor function
equivalent to the more verbose example shown in the documentation
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();
});
},
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());
}
}
});
*/
maria.SetModel.subclass = maria.Model.subclass;
maria.SetView = function() {
maria.ElementView.apply(this, arguments);
};
maria.SetView.prototype = new maria.ElementView();
maria.SetView.prototype.constructor = maria.SetView;
maria.SetView.prototype.setModel = function(model) {
if (this.getModel() !== model) {
maria.ElementView.prototype.setModel.call(this, model);
var childViews = this.childNodes.slice(0);
for (var i = 0, ilen = childViews.length; i < ilen; i++) {
this.removeChild(childViews[i]);
}
var childModels = this.getModel().toArray();
for (var i = 0, ilen = childModels.length; i < ilen; i++) {
this.appendChild(this.createChildView(childModels[i]));
}
}
};
maria.SetView.prototype.createChildView = function(model) {
return new maria.ElementView(model);
};
maria.SetView.prototype.update = function(evt) {
// Check if there is an event as this method is also called
// at the end of building the view.
if (evt) {
if (evt.addedTargets && evt.addedTargets.length) {
this.handleAdd(evt);
}
if (evt.deletedTargets && evt.deletedTargets.length) {
this.handleDelete(evt);
}
}
};
maria.SetView.prototype.handleAdd = function(evt) {
var childModels = evt.addedTargets;
for (var i = 0, ilen = childModels.length; i < ilen; i++) {
this.appendChild(this.createChildView(childModels[i]));
}
};
maria.SetView.prototype.handleDelete = function(evt) {
var childModels = evt.deletedTargets;
for (var i = 0, ilen = childModels.length; i < ilen; i++) {
var childModel = childModels[i];
var childViews = this.childNodes;
for (var j = 0, jlen = childViews.length; j < jlen; j++) {
var childView = childViews[j];
if (childView.getModel() === childModel) {
this.removeChild(childView);
break;
}
}
}
};
maria.SetView.subclass = maria.ElementView.subclass;
maria.View = function(model, controller) {
maria.Node.call(this);
this.setModel(model);
this.setController(controller);
this.initialize();
};
maria.Node.mixin(maria.View.prototype);
maria.View.prototype.initialize = function() {};
maria.View.prototype.destroy = function() {
maria.purgeEventListener(this);
this._model = null;
if (this._controller) {
this._controller.destroy();
this._controller = null;
}
maria.Node.prototype.destroy.call(this);
};
maria.View.prototype.update = function() {
// to be overridden by concrete view subclasses
};
maria.View.prototype.getModel = function() {
return this._model;
};
maria.View.prototype.setModel = function(model) {
this._setModelAndController(model, this._controller);
};
maria.View.prototype.getDefaultControllerConstructor = function() {
return maria.Controller;
};
maria.View.prototype.getDefaultController = function() {
var constructor = this.getDefaultControllerConstructor();
return new constructor();
};
maria.View.prototype.getController = function() {
if (!this._controller) {
this.setController(this.getDefaultController());
}
return this._controller;
};
maria.View.prototype.setController = function(controller) {
this._setModelAndController(this._model, controller);
};
maria.View.prototype.getModelActions = function() {
return {'change': 'update'};
};
maria.View.prototype._setModelAndController = function(model, controller) {
var type, eventMap;
if (this._model !== model) {
if (this._model) {
eventMap = this._lastModelActions;
for (type in eventMap) {
if (Object.prototype.hasOwnProperty.call(eventMap, type)) {
maria.removeEventListener(this._model, type, this, eventMap[type]);
}
}
delete this._lastModelActions;
}
if (model) {
eventMap = this._lastModelActions = this.getModelActions() || {};
for (type in eventMap) {
if (Object.prototype.hasOwnProperty.call(eventMap, type)) {
maria.addEventListener(model, type, this, eventMap[type]);
}
}
}
this._model = model;
}
if (controller) {
controller.setView(this);
controller.setModel(model);
}
this._controller = controller;
};
maria.View.subclass = function(namespace, name, options) {
options = options || {};
var modelConstructor = options.modelConstructor;
var modelConstructorName = options.modelConstructorName || name.replace(/(View|)$/, 'Model');
var controllerConstructor = options.controllerConstructor;
var controllerConstructorName = options.controllerConstructorName || name.replace(/(View|)$/, 'Controller');
var modelActions = options.modelActions;
var properties = options.properties || (option.properties = {});
if (!Object.prototype.hasOwnProperty.call(properties, 'getDefaultControllerConstructor')) {
properties.getDefaultControllerConstructor = function() {
return controllerConstructor || namespace[controllerConstructorName];
};
}
if (modelActions && !Object.prototype.hasOwnProperty.call(properties, 'getModelActions')) {
properties.getModelActions = function() {
return modelActions;
};
}
if (!Object.prototype.hasOwnProperty.call(properties, 'initialize')) {
properties.initialize = function() {
if (!this.getModel()) {
var mc = modelConstructor || namespace[modelConstructorName];
this.setModel(new mc());
}
};
}
maria.subclass.call(this, namespace, name, options);
};
maria.borrow = function(sink, source) {
for (var p in source) {
if (Object.prototype.hasOwnProperty.call(source, p)) {
sink[p] = source[p];
}
}
};
/*
Maria version 0 - 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
*/
\ No newline at end of file
// "this" must be a constructor function
// mix the "subclass" function into your constructor function
//
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();
prototype.constructor = Constructor;
if (properties) {
maria.borrow(prototype, properties);
}
Constructor.subclass = function() {
SuperConstructor.subclass.apply(this, arguments);
};
};
......@@ -71,6 +71,7 @@
<li>
<a href="architecture-examples/stapes/index.html" data-source="http://hay.github.com/stapes/" data-content="Stapes is a (really) tiny Javascript MVC micro-framework (1.7kb) that has all the building blocks you need when writing an MVC app. It includes a powerful event system, support for inheritance, use with AMD, plugin support and more. A RequireJS Todo application is <a href='dependency-examples/stapes_require/index.html'>also</a> available.">Stapes *</a>
</li>
<li>
<a href="architecture-examples/troopjs/index.html" data-source="https://github.com/troopjs/" data-content="TroopJS attempts to package popular front-end technologies and bind them with minimal effort for the developer. It includes jQuery for DOM manipulation, ComposeJS for object composition, RequireJS for modularity and Has.js for feature detection. On top, it includes Pub/Sub support, templating, weaving (widgets to DOM) and auto-wiring.">TroopJS *</a>
</li>
......@@ -103,6 +104,9 @@
<li>
<a href="architecture-examples/gwt/index.html" data-source="https://developers.google.com/web-toolkit/" data-content="Google Web Toolkit (GWT) is an MVP development toolkit for building and optimizing complex browser-based applications. GWT is used by many products at Google, including Google AdWords.">Google Web Toolkit</a>
</li>
<li>
<a href="architecture-examples/maria/index.html" data-source="https://github.com/petermichaux/maria" data-content="An MVC framework for JavaScript applications. The real MVC. The Smalltalk MVC. The Gang of Four MVC. The three core design patterns of MVC (observer, composite, and strategy) are embedded in Maria's Model, View, and Controller objects. Other patterns traditionally included in MVC implementations (e.g. factory method and template) make appearances too.">Maria</a>
</li>
<li>
<a href="architecture-examples/olives/index.html" data-source="https://github.com/flams/olives" data-content="Olives is a JS MVC framework that helps you create realtime UIs. It includes a set of AMD/CommonJS modules that are easily extensive, a high level of abstraction to reduce boilerplate and is based on socket.io, to provide a powerful means to communicate with node.js.">Olives *</a>
</li>
......
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