/*!
* Tempo Template Engine 1.6
*
* http://tempojs.com/
*/
function TempoEvent(type, item, element) {
this.type = type;
this.item = item;
this.element = element;
return this;
}
TempoEvent.Types = {
RENDER_STARTING : 'render_starting',
ITEM_RENDER_STARTING : 'item_render_starting',
ITEM_RENDER_COMPLETE : 'item_render_complete',
RENDER_COMPLETE : 'render_complete'
};
var Tempo = (function (tempo) {
/*!
* Constants
*/
var NUMBER_FORMAT_REGEX = /(\d+)(\d{3})/;
/*!
* Helpers
*/
var utils = {
memberRegex : function (obj) {
var member_regex = '';
for (var member in obj) {
if (obj.hasOwnProperty(member)) {
if (member_regex.length > 0) {
member_regex += '|';
}
member_regex += member;
}
}
return member_regex;
},
pad : function (val, pad, size) {
while (val.length < size) {
val = pad + val;
}
return val;
},
trim : function (str) {
return str.replace(/^\s*([\S\s]*?)\s*$/, '$1');
},
startsWith : function (str, prefix) {
return (str.indexOf(prefix) === 0);
},
clearContainer : function (el) {
if (el !== undefined && el.childNodes !== undefined) {
for (var i = el.childNodes.length; i >= 0; i--) {
if (el.childNodes[i] !== undefined && el.childNodes[i].getAttribute !== undefined && el.childNodes[i].getAttribute('data-template') !== null) {
el.childNodes[i].parentNode.removeChild(el.childNodes[i]);
}
}
}
},
isNested : function (el) {
var p = el.parentNode;
while (p) {
if (p.getAttribute !== undefined && p.getAttribute('data-template') !== null) {
return true;
}
p = p.parentNode;
}
return false;
},
equalsIgnoreCase : function (str1, str2) {
return str1.toLowerCase() === str2.toLowerCase();
},
getElement : function (template, html) {
if (utils.equalsIgnoreCase(template.tagName, 'tr')) {
// Wrapping to get around read-only innerHTML
var el = document.createElement('div');
el.innerHTML = '
';
var depth = 3;
while (depth--) {
el = el.lastChild;
}
return el;
} else {
// No need to wrap
template.innerHTML = html;
return template;
}
},
typeOf : function (obj) {
if (typeof(obj) === "object") {
if (obj === null) {
return "null";
}
if (obj.constructor === ([]).constructor) {
return "array";
}
if (obj.constructor === (new Date()).constructor) {
return "date";
}
if (obj.constructor === (new RegExp()).constructor) {
return "regex";
}
return "object";
}
return typeof(obj);
},
notify : function (listener, event) {
if (listener !== undefined) {
listener(event);
}
}
};
function Templates(params, nestedItem) {
this.params = params;
this.defaultTemplate = null;
this.namedTemplates = {};
this.container = null;
this.nestedItem = nestedItem !== undefined ? nestedItem : null;
this.var_brace_left = '\\{\\{';
this.var_brace_right = '\\}\\}';
this.tag_brace_left = '\\{%';
this.tag_brace_right = '%\\}';
if (typeof params !== 'undefined') {
for (var prop in params) {
if (prop === 'var_braces') {
this.var_brace_left = params[prop].substring(0, params[prop].length / 2);
this.var_brace_right = params[prop].substring(params[prop].length / 2);
} else if (prop === 'tag_braces') {
this.tag_brace_left = params[prop].substring(0, params[prop].length / 2);
this.tag_brace_right = params[prop].substring(params[prop].length / 2);
} else if (typeof this[prop] !== 'undefined') {
this[prop] = params[prop];
}
}
}
return this;
}
Templates.prototype = {
parse: function (container) {
this.container = container;
var children = container.getElementsByTagName('*');
for (var i = 0; i < children.length; i++) {
if (children[i].getAttribute !== undefined && children[i].getAttribute('data-template') !== null && (this.nestedItem === children[i].getAttribute('data-template') || children[i].getAttribute('data-template') === '' || children[i].getAttribute('data-template') === 'data-template' && !utils.isNested(children[i]))) {
this.createTemplate(children[i]);
} else if (children[i].getAttribute !== undefined && children[i].getAttribute('data-template-fallback') !== null) {
// Hiding the fallback template
children[i].style.display = 'none';
}
}
// If there is no default template (data-template) then create one from container
if (this.defaultTemplate === null) {
// Creating a template inside the container
var el = document.createElement('div');
el.setAttribute('data-template', '');
el.innerHTML = this.container.innerHTML;
// Clearing container before adding the wrapped contents
this.container.innerHTML = '';
// There is now a default template present with a data-template attribute
this.container.appendChild(el);
this.createTemplate(el);
}
utils.clearContainer(this.container);
},
createTemplate : function (node) {
var element = node.cloneNode(true);
// Clear display: none;
if (element.style.removeAttribute) {
element.style.removeAttribute('display');
}
else {
element.style.removeProperty('display');
}
// Remapping container element in case template
// is deep in container
this.container = node.parentNode;
// Element is a template
var nonDefault = false;
for (var a = 0; a < element.attributes.length; a++) {
var attr = element.attributes[a];
// If attribute
if (utils.startsWith(attr.name, 'data-if-')) {
var val;
if (attr.value === '') {
val = true;
} else {
val = '\'' + attr.value + '\'';
}
this.namedTemplates[attr.name.substring(8, attr.name.length) + '==' + val] = element;
element.removeAttribute(attr.name);
nonDefault = true;
}
}
// Setting as default template, last one wins
if (!nonDefault) {
this.defaultTemplate = element;
}
},
templateFor: function (i) {
for (var templateName in this.namedTemplates) {
if (eval('i.' + templateName)) {
return this.namedTemplates[templateName].cloneNode(true);
}
}
if (this.defaultTemplate) {
return this.defaultTemplate.cloneNode(true);
}
}
};
/*!
* Renderer for populating containers with data using templates.
*/
function Renderer(templates) {
this.templates = templates;
this.listener = undefined;
this.started = false;
this.varRegex = new RegExp(this.templates.var_brace_left + '[ ]?([A-Za-z0-9$\\._\\[\\]]*?)([ ]?\\|[ ]?.*?)?[ ]?' + this.templates.var_brace_right, 'g');
this.tagRegex = new RegExp(this.templates.tag_brace_left + '[ ]?([\\s\\S]*?)( [\\s\\S]*?)?[ ]?' + this.templates.tag_brace_right + '(([\\s\\S]*?)(?=' + this.templates.tag_brace_left + '[ ]?end\\1[ ]?' + this.templates.tag_brace_right + '))?', 'g');
return this;
}
Renderer.prototype = {
notify : function (listener) {
this.listener = listener;
return this;
},
_replaceVariables : function (renderer, _tempo, i, str) {
return str.replace(this.varRegex, function (match, variable, args) {
try {
var val = null;
// Handling tempo_info variable
if (utils.startsWith(variable, '_tempo.')) {
return eval(variable);
}
if (utils.typeOf(i) === 'array') {
val = eval('i' + variable);
} else {
val = eval('i.' + variable);
}
// Handle filters
var filterSplitter = new RegExp('\\|[ ]?(?=' + utils.memberRegex(renderer.filters) + ')', 'g');
if (args !== undefined && args !== '') {
var filters = utils.trim(utils.trim(args).substring(1)).split(filterSplitter);
for (var p = 0; p < filters.length; p++) {
var filter = utils.trim(filters[p]);
var filter_args = [];
// If there is a space, there must be arguments
if (filter.indexOf(' ') > -1) {
var f = filter.substring(filter.indexOf(' ')).replace(/^[ ']*|[ ']*$/g, '');
filter_args = f.split(/(?:[\'"])[ ]?,[ ]?(?:[\'"])/);
filter = filter.substring(0, filter.indexOf(' '));
}
val = renderer.filters[filter](val, filter_args);
}
}
if (val !== undefined) {
return val;
}
} catch (err) {
}
return '';
});
},
_replaceObjects : function (renderer, _tempo, i, str) {
var regex = new RegExp('(?:__[\\.]?)((_tempo|\\[|' + utils.memberRegex(i) + ')([A-Za-z0-9$\\._\\[\\]]+)?)', 'g');
return str.replace(regex, function (match, variable, args) {
try {
var val = null;
// Handling tempo_info variable
if (utils.startsWith(variable, '_tempo.')) {
return eval(variable);
}
if (utils.typeOf(i) === 'array') {
val = eval('i' + variable);
} else {
val = eval('i.' + variable);
}
if (val !== undefined) {
if (utils.typeOf(val) === 'string') {
return '\'' + val + '\'';
} else {
return val;
}
}
} catch (err) {
}
return undefined;
});
},
_applyAttributeSetters : function (renderer, item, str) {
return str.replace(/([A-z0-9]+?)(?==).*?data-\1="(.*?)"/g, function (match, attr, data_value) {
if (data_value !== '') {
return attr + '="' + data_value + '"';
}
return match;
});
},
_applyTags : function (renderer, item, str) {
return str.replace(this.tagRegex, function (match, tag, args, body) {
if (renderer.tags.hasOwnProperty(tag)) {
args = args.substring(args.indexOf(' ')).replace(/^[ ]*|[ ]*$/g, '');
var filter_args = args.split(/(?:['"])[ ]?,[ ]?(?:['"])/);
return renderer.tags[tag](renderer, item, match, filter_args, body);
} else {
return '';
}
});
},
starting : function () {
// Use this to manually fire the RENDER_STARTING event e.g. just before you issue an AJAX request
// Useful if you're not calling prepare immediately before render
this.started = true;
utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_STARTING, undefined, undefined));
return this;
},
renderItem : function (renderer, tempo_info, i, fragment) {
var template = renderer.templates.templateFor(i);
if (template && i) {
utils.notify(this.listener, new TempoEvent(TempoEvent.Types.ITEM_RENDER_STARTING, i, template));
var nestedDeclaration = template.innerHTML.match(/data-template="(.*?)"/g);
if (nestedDeclaration) {
for (var p = 0; p < nestedDeclaration.length; p++) {
var nested = nestedDeclaration[p].match(/"(.*?)"/)[1];
var t = new Templates(renderer.templates.params, nested);
t.parse(template);
var r = new Renderer(t);
r.render(eval('i.' + nested));
}
}
// Dealing with HTML as a String from now on (to be reviewed)
// Attribute values are escaped in FireFox so making sure there are no escaped tags
var html = template.innerHTML.replace(/%7B%7B/g, '{{').replace(/%7D%7D/g, '}}');
// Tags
html = this._applyTags(this, i, html);
// Content
html = this._replaceVariables(this, tempo_info, i, html);
// JavaScript objects
html = this._replaceObjects(this, tempo_info, i, html);
// Template class attribute
if (template.getAttribute('class')) {
template.className = this._replaceVariables(this, tempo_info, i, template.className);
}
// Template id
if (template.getAttribute('id')) {
template.id = this._replaceVariables(this, tempo_info, i, template.id);
}
html = this._applyAttributeSetters(this, i, html);
fragment.appendChild(utils.getElement(template, html));
utils.notify(this.listener, new TempoEvent(TempoEvent.Types.ITEM_RENDER_COMPLETE, i, template));
}
},
_createFragment : function (data) {
if (data) {
var tempo_info = {};
var fragment = document.createDocumentFragment();
// If object then wrapping in an array
if (utils.typeOf(data) === 'object') {
data = [data];
}
for (var i = 0; i < data.length; i++) {
tempo_info.index = i;
this.renderItem(this, tempo_info, data[i], fragment);
}
return fragment;
}
return null;
},
render : function (data) {
// Check if starting event was manually fired
if (!this.started) {
utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_STARTING, undefined, undefined));
}
this.clear();
this.append(data);
return this;
},
append : function (data) {
// Check if starting event was manually fired
if (!this.started) {
utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_STARTING, undefined, undefined));
}
var fragment = this._createFragment(data);
if (fragment !== null) {
this.templates.container.appendChild(fragment);
}
utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_COMPLETE, undefined, undefined));
return this;
},
prepend : function (data) {
// Check if starting event was manually fired
if (!this.started) {
utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_STARTING, undefined, undefined));
}
var fragment = this._createFragment(data);
if (fragment !== null) {
this.templates.container.insertBefore(fragment, this.templates.container.firstChild);
}
utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_COMPLETE, undefined, undefined));
return this;
},
clear : function (data) {
utils.clearContainer(this.templates.container);
},
tags : {
'if' : function (renderer, i, match, args, body) {
var member_regex = utils.memberRegex(i);
var expr = args[0].replace(/&/g, '&');
expr = expr.replace(new RegExp(member_regex, 'gi'), function (match) {
return 'i.' + match;
});
var blockRegex = new RegExp(renderer.templates.tag_brace_left + '[ ]?else[ ]?' + renderer.templates.tag_brace_right, 'g');
var blocks = body.split(blockRegex);
if (eval(expr)) {
return blocks[0];
} else if (blocks.length > 1) {
return blocks[1];
}
return '';
}
},
filters : {
'truncate' : function (value, args) {
if (value !== undefined) {
var len = 0;
var rep = '...';
if (args.length > 0) {
len = parseInt(args[0]);
}
if (args.length > 1) {
rep = args[1];
}
if (value.length > len - 3) {
return value.substr(0, len - 3) + rep;
}
return value;
}
},
'format' : function (value, args) {
if (value !== undefined) {
var x = (value + '').split('.');
var x1 = x[0];
var x2 = x.length > 1 ? '.' + x[1] : '';
while (NUMBER_FORMAT_REGEX.test(x1)) {
x1 = x1.replace(NUMBER_FORMAT_REGEX, '$1' + ',' + '$2');
}
return x1 + x2;
}
},
'upper' : function (value, args) {
return value.toUpperCase();
},
'lower' : function (value, args) {
return value.toLowerCase();
},
'trim' : function (value, args) {
return utils.trim(value);
},
'replace' : function (value, args) {
if (value !== undefined && args.length === 2) {
return value.replace(new RegExp(args[0], 'g'), args[1]);
}
return value;
},
'append' : function (value, args) {
if (value !== undefined && args.length === 1) {
return value + '' + args[0];
}
return value;
},
'prepend' : function (value, args) {
if (value !== undefined && args.length === 1) {
return args[0] + '' + value;
}
return value;
},
'default' : function (value, args) {
if (value !== undefined && value !== null) {
return value;
}
if (args.length === 1) {
return args[0];
}
return value;
},
'date' : function (value, args) {
if (value !== undefined && args.length === 1) {
var date = new Date(value);
var format = args[0];
if (format === 'localedate') {
return date.toLocaleDateString();
} else if (format === 'localetime') {
return date.toLocaleTimeString();
} else if (format === 'date') {
return date.toDateString();
} else if (format === 'time') {
return date.toTimeString();
} else {
var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var DATE_PATTERNS = {
'YYYY' : function (date) {
return date.getFullYear();
},
'YY' : function (date) {
return date.getFullYear().toFixed().substring(2);
},
'MMMM' : function (date) {
return MONTHS[date.getMonth()];
},
'MMM' : function (date) {
return MONTHS[date.getMonth()].substring(0, 3);
},
'MM' : function (date) {
return utils.pad((date.getMonth() + 1).toFixed(), '0', 2);
},
'M' : function (date) {
return date.getMonth() + 1;
},
'DD' : function (date) {
return utils.pad(date.getDate().toFixed(), '0', 2);
},
'D' : function (date) {
return date.getDate();
},
'EEEE' : function (date) {
return DAYS[date.getDay()];
},
'EEE' : function (date) {
return DAYS[date.getDay()].substring(0, 3);
},
'E' : function (date) {
return date.getDay();
},
'HH' : function (date) {
return utils.pad(date.getHours().toFixed(), '0', 2);
},
'H' : function (date) {
return date.getHours();
},
'mm' : function (date) {
return utils.pad(date.getMinutes().toFixed(), '0', 2);
},
'm' : function (date) {
return date.getMinutes();
},
'ss' : function (date) {
return utils.pad(date.getSeconds().toFixed(), '0', 2);
},
's' : function (date) {
return date.getSeconds();
},
'SSS' : function (date) {
return utils.pad(date.getMilliseconds().toFixed(), '0', 3);
},
'S' : function (date) {
return date.getMilliseconds();
},
'a' : function (date) {
return date.getHours() < 12 ? 'AM' : 'PM';
}
};
format = format.replace(/(\\)?(Y{2,4}|M{1,4}|D{1,2}|E{1,4}|H{1,2}|m{1,2}|s{1,2}|S{1,3}|a)/g, function (match, escape, pattern) {
if (!escape) {
if (DATE_PATTERNS.hasOwnProperty(pattern)) {
return DATE_PATTERNS[pattern](date);
}
}
return pattern;
});
return format;
}
}
return '';
}
}
};
/*!
* Prepare a container for rendering, gathering templates and
* clearing afterwards.
*/
tempo.prepare = function (container, params) {
if (typeof container === 'string') {
container = document.getElementById(container);
}
var templates = new Templates(params);
templates.parse(container);
return new Renderer(templates);
};
tempo.test = {
'utils' : utils,
'templates': new Templates({}),
'renderer' : new Renderer(new Templates({}))
};
return tempo;
})(Tempo || {});