todomvc
Version:
> Helping you select an MV\* framework
1,593 lines (1,526 loc) • 43.5 kB
JavaScript
(function (soma) {
'use strict';
soma.template = soma.template || {};
soma.template.version = '0.2.8';
soma.template.errors = {
TEMPLATE_STRING_NO_ELEMENT: 'Error in soma.template, a string template requirement a second parameter: an element target - soma.template.create(\'string\', element)',
TEMPLATE_NO_PARAM: 'Error in soma.template, a template requires at least 1 parameter - soma.template.create(element)'
};
var tokenStart = '{{';
var tokenEnd = '}}';
var helpersObject = {};
var helpersScopeObject = {};
var settings = soma.template.settings = soma.template.settings || {};
settings.autocreate = true;
var tokens = settings.tokens = {
start: function(value) {
if (isDefined(value) && value !== '') {
tokenStart = escapeRegExp(value);
setRegEX(value, true);
}
return tokenStart;
},
end: function(value) {
if (isDefined(value) && value !== '') {
tokenEnd = escapeRegExp(value);
setRegEX(value, false);
}
return tokenEnd;
}
};
var attributes = settings.attributes = {
skip: 'data-skip',
repeat: 'data-repeat',
src: 'data-src',
href: 'data-href',
show: 'data-show',
hide: 'data-hide',
cloak: 'data-cloak',
checked: 'data-checked',
disabled: 'data-disabled',
multiple: 'data-multiple',
readonly: 'data-readonly',
selected: 'data-selected',
template: 'data-template',
html: 'data-html'
};
var vars = settings.vars = {
index: '$index',
key: '$key',
element: '$element',
parentElement: '$parentElement',
attribute: '$attribute',
scope: '$scope'
};
var events = settings.events = {};
settings.eventsPrefix = 'data-';
var eventsString = 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup focus blur change select selectstart scroll copy cut paste mousewheel keypress error contextmenu input textinput drag dragenter dragleave dragover dragend dragstart dragover drop load submit reset search resize beforepaste beforecut beforecopy';
eventsString += ' touchstart touchend touchmove touchenter touchleave touchcancel gesturestart gesturechange gestureend';
var eventsArray = eventsString.split(' ');
var i = -1, l = eventsArray.length;
while(++i < l) {
events[settings.eventsPrefix + eventsArray[i]] = eventsArray[i];
}
var regex = {
sequence: null,
token: null,
expression: null,
escape: /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
trim: /^[\s+]+|[\s+]+$/g,
repeat: /(.*)\s+in\s+(.*)/,
func: /(.*)\((.*)\)/,
params: /,\s+|,|\s+,\s+/,
quote: /\"|\'/g,
content: /[^.|^\s]/gm,
depth: /..\//g,
string: /^(\"|\')(.*)(\"|\')$/
};
var ie = (function(){
if (typeof document !== 'object') {
return undefined;
}
var v = 3,
stop = false,
div = document.createElement('div');
while (!stop) {
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->';
if (!div.getElementsByTagName('i')[0]) {
stop = true;
}
}
return v > 4 ? v : undefined;
}());
function isArray(value) {
return Object.prototype.toString.apply(value) === '[object Array]';
}
function isObject(value) {
return typeof value === 'object';
}
function isString(value) {
return typeof value === 'string';
}
function isElement(value) {
return value ? value.nodeType > 0 : false;
}
function isTextNode(el) {
return el && el.nodeType && el.nodeType === 3;
}
function isFunction(value) {
return value && typeof value === 'function';
}
function isDefined(value) {
return value !== null && value !== undefined;
}
function normalizeBoolean(value) {
if (!isDefined(value)) {
return false;
}
if (value === 'true' || value === '1' || value === true || value === 1) {
return true;
}
if (value === 'false' || value === '0' || value === false || value === 0 || (isString(value) && hasInterpolation(value))) {
return false;
}
return !!value;
}
function isExpression(value) {
return value && isFunction(value.toString) && value.toString() === '[object Expression]';
}
function isExpFunction(value) {
if (!isString(value)) {
return false;
}
return !!value.match(regex.func);
}
function childNodeIsTemplate(node) {
return node && node.parent && templates.get(node.element);
}
function escapeRegExp(str) {
return str.replace(regex.escape, '\\$&');
}
function setRegEX(nonEscapedValue, isStartToken) {
// sequence: \{\{.+?\}\}|[^{]+|\{(?!\{)[^{]*
var unescapedCurrentStartToken = tokens.start().replace(/\\/g, '');
var endSequence = '';
var ts = isStartToken ? nonEscapedValue : unescapedCurrentStartToken;
if (ts.length > 1) {
endSequence = '|\\' + ts.substr(0, 1) + '(?!\\' + ts.substr(1, 1) + ')[^' + ts.substr(0, 1) + ']*';
}
regex.sequence = new RegExp(tokens.start() + '.+?' + tokens.end() + '|[^' + tokens.start() + ']+' + endSequence, 'g');
regex.token = new RegExp(tokens.start() + '.*?' + tokens.end(), 'g');
regex.expression = new RegExp(tokens.start() + '|' + tokens.end(), 'gm');
}
function trim(value) {
return value.replace(regex.trim, '');
}
function trimQuotes(value) {
if (regex.string.test(value)) {
return value.substr(1, value.length-2);
}
return value;
}
function trimArray(value) {
if (value[0] === '') {
value.shift();
}
if (value[value.length-1] === '') {
value.pop();
}
return value;
}
function trimTokens(value) {
return value.replace(regex.expression, '');
}
function trimScopeDepth(value) {
return value.replace(regex.depth, '');
}
function insertBefore(referenceNode, newNode) {
if (!referenceNode.parentNode) {
return;
}
referenceNode.parentNode.insertBefore(newNode, referenceNode);
}
function insertAfter(referenceNode, newNode) {
if (!referenceNode.parentNode) {
return;
}
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
function removeClass(elm, className) {
var rmc;
if (typeof document === 'object' && document.documentElement.classList) {
rmc = function (elm, className) {
elm.classList.remove(className);
};
} else {
rmc = function (elm, className) {
if (!elm || !elm.className) {
return false;
}
var reg = new RegExp('(^|\\s)' + className + '(\\s|$)', 'g');
elm.className = elm.className.replace(reg, '$2');
};
}
rmc(elm, className);
}
// jquery contains
var contains = typeof document !== 'object' ? function(){} : document.documentElement.contains ?
function( a, b ) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
} :
document.documentElement.compareDocumentPosition ?
function( a, b ) {
return b && !!( a.compareDocumentPosition( b ) & 16 );
} :
function( a, b ) {
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
return false;
};
function HashMap(id) {
var items = {};
var count = 0;
//var uuid = function(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b;}
function uuid() { return ++count + id; }
function getKey(target) {
if (!target) {
return;
}
if (typeof target !== 'object') {
return target;
}
var result;
try {
// IE 7-8 needs a try catch, seems like I can't add a property on text nodes
result = target[id] ? target[id] : target[id] = uuid();
} catch(err){}
return result;
}
this.remove = function(key) {
delete items[getKey(key)];
};
this.get = function(key) {
return items[getKey(key)];
};
this.put = function(key, value) {
items[getKey(key)] = value;
};
this.has = function(key) {
return typeof items[getKey(key)] !== 'undefined';
};
this.getData = function() {
return items;
};
this.dispose = function() {
for (var key in items) {
if (items.hasOwnProperty(key)) {
delete items[key];
}
}
this.length = 0;
};
}
function getRepeaterData(repeaterValue, scope) {
var parts = repeaterValue.match(regex.repeat);
if (!parts) {
return;
}
var source = parts[2];
var exp = new Expression(source);
return exp.getValue(scope);
}
function updateScopeWithRepeaterData(repeaterValue, scope, data) {
var parts = repeaterValue.match(regex.repeat);
if (!parts) {
return;
}
var name = parts[1];
scope[name] = data;
}
function getWatcherValue(exp, newValue) {
var node = exp.node || exp.attribute.node;
var watchers = node.template.watchers;
var nodeTarget = node.element;
if (!watchers) {
return newValue;
}
var watcherNode = watchers.get(nodeTarget);
if (!watcherNode && isTextNode(node.element) && node.parent) {
watcherNode = watchers.get(node.parent.element);
}
var watcher = watcherNode ? watcherNode : watchers.get(exp.pattern);
if (isFunction(watcher)) {
var watcherValue = watcher(exp.value, newValue, exp.pattern, node.scope, node, exp.attribute);
if (isDefined(watcherValue)) {
return watcherValue;
}
}
return newValue;
}
function getScopeFromPattern(scope, pattern) {
var depth = getScopeDepth(pattern);
var scopeTarget = scope;
while (depth > 0) {
scopeTarget = scopeTarget._parent ? scopeTarget._parent : scopeTarget;
depth--;
}
return scopeTarget;
}
function getValueFromPattern(scope, pattern, context) {
var exp = new Expression(pattern);
return getValue(scope, exp.pattern, exp.path, exp.params, undefined, undefined, undefined, context);
}
function getValue(scope, pattern, pathString, params, getFunction, getParams, paramsFound, context) {
// context
if (pattern === vars.element) {
return context[vars.element];
}
if (pattern === vars.parentElement) {
return context[vars.parentElement];
}
if (pattern === vars.attribute) {
return context[vars.attribute];
}
if (pattern === vars.scope) {
return context[vars.scope];
}
// string
if (regex.string.test(pattern)) {
return trimQuotes(pattern);
}
// find params
var paramsValues = [];
if (!paramsFound && params) {
for (var j = 0, jl = params.length; j < jl; j++) {
paramsValues.push(getValueFromPattern(scope, params[j], context));
}
}
else {
paramsValues = paramsFound;
}
if (getParams) {
return paramsValues;
}
// find scope
var scopeTarget = getScopeFromPattern(scope, pattern);
// remove parent string
pattern = pattern.replace(/..\//g, '');
pathString = pathString.replace(/..\//g, '');
if (!scopeTarget) {
return undefined;
}
// search path
var path = scopeTarget;
var pathParts = pathString.split(/\.|\[|\]/g);
if (pathParts.length > 0) {
for (var i = 0, l = pathParts.length; i < l; i++) {
if (pathParts[i] !== '') {
path = path[pathParts[i]];
}
if (!isDefined(path)) {
// no path, search in parent
if (scopeTarget._parent) {
return getValue(scopeTarget._parent, pattern, pathString, params, getFunction, getParams, paramsValues);
}
else {
return undefined;
}
}
}
}
// return value
if (!isFunction(path)) {
return path;
}
else {
if (getFunction) {
return path;
}
else {
return path.apply(null, paramsValues);
}
}
return undefined;
}
function getExpressionPath(value) {
var val = value.split('(')[0];
val = trimScopeDepth(val);
return val;
}
function getParamsFromString(value) {
return trimArray(value.split(regex.params));
}
function getScopeDepth(value) {
var val = value.split('(')[0];
var matches = val.match(regex.depth);
return !matches ? 0 : matches.length;
}
function addAttribute(node, name, value) {
var attr;
node.attributes = node.attributes || [];
if (name === settings.attributes.skip) {
node.skip = normalizeBoolean(value);
}
if (name === settings.attributes.html) {
node.html = normalizeBoolean(value);
}
if (name === settings.attributes.repeat && !node.isRepeaterDescendant) {
node.repeater = value;
}
if (
hasInterpolation(name + ':' + value) ||
name === settings.attributes.repeat ||
name === settings.attributes.skip ||
name === settings.attributes.html ||
name === settings.attributes.show ||
name === settings.attributes.hide ||
name === settings.attributes.href ||
name === settings.attributes.checked ||
name === settings.attributes.disabled ||
name === settings.attributes.multiple ||
name === settings.attributes.readonly ||
name === settings.attributes.selected ||
value.indexOf(settings.attributes.cloak) !== -1
) {
attr = new Attribute(name, value, node);
node.attributes.push(attr);
}
if (events[name]) {
attr = new Attribute(name, value, node);
node.attributes.push(attr);
}
return attr;
}
function getNodeFromElement(element, scope) {
var node = new Node(element, scope);
node.previousSibling = element.previousSibling;
node.nextSibling = element.nextSibling;
var eventsArray = [];
for (var attr, attrs = element.attributes, j = 0, jj = attrs && attrs.length; j < jj; j++) {
attr = attrs[j];
if (attr.specified || attr.name === 'value') {
var newAttr = addAttribute(node, attr.name, attr.value);
if (events[attr.name]) {
if (events[attr.name] && !node.isRepeaterChild) {
eventsArray.push({name:events[attr.name], value:attr.value, attr: newAttr});
}
}
}
}
for (var a=0, b=eventsArray.length; a<b; a++) {
node.addEvent(eventsArray[a].name, eventsArray[a].value, eventsArray[a].attr);
}
return node;
}
function hasInterpolation(value) {
var matches = value.match(regex.token);
return matches && matches.length > 0;
}
function hasContent(value) {
return regex.content.test(value);
}
function isElementValid(element) {
if (!element) {
return;
}
var type = element.nodeType;
if (!element || !type) {
return false;
}
// comment
if (type === 8) {
return false;
}
// empty text node
if (type === 3 && !hasContent(element.nodeValue) && !hasInterpolation(element.nodeValue)) {
return false;
}
// result
return true;
}
function compile(template, element, parent, nodeTarget) {
if (!isElementValid(element)) {
return;
}
// get node
var node;
if (!nodeTarget) {
node = getNodeFromElement(element, parent ? parent.scope : new Scope(helpersScopeObject)._createChild());
}
else {
node = nodeTarget;
node.parent = parent;
}
if (parent && (parent.repeater || parent.isRepeaterChild)) {
node.isRepeaterChild = true;
}
node.template = template;
// children
if (node.skip) {
return;
}
var child = element.firstChild;
while (child) {
var childNode = compile(template, child, node);
if (childNode) {
childNode.parent = node;
node.children.push(childNode);
}
child = child.nextSibling;
}
return node;
}
function updateScopeWithData(scope, data) {
clearScope(scope);
for (var d in data) {
if (data.hasOwnProperty(d)) {
scope[d] = data[d];
}
}
}
function clearScope(scope) {
for (var key in scope) {
if (scope.hasOwnProperty(key)) {
if (key.substr(0, 1) !== '_') {
scope[key] = null;
delete scope[key];
}
}
}
}
function updateNodeChildren(node) {
if (node.repeater || !node.children || childNodeIsTemplate(node)) {
return;
}
for (var i = 0, l = node.children.length; i < l; i++) {
node.children[i].update();
}
}
function renderNodeChildren(node) {
if (!node.children || childNodeIsTemplate(node)) {
return;
}
for (var i = 0, l = node.children.length; i < l; i++) {
node.children[i].render();
}
}
function renderNodeRepeater(node) {
var data = getRepeaterData(node.repeater, node.scope);
var previousElement;
if (isArray(data)) {
// process array
for (var i = 0, l1 = data.length, l2 = node.childrenRepeater.length, l = l1 > l2 ? l1 : l2; i < l; i++) {
if (i < l1) {
previousElement = createRepeaterChild(node, i, data[i], vars.index, i, previousElement);
}
else {
node.parent.element.removeChild(node.childrenRepeater[i].element);
node.childrenRepeater[i].dispose();
}
}
if (node.childrenRepeater.length > data.length) {
node.childrenRepeater.length = data.length;
}
}
else {
// process object
var count = -1;
for (var o in data) {
if (data.hasOwnProperty(o)) {
count++;
previousElement = createRepeaterChild(node, count, data[o], vars.key, o, previousElement);
}
}
var size = count;
while (count++ < node.childrenRepeater.length-1) {
node.parent.element.removeChild(node.childrenRepeater[count].element);
node.childrenRepeater[count].dispose();
}
node.childrenRepeater.length = size+1;
}
if (node.element.parentNode) {
node.element.parentNode.removeChild(node.element);
}
}
function compileClone(node, newNode) {
if (!isElementValid(newNode.element)) {
return;
}
// create attribute
if (node.attributes) {
for (var i= 0, l=node.attributes.length; i<l; i++) {
var attr = node.attributes[i];
var newAttr = addAttribute(newNode, attr.name, attr.value);
if (events[attr.name]) {
newNode.addEvent(events[attr.name], attr.value, newAttr);
}
}
}
// children
var child = node.element.firstChild;
var newChild = newNode.element.firstChild;
// loop
while (child && newChild) {
var childNode = node.getNode(child);
var newChildNode = new Node(newChild, newNode.scope);
newNode.children.push(newChildNode);
newChildNode.parent = newNode;
newChildNode.template = newNode.template;
newChildNode.isRepeaterChild = true;
var compiledNode = compileClone(childNode, newChildNode);
if (compiledNode) {
compiledNode.parent = newChildNode;
compiledNode.template = newChildNode.template;
newChildNode.children.push(compiledNode);
}
child = child.nextSibling;
newChild = newChild.nextSibling;
}
return newChildNode;
}
function cloneRepeaterNode(element, node) {
var newNode = new Node(element, node.scope._createChild());
newNode.template = node.template;
newNode.parent = node;
newNode.isRepeaterChild = true;
newNode.isRepeaterDescendant = true;
compileClone(node, newNode);
return newNode;
}
function appendRepeaterElement(previousElement, node, newElement) {
if (!previousElement) {
if (node.element.previousSibling) {
insertAfter(node.element.previousSibling, newElement);
}
else if (node.element.nextSibling) {
insertBefore(node.element.nextSibling, newElement);
}
else {
node.parent.element.appendChild(newElement);
}
}
else {
insertAfter(previousElement, newElement);
}
}
function createRepeaterChild(node, count, data, indexVar, indexVarValue, previousElement) {
var existingChild = node.childrenRepeater[count];
if (!existingChild) {
var newElement = node.element.cloneNode(true);
// need to append the cloned element to the DOM
// before changing attributes or IE will crash
appendRepeaterElement(previousElement, node, newElement);
// can't recreate the node with a cloned element on IE7
// be cause the attributes are not specified anymore (attribute.specified)
//var newNode = getNodeFromElement(newElement, node.scope._createChild(), true);
var newNode = cloneRepeaterNode(newElement, node);
node.childrenRepeater[count] = newNode;
updateScopeWithRepeaterData(node.repeater, newNode.scope, data);
newNode.scope[indexVar] = indexVarValue;
newNode.update();
newNode.render();
return newElement;
}
else {
// existing node
updateScopeWithRepeaterData(node.repeater, existingChild.scope, data);
existingChild.scope[indexVar] = indexVarValue;
existingChild.update();
existingChild.render();
return existingChild.element;
}
}
var Scope = function(data) {
var self;
function createChild(data) {
var obj = createObject(data);
obj._parent = self;
self._children.push(obj);
return obj;
}
function createObject(data) {
var obj = data || {};
obj._parent = null;
obj._children = [];
obj._createChild = function() {
self = obj;
return createChild.apply(obj, arguments);
};
return obj;
}
return createObject(data);
};
var Node = function(element, scope) {
this.element = element;
this.scope = scope;
this.attributes = null;
this.value = null;
this.interpolation = null;
this.invalidate = false;
this.skip = false;
this.repeater = null;
this.isRepeaterDescendant = false;
this.isRepeaterChild = false;
this.parent = null;
this.children = [];
this.childrenRepeater = [];
this.previousSibling = null;
this.nextSibling = null;
this.template = null;
this.eventHandlers = {};
this.html = false;
if (isTextNode(this.element)) {
this.value = this.element.nodeValue;
this.interpolation = new Interpolation(this.value, this, undefined);
}
};
Node.prototype = {
toString: function() {
return '[object Node]';
},
dispose: function() {
this.clearEvents();
var i, l;
if (this.children) {
for (i = 0, l = this.children.length; i < l; i++) {
this.children[i].dispose();
}
}
if (this.childrenRepeater) {
for (i = 0, l = this.childrenRepeater.length; i < l; i++) {
this.childrenRepeater[i].dispose();
}
}
if (this.attributes) {
for (i = 0, l = this.attributes.length; i < l; i++) {
this.attributes[i].dispose();
}
}
if (this.interpolation) {
this.interpolation.dispose();
}
this.element = null;
this.scope = null;
this.attributes = null;
this.value = null;
this.interpolation = null;
this.repeater = null;
this.parent = null;
this.children = null;
this.childrenRepeater = null;
this.previousSibling = null;
this.nextSibling = null;
this.template = null;
this.eventHandlers = null;
},
getNode: function(element) {
var node;
if (element === this.element) {
return this;
}
if (this.childrenRepeater.length > 0) {
for (var k = 0, kl = this.childrenRepeater.length; k < kl; k++) {
node = this.childrenRepeater[k].getNode(element);
if (node) {
return node;
}
}
}
for (var i = 0, l = this.children.length; i < l; i++) {
node = this.children[i].getNode(element);
if (node) {
return node;
}
}
return null;
},
getAttribute: function(name) {
if (this.attributes) {
for (var i = 0, l = this.attributes.length; i < l; i++) {
var att = this.attributes[i];
if (att.interpolationName && att.interpolationName.value === name) {
return att;
}
}
}
},
update: function() {
if (childNodeIsTemplate(this)) {
return;
}
if (isDefined(this.interpolation)) {
this.interpolation.update();
}
if (isDefined(this.attributes)) {
for (var i = 0, l = this.attributes.length; i < l; i++) {
this.attributes[i].update();
}
}
updateNodeChildren(this);
},
invalidateData: function() {
if (childNodeIsTemplate(this)) {
return;
}
this.invalidate = true;
var i, l;
if (this.attributes) {
for (i = 0, l = this.attributes.length; i < l; i++) {
this.attributes[i].invalidate = true;
}
}
for (i = 0, l = this.childrenRepeater.length; i < l; i++) {
this.childrenRepeater[i].invalidateData();
}
for (i = 0, l = this.children.length; i < l; i++) {
this.children[i].invalidateData();
}
},
addEvent: function(type, pattern, attr) {
if (this.repeater) {
return;
}
if (this.eventHandlers[type]) {
this.removeEvent(type);
}
var scope = this.scope;
var node = this;
var handler = function(event) {
var exp = new Expression(pattern, node, attr);
var func = exp.getValue(scope, true);
var params = exp.getValue(scope, false, true);
params.unshift(event);
if (func) {
func.apply(null, params);
}
};
this.eventHandlers[type] = handler;
addEvent(this.element, type, handler);
},
removeEvent: function(type) {
removeEvent(this.element, type, this.eventHandlers[type]);
this.eventHandlers[type] = null;
delete this.eventHandlers[type];
},
clearEvents: function() {
if (this.eventHandlers) {
for (var key in this.eventHandlers) {
if (this.eventHandlers.hasOwnProperty(key)) {
this.removeEvent(key);
}
}
}
if (this.children) {
for (var k = 0, kl = this.children.length; k < kl; k++) {
this.children[k].clearEvents();
}
}
if (this.childrenRepeater) {
for (var f = 0, fl = this.childrenRepeater.length; f < fl; f++) {
this.childrenRepeater[f].clearEvents();
}
}
},
render: function() {
if (childNodeIsTemplate(this)) {
return;
}
if (this.invalidate) {
this.invalidate = false;
if (isTextNode(this.element)) {
if (this.parent && this.parent.html) {
this.value = this.parent.element.innerHTML = this.interpolation.render();
}
else {
this.value = this.element.nodeValue = this.interpolation.render();
}
}
}
if (this.attributes) {
for (var i = 0, l = this.attributes.length; i < l; i++) {
this.attributes[i].render();
}
}
if (this.repeater) {
renderNodeRepeater(this);
}
else {
renderNodeChildren(this);
}
}
};
var Attribute = function(name, value, node) {
this.name = name;
this.value = value;
this.node = node;
this.interpolationName = new Interpolation(this.name, null, this);
this.interpolationValue = new Interpolation(this.value, null, this);
this.invalidate = false;
};
Attribute.prototype = {
toString: function() {
return '[object Attribute]';
},
dispose: function() {
if (this.interpolationName) {
this.interpolationName.dispose();
}
if (this.interpolationValue) {
this.interpolationValue.dispose();
}
this.interpolationName = null;
this.interpolationValue = null;
this.node = null;
this.name = null;
this.value = null;
this.previousName = null;
},
update: function() {
if (this.node.repeater) {
return;
}
this.interpolationName.update();
this.interpolationValue.update();
},
render: function() {
if (this.node.repeater) {
return;
}
// normal attribute
function renderAttribute(name, value, node) {
if (name === 'value' && node.element['value'] !== undefined) {
element.value = value;
}
else if (ie === 7 && name === 'class') {
element.className = value;
}
else {
element.setAttribute(name, value);
}
}
// boolean attribute
function renderBooleanAttribute(name, value) {
element.setAttribute(name, value);
}
// special attribute
function renderSpecialAttribute(value, attrName) {
if (normalizeBoolean(value)) {
element.setAttribute(attrName, attrName);
}
else {
element.removeAttribute(attrName);
}
}
// src attribute
function renderSrc(value) {
element.setAttribute('src', value);
}
// href attribute
function renderHref(value) {
element.setAttribute('href', value);
}
var element = this.node.element;
if (this.invalidate) {
this.invalidate = false;
this.previousName = this.name;
this.name = isDefined(this.interpolationName.render()) ? this.interpolationName.render() : this.name;
this.value = isDefined(this.interpolationValue.render()) ? this.interpolationValue.render() : this.value;
if (this.name === attributes.src) {
renderSrc(this.value);
}
else if (this.name === attributes.href) {
renderHref(this.value);
}
else {
if (ie !== 7 || (ie === 7 && !this.node.isRepeaterChild)) {
this.node.element.removeAttribute(this.interpolationName.value);
}
if (this.previousName) {
if (ie === 7 && this.previousName === 'class') {
// iE
this.node.element.className = '';
}
else {
if (ie !== 7 || (ie === 7 && !this.node.isRepeaterChild)) {
this.node.element.removeAttribute(this.previousName);
}
}
}
renderAttribute(this.name, this.value, this.node);
}
}
// cloak
if (this.name === 'class' && this.value.indexOf(settings.attributes.cloak) !== -1) {
removeClass(this.node.element, settings.attributes.cloak);
}
// hide
if (this.name === attributes.hide) {
var bool = normalizeBoolean(this.value);
renderAttribute(this.name, bool, this.node);
element.style.display = bool ? 'none' : '';
}
// show
if (this.name === attributes.show) {
var bool = normalizeBoolean(this.value);
renderAttribute(this.name, bool, this.node);
element.style.display = bool ? '' : 'none';
}
// checked
if (this.name === attributes.checked) {
renderSpecialAttribute(this.value, 'checked');
renderAttribute(this.name, normalizeBoolean(this.value) ? true : false, this.node);
element.checked = normalizeBoolean(this.value) ? true : false;
}
// disabled
if (this.name === attributes.disabled) {
renderSpecialAttribute(this.value, 'disabled');
renderAttribute(this.name, normalizeBoolean(this.value) ? true : false, this.node);
}
// multiple
if (this.name === attributes.multiple) {
renderSpecialAttribute(this.value, 'multiple');
renderAttribute(this.name, normalizeBoolean(this.value) ? true : false, this.node);
}
// readonly
if (this.name === attributes.readonly) {
var bool = normalizeBoolean(this.value);
if (ie === 7) {
element.readOnly = bool ? true : false;
}
else {
renderSpecialAttribute(this.value, 'readonly');
}
renderAttribute(this.name, bool ? true : false, this.node);
}
// selected
if (this.name === attributes.selected) {
renderSpecialAttribute(this.value, 'selected');
renderAttribute(this.name, normalizeBoolean(this.value) ? true : false, this.node);
}
}
};
var Interpolation = function(value, node, attribute) {
this.value = node && !isTextNode(node.element) ? trim(value) : value;
this.node = node;
this.attribute = attribute;
this.sequence = [];
this.expressions = [];
var parts = this.value.match(regex.sequence);
if (parts) {
for (var i = 0, l = parts.length; i < l; i++) {
if (parts[i].match(regex.token)) {
var exp = new Expression(trimTokens(parts[i]), this.node, this.attribute);
this.sequence.push(exp);
this.expressions.push(exp);
}
else {
this.sequence.push(parts[i]);
}
}
trimArray(this.sequence);
}
};
Interpolation.prototype = {
toString: function() {
return '[object Interpolation]';
},
dispose: function() {
if (this.expressions) {
for (var i = 0, l = this.expressions.length; i < l; i++) {
this.expressions[i].dispose();
}
}
this.value = null;
this.node = null;
this.attribute = null;
this.sequence = null;
this.expressions = null;
},
update: function() {
var i = -1, l = this.expressions.length;
while (++i < l) {
this.expressions[i].update();
}
},
render: function() {
var rendered = '';
if (this.sequence) {
for (var i = 0, l = this.sequence.length; i < l; i++) {
var val = '';
if (isExpression(this.sequence[i])) {
val = this.sequence[i].value;
}
else {
val = this.sequence[i];
}
if (!isDefined(val)) {
val = '';
}
rendered += val;
}
}
return rendered;
}
};
var Expression = function(pattern, node, attribute) {
if (!isDefined(pattern)) {
return;
}
this.pattern = pattern;
this.isString = regex.string.test(pattern);
this.node = node;
this.attribute = attribute;
this.value = this.isString ? this.pattern : undefined;
if (this.isString) {
this.isFunction = false;
this.depth = null;
this.path = null;
this.params = null;
}
else {
this.isFunction = isExpFunction(this.pattern);
this.depth = getScopeDepth(this.pattern);
this.path = getExpressionPath(this.pattern);
this.params = !this.isFunction ? null : getParamsFromString(this.pattern.match(regex.func)[2]);
}
};
Expression.prototype = {
toString: function() {
return '[object Expression]';
},
dispose: function() {
this.pattern = null;
this.node = null;
this.attribute = null;
this.path = null;
this.params = null;
this.value = null;
},
update: function() {
var node = this.node;
if (!node && this.attribute) {
node = this.attribute.node;
}
if (!node && node.scope) {
return;
}
var newValue = this.getValue(node.scope);
newValue = getWatcherValue(this, newValue);
if (this.value !== newValue) {
this.value = newValue;
(this.node || this.attribute).invalidate = true;
}
},
getValue: function(scope, getFunction, getParams) {
var node = this.node;
if (!node && this.attribute) {
node = this.attribute.node;
}
var context = {};
if (node) {
context[vars.element] = node.element;
if (node.element) {
context[vars.parentElement] = node.element.parentNode;
}
}
context[vars.attribute] = this.attribute;
context[vars.scope] = scope;
return getValue(scope, this.pattern, this.path, this.params, getFunction, getParams, undefined, context);
}
};
var templates = new HashMap('st');
var Template = function(element) {
this.watchers = new HashMap('stw');
this.node = null;
this.scope = null;
this.compile(element);
};
Template.prototype = {
toString: function() {
return '[object Template]';
},
compile: function(element) {
if (element) {
this.element = element;
}
if (this.node) {
this.node.dispose();
}
this.node = compile(this, this.element);
this.node.root = true;
this.scope = this.node.scope;
},
update: function(data) {
if (isDefined(data)) {
updateScopeWithData(this.node.scope, data);
}
if (this.node) {
this.node.update();
}
},
render: function(data) {
this.update(data);
if (this.node) {
this.node.render();
}
},
invalidate: function() {
if (this.node) {
this.node.invalidateData();
}
},
watch: function(target, watcher) {
if ( (!isString(target) && !isElement(target)) || !isFunction(watcher)) {
return;
}
this.watchers.put(target, watcher);
},
unwatch: function(target) {
this.watchers.remove(target);
},
clearWatchers: function() {
this.watchers.dispose();
},
clearEvents: function() {
this.node.clearEvents();
},
getNode: function(element) {
return this.node.getNode(element);
},
dispose: function() {
templates.remove(this.element);
if (this.watchers) {
this.watchers.dispose();
}
if (this.node) {
this.node.dispose();
}
this.element = null;
this.watchers = null;
this.node = null;
}
};
// written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini
// http://dean.edwards.name/weblog/2005/10/add-event/
function addEvent(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
// assign each event handler a unique ID
if (!handler.$$guid) {
handler.$$guid = addEvent.guid++;
}
// create a hash table of event types for the element
if (!element.events) {
element.events = {};
}
// create a hash table of event handlers for each element/event pair
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
// store the existing event handler (if there is one)
if (element['on' + type]) {
handlers[0] = element['on' + type];
}
}
// store the event handler in the hash table
handlers[handler.$$guid] = handler;
// assign a global event handler to do all the work
element['on' + type] = function(event) {
var returnValue = true;
// grab the event object (IE uses a global event object)
event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
// get a reference to the hash table of event handlers
var handlers = this.events[event.type];
// execute each event handler
for (var i in handlers) {
if (handlers.hasOwnProperty(i)) {
this.$$handleEvent = handlers[i];
if (this.$$handleEvent(event) === false) {
returnValue = false;
}
}
}
return returnValue;
};
}
}
// a counter used to create unique IDs
addEvent.guid = 1;
function removeEvent(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else {
// delete the event handler from the hash table
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
}
}
function fixEvent(event) {
// add W3C standard event methods
event.preventDefault = fixEvent.preventDefault;
event.stopPropagation = fixEvent.stopPropagation;
return event;
}
fixEvent.preventDefault = function() {
this.returnValue = false;
};
fixEvent.stopPropagation = function() {
this.cancelBubble = true;
};
var maxDepth;
var eventStore = [];
function parseEvents(element, object, depth) {
maxDepth = depth === undefined ? Number.MAX_VALUE : depth;
parseNode(element, object, 0, true);
}
function parseNode(element, object, depth, isRoot) {
if (!isElement(element)) {
throw new Error('Error in soma.template.parseEvents, only a DOM Element can be parsed.');
}
if (isRoot) {
parseAttributes(element, object);
}
if (maxDepth === 0) {
return;
}
var child = element.firstChild;
while (child) {
if (child.nodeType === 1) {
if (depth < maxDepth) {
parseNode(child, object, ++depth);
parseAttributes(child, object);
}
}
child = child.nextSibling;
}
}
function parseAttributes(element, object) {
for (var attr, name, value, attrs = element.attributes, j = 0, jj = attrs && attrs.length; j < jj; j++) {
attr = attrs[j];
if (attr.specified) {
name = attr.name;
value = attr.value;
if (events[name]) {
var handler = getHandlerFromPattern(object, value);
if (handler && isFunction(handler)) {
addEvent(element, events[name], handler);
eventStore.push({element:element, type:events[name], handler:handler});
}
}
}
}
}
function getHandlerFromPattern(object, pattern) {
var parts = pattern.match(regex.func);
if (parts) {
var func = parts[1];
if (isFunction(object[func])) {
return object[func];
}
}
}
function clearEvents(element) {
var i = eventStore.length, l = 0;
while (--i >= l) {
var item = eventStore[i];
if (element === item.element || contains(element, item.element)) {
removeEvent(item.element, item.type, item.handler);
eventStore.splice(i, 1);
}
}
}
var ready;
if (typeof document === 'object') {
// https://github.com/ded/domready
var ready=function(){function l(b){for(k=1;b=a.shift();)b()}var b,a=[],c=!1,d=document,e=d.documentElement,f=e.doScroll,g="DOMContentLoaded",h="addEventListener",i="onreadystatechange",j="readyState",k=/^loade|c/.test(d[j]);return d[h]&&d[h](g,b=function(){d.removeEventListener(g,b,c),l()},c),f&&d.attachEvent(i,b=function(){/^c/.test(d[j])&&(d.detachEvent(i,b),l())}),f?function(b){self!=top?k?b():a.push(b):function(){try{e.doScroll("left")}catch(a){return setTimeout(function(){ready(b)},50)}b()}()}:function(b){k?b():a.push(b)}}();
if (settings.autocreate) {
var parse = function(element) {
var child = !element ? document.body : element.firstChild;
while (child) {
if (child.nodeType === 1) {
parse(child);
var attrValue = child.getAttribute(attributes.template);
if (attrValue) {
var getFunction = new Function('return ' + attrValue + ';');
try {
var f = getFunction();
if (isFunction(f)) {
soma.template.bootstrap(attrValue, child, f);
}
} catch(err){}
}
}
child = child.nextSibling;
}
};
ready(parse);
}
}
function bootstrapTemplate(attrValue, element, func) {
var tpl = createTemplate(element);
func(tpl, tpl.scope, tpl.element, tpl.node);
}
function createTemplate(source, target) {
var element;
if (isString(source)) {
// string template
if (!isElement(target)) {
throw new Error(soma.template.errors.TEMPLATE_STRING_NO_ELEMENT);
}
target.innerHTML = source;
element = target;
}
else if (isElement(source)) {
if (isElement(target)) {
// element template with target
target.innerHTML = source.innerHTML;
element = target;
}
else {
// element template
element = source;
}
}
else {
throw new Error(soma.template.errors.TEMPLATE_NO_PARAM);
}
// existing template
if (getTemplate(element)) {
getTemplate(element).dispose();
templates.remove(element);
}
// create template
var template = new Template(element);
templates.put(element, template);
return template;
}
function getTemplate(element) {
return templates.get(element);
}
function renderAllTemplates() {
var data = templates.getData();
for (var key in templates.getData()) {
if (data.hasOwnProperty(key)) {
templates.get(key).render();
}
}
}
function appendHelpers(obj) {
if (obj === null) {
helpersObject = {};
helpersScopeObject = {};
}
if (isDefined(obj) && isObject(obj)) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
helpersObject[key] = helpersScopeObject[key] = obj[key];
}
}
}
return helpersObject;
}
// set regex
tokens.start(tokenStart);
tokens.end(tokenEnd);
// plugins
soma.plugins = soma.plugins || {};
var TemplatePlugin = function(instance, injector) {
instance.constructor.prototype.createTemplate = function(cl, domElement) {
if (!cl || typeof cl !== 'function') {
throw new Error('Error creating a template, the first parameter must be a function.');
}
if (domElement && isElement(domElement)) {
var template = soma.template.create(domElement);
for (var key in template) {
if (typeof template[key] === 'function') {
cl.prototype[key] = template[key].bind(template);
}
}
cl.prototype.render = template.render.bind(template);
var childInjector = injector.createChild();
childInjector.mapValue('template', template);
childInjector.mapValue('scope', template.scope);
childInjector.mapValue('element', template.element);
return childInjector.createInstance(cl);
}
return null;
};
soma.template.bootstrap = function(attrValue, element, func) {
instance.createTemplate(func, element);
};
};
if (soma.plugins && soma.plugins.add) {
soma.plugins.add(TemplatePlugin);
}
soma.template.Plugin = TemplatePlugin;
// exports
soma.template.create = createTemplate;
soma.template.get = getTemplate;
soma.template.renderAll = renderAllTemplates;
soma.template.helpers = appendHelpers;
soma.template.bootstrap = bootstrapTemplate;
soma.template.addEvent = addEvent;
soma.template.removeEvent = removeEvent;
soma.template.parseEvents = parseEvents;
soma.template.clearEvents = clearEvents;
soma.template.ready = ready;
// register for AMD module
if (typeof define === 'function' && typeof define.amd !== 'undefined') {
define('soma-template', soma.template);
}
// export for node.js
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = soma.template;
}
if (typeof exports !== 'undefined') {
exports = soma.template;
}
})(this['soma'] = this['soma'] || {});