var CompiledTemplate = Class.create(); Object.extend(CompiledTemplate, { tagSplitter: /(\{(?:\$|\/?[a-z]{2,16}).*?\})/, tagMatcher: /^\{(\$|\/?[a-z]{2,16})(.*?)\}$/, varToken: '$' }); CompiledTemplate.prototype = { initialize: function(template, options) { if (template) { this.compile(template.toString(), options || {}); } }, compile: function(template, options) { this.assignments = {}; options = options || {}; var tagSplitter = options.tagSplitter || CompiledTemplate.tagSplitter; var tagMatcher = options.tagMatcher || CompiledTemplate.tagMatcher; this.varToken = options.varToken || CompiledTemplate.varToken; this.varTokenGlobal = new RegExp('\\' + this.varToken, 'g'); this.compiled = ''; var spaceAt, match; if (Prototype.Browser.IE || Prototype.Browser.WebKit) { var stack = [], match; while (template.length > 0) { if (match = template.match(tagSplitter)) { stack.push(template.slice(0, match.index)); stack.push(template.slice(match.index, match.index + match[0].length)); template = template.slice(match.index + match[0].length); } else { stack.push(template); template = ''; } } } else { var stack = template.split(tagSplitter); } for (var i = 0, length = stack.length; i < length; ++i) { match = tagMatcher.exec(stack[i]); if (match) { if (match[1] == this.varToken) { this.compiled += this.handlers._variable(match[2], this); } else { if (this.handlers[match[1]]) { this.compiled += this.handlers[match[1]](match[2].substring(1), this); } else { throw new Exception('CompiledTemplate Exception: tag name "' + match[1] + '" not recognized by tag handler'); } } } else if (stack[i] != '') { this.compiled += this.handlers._content(stack[i], this); } } }, handlers: { '_variable': function(name) { return "output += object." + name + "===undefined ? '' : object." + name + ";\n"; }, '_content': function(content) { return "output += '" + content.replace(/\n/g, " ").replace(/\r/g, " ").replace(/\\/g, "\\\\").replace(/\'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n") + "';\n"; }, 'if': function(expression, instance) { return 'if (' + expression.replace(instance.varTokenGlobal, 'object.') + ') {\n'; }, 'elseif': function(expression, instance) { return '} else if (' + expression.replace(instance.varTokenGlobal, 'object.') + ') {\n'; }, 'else': function() { return '} else {\n'; }, '/if': function() { return '}\n'; }, 'foreach': function(options, instance) { var tokenSize = instance.varToken.length; var compiled = ''; var type = options.split(' in '); if (type[1]) { var alias = type[1].split(' as '); compiled += 'for (var property in object.' + alias[0].substring(tokenSize) + ') {\n'; compiled += 'object.' + alias[0].substring(tokenSize) + ' = property;\n'; if (alias[1]) { compiled += 'object.' + alias[1].substring(tokenSize) + ' = object.' + type[0].substring(tokenSize) + '[property];\n'; } } else { var alias = type[0].split(' as '); compiled += 'for (var i=0, len=(object.' + alias[0].substring(tokenSize) + ' ? object.' + alias[0].substring(tokenSize) + '.length : 0); i '); if (pair[1]) { compiled += 'object.' + pair.shift().substring(tokenSize) + ' = i;\n'; } compiled += 'object.' + pair[0].substring(tokenSize) + ' = object.' + alias[0].substring(1) + '[i];\n'; } } return compiled; }, '/foreach': function() { return '}\n'; } }, assign: function(name, value) { this.assignments[name] = value; return this; }, unassign: function(name) { if (this.assignments[name] !== undefined) { delete this.assignments[name]; } return this; }, unassignAll: function() { this.assignments = {}; return this; }, setHandler: function(name, callback) { this.handlers[name] = callback; return this; }, evaluate: function(object) { object = Object.extend(object || {}, this.assignments); output = ''; eval(this.compiled); return output; } }; // usage var tpl = "{$name}{$phone}\n"; var td = new CompiledTemplate(tpl); var people = [ {name: 'John Doe', phone: '555-555-5555'}, {name: 'James Smith', phone: '555-555-6666'}, {name: 'William Johnson', phone: '555-555-7777'} ]; var html = people.inject('', function(memo, entry) { return memo += td.evaluate(entry); }); $('mytable').update(html);