\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);

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}