Commit b5adebe0 authored by Sindre Sorhus's avatar Sindre Sorhus

Merge pull request #187 from petermichaux/master

use built maria.js file and organize files according to framework's recommended best practices
parents 66a67121 6d40fc20
/*
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);
};
}());
.buster-test,
html.buster-test {
margin: 0;
padding: 0;
}
.buster-test,
body.buster-test {
font-family: Georgia, Times, serif;
font-size: 16px;
padding: 0 12px;
}
.buster-test h2 {
display: inline-block;
font-weight: normal;
margin: 0 0 0.25em;
}
.buster-test ul {
margin: 0 0 1em 2em;
padding: 0;
}
.buster-test ul + ul {
margin-top: -1em;
}
.buster-test ul li {
list-style: none;
}
.buster-test li h3 {
display: inline-block;
font-size: 1.2em;
margin: 0 0 .25em;
padding: 0;
}
.buster-test li ul,
.buster-test li p {
margin: 0 0 0.5em 2em;
}
.buster-test .success h3:before {
color: #3c3;
content: "✓";
padding-right: 6px;
}
.buster-test .timeout h3,
.buster-test .failure h3,
.buster-test .error-message,
.buster-test .stats.failure {
color: #c33;
}
.buster-test .stats.success {
color: #3c3;
}
.buster-test .error h3,
.buster-test .error .error-message {
color: #cc3;
}
.buster-test .failure h3:before,
.buster-test .error h3:before {
content: "✖";
padding-right: 6px;
}
.buster-test .deferred h3 {
color: #33c;
}
.buster-test .deferred h3:before {
content: "✎";
padding-right: 6px;
}
.buster-test .stats ul {
margin: 0 0 1em;
}
.buster-test .stats h2 {
margin: 0 0 .25em;
}
.buster-test .stats li {
display: inline;
padding-right: 1em;
}
.buster-test p,
.buster-test li {
line-height: 1.5;
}
.buster-test h1,
.buster-test h2,
.buster-test h3,
.buster-test h4,
.buster-test h5,
.buster-test h6,
.buster-test p {
font-weight: normal;
margin: 0 0 1em;
}
.buster-test ul {
margin: 0 0 1em 15px;
}
/* Masthead */
.buster-test h1 {
background: #333;
background: -webkit-gradient(
linear,
left top,
left bottom,
from(#5f5f5f),
to(#222)
);
background: -moz-linear-gradient(
top center,
#5f5f5f,
#222
);
/* For Internet Explorer 5.5 - 7 */
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ff5f5f5f, endColorstr=#ff222222);
/* For Internet Explorer 8 */
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#ff5f5f5f, endColorstr=#ff222222)";
zoom: 1;
border-bottom: 4px solid #c0c6cc;
box-shadow: 0 0 10px #ddd;
-moz-box-shadow: 0 0 10px #ddd;
-webkit-box-shadow: 0 0 10px #ddd;
color: #fff;
font-size: 2em;
margin: 0 -12px 1em;
overflow: auto;
padding: 6px 12px;
}
.buster-test h1 a {
color: #fff;
padding-right: 1em;
text-decoration: none;
}
.buster-test h1 .buster-logo {
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABHCAYAAABCvgiTAAAgAElEQVR4nO2deZycRZ3/P9+qep6neyZzT/fkPgkC4ZIEOeIVQBDFlRUBZT0W1xNQEU88MILiKoKrHLu6qz/vA1xZVlRQIICCGoOYSDAxgSEOJDP9dE/PkZnu53mq6vv7Y55nnMyRSc9B3NDv16tfkO7nqarnmfpWfet7VBEOMn19fS1a6/nGmKUAVjLzqUS0CsBhRKSYGQBARGDmHDPvJKLHADwK4BkAzwghct3d3f7KlSuDg/YgVQ5J6NmusFgsLrHWvgDAWgCrrbXLAWSVUkoIAWvt8GdMY4lARJBSQggBYwyMMQZAPzPvIaIdzPwoM//OWvvI3Llzc8/y41U5xHhWBKRYLC4xxrwCwLkAXuA4TuOIDv63xsQCIITY5zsASGYSZgYzjxEgIcTwR2sNZn5aa/36bDb761l/wCqHLGo2C+/u7l5rrX27tfYc13WbmRlaa2ith2cBIQTCMAQz9zNzgYh6APRgaFYIAQwQkQIwB4DDzA1E1ACgAUATgFrXdZHMPlEUwVqL2trahQMDA+cAqApIlSkzKwJSKBROYeYrmfls13VVFEUIwxCO40AphSiKBrTWfwbw+1gl+rPjOB3W2mJzc3M/EfH+yt+0aZPT1tZW5zhOg+M486IoWgHgKGY+hpmPEkIsKZVKe5n557PxfFWeO8yoirVnz56M4zhXCSHeJqX0oigCEcFxHIRhWCKiB4nox0qp+++6664nLrjgAjN5qZWxbdu2upaWlsMB9Gcymb/MdPlVnlvMmIB0dnae7rrujY7jHBkEwUjB6CKi70ZR9I25c+f+aabqq1Ll2WBGBKRQKLyXiD4rhEhHUQTXdRFF0QARfQXAv7W0tHTMRD1VqvyfgpnJ9/3P9/T0cKFQ4EKhwL29vVwoFO71fX/1wW5flSoHjfXr1wvf97/Q29vL+Xyeu7u7ubu7O/J9f/2OHTu8g92+KlUOKrlc7ppEOIrFInd3dxdzudwFB7tdVaocdHK53JuLxaIdIRzdXV1dLzvY7apSZaapeJFeLBaPt9bejyFHHQDs1Vqf19bW9osZbVmVKn8HVOQo7OjoSBtjbnYcpyGKIkgpbRRFl01HOM4444zlzHxUGPLC69f30okv6C1EBjud/vKfadHTpamWW6XKTFCRgLiue4nruqcGQQDP81Aul2/JZrPfnErFp5122quI6F3MvJaI6oUQcFMMOBJSC821cocpHnGb0PwVymzfPZU6qlSZLgcsIN3d3YuNMR+OogiO4yAIgseVUh+vtMLTTz+9DcCXiOhCIoIxBswMY4EwZCBkgFmRFEeSg6ss4S1B/nlXeq3bv1NpXVWqTBcx+SVDaK3fl0qlMtZaMLMF8JHm5ubeSio77bTTlhDRz4UQF1pr94nkHQkzYDTDlBiCaKHj0Lei/BFXVlJXlSozwQEJSHd392IhxJvDMEy85He3trbeWUlFa9eurSOi7xHR8ycSjPEwmgELki5dGxae9y+V1FmlynQ5IAHRWr/J87wmZoYxRgP47GQRt6NJpVIfEEKcWolwJFgLgBlS0OfLuRWHVVxAlSpTZFIB6ezsrAXwBq01HMeBtfa+TCZTUY7F2WefvZCZL02SnqaC1YDwqFkp+b4pF1KlSoVMKiBEtNZxnMOTxbS19muVzh5RFP2jlLJlvDTaSuCQARbn9e+Zmxnv92KxuLRQKLy3WCz+j+/7t+dyubnTqrDKc55JrVhCiJOUUhRn6z1dKpV+WWkl1tozpJRTa+HIcgwgFLWl0HgC0Hl38n1nZ+fRruu+21r7WsdxmpkZc+bMQV9f3+0AvjXtiqs8Z5lUQJg5AwzlfDPzM0uWLClWUsGqVavcwTItkwJgO/GEVQ4BKQG4DCn2M9N4BDXIRwG4GwB27979Ys/zfum6rlsulxEEQxubxPntdZW0tUqV0UwqIETkAUg2QxiotIJPXpJxn+ouNYIIdj+KmbXAziddFHvrwfuRD+kKsLa1yb9ra2t3a63vLJVKZwsh0iOv5ekseqpUwYE5ChuTtQMzFyqt4PzztwJuM6AIMPvr+cAVH81iw0P1SLsT92uSAmxsT/LvhoaGnQDO831/jTHmh1LK5VOxlFWpMh6TLtKZeTEzJ9vxPFlxDRl/UBt6GkwwWkz4YS3gKKC2hpFO2wk/NSmLOXPor2OqyWQ2EdFPlZrVjVqqPMfYr4DEVqCVIyxY2yqtgAgWwG8P3Ge/f6y1/caYzRP8XA1urDKjTNZtz/M8r4WZEUURT0VAAADE/80RW5pmBnxsCbv/vvvuGzODAADRdGs4dOnp6Vnm+/6ag92O/2tMKCC7du1qAnCFMQZSSlhr/9TW1jbRyL1fbr9n+2+tsT8X7vT6r7XWENEXAVQX3xXg+35dFEU/EUI81N3dfczBbs9swIyFXMYK5pnSVYaYsLCamprLU6nUcmNMsgPidUQ0JRXmggtgDOyHbMQFqaYmJFJKMPNX77333g1TKuA5DBEdJYRY5TiOq7U+4mC3ZyZhhmdC3AyDLZDYwho/GxzEwpkqf1wB6ezsPEYIcXmyhU8Yhr9raWm5bToVeS07H2fD/8LgQelUJiRSShhjfhkEwYen04bnKsy8XCmF2NhyaFkxDC4ShEvAaAJQQ8BZnsI1M1X8GAFhZiGl/KzjOPXxwlxbaz9ORNM+WkC1br/DBHyetfirTBPEJM51IUSi3n3HWnvBQw891D/dNjxHmTdiQ/BDSj01GquBIQ9C4kVg4Ejmmdnzbcxoks/nL1BKvTIIAqRSKZRKpe+0tbXdMxOVAYCT3X4XF1a80Ebq/QT6J+lRKwgAAVISpBCQkpLd3P8I4Pp77733uzjE/rDPJtbaIw5Vn6kFto8ZZy0eI5qZ/rKPgHR1da0goi8wM6SUCIKgg4g+NhMVjYRanugAcPlg4ejr02Reao15gRC0MAxsg7G2z1reSkQbmpqaHrrtttuqpttpwMwyn8+fYK3FoWjkczx8U5dwhlB4JQBog4c1z5yKNSwgu3btapJSftdxnAVhGCLWWT+SyWRmLR+8puWxDgDfjj8Atk+rPGauutBHUSwWjySio5N0hUMNIvRt2IDzXroWx0cRaMdT2LxqFcKZKl8BQ+uOXC53k+d5J5XLZaRSKZTL5W9nMpnvzVRFMwEz0549e9LGmFQqlfKUUq4QwjHG1GitJYD5I1UJIfYX9fjcwBjzFs/zvBFBnIfcMXXr1kED2DQbZRMA5HK5Sz3PuylJqdVa/zEMwzPnzZvnz0alE9He3t5YX1+/QGs9T0q5wFq7SAgx11rbRkQtAJqYuZaI0gBSADwALhFJZiYMGR0IQHJE2w4iehqV7f+1VymltdY/z2QyXwWGBLNQKKwRQpxurT1ZCLHIGLPRGPOBuXPnHnAA5549e17iuu6rmfl4pVRdFEVf3N8gtG3btrq2trbTrLXrrLWHA8gAkERUBLALwB+EEPc3Nzc/ltyTz+ePVEq9OAiCeiHEcUR0AYYOHoKUElrrbwL4Q/JOiIiUUn1a61/v77iIzZs3186fP/9EIcSLAKyy1s4nojnMHBHRHiHEY8aYezZs2PCriY61yOVyL5ZSvkcI0RKG4cfa2toeHn1NZ2fn0Y7jvBXASQAsM9/HzJ/PZDIHxUBDuVxupRBiIxE1xt/9lZnPam1tnZrXvEI2bNigjj322HOJ6A3W2hOYOSuE8JRSw0eyJcGS8YYRmGjBOfI3ZobnefuoFZMtVLXWAIYPDEUQBG/NZrNfy+VyP1RKne84DiVtcBwHpVLpza2trZPmm3R0dKQ9z/vGyDKklBgcHPxLEATHL1q0aMw6K5/Pv1EI8REp5VGe5w23KTl+LvlvGIZlIcSDWusvZrPZu/L5/ENNTU2nlstlAEC5XN7nuDrXdTE6Xs3zPBSLxXsymcyY3THjM1/eSkRvAnBEKpUaPiJv5LuO2wJmfgDA1a2trfclZWzevLl24cKFnySi9yqlXCklBgYGNg8MDJy8bNmycnJdLpe7VCl1reu69cnfwvM87N279/OZTGZSE3+phMXWIlNTgz9MtEhnhoMAiyDRBkCBUYaDTgB7iKBHX68AXOs4TmMye4Rh+OVMJvOsCEdHR0dzOp3+ulLq1UON5+GFpNZ6+Ii25LuRSVejDvu0cWh7MotASokwDLeEYdgef08A6uLfx7w8ZvaI6OTkKLf4iLhrC4XCL5h5rxDChmEok84Wr9EOyCs9MDCgUqlUqxCCko4bP1umpqamBcDTybWbNm1yli5deoNS6rJYCE0Yho8A+BMRDTKzG5+itcZxnDQRpaSUZwohTsvn82dZa7+8d+9eG0VRhpklgHlEVJu82yiKusMwTASSiMhEUVQgojGC7vv+RVLKTzuOsyx554ODgz6A7cz8NICAiBqZ+WjHcVYAgOM4L7HW3uX7/qczmczVvu/XMfNtnuedFQQBkrNjhBDH1tXVHQtgIwDkcrmPuK77WWstgiAYPdi9gpmvJKJxVebeXhzmKfleNni9K9FU2ovzAfPjff8GmJdSeCtHeA0kDqOhI/3ABIZGLxhPmgC/EMB3ycPwjKyIqJB0wHi3xI/k83nXWntjNpvdeyAdYCows/B9/5Z0Ov3qwcFBSCmTA3fAzHsB9Blj8kTkA8gTUZ6ZC8aYolKqh4j2GmMGiKhERAPMrIUQlzmO85bkuLdSqXRLW1vbVw6kPevXrxeXXnrp5UKI65hZGGOQSqWy5XL5LZlM5l98379dCPEdIURDMoIfKEcccUT/pk2bXr548eJrHMf5cDI64m+CO8zSpUuv8TzvMmstyuXyw9baD2cymd8Q0T5qi+/7q7XWn1JKvTKKIqRSKRVF0TnZbPYKAD9sb29PpdNpKaX8D8dx3pC8kzAMP+y67o/7+/sJAObMmWMfffTR/nXr1g03atOmTc6SJUs+r5S6PJnBtdZ/ZOYvAbg7k8nsGdWWOq3164UQ12mt6wE4rut+yvf9LiHET621J48zu1MQBGsBbMzn82+UUn4m3idNA+gC0OZ5nor75rjWhZ6e1HJXmMuI8Cal0BKFBCEJNuBTRgqILuNsIXAzEZYl3yU+EylAABpBOIEIJ1jGu6My/l15+BgRQiWE+FAYhsvS6fSZ5XIZzNzquu61YRie7/v+VZlMpqLtfQ6Urq6uFziOc36pVEo85e1a668w868AdKVSqWJ9fX0fEY2Z9iYin8/7o0yZB5znu379ert+/fob8vn8Ktd13xIEAaIoAoA3bNu27YZMJnNnLpf7eSqVel2y4K2ENWvWRIVC4Wat9SVCiHEzHfP5/IlEdIXWGtbap7TWr2lra+sa79pMJvMIM7/a9/0rlVLrhRCSiIa9gYnq4vt+eeQ7Yea+hoaG7onaycwql8vdnEql3haGIaSUiKLoJmvtlRMNmPH64KtdXV3C87x/Tw5SBfCJvXv33ppOp08Mw/CdRPQeAGrE4PL8np6eFVrrLzmOI0ql0kPW2g+5rvu4MWZlGIZv9jxvHRHdOnr26Oiob5asH0ynxYJS2aIcz4lDWaumKbmuPIB/IIHvE6HGGPRZxvfY4i4COgBIQzhMCpwP4B9JAMyoVRIf0GX8HMB9qqWlpc/3/dcGQfAFInp77P+A4zjPB/C/+Xz+4tbW1iltL7o/pJSnuK4r4hmjEIbh2QsWLJiWnXdkB5lGu64Jw/BcIURzHKh5WFNT0ykAfkFE26bjSyCiPgC9GFL1xsDMF3ie58TR0w9OJBwjyjMAPu37fru19nMY305Oo+7Z7zvK5/NX1tTUvK1cLsN1XQRBcEs2m333fh8sRgjxv1rra4UQTXEM34KamppjM5nMAwDen8vlfNd1PxtFUbLeOzaKohvq6uqaBgYGHhocHHzViJTu32PokFevrq5uzIhULvcNcF3td8plepU18igAYLawTGDQHCDA4CAWEvAfglCjNZ6OGOfV1AypdCP4PYDvB4O4UDK+LBSyAMAGi4BYX89kMv0tLS3vYOZzrbWbhRAcRRGUUsTMJx3Iy6kUIsokerG1Nv/QQw/tnI16KqWpqekpa+0vkoWs4zggonXxz33T8Uj39vYGAML9CNmRI9S32okuGk0mk/luX1/f8S0tLV+fcuMAFAqFk4UQHx2hjm3SWn/wQO/XWkfx0d1g5mQxn5wCACK6OYzCjlhjAIBjpZT/MDAwkDPGXDzefgcTmaVXrkRQ3zjwkc5862prxYeFFGAoWOuAWTgAIBkXuw7mWQaiENePIxzDeDX4YRDgFVGEJwCCZgTAqFis1tbWO8IwfCcAO8J69McDfUEVMrKX2OXLl89omPI0+U3SiWMP9HHx94OzGbKRBBJqraGUelkulzv7QO+dP39+fjo+DmaWxpirlVKpEdvLrp8/f/7ggZbhuu5hUsqWZOAzxhil1HDuTiaT6SfQEyOMLRQLy9XZbHbHVNq9bNlT5cA434ki0QcIsBVgq2oAwBh1mtYSWksIia2TlVXbiEdYeC8PQvGfkfUeAcYJVpRSvtZxHElECIJgkJlnK7x82F8BYGD16tV/T17wPaNMy4vi72c1VoOZdyVWNGauF0L8OJ/Pfy2Xy72ImWf1WLtcLneyUuq0WHOA1vrRXbt2HfCxFhs2bFDW2o9JKVViBmfmR/P5/J9HXsfMUfL/saVxh7X2G9Np++BgVLJW9BEEGAJsidevhzDWbTHGAZEHbWsPyOKYSgU7U7Xm7Q0NwQ5glIAUi8WlQog3Jy8JwD2ZTGZWVB9mbkj8HAAG8XcUjCiEiEZaXACkY0fkbLfxp0nMVCygKdd13yKEeCCfzz+Sz+e/1t3d/c7Ozs6T29vbGycrrBKEEK91HEcyc/K3/+maNWuiye4DgN27d7cec8wxX3dd95XJwt4YEzHzJ1auXLnPrDYy6zP2df2sEmfrRDAEMwSYCRYSn/wk2FoVWfYQhg7A8kN9xbpzKy13HwHRWr/Xdd3WeA/eyFp7Y6W7KFZAwwh1pX8W65kKByWqr7W19Wflcvl76XQ62Ycs2eeLlFKrXNd9i1Lq35VSD9fX1/+xu7v7277vv6q9vT01nXqZWQAY3jc5XkD/frL79uzZk8nn85e4rvuw4zhvTPKHAAxord+WzWbvmqReYKZCRBhgFhh6FIr9quovglzoSIGt0wa4t/cUWn7ZW2x5Y3d30+IDCYkfdqlu2rTJYeYzkhTbKIoem8kw99EQ0bCuSkQTmh0PEqOF9VkRXiLSHR0dbx0cHNwhhHhXKpXKGmOgtR7+xNcRES1RSi0hojc0NDQ8ksvlrspmsz+bSr19fX2NABYaY0BE0FobIhqjOTzxxBMNTU1NS5h5DTOfxszrHMeZH7cp2TvtXq31VeOFkYwmdvTmp9LmMWUZB4IAFgLGDgl6FInvKyleBwhEGiACHIfPAHCGjURPbyG7vSePR0D8QGT5V5lMfs/ococFZPXq1Tqfz+8UQhwdWzFW+b7/qVwu95lVq1bNWHQkMDRi5fP5TOKxJqLcTJafMNVNHIjITW6NBbiHiNj3/VmfWeKwk/Xd3d1fj6LoQmvta5j5ONd108mBQyM/AOC67moAd+RyuXdks9mKLVlhGKYAjNx0zzLzK3zffykRLbDWLiSiZUS01BizsKamRiZrpdhkmyOie40x38pkMr+YyOM9ATPwThvAbMAgDGnCQ0aA1rb8nQV//o2eK95NFogii2TLNCHQKKQ4SUqcZC0uUdbmivkFP9WhuCUzv2N4VhsWkLgDfCaKonVE1GCMcdPp9FXZbLYDwH9N/yH+xlNPPVVfO6e2NVGxjDEdM1Hu6J0UhRD1UynHWjs/PgclmU3bZ6J9ldDc3PxXANcx8/X9/f0rtdarrbVrmfkUIjrS87xU0kFjvV8JIW7s6up6ZCqbayQqbvwKHc/zrnccJ9kLYDjWKgzDoFQqdQDYSUSPMvNDzLyxtbV1zOj7bMIQsBYgEuDYwDK05dTu9+S7Fm8Ugj4opTxWSkBrwBrGyP0FhaCs64iLme3rOncvurZtXsdniMCjMwr7rLWU6L/xJ40ZZs6cOVlmziQvXgixaybKtdb2jpo0prSQZebjElmLldnfx+U/62uTeDTeHn++x8yqWCweEQTBmUKI1ymlTtRaIw6NqSmXy+8AcEkldezdu3dwzpw5A0TUDAxZFoMg6AjD8C8A9gB4moie1FrvEkLs6u3t7VyxYkVFp4vNPkOCDCaA9w2gaG3763c2bZr/46UL3ZcZg9cYixcSxFLXJcE8JDBaM7QGhBDplEfX7Hlm+SDw5A37CIi1NuM4Tn2i+pRKpSccx/n2LDzNSsdxUsYYRFGkpZSV79g4DkSUGzmJMPOSSsvo6OhIE9EpiT4ehqEWQiSRqaMFpCKBCYLAmTNnjuJp+FLi0JvHADy2Y8eOm5uamj7nOM57oyhKVK4TmJkqMXosXbq0N5/PP6mUWpQ4Ccvl8r2ZTObiKTf0WcZaCSKGZQHGWI/BmjW7BwHcAeAO32+t0+W6IwMWL2TGWcx0iudRXRQNCQkzQIxPFncf+d/7WLGy2exGrfV/xwflQAgxT2u9Yhae5wSlVOID6SQau5XoVDDGtMfxU0lnOXrr1q1uJWW4rvtiKeXhIw4Mevixxx5LVJbhUTOeXZsmKGZcGhsb08xcU8k9+2PlypVBqVS6OoqiPSNC0B1UKLhExMx8d1JGFEUgolf4vj9vum1kHqOlzArMFFuxCJb3H4KXyeT75y1q39g2f+cNcxfsPEtYfn5Qoquskb0EiSiScBxVPxDxRfsICBFFRPSxKIr6iQie59UAuGAWnufUEQv0Pzc1NfVMfsvkOI6z3VrbGydLQQhxeHNz8/EHen888l4hpaQRvogvJJGuSql2rTUnuRlCiAWVtI+Ijnccp2m8GaRQKCwqFos3FAqF11ZS5s6dO/sAdCVpAUS0e3+L5NHrtATXdX8QBEFvsvh2XTcL4BOVtGUk+Xz+iEKhcHc+n//JdM3QCcwTb7aeCAez2O/pAOPRtnjnE/MWb79GG3EpSFpmiTASYBbnj/GkZzKZ7cz8gFIq6SAnxk6yGWH37t0vFkKckuR7MPNvZqrs5ubmZ4hoU7IHlFLKkVJ+CAc4ohYKhTe6rvuyMAzheR6iKPpha2vrT5PflVLbrLWdsSkUzHx0sVg8oHVOsVhcopS6noayH4fXd8YYDwCstR+rra19nxDiVt/3Lz/QZ16+fHkdgGw844OZ757o2tgU2zzeb42Nje3W2utiP0ZioHin7/sVH3nX1dV1FhHdU1tbeybwtxBzYFwBnVQVLJWwzIT4TxvidzbEr02Aq5iH8jlGFsIshtYgsXtv69ZVFWkPA+Wm/w0D+QygwCxhjCyOG/9ERJuTkQRA9vHHH592tv+GDRtUoVB4n+d5dyJ2EoZhyMaYKdnuxyNWFf5f8u9Ynz7P9/2rJru3q6vrTCL6kjGGPM9DGIabrbXvGTkaNzQ0dBPRXa7rwloLx3EWGGM+MFnZnZ2dpzPz3alU6ugoioY7RJxPn6hpg3GYOEkpr/d9/xO33nrrpOH6NTU173McZ368ZnxSa/2Dia6NhfLlE/0+MDBwfblc/kk6nU6uJSHEDYVC4Su+78+frC27du1qyufz1ziOc4fjOAtKpZImoo+OzBokopqRBhAi2u8z9vcjSwZ3CsJbmXECA2uFwKcG+/GlfRx9rACWYEgwy6B9x/GvbmlKPfLUE8+/YrJ2JxjTExgWg8wSRA4Y9KNxpyxmdpMHABDkcrlpbX6Qy+WOl1L+q1LqrHjkRTqdxuDg4HcffPDBGU22b21tvc33/YtqampeUSqVYIyBUmp9Pp8/3Bjz+Ww2+6eRnb5YLC7RWl9MRB8QQtQqpRCG4SYp5YUtLS1j/DNEdH0Yhq+RUjZorUFEH+3u7m4BcEsYhk+2tbWFW7ZscRctWtRirV1jrb1ISnluOp2We/fu/TaAJtd1z4lNs8J13ff7vv9JpdSXS6XSea7rLo6iSLiue/W6detOzefzXygWi78eHbLR09OzzFr7fma+BACstYPM/M758+fnR7V3uBOFYQghxDldXV3fUkr9hJmLURSZVCrV09XVtXPZsmX9HR0d/wzgm6lU6pw4FQGO47w9DMNzCoXCD6Io+pnjONu7u7v7GhoarLW2Rkq5mIhOB3Cx67rPozhz0Rjz7kwm82MAiNcz/0REJyQOz3jW+8iePXsG586d+8B4qqEN6s+XteKovv6/yYIgwFpc1OerfwX8HegFbN1Q2BJZgJnIsnwbQR4tJV3/xI7VWLHykRsm6zs1NanlZGkhMxAE6C2F8mfjqh65XO5HnuedByA5E33CUWd/xPnMlwshLpNS1ifbCQHQxphbwjC8spJo0QOlq6urTUr5Lc/zzkySdxzHQRAEARFtIaKnrLVMRAsArEqlUo0AEEWRYeZvlkqlDy1cuHDCw4Jyudw/O47zNQDCGJOEhgc8lIa6F0AtEWVc121IpVIYGBgoGWM+19raeo3v+x+qra39bKk0lOETGwIGoyj6rDHmds/zvqaUOinZEzk2OvyJmTcy85NCiBpmPpaI1qZSqWYACMPwGWPMO8dLbsvn87ek0+l3JfUBQ3npI9Q80FDa7QOtra0vJ6Kovb09VVdXd5UQ4nKlVDoxfDiOgyiKwMw9zNwHwACoI6Imz/NkonoaY+41xnwgm83+EQAKhcKpQoj/EUJkkgEyIdkzIAzDL2YymTGjfdfuJdfX1tAVpfK+mphSQBTal2Tndzy4a8viJlFXv1lIsUhKIArN7UFUs7Um7Xy8XDYggrUGH//r7vR169bdP24C3tYNL53jLRj4ruvIf5BSoFSOvnDY4b//4JgZJDZzHpP84a21E+50MRHFYrHRGPOm+AUvi6IIcVoowjD8izHmfVMNizgQ2traujo6Os4F8B4hxLscx1lCREilUp6U8kQiOjEZWI0xKJfLA0KIe8IwvHHu3Ln3TlZ+Npv9hu/75XiDgWUA4IW/2xcAAAV+SURBVLquJ4RYMWKLT5RKpZK19qdBEFw3b968jQDQ09NzW7lcfjsRLUmcb3Fu+dp58+Z9euvWrWe0tbVdTETvsNau8jwPQohjhBDHjAjuRBRFCIKgm5l/KKX8fCaTeWq8tjLzz6MouhBAY9IxR2ZEEhGklBZAH+L1QKwSfbSzs/MOAJcDOFsp1QAgGeAaiahx5LMGQRAB2EhEN23ZsuVHI1N4tdaklLJa6zEzRPz8ZUywTtThnPZAEYLgb7cKAYQhl22EYedkskgHAIaKIkP/NjjIr/Q85/lBYIWUdO3iBcHpO7ad+h92b3D/89Y8kgeAHb89qd42qRcqEXxUCnctEaFU0vdqdq/GeI2K0z4fZmYVj4wXZTKZ74/X+NHkcrm5RHSREOIdSqnDk3AI13VhjAkAfDUMw0/PnTt3VkJLxmP37t2tnue9FMBaa+3hRNTEzJKI9gJ4ipk3ep53f319fcXZjHHZrwLwYmZeysw1QoiytfYZIvqt53n31NXVPT76vq6urraamppMMjIDQH9//z4HpHZ2dtYqpU4B8FJmPpqGEswUEfUT0ZNCiIeZ+b7Y475fCoXCQtd1G0fWNxJrrS4Wi+2j1biEnp6e5cz8QmY+yRizjJmbiEgQ0V5mfkYI8aiU8sGGhoZHJ7KgdXZ2ZmPL2D44jgNjTKmhoaF9vHt3bj42SzXehtq0OCqMeCieShEGyuYry1dsehcReNeWxU2Y07RZCFrkKIEg5G8vWf7om7ZvP2WBhLiZSLw6kWVrAWN4NzOeAlgDYpGUvMzzJLS20Jp/lObwnQuP3FgAxhGQXC53WSqVujGZSqWUxzc1Ne3X093V1XWsUuqNzPx613UXJJ7dOCcAxph7jDHr29raHtpfOc8GsUWOKowXOtCyxWyUG5c9a+3+e2/LX/7ywuVk6UoGrYFFaGHu9NLOdcuW3V8GgF1bFjfZmpbNUopFrkMolewXl638wxVDbQVtf/xF55Ggd4FxiuOItJRieON0oxlBYLQg2mg5uvH7tz586/r1GH6u8Rbp9ck0zswDzDyujyKfzy+QUp5prb2QmV/iOE4qiQuK01RhjPkNgOtvuumm29evX3/Q/7DAcMzRrETnzmaHmc12V8qz3ZbDD//1kwDetmnTaufOOx8xIzvwEIvBXIqdhAIWPLyN0tD+WL/6EYAfbd36oqN0GSdI4sMZqLXgkFjudIT8w6OPNW254ILbxrjgxwgIEW0xxgybMcMwvBbApQDQ09OzwhjzEmY+h5lfJIRoTRaSURQliziOouhBIrq5vb39fw406aZKlclYs+aRCftSsrGm1gDb8UOXVq361eMAxqi8+2OMgOzevXvDvHnzHvU87/mxKfIS3/cXAajXWp/gOE4dMJRUk2QexhvO9Witf2at/a/W1tZxTXZVqswWSai71ihHlisSgv0xRkCOO+64gVwu9x6t9V1KqVqtNVzXfVW8lthHKIIgMMaYTcaY25RSP25sbHzWw8KrVDEUucwyNZSwZf88MKBnrB+O60nPZrO/1lq/EUAunR6KdieiZNFdNsZsLJfLVzPzyVu2bHlhS0vL9VXhqHKwiFzRxJYaiAQs0+37U8UqZcLgr7a2tttzudxj1trLeGgP2gKADUKIB5qbm7fRqK0wq1R5tnhi0xkNtmGwNZXi3YsW/aYEK1+ZSpFbLnEnC0xrb7DRHHpHDlU5pPnzlhe9xE3hP43BQmvxFIA9QuBUpUQqCO3FR6761Tdmsr5D68TTKoc8TPRBpdRKrQ2kxJFK0ZEAISibzx159MwKB7Cfc9KrVPl7hCC2WQtYO5T7EUYUlANe/4MfPfjR2aivOoNU+T/FQFB3FcReVyrxbqPxe2v43auOu/93s1Xf/wdTthQvHee9dgAAAABJRU5ErkJggg==);
float: left;
height: 71px;
margin-right: 20px;
width: 200px;
}
.buster-test h1 .title {
border-left: 1px solid #aaa;
float: left;
padding: 30px 0 0 20px;
}
.buster-test .messages li:before {
background: #f0f0f0;
border-radius: 6px;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border: 1px solid #ccc;
display: inline-block;
font-family: monospace;
font-size: 10px;
font-variant: small-caps;
margin-right: 1em;
padding: 0 4px;
}
.buster-test .log:before {
content: "LOG";
}
.buster-test .info:before {
content: "INFO";
}
.buster-test .messages .warn:before {
content: "WARN";
border-color: #cc3;
}
.buster-test .messages .error:before {
content: "ERR";
border-color: #c00;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
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
/*
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 || {};
/**
@property evento.EventTarget
@description
A constructor function for creating event target objects.
var et = new evento.EventTarget();
The methods of an event target object are inspired by the DOM2 standard.
*/
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();
};
}());
/**
@property evento.EventTarget.prototype.addEventListener
@parameter type {string} The name of the event.
@parameter listener {object|function} The listener object or callback function.
@description
If the listener is an object then when a matching event type is dispatched on
the event target, the listener object's handleEvent method will be called.
If the listener is a function then when a matching event type is dispatched on
the event target, the listener function is called with event target object set as
the "this" object.
One listener (or type/listener pair to be more precise) can be added only once.
et.addEventListener('change', {handleEvent:function(){}});
et.addEventListener('change', function(){});
*/
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) {
// can only add a listener once
return;
}
}
listeners.push(listener);
};
/**
@property evento.EventTarget.prototype.removeEventListener
@parameter type {string} The name of the event.
@parameter listener {object|function} The listener object or callback function.
@description
Removes added listener matching the type/listener combination exactly.
If this combination is not found there are no errors.
var o = {handleEvent:function(){}};
et.removeEventListener('change', o);
et.removeEventListener('change', fn);
*/
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);
// no need to continue since a particular listener
// can only be added once
return;
}
}
}
};
/**
@property evento.EventTarget.prototype.addParentEventTarget
@parameter parent {EventTarget} A parent to call when bubbling an event.
@description
When an event is dispatched on an event target, if that event target has parents
then the event is also dispatched on the parents as long as bubbling has not
been canceled on the event.
One parent can be added only once.
var o = new evento.EventTarget();
et.addParentEventTarget(o);
*/
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) {
// can only add a particular parent once
return;
}
}
parents.push(parent);
};
/**
@property evento.EventTarget.prototype.removeParentEventTarget
@parameter parent {EventTarget} The parent to remove.
@description
Removes parent added with addParentEventTarget. If the listener is
not found there are no errors.
var o = {handleEvent:function(){}};
et.removeParentEventTarget(o);
*/
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);
// no need to continue as parent can be added only once
return;
}
}
}
};
/**
@property evento.EventTarget.prototype.dispatchEvent
@parameter evt {object} The event object to dispatch to all listeners.
@description
The evt.type property is required. All listeners registered for this
event type are called with evt passed as an argument to the listeners.
If not set, the evt.target property will be set to be the event target.
The evt.currentTarget will be set to be the event target.
Call evt.stopPropagation() to stop bubbling to parents.
et.dispatchEvent({type:'change'});
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
// 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);
('target' in evt) || (evt.target = this); // don't change target on bubbling event
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)) {
// Copy the list of listeners in case one of the
// listeners modifies the list while we are
// iterating over the list.
//
// 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 would result in skipping that
// not-yet-called listner. The worst case scenario
// is a listener adding itself again which would
// create an infinite loop.
//
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);
}
}
};
// Insure the prototype object is initialized properly
evento.EventTarget.call(evento.EventTarget.prototype);
/**
@property evento.EventTarget.mixin
@parameter obj {object} The object to be made into an event target.
@description
Mixes in the event target methods into any object.
// Example 1
app.Person = function(name) {
evento.EventTarget.call(this);
this.setName(name);
};
evento.EventTarget.mixin(app.Person.prototype);
app.Person.prototype.setName = function(newName) {
var oldName = this.name;
this.name = newName;
this.dispatchEvent({
type: "change",
oldName: oldName,
newName: newName
});
};
var person = new app.Person('David');
person.addEventListener('change', function(evt) {
alert('"' + evt.oldName + '" is now called "' + evt.newName + '".');
});
person.setName('Dave');
// Example 2
var o = {};
evento.EventTarget.mixin(o);
o.addEventListener('change', function(){alert('change');});
o.dispatchEvent({type:'change'});
*/
evento.EventTarget.mixin = function(obj) {
var pt = evento.EventTarget.prototype;
for (var p in pt) {
if (hasOwnProperty(pt, p) &&
// Don't want to copy evento.EventTarget.prototype._evento_listeners object
// or the evento.EventTarget.prototype._evento_parents object. Want the obj object
// to have its own listeners and parents and not share listeners and parents
// with evento.EventTarget.prototype.
(typeof pt[p] === 'function')) {
obj[p] = pt[p];
}
}
evento.EventTarget.call(obj);
};
}());
/**
@property evento.addEventListener
@parameter element {EventTarget} The object you'd like to observe.
@parameter type {string} The name of the event.
@parameter listener {object|function} The listener object or callback function.
@parameter auxArg {string|object} Optional. See description.
@description
If the listener is an object then when a matching event type is dispatched on
the event target, the listener object's handleEvent method will be called.
By supplying a string value for auxArg you can specify the name of
the method to be called. You can also supply a function object for auxArg for
early binding.
If the listener is a function then when a matching event type is dispatched on
the event target, the listener function is called with event target object set as
the "this" object. Using the auxArg you can specifiy a different object to be
the "this" object.
One listener (or type/listener/auxArg pair to be more precise) can be added
only once.
var o = {
handleEvent: function(){},
handleClick: function(){}
};
// late binding. handleEvent is found when each event is dispatched
evento.addEventListener(document.body, 'click', o);
// late binding. handleClick is found when each event is dispatched
evento.addEventListener(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(){});
// supplied function will be called with document.body as this object
evento.addEventListener(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);
*/
/**
@property evento.removeEventListener
@parameter element {EventTarget} The object you'd like to stop observing.
@parameter type {string} The name of the event.
@parameter listener {object|function} The listener object or callback function.
@parameter auxArg {string|object} Optional.
@description
Removes added listener matching the element/type/listener/auxArg combination exactly.
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);
*/
/**
@property evento.purgeEventListener
@parameter listener {EventListener} The listener object that should stop listening.
@description
Removes all registrations of the listener added through evento.addEventListener.
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
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
methods. For example,
var APP_BoxView = function(model, controller) {
this.model = model || new APP_BoxModel();
this.controller = controller || new APP_BoxController();
this.rootEl = document.createElement('div');
// subscribe to DOM node(s) and model object(s) or anything else
// 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');
};
APP_BoxView.prototype.handleClick = function() {
// might subscribe/unsubscribe to more DOM nodes or models here
};
APP_BoxView.prototype.handleModelChange = function() {
// might subscribe/unsubscribe to more DOM nodes or models here
};
APP_BoxView.prototype.destroy = function() {
// Programmer doesn't need to remember anything. Purge all subscriptions
// to DOM nodes, model objects, or anything else implementing
// the EventTarget interface in one fell swoop.
//
evento.purgeEventListener(this);
};
*/
(function() {
function createBundle(element, type, listener, /*optional*/ 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, /*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.
var bundle = createBundle.apply(null, arguments);
if (listener._evento_bundles) {
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);
}
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, /*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.');
}
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);
}
}
}
};
}());
/*
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
/*
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 || {};
/**
@property hijos.Leaf
@description
A constructor function for creating Leaf objects to be used as part
of the composite design pattern.
Leaf objects have three read-only properties describing the Leaf object's
relationships to other Leaf and Node objects participating in
the composite pattern.
1. parentNode
2. previousSibling
3. nextSibling
These properties will be null when the Leaf is not a child
of a Node object. To attach a Leaf to a Node, use the Node's child
manipulation methods: appendChild, insertBefore, replaceChild.
To remove a Leaf from a Node use the Node's removeChild method.
var leaf = new hijos.Leaf();
*/
hijos.Leaf = function() {
this.parentNode = null;
this.previousSibling = null;
this.nextSibling = null;
};
/**
@property hijos.Leaf.prototype.destroy
@description
Call before your application code looses its last reference to the object.
Generally this will be called for you by the destroy method of the containing
Node object unless this Leaf object is not contained by a Node.
*/
hijos.Leaf.prototype.destroy = function() {
// Loosing references to relations may help garbage collection.
this.parentNode = null;
this.previousSibling = null;
this.nextSibling = null;
};
// insure prototype object is initialized properly
hijos.Leaf.call(hijos.Leaf.prototype);
/**
@property hijos.Leaf.mixin
@parameter obj {object} The object to become a Leaf.
@description
Mixes in the Leaf methods into any object. Be sure to call the hijos.Leaf
constructor to initialize the Leaf's properties.
app.MyView = function() {
hijos.Leaf.call(this);
};
hijos.Leaf.mixin(app.MyView.prototype);
*/
hijos.Leaf.mixin = function(obj) {
obj.destroy = hijos.Leaf.prototype.destroy;
hijos.Leaf.call(obj);
};
/**
@property hijos.Node
@description
A constructor function for creating Node objects with ordered children
to be used as part of the composite design pattern.
Node objects have six read-only properties describing the Node's
relationships to other Leaf and Node objects participating in
the composite pattern.
1. childNodes
2. firstChild
3. lastChild
4. parentNode
5. previousSibling
6. nextSibling
The firstChild and lastChild properties will be null when the Node has
no children. Do not mutate the elements of the childNodes array directly.
Instead use the appendChild, insertBefore, replaceChild, and removeChild
methods to manage the children.
The parentNode, previousSibling, and nextSibling properties will be null
when the Node object is not a child of another Node object.
var node = new hijos.Node();
*/
hijos.Node = function() {
hijos.Leaf.call(this);
this.childNodes = [];
this.firstChild = null;
this.lastChild = null;
};
hijos.Leaf.mixin(hijos.Node.prototype);
/**
@property hijos.Node.prototype.destroy
@description
Call before your application code looses its last reference to the object.
Generally this will be called for you by the destroy method of the containing
Node object unless this object is not contained by another Node.
*/
hijos.Node.prototype.destroy = function() {
// copy in case one of the destroy methods modifies this.childNodes
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);
// Loosing references to relations may help garbage collection.
this.childNodes = null;
this.firstChild = null;
this.lastChild = null;
};
/**
@property hijos.Node.prototype.hasChildNodes
@description
Returns true if this Node has children. Otherwise returns false.
*/
hijos.Node.prototype.hasChildNodes = function() {
return this.childNodes.length > 0;
};
/**
@property hijos.Node.prototype.insertBefore
@parameter newChild {object} The Leaf or Node object to insert.
@parameter oldChild {object|null} The child object to insert before.
@description
Inserts newChild before oldChild. If oldChild is null then this is equivalent
to appending newChild. If newChild is a child of another Node then newChild is
removed from that other Node before appending to this Node.
var parent = new hijos.Node();
var child0 = new hijos.Leaf();
parent.insertBefore(child0, null);
var child1 = new hijos.Node();
parent.insertBefore(child1, child0);
*/
hijos.Node.prototype.insertBefore = function(newChild, oldChild) {
if (arguments.length < 2) {
throw new Error('hijos.Node.prototype.insertBefore: not enough arguments.');
}
// Allow caller to be sloppy and send undefined instead of null.
if (oldChild === undefined) {
oldChild = null;
}
// Is newChild is already in correct position?
if ((newChild === oldChild) || // inserting a node before itself
(oldChild && (oldChild.previousSibling === newChild)) || // inserting newChild where it already is
((oldChild === null) && this.lastChild === newChild)) { // inserting child at end when it is already at the end
return;
}
// do not allow the creation of a circular tree structure
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;
}
// continue with insertion
var children = this.childNodes;
// find index for newChild
var indexForNewChild;
if (oldChild === null) {
// want to append to end of children
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.');
}
}
// remove from previous composite
var parent = newChild.parentNode;
if (parent) {
parent.removeChild(newChild);
}
// add to this composite
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;
}
};
/**
@property hijos.Node.prototype.appendChild
@parameter newChild {object} The Leaf or Node object to append.
@description
Adds newChild as the last child of this Node. If newChild is a child of
another Node then newChild is removed from that other Node before appending
to this Node.
var parent = new hijos.Node();
var child = new hijos.Leaf();
parent.appendChild(child);
var child = new hijos.Node();
parent.appendChild(child);
*/
hijos.Node.prototype.appendChild = function(newChild) {
if (arguments.length < 1) {
throw new Error('hijos.Node.prototype.appendChild: not enough arguments.');
}
this.insertBefore(newChild, null);
};
/**
@property hijos.Node.prototype.replaceChild
@parameter newChild {object} The Leaf or Node object to insert.
@parameter oldChild {object} The child object to remove/replace.
@description
Replaces oldChild with newChild. If newChild is a child of another Node
then newChild is removed from that other Node before appending to this Node.
var parent = new hijos.Node();
var child0 = new hijos.Leaf();
parent.appendChild(child0);
var child1 = new hijos.Node();
parent.replaceChild(child1, child0);
*/
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.');
}
// child is already in correct position and
// do not want removeChild to be called below
if (newChild === oldChild) {
return;
}
this.insertBefore(newChild, oldChild);
this.removeChild(oldChild);
};
/**
@property hijos.Node.prototype.removeChild
@parameter oldChild {object} The child object to remove.
@description
Removes oldChild.
var parent = new hijos.Node();
var child = new hijos.Leaf();
parent.appendChild(child);
parent.removeChild(child);
*/
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; // stop looking
}
}
throw new Error('hijos.Node.prototype.removeChild: node not found.');
};
// insure prototype object is initialized correctly
hijos.Node.call(hijos.Node.prototype);
/**
@property hijos.Node.mixin
@parameter obj {object} The object to become a Node.
@description
Mixes in the Node methods into any object.
// Example 1
app.MyView = function() {
hijos.Node.call(this);
};
hijos.Node.mixin(app.MyView.prototype);
// Example 2
var obj = {};
hijos.Node.mixin(obj);
*/
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);
};
/*
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
/*
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;
}
/**
@property hormigas.ObjectSet
@description
A constructor function for creating set objects. ObjectSets are designed
to hold JavaScript objects. They cache a marker on the objects.
Do not attempt to add primitives or host objects in a ObjectSet. This
is a compromise to make ObjectSet objects efficient for use in the model
layer of your application.
When using the set iterators (e.g. forEach, map) do not depend
on the order of iteration of the set's elements. ObjectSets are unordered.
var set = new hormigas.ObjectSet(); // an empty set
ObjectSets have a length property that is the number of elements in the set.
var alpha = {};
var beta = {};
var set = new hormigas.ObjectSet(alpha, beta, alpha);
set.length; // 2
The methods of an event target object are inspired by the incomplete
Harmony Set proposal and the Array.prototype iterators.
*/
hormigas.ObjectSet = function() {
initSet(this);
for (var i = 0, ilen = arguments.length; i < ilen; i++) {
this.add(arguments[i]);
}
};
/**
@property hormigas.ObjectSet.prototype.has
@parameter element
@description
Returns true if element is in the set. Otherwise returns false.
var alpha = {};
var beta = {};
var set = new hormigas.ObjectSet(alpha);
set.has(alpha); // true
set.has(beta); // false
*/
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);
};
/**
@property hormigas.ObjectSet.prototype.add
@parameter element
@description
If element is not already in the set then adds element to the set
and returns true. Otherwise returns false.
var alpha = {};
var set = new hormigas.ObjectSet();
set.add(alpha); // true
set.has(alpha); // false
*/
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;
}
};
/**
@property hormigas.ObjectSet.prototype.delete
@parameter element
@description
If element is in the set then removes element from the set
and returns true. Otherwise returns false.
"delete" is a reserved word and older implementations
did not allow bare reserved words in property name
position so quote "delete".
var alpha = {};
var set = new hormigas.ObjectSet(alpha);
set['delete'](alpha); // true
set['delete'](alpha); // false
*/
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;
}
};
/**
@property hormigas.ObjectSet.prototype.empty
@description
If the set has elements then removes all the elements and
returns true. Otherwise returns false.
var alpha = {};
var set = new hormigas.ObjectSet(alpha);
set.empty(); // true
set.empty(); // false
*/
hormigas.ObjectSet.prototype.empty = function() {
if (this.length > 0) {
initSet(this);
return true;
}
else {
return false;
}
};
/**
@property hormigas.ObjectSet.prototype.toArray
@description
Returns the elements of the set in a new array.
*/
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;
};
/**
@property hormigas.ObjectSet.prototype.forEach
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set.
var alpha = {value: 0};
var beta = {value: 1};
var gamma = {value: 2};
var set = new hormigas.ObjectSet(alpha, beta, gamma);
set.forEach(function(element, set) {
console.log(element.value);
});
*/
hormigas.ObjectSet.prototype.forEach = function(callbackfn /*, thisArg */) {
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);
}
}
};
/**
@property hormigas.ObjectSet.prototype.every
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set. If callbackfn returns a truthy value
for all elements then every returns true. Otherwise returns false.
var one = {value: 1};
var two = {value: 2};
var three = {value: 3};
var set = new hormigas.ObjectSet(one, two, three);
set.every(function(element, set) {
return element.value < 2;
}); // false
*/
hormigas.ObjectSet.prototype.every = function(callbackfn /*, thisArg */) {
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;
};
/**
@property hormigas.ObjectSet.prototype.some
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set. If callbackfn returns a truthy value
for at least one element then some returns true. Otherwise returns false.
var one = {value: 1};
var two = {value: 2};
var three = {value: 3};
var set = new hormigas.ObjectSet(one, two, three);
set.some(function(element, set) {
return element.value < 2;
}); // true
*/
hormigas.ObjectSet.prototype.some = function(callbackfn /*, thisArg */) {
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;
};
/**
@property hormigas.ObjectSet.prototype.reduce
@parameter callbackfn {function} The function to call for each element in the set.
@parameter initialValue {object} The optional starting value for accumulation.
@description
Calls callbackfn for each element of the set.
For the first call to callbackfn, if initialValue is supplied then initalValue is
the first argument passed to callbackfn and the second argument is the first
element in the set to be iterated. Otherwise the first argument is
the first element to be iterated in the set and the second argument is
the next element to be iterated in the set.
For subsequent calls to callbackfn, the first argument is the value returned
by the last call to callbackfn. The second argument is the next value to be
iterated in the set.
var one = {value: 1};
var two = {value: 2};
var three = {value: 3};
var set = new hormigas.ObjectSet(one, two, three);
set.reduce(function(accumulator, element) {
return {value: accumulator.value + element.value};
}); // {value:6}
set.reduce(function(accumulator, element) {
return accumulator + element.value;
}, 4); // 10
*/
hormigas.ObjectSet.prototype.reduce = function(callbackfn /*, initialValue */) {
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;
};
/**
@property hormigas.ObjectSet.prototype.map
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set. The values returned by callbackfn
are added to a new array. This new array is the value returned by map.
var alpha = {length: 5};
var beta = {length: 4};
var gamma = {length: 5};
var set = new hormigas.ObjectSet(alpha, beta, gamma);
set.map(function(element) {
return element.length;
}); // [5,5,4] or [5,4,5] or [4,5,5]
*/
hormigas.ObjectSet.prototype.map = function(callbackfn /*, thisArg */) {
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;
};
/**
@property hormigas.ObjectSet.prototype.filter
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set. If callbackfn returns true
for an element then that element is added to a new array. This new array
is the value returned by filter.
var alpha = {length: 5};
var beta = {length: 4};
var gamma = {length: 5};
var set = new hormigas.ObjectSet(alpha, beta, gamma);
set.filter(function(element) {
return element.length > 4;
}); // [alpha, gamma] or [gamma, alpha]
*/
hormigas.ObjectSet.prototype.filter = function(callbackfn /*, thisArg */) {
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;
};
}());
// insure prototype object is initialized properly
hormigas.ObjectSet.call(hormigas.ObjectSet.prototype);
/**
@property hormigas.ObjectSet.mixin
@parameter obj {object} The object to become a ObjectSet.
@description
Mixes in the ObjectSet methods into any object.
// Example 1
app.MyModel = function() {
hormigas.ObjectSet.call(this);
};
hormigas.ObjectSet.mixin(app.MyModel.prototype);
// Example 2
var obj = {};
hormigas.ObjectSet.mixin(obj);
*/
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);
};
/*
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 || {};
/**
@property evento.EventTarget
@description
A constructor function for creating event target objects.
var et = new evento.EventTarget();
The methods of an event target object are inspired by the DOM2 standard.
*/
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();
};
}());
/**
@property evento.EventTarget.prototype.addEventListener
@parameter type {string} The name of the event.
@parameter listener {object|function} The listener object or callback function.
@description
If the listener is an object then when a matching event type is dispatched on
the event target, the listener object's handleEvent method will be called.
If the listener is a function then when a matching event type is dispatched on
the event target, the listener function is called with event target object set as
the "this" object.
One listener (or type/listener pair to be more precise) can be added only once.
et.addEventListener('change', {handleEvent:function(){}});
et.addEventListener('change', function(){});
*/
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) {
// can only add a listener once
return;
}
}
listeners.push(listener);
};
/**
@property evento.EventTarget.prototype.removeEventListener
@parameter type {string} The name of the event.
@parameter listener {object|function} The listener object or callback function.
@description
Removes added listener matching the type/listener combination exactly.
If this combination is not found there are no errors.
var o = {handleEvent:function(){}};
et.removeEventListener('change', o);
et.removeEventListener('change', fn);
*/
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);
// no need to continue since a particular listener
// can only be added once
return;
}
}
}
};
/**
@property evento.EventTarget.prototype.addParentEventTarget
@parameter parent {EventTarget} A parent to call when bubbling an event.
@description
When an event is dispatched on an event target, if that event target has parents
then the event is also dispatched on the parents as long as bubbling has not
been canceled on the event.
One parent can be added only once.
var o = new evento.EventTarget();
et.addParentEventTarget(o);
*/
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) {
// can only add a particular parent once
return;
}
}
parents.push(parent);
};
/**
@property evento.EventTarget.prototype.removeParentEventTarget
@parameter parent {EventTarget} The parent to remove.
@description
Removes parent added with addParentEventTarget. If the listener is
not found there are no errors.
var o = {handleEvent:function(){}};
et.removeParentEventTarget(o);
*/
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);
// no need to continue as parent can be added only once
return;
}
}
}
};
/**
@property evento.EventTarget.prototype.dispatchEvent
@parameter evt {object} The event object to dispatch to all listeners.
@description
The evt.type property is required. All listeners registered for this
event type are called with evt passed as an argument to the listeners.
If not set, the evt.target property will be set to be the event target.
The evt.currentTarget will be set to be the event target.
Call evt.stopPropagation() to stop bubbling to parents.
et.dispatchEvent({type:'change'});
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
// 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);
('target' in evt) || (evt.target = this); // don't change target on bubbling event
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)) {
// Copy the list of listeners in case one of the
// listeners modifies the list while we are
// iterating over the list.
//
// 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 would result in skipping that
// not-yet-called listner. The worst case scenario
// is a listener adding itself again which would
// create an infinite loop.
//
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);
}
}
};
// Insure the prototype object is initialized properly
evento.EventTarget.call(evento.EventTarget.prototype);
/**
@property evento.EventTarget.mixin
@parameter obj {object} The object to be made into an event target.
@description
Mixes in the event target methods into any object.
// Example 1
app.Person = function(name) {
evento.EventTarget.call(this);
this.setName(name);
};
evento.EventTarget.mixin(app.Person.prototype);
app.Person.prototype.setName = function(newName) {
var oldName = this.name;
this.name = newName;
this.dispatchEvent({
type: "change",
oldName: oldName,
newName: newName
});
};
var person = new app.Person('David');
person.addEventListener('change', function(evt) {
alert('"' + evt.oldName + '" is now called "' + evt.newName + '".');
});
person.setName('Dave');
// Example 2
var o = {};
evento.EventTarget.mixin(o);
o.addEventListener('change', function(){alert('change');});
o.dispatchEvent({type:'change'});
*/
evento.EventTarget.mixin = function(obj) {
var pt = evento.EventTarget.prototype;
for (var p in pt) {
if (hasOwnProperty(pt, p) &&
// Don't want to copy evento.EventTarget.prototype._evento_listeners object
// or the evento.EventTarget.prototype._evento_parents object. Want the obj object
// to have its own listeners and parents and not share listeners and parents
// with evento.EventTarget.prototype.
(typeof pt[p] === 'function')) {
obj[p] = pt[p];
}
}
evento.EventTarget.call(obj);
};
}());
/**
@property evento.addEventListener
@parameter element {EventTarget} The object you'd like to observe.
@parameter type {string} The name of the event.
@parameter listener {object|function} The listener object or callback function.
@parameter auxArg {string|object} Optional. See description.
@description
If the listener is an object then when a matching event type is dispatched on
the event target, the listener object's handleEvent method will be called.
By supplying a string value for auxArg you can specify the name of
the method to be called. You can also supply a function object for auxArg for
early binding.
If the listener is a function then when a matching event type is dispatched on
the event target, the listener function is called with event target object set as
the "this" object. Using the auxArg you can specifiy a different object to be
the "this" object.
One listener (or type/listener/auxArg pair to be more precise) can be added
only once.
var o = {
handleEvent: function(){},
handleClick: function(){}
};
// late binding. handleEvent is found when each event is dispatched
evento.addEventListener(document.body, 'click', o);
// late binding. handleClick is found when each event is dispatched
evento.addEventListener(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(){});
// supplied function will be called with document.body as this object
evento.addEventListener(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);
*/
/**
@property evento.removeEventListener
@parameter element {EventTarget} The object you'd like to stop observing.
@parameter type {string} The name of the event.
@parameter listener {object|function} The listener object or callback function.
@parameter auxArg {string|object} Optional.
@description
Removes added listener matching the element/type/listener/auxArg combination exactly.
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);
*/
/**
@property evento.purgeEventListener
@parameter listener {EventListener} The listener object that should stop listening.
@description
Removes all registrations of the listener added through evento.addEventListener.
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
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
methods. For example,
var APP_BoxView = function(model, controller) {
this.model = model || new APP_BoxModel();
this.controller = controller || new APP_BoxController();
this.rootEl = document.createElement('div');
// subscribe to DOM node(s) and model object(s) or anything else
// 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');
};
APP_BoxView.prototype.handleClick = function() {
// might subscribe/unsubscribe to more DOM nodes or models here
};
APP_BoxView.prototype.handleModelChange = function() {
// might subscribe/unsubscribe to more DOM nodes or models here
};
APP_BoxView.prototype.destroy = function() {
// Programmer doesn't need to remember anything. Purge all subscriptions
// to DOM nodes, model objects, or anything else implementing
// the EventTarget interface in one fell swoop.
//
evento.purgeEventListener(this);
};
*/
(function() {
function createBundle(element, type, listener, /*optional*/ 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, /*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.
var bundle = createBundle.apply(null, arguments);
if (listener._evento_bundles) {
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);
}
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, /*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.');
}
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);
}
}
}
};
}());
/*
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 || {};
/**
@property hijos.Leaf
@description
A constructor function for creating Leaf objects to be used as part
of the composite design pattern.
Leaf objects have three read-only properties describing the Leaf object's
relationships to other Leaf and Node objects participating in
the composite pattern.
1. parentNode
2. previousSibling
3. nextSibling
These properties will be null when the Leaf is not a child
of a Node object. To attach a Leaf to a Node, use the Node's child
manipulation methods: appendChild, insertBefore, replaceChild.
To remove a Leaf from a Node use the Node's removeChild method.
var leaf = new hijos.Leaf();
*/
hijos.Leaf = function() {
this.parentNode = null;
this.previousSibling = null;
this.nextSibling = null;
};
/**
@property hijos.Leaf.prototype.destroy
@description
Call before your application code looses its last reference to the object.
Generally this will be called for you by the destroy method of the containing
Node object unless this Leaf object is not contained by a Node.
*/
hijos.Leaf.prototype.destroy = function() {
// Loosing references to relations may help garbage collection.
this.parentNode = null;
this.previousSibling = null;
this.nextSibling = null;
};
// insure prototype object is initialized properly
hijos.Leaf.call(hijos.Leaf.prototype);
/**
@property hijos.Leaf.mixin
@parameter obj {object} The object to become a Leaf.
@description
Mixes in the Leaf methods into any object. Be sure to call the hijos.Leaf
constructor to initialize the Leaf's properties.
app.MyView = function() {
hijos.Leaf.call(this);
};
hijos.Leaf.mixin(app.MyView.prototype);
*/
hijos.Leaf.mixin = function(obj) {
obj.destroy = hijos.Leaf.prototype.destroy;
hijos.Leaf.call(obj);
};
/**
@property hijos.Node
@description
A constructor function for creating Node objects with ordered children
to be used as part of the composite design pattern.
Node objects have six read-only properties describing the Node's
relationships to other Leaf and Node objects participating in
the composite pattern.
1. childNodes
2. firstChild
3. lastChild
4. parentNode
5. previousSibling
6. nextSibling
The firstChild and lastChild properties will be null when the Node has
no children. Do not mutate the elements of the childNodes array directly.
Instead use the appendChild, insertBefore, replaceChild, and removeChild
methods to manage the children.
The parentNode, previousSibling, and nextSibling properties will be null
when the Node object is not a child of another Node object.
var node = new hijos.Node();
*/
hijos.Node = function() {
hijos.Leaf.call(this);
this.childNodes = [];
this.firstChild = null;
this.lastChild = null;
};
hijos.Leaf.mixin(hijos.Node.prototype);
/**
@property hijos.Node.prototype.destroy
@description
Call before your application code looses its last reference to the object.
Generally this will be called for you by the destroy method of the containing
Node object unless this object is not contained by another Node.
*/
hijos.Node.prototype.destroy = function() {
// copy in case one of the destroy methods modifies this.childNodes
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);
// Loosing references to relations may help garbage collection.
this.childNodes = null;
this.firstChild = null;
this.lastChild = null;
};
/**
@property hijos.Node.prototype.hasChildNodes
@description
Returns true if this Node has children. Otherwise returns false.
*/
hijos.Node.prototype.hasChildNodes = function() {
return this.childNodes.length > 0;
};
/**
@property hijos.Node.prototype.insertBefore
@parameter newChild {object} The Leaf or Node object to insert.
@parameter oldChild {object|null} The child object to insert before.
@description
Inserts newChild before oldChild. If oldChild is null then this is equivalent
to appending newChild. If newChild is a child of another Node then newChild is
removed from that other Node before appending to this Node.
var parent = new hijos.Node();
var child0 = new hijos.Leaf();
parent.insertBefore(child0, null);
var child1 = new hijos.Node();
parent.insertBefore(child1, child0);
*/
hijos.Node.prototype.insertBefore = function(newChild, oldChild) {
if (arguments.length < 2) {
throw new Error('hijos.Node.prototype.insertBefore: not enough arguments.');
}
// Allow caller to be sloppy and send undefined instead of null.
if (oldChild === undefined) {
oldChild = null;
}
// Is newChild is already in correct position?
if ((newChild === oldChild) || // inserting a node before itself
(oldChild && (oldChild.previousSibling === newChild)) || // inserting newChild where it already is
((oldChild === null) && this.lastChild === newChild)) { // inserting child at end when it is already at the end
return;
}
// do not allow the creation of a circular tree structure
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;
}
// continue with insertion
var children = this.childNodes;
// find index for newChild
var indexForNewChild;
if (oldChild === null) {
// want to append to end of children
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.');
}
}
// remove from previous composite
var parent = newChild.parentNode;
if (parent) {
parent.removeChild(newChild);
}
// add to this composite
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;
}
};
/**
@property hijos.Node.prototype.appendChild
@parameter newChild {object} The Leaf or Node object to append.
@description
Adds newChild as the last child of this Node. If newChild is a child of
another Node then newChild is removed from that other Node before appending
to this Node.
var parent = new hijos.Node();
var child = new hijos.Leaf();
parent.appendChild(child);
var child = new hijos.Node();
parent.appendChild(child);
*/
hijos.Node.prototype.appendChild = function(newChild) {
if (arguments.length < 1) {
throw new Error('hijos.Node.prototype.appendChild: not enough arguments.');
}
this.insertBefore(newChild, null);
};
/**
@property hijos.Node.prototype.replaceChild
@parameter newChild {object} The Leaf or Node object to insert.
@parameter oldChild {object} The child object to remove/replace.
@description
Replaces oldChild with newChild. If newChild is a child of another Node
then newChild is removed from that other Node before appending to this Node.
var parent = new hijos.Node();
var child0 = new hijos.Leaf();
parent.appendChild(child0);
var child1 = new hijos.Node();
parent.replaceChild(child1, child0);
*/
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.');
}
// child is already in correct position and
// do not want removeChild to be called below
if (newChild === oldChild) {
return;
}
this.insertBefore(newChild, oldChild);
this.removeChild(oldChild);
};
/**
@property hijos.Node.prototype.removeChild
@parameter oldChild {object} The child object to remove.
@description
Removes oldChild.
var parent = new hijos.Node();
var child = new hijos.Leaf();
parent.appendChild(child);
parent.removeChild(child);
*/
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; // stop looking
}
}
throw new Error('hijos.Node.prototype.removeChild: node not found.');
};
// insure prototype object is initialized correctly
hijos.Node.call(hijos.Node.prototype);
/**
@property hijos.Node.mixin
@parameter obj {object} The object to become a Node.
@description
Mixes in the Node methods into any object.
// Example 1
app.MyView = function() {
hijos.Node.call(this);
};
hijos.Node.mixin(app.MyView.prototype);
// Example 2
var obj = {};
hijos.Node.mixin(obj);
*/
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);
};
/*
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);
};
}());
/*
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+'".');
}
};
}());
/*
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;
}
/**
@property hormigas.ObjectSet
@description
A constructor function for creating set objects. ObjectSets are designed
to hold JavaScript objects. They cache a marker on the objects.
Do not attempt to add primitives or host objects in a ObjectSet. This
is a compromise to make ObjectSet objects efficient for use in the model
layer of your application.
When using the set iterators (e.g. forEach, map) do not depend
on the order of iteration of the set's elements. ObjectSets are unordered.
var set = new hormigas.ObjectSet(); // an empty set
ObjectSets have a length property that is the number of elements in the set.
var alpha = {};
var beta = {};
var set = new hormigas.ObjectSet(alpha, beta, alpha);
set.length; // 2
The methods of an event target object are inspired by the incomplete
Harmony Set proposal and the Array.prototype iterators.
*/
hormigas.ObjectSet = function() {
initSet(this);
for (var i = 0, ilen = arguments.length; i < ilen; i++) {
this.add(arguments[i]);
}
};
/**
@property hormigas.ObjectSet.prototype.has
@parameter element
@description
Returns true if element is in the set. Otherwise returns false.
var alpha = {};
var beta = {};
var set = new hormigas.ObjectSet(alpha);
set.has(alpha); // true
set.has(beta); // false
*/
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);
};
/**
@property hormigas.ObjectSet.prototype.add
@parameter element
@description
If element is not already in the set then adds element to the set
and returns true. Otherwise returns false.
var alpha = {};
var set = new hormigas.ObjectSet();
set.add(alpha); // true
set.has(alpha); // false
*/
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;
}
};
/**
@property hormigas.ObjectSet.prototype.delete
@parameter element
@description
If element is in the set then removes element from the set
and returns true. Otherwise returns false.
"delete" is a reserved word and older implementations
did not allow bare reserved words in property name
position so quote "delete".
var alpha = {};
var set = new hormigas.ObjectSet(alpha);
set['delete'](alpha); // true
set['delete'](alpha); // false
*/
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;
}
};
/**
@property hormigas.ObjectSet.prototype.empty
@description
If the set has elements then removes all the elements and
returns true. Otherwise returns false.
var alpha = {};
var set = new hormigas.ObjectSet(alpha);
set.empty(); // true
set.empty(); // false
*/
hormigas.ObjectSet.prototype.empty = function() {
if (this.length > 0) {
initSet(this);
return true;
}
else {
return false;
}
};
/**
@property hormigas.ObjectSet.prototype.toArray
@description
Returns the elements of the set in a new array.
*/
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;
};
/**
@property hormigas.ObjectSet.prototype.forEach
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set.
var alpha = {value: 0};
var beta = {value: 1};
var gamma = {value: 2};
var set = new hormigas.ObjectSet(alpha, beta, gamma);
set.forEach(function(element, set) {
console.log(element.value);
});
*/
hormigas.ObjectSet.prototype.forEach = function(callbackfn /*, thisArg */) {
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);
}
}
};
/**
@property hormigas.ObjectSet.prototype.every
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set. If callbackfn returns a truthy value
for all elements then every returns true. Otherwise returns false.
var one = {value: 1};
var two = {value: 2};
var three = {value: 3};
var set = new hormigas.ObjectSet(one, two, three);
set.every(function(element, set) {
return element.value < 2;
}); // false
*/
hormigas.ObjectSet.prototype.every = function(callbackfn /*, thisArg */) {
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;
};
/**
@property hormigas.ObjectSet.prototype.some
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set. If callbackfn returns a truthy value
for at least one element then some returns true. Otherwise returns false.
var one = {value: 1};
var two = {value: 2};
var three = {value: 3};
var set = new hormigas.ObjectSet(one, two, three);
set.some(function(element, set) {
return element.value < 2;
}); // true
*/
hormigas.ObjectSet.prototype.some = function(callbackfn /*, thisArg */) {
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;
};
/**
@property hormigas.ObjectSet.prototype.reduce
@parameter callbackfn {function} The function to call for each element in the set.
@parameter initialValue {object} The optional starting value for accumulation.
@description
Calls callbackfn for each element of the set.
For the first call to callbackfn, if initialValue is supplied then initalValue is
the first argument passed to callbackfn and the second argument is the first
element in the set to be iterated. Otherwise the first argument is
the first element to be iterated in the set and the second argument is
the next element to be iterated in the set.
For subsequent calls to callbackfn, the first argument is the value returned
by the last call to callbackfn. The second argument is the next value to be
iterated in the set.
var one = {value: 1};
var two = {value: 2};
var three = {value: 3};
var set = new hormigas.ObjectSet(one, two, three);
set.reduce(function(accumulator, element) {
return {value: accumulator.value + element.value};
}); // {value:6}
set.reduce(function(accumulator, element) {
return accumulator + element.value;
}, 4); // 10
*/
hormigas.ObjectSet.prototype.reduce = function(callbackfn /*, initialValue */) {
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;
};
/**
@property hormigas.ObjectSet.prototype.map
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set. The values returned by callbackfn
are added to a new array. This new array is the value returned by map.
var alpha = {length: 5};
var beta = {length: 4};
var gamma = {length: 5};
var set = new hormigas.ObjectSet(alpha, beta, gamma);
set.map(function(element) {
return element.length;
}); // [5,5,4] or [5,4,5] or [4,5,5]
*/
hormigas.ObjectSet.prototype.map = function(callbackfn /*, thisArg */) {
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;
};
/**
@property hormigas.ObjectSet.prototype.filter
@parameter callbackfn {function} The function to call for each element in the set.
@parameter thisArg {object} The optional object to use as the this object in calls to callbackfn.
@description
Calls callbackfn for each element of the set. If callbackfn returns true
for an element then that element is added to a new array. This new array
is the value returned by filter.
var alpha = {length: 5};
var beta = {length: 4};
var gamma = {length: 5};
var set = new hormigas.ObjectSet(alpha, beta, gamma);
set.filter(function(element) {
return element.length > 4;
}); // [alpha, gamma] or [gamma, alpha]
*/
hormigas.ObjectSet.prototype.filter = function(callbackfn /*, thisArg */) {
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;
};
}());
// insure prototype object is initialized properly
hormigas.ObjectSet.call(hormigas.ObjectSet.prototype);
/**
@property hormigas.ObjectSet.mixin
@parameter obj {object} The object to become a ObjectSet.
@description
Mixes in the ObjectSet methods into any object.
// Example 1
app.MyModel = function() {
hormigas.ObjectSet.call(this);
};
hormigas.ObjectSet.mixin(app.MyModel.prototype);
// Example 2
var obj = {};
hormigas.ObjectSet.mixin(obj);
*/
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);
};
/*
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
*/var maria = maria || {};
// "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);
};
};
maria.borrow = function(sink, source) {
for (var p in source) {
if (Object.prototype.hasOwnProperty.call(source, p)) {
sink[p] = source[p];
}
}
};
maria.borrow(maria, evento);
maria.borrow(maria, hijos);
maria.borrow(maria, arbutus);
maria.borrow(maria, grail);
maria.borrow(maria, hormigas);
/**
@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.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);
}
};
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.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.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;
}
}
}
};
/**
@property maria.Controller
@description
A constructor function to create new controller objects.
Controller objects are usually created lazily on demand by a view
object but you can create one explicitly. You might want to do this if
you want to exercise the strategy pattern and dynamically change a
view's controller.
var controller = new maria.Controller();
A controller object has a view object. The view will automatically
sets itself as the controller's view when the view creates
the controller or when view.setController is called with the
controller. You can set the view object explicitely but you may
never have a need to do this.
var view = new maria.View();
controller.setView(view);
You can get the controller's view object when needed and you will do
this frequently in the methods of the controller.
controller.getView();
A controller object also has a model object. A controller-view pair
will usually have the same model object and the view object will keep
the controller object's model in sync with the view object's model
object. The model is usually set automatically by the view when the
view creates the controller or when view.setController is called with
the controller. You can set the model object explicitely but you
many never have a need to do this.
var model = new maria.Model();
controller.setModel(model);
You can get the controller's model object when needed and you will do
this frequently in the methods of the controller.
controller.getModel();
A particularly useful pattern is using maria.Controller as the
"superclass" of your application's controller. The following
example shows how this can be done at a low level for a to-do
application. See maria.Controller.subclass for a more compact way
to accomplish the same.
checkit.TodoController = function() {
maria.Controller.apply(this, arguments);
};
checkit.TodoController.prototype = new maria.Controller();
checkit.TodoController.prototype.constructor = checkit.TodoController;
checkit.TodoController.prototype.onClickCheck = function() {
this.getModel().toggleDone();
};
checkit.TodoController.prototype.onDblclickDisplay = function() {
this.getView().showEdit();
};
checkit.TodoController.prototype.onKeyupInput = function() {
var view = this.getView();
if (/\S/.test(view.getInputValue())) {
view.showToolTip();
} else {
view.hideToolTip();
}
};
checkit.TodoController.prototype.onKeypressInput = function(evt) {
if (evt.keyCode === 13) {
this.onBlurInput();
}
};
checkit.TodoController.prototype.onBlurInput = function() {
var view = this.getView();
var value = view.getInputValue();
view.hideToolTip();
view.showDisplay();
if (!/^\s*$/.test(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.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;
};
/**
@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.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.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.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);
};
maria.SetView.subclass = maria.ElementView.subclass;
/**
@property maria.Controller.subclass
@description
A function that makes subclassing maria.Controller more compact.
The following example creates a checkit.TodoController constructor
function equivalent to the more verbose example shown in
the documentation for maria.Controller.
maria.Controller.subclass(checkit, 'TodoController', {
properties: {
onClickCheck: function() {
this.getModel().toggleDone();
},
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 = maria.subclass;
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.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
......@@ -15,35 +15,9 @@
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="../lib/maria/maria.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="../lib/aristocrat/aristocrat.js"></script>
<script src="js/namespace.js"></script>
<script src="js/models/TodoModel.js"></script>
......
// "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);
};
};
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