'use strict'; /*global require, module*/ (function(module) { var templates = { cache: {} }, helpers = {}, globals = {}, loader, originalObj; templates.parse = function(template, obj, bind, callback) { if (typeof bind === 'function') { callback = bind; bind = false; } obj = registerGlobals(obj || {}); bind = bind ? Math.random() : false; template = template.toString() || ''; if (bind) { obj.__template = template; } if (loader && callback) { if (!templates.cache[template]) { loader(template, function(loaded) { if (loaded) { templates.cache[template] = loaded; } callback(parse(loaded, obj, bind)); }); } else { callback(parse(templates.cache[template], obj, bind)); } } else { return parse(template, obj, bind); } }; templates.registerHelper = function(name, func) { helpers[name] = func; }; templates.registerLoader = function(func) { loader = func; }; templates.setGlobal = function(key, value) { globals[key] = value; }; templates.getBlock = function(template, block) { return template.replace(new RegExp('[\\s\\S]*([\r\n]*[\\s\\S]*?[\r\n]*)[\\s\\S]*', 'g'), '$1'); }; function express(filename, options, fn) { var fs = require('fs'), tpl = filename.replace(options.settings.views + '/', ''); if (!templates.cache[tpl]) { fs.readFile(filename, function(err, html) { templates.cache[tpl] = (html || '').toString(); return fn(err, templates.parse(templates.cache[tpl], options)); }); } else { return fn(null, templates.parse(templates.cache[tpl], options)); } } function replace(template, key, value) { var searchRegex = new RegExp('{' + key + '}', 'g'); return template.replace(searchRegex, value); } function makeRegex(block) { return new RegExp('[\\s\\S]*?'); } function makeBlockRegex(block) { return new RegExp('([\\n]?[\\n]?)|([\\n]?[\\n]?)', 'g'); } function makeConditionalRegex(block) { return new RegExp('([\\s\\S]*?)', 'g'); } function makeStatementRegex(key) { return new RegExp('([\\s]*)|([\\s]*)', 'gi'); } function registerGlobals(obj) { for (var g in globals) { if (globals.hasOwnProperty(g)) { obj[g] = obj[g] || globals[g]; } } return obj; } function checkConditional(template, key, value) { var matches = template.match(makeConditionalRegex(key)); if (matches !== null) { for (var i = 0, ii = matches.length; i < ii; i++) { var statement = makeStatementRegex(key), nestedConditionals = matches[i].match(/[\s|\S][\s|\S]/), match = matches[i].replace(statement, '').replace(/[\s|\S][\s|\S]/gi, ''), conditionalBlock = match.split(/\s*\s*/); if (conditionalBlock[1]) { // there is an else statement if (!value) { template = template.replace(matches[i], conditionalBlock[1].replace(/(^[\r\n\t]*)|([\r\n\t]*$)/gi, '')); } else { template = template.replace(matches[i], conditionalBlock[0].replace(/(^[\r\n\t]*)|([\r\n\t]*$)/gi, '')); } } else { // regular if statement if (!value) { template = template.replace(matches[i], ''); } else { template = template.replace(matches[i], match.replace(/(^[\r\n\t]*)|([\r\n\t]*$)/gi, '')); } } if (nestedConditionals) { for (var x = 0, xx = nestedConditionals.length; x < xx; x++) { template = template.replace('', nestedConditionals[x]); } } } } return template; } function checkConditionalHelper(template, obj) { var func = /IF function.([\S]*)/gi.exec(template); if (func && helpers[func[1]]) { template = checkConditional(template, 'function.' + func[1], helpers[func[1]](obj)); } return template; } function callMethod(method, parameters) { return method.apply(templates, [parameters.data, parameters.iterator, parameters.numblocks]); } function parseFunctions(block, result, parameters) { var functions = block.match(/{function.*?}/gi, ''); for (var fn in functions) { if (functions.hasOwnProperty(fn)) { var func = functions[fn], method = functions[fn].split('.').pop().split('}').shift(); if (helpers[method]) { result = result.replace(new RegExp(func, 'gi'), callMethod(helpers[method], parameters)); } } } return result; } function parseArray(template, array, key, namespace, bind) { template = checkConditional(template, namespace + 'length', array[key].length); template = checkConditional(template, '!' + namespace + 'length', !array[key].length); var regex = makeRegex(key), block; if (!array[key].length) { return template; } while (block = template.match(regex)) { block = block[0].replace(makeBlockRegex(key), ''); var numblocks = array[key].length - 1, iterator = 0, result = '', parsedBlock; do { parsedBlock = parse(block, array[key][iterator], bind, namespace, {iterator: iterator, total: numblocks}) + ((iterator < numblocks) ? '\r\n':''); result += (!bind) ? parsedBlock : setBindContainer(parsedBlock, bind + namespace + iterator); result = parseFunctions(block, result, { data: array[key][iterator], iterator: iterator, numblocks: numblocks }); result = checkConditional(result, '@first', iterator === 0); result = checkConditional(result, '!@first', iterator !== 0); result = checkConditional(result, '@last', iterator === numblocks); result = checkConditional(result, '!@last', iterator !== numblocks); if (bind) { array[key][iterator].__template = block; } } while (iterator++ < numblocks); template = template.replace(regex, result); } return template; } function setBindContainer(block, namespace) { return namespace ? '' + block + '' : block; } function parseValue(template, key, value) { value = typeof value === 'string' ? value.replace(/^\s+|\s+$/g, '') : value; template = checkConditional(template, key, value); template = checkConditional(template, '!' + key, !value); return replace(template, key, value); } function setupBindings(parameters) { var obj = parameters.obj, key = parameters.key, bind = parameters.bind, value = obj[key]; obj.__namespace = parameters.namespace; obj.__iterator = parameters.blockInfo ? parameters.blockInfo.iterator : false; Object.defineProperty(obj, key, { get: function() { return this['__' + key]; }, set: function(value) { this['__' + key] = value; var els = document.querySelectorAll('[data-binding="' + (this.__iterator !== false ? (bind + this.__namespace + this.__iterator) : bind) + '"]'); for (var el in els) { if (els.hasOwnProperty(el)) { if (this.__parent) { var parent = this.__parent(); els[el].innerHTML = parse(parent.template, parent.data, false); } else { els[el].innerHTML = parse(this.__template, obj, false, this.__namespace); } } } } }); obj[key] = value; } function defineParent(obj, parent) { obj.__parent = function() { return { data: parent, template: parent.__template }; }; } function parse(template, obj, bind, namespace, blockInfo) { namespace = namespace || ''; originalObj = originalObj || obj; template = checkConditionalHelper(template, obj); for (var key in obj) { if (obj.hasOwnProperty(key)) { if (typeof obj[key] === 'undefined' || typeof obj[key] === 'function') { continue; } else if (obj[key] === null) { template = replace(template, namespace + key, ''); } else if (obj[key].constructor === Array) { template = parseArray(template, obj, key, namespace + key + '.', bind); } else if (obj[key] instanceof Object) { defineParent(obj[key], originalObj); template = parse(template, obj[key], bind, namespace + key + '.'); } else { template = parseValue(template, namespace + key, obj[key]); if (bind && obj[key]) { setupBindings({ obj: obj, key: key, namespace: namespace, blockInfo: blockInfo, bind: bind }); } } } } if (namespace) { template = template.replace(new RegExp('{' + namespace + '\\.[\\s\\S]*?}', 'g'), ''); namespace = ''; } else { // clean up all undefined conditionals template = template.replace(/\s*\s*/gi, 'ENDIF -->\r\n') .replace(/\s*/gi, '') .replace(/\s*/gi, '') .replace(/\s*\s*/gi, ''); template = setBindContainer(template, bind); } return template; } module.exports = templates; module.exports.__express = express; if ('undefined' !== typeof window) { window.templates = module.exports; } })('undefined' === typeof module ? { module: { exports: {} } } : module);