` Code Snippets - Tinplate

Tiny Template Engine (Tinplate)

This javascript code snippet defines a very simple client-side template processing engine.

The equivalent functionality is also available as a python script.

The python version can be used to apply the template on the server. The javascript version can be used to create or modify an html document containing markup that is constructed dynamically from the template when the page is loaded.

Consider a simple tinplate-powered example page.

tinplate-example.html
<html>
<head>
	<script src="tinplate.js" type="text/JavaScript"></script>
	<script src="tinplate-example.js" type="text/JavaScript"></script>
</head>
<body onload="addTable('example_table');">
<div id="example_table">
</div>
</body>
</html>

In this example, the following javascript code calls tinplate to construct the table from some data and a template.

tinplate-example.js
/* tinplate-example.js - example program creating an HTML table using tinplate
   Copyright (c) 2008 Niall McCarroll  
   Distributed under the MIT/X11 License (http://www.mccarroll.net/snippets/license.txt)
*/

data = ["sample table",['a','b','c'],[[10,11,12],[13,14,15],[16,17,18]]];

format = [
    "<h2>$</h2>\n",
	"<table>\n",
    "<tr>\n",
    { 'foreach': [ '<th>$</th>' ] },
    "\n</tr>\n",
	{ 'foreach': [	
		"<tr>\n",
		{ 'foreach': ["<td>",
				"$",
				"</td>\n"] },
		"</tr>\n"
	]},
	"</table>\n",
];

function addTable(id) {
    genhtml = new tinplate().process(format,data);
    document.getElementById(id).innerHTML = genhtml;
}
		

In the tinplate process function, the arguments passed are:

The format and data lists have to match up.

The listings for the javascript and python implementations of tinplate are given below:

tinplate.js
/* tinplate.js - tiny templates for javascript
   Copyright (c) 2008 Niall McCarroll  
   Distributed under the MIT/X11 License (http://www.mccarroll.net/snippets/license.txt)
 
   functionality: 
       apply a template to data to generate output text
       can supply an optional stringizer function to convert data values to string
  
    usage:
        
        var t = new tinplate();
        var s = t.process(format,data[,stringizer]);
*/

function tinplate() {
	this.matched = 0;
}

tinplate.prototype.format = function(fmt,val,stringizer) {
	var pos = fmt.indexOf("$");
	if (pos == -1) { return fmt; }
	if (fmt.charAt(pos+1) == "$") { 
		return fmt.slice(0,pos+1) + this.format(fmt.slice(pos+2),val,stringizer); 
	}
	if (fmt.charAt(pos+1) == "(") { 
		var rp = fmt.slice(pos+1).indexOf(")");
		if (rp != -1) {
			var name = fmt.slice(pos+2,pos+1+rp);
			this.matched += 1;
			var value = '';
			if (name in val) {
				value = stringizer(val[name]);
            }
			return fmt.slice(0,pos)+value+this.format(fmt.slice(pos+2+rp),val,stringizer);
		}
	}
	this.matched += 1;
	return fmt.slice(0,pos)+stringizer(val)+this.format(fmt.slice(pos+1),val,stringizer);
}


tinplate.prototype.process = function(format,data,stringizer) {
	if (stringizer == undefined) { stringizer = String; }
	if (typeof(format)=="function") return format(data);
	if (typeof(format)=="string") return this.format(format,data,stringizer);
	if (format.foreach) {
		this.matched += 1
		var f,d,txt='';
		for(d in data) {
			for(f in format.foreach) {
				txt += this.process(format.foreach[f],data[d],stringizer);
			}
		}
		return txt;
	} else {
		var d = 0;
		var txt = '';
		for(f in format) {
			var m1 = this.matched;
			txt += this.process(format[f],data[d],stringizer);
			if (this.matched > m1) {
				d += 1;
			}
		}
		return txt;
	}
}
tinplate.py
# tinplate.py - tiny templates for python
# Copyright (c) 2008 Niall McCarroll  
# Distributed under the MIT/X11 License (http://www.mccarroll.net/snippets/license.txt)

# functionality:
#   apply a template to data to generate output text
#   can supply an optional stringizer function to convert data values to string
  
# usage:
#   from tinplate import tinplate
#   t = tinplate()
#   print t.process(format,data[,stringizer])

class tinplate:

    def __init__(self):
        self.matched = 0

    def typeof(self,obj):
        if isinstance(obj,str):
            return 'string'
        if isinstance(obj,list):
            return 'list'
        if isinstance(obj,dict):
            return 'dict'
        return 'function'

    def format(self,fmt,val,stringizer):
        pos = fmt.find("$")
        if pos == -1:
            return fmt
        if pos+1 < len(fmt) and fmt[pos+1] == "$": 
            return fmt[:pos+1] + self.format(fmt[pos+2:],val,stringizer)
        if pos+1 < len(fmt) and fmt[pos+1] == "(": 
            rp = fmt[pos+1:].find(")")
            if rp != -1:
                name = fmt[pos+2:pos+1+rp]
                self.matched += 1
                value = ''
                if name in val:
                    value = stringizer(val[name])
                return fmt[0:pos]+value+self.format(fmt[pos+2+rp:],val,stringizer)
        self.matched += 1
        return fmt[0:pos]+stringizer(val)+self.format(fmt[pos+1:],val,stringizer)


    def process(self,format,data,stringizer=str):
        if self.typeof(format)=="function":
            return format(data)
        if self.typeof(format)=="string":
            return self.format(format,data,stringizer)
        if self.typeof(format) == "dict" and 'foreach' in format:
            self.matched += 1
            txt=''
            for d in data:
                for f in format["foreach"]:
                    txt += self.process(f,d,stringizer)
            return txt
        else:
            txt = ''
            d = 0
            for f in format:
                m1 = self.matched
                if d < len(data):
                    txt += self.process(f,data[d],stringizer)
                else:
                    txt += f
                if self.matched > m1:
                    d += 1
            return txt

 

Leave a comment

Anti-Spam Check
Comment