JSON Template Engine //JavaScript Repository
Description
Simple, extensible and efficient template engine. It's able to parse indented template tags and build its respective structure using JSON. The way it was implemented allows a better organization and turns the filling of the template easier.
Created: 2006.02.04
Created: 2006.02.04
Code (Download)
//+ Carlos R. L. Rodrigues
//@ http://jsfromhell.com/classes/template [rev. #2]
Template = function(s, oo, oc, co, cc){
var o = this; o.oo = oo || "<js:", o.oc = oc || ">", o.co = co || "</js:", o.cc = cc || ">";
if(o.oo == o.co || o.oo == o.oc || o.oo == o.cc || o.co == o.oc || o.co == o.cc) throw Error("Tag definition not allowed");
o.tags = [], o.root = o.node("_root", s, null), o.parse(s);
}
Template.prototype.node = function(n, v, p){
var _ = this;
return {
_default: v, _value: [v], _parent: p, _children: [], _name: n,
_render: function(_o){
var o = this._children, s = (v = this._value)[v.length - 1], p = [], i = -1,
t = _.oo, c = _.co, tc = _.oc, cc = _.cc, y, v, a, f, j, to;
while((y = o[++i]) && (v = s.slice(0, j = s.indexOf(to = t + (a = y._name) + tc)), f = s.indexOf(a = c + a + cc, j))){
while((j = s.indexOf(to, j + 1)) + 1 && j < f) f = s.indexOf(a, f + 1);
if(f + 1 && (s = v + y._value.join("") + s.slice(a.length + f)), y._children[0]) p.push(i, o), o = y._children, i = -1;
else if(!o[i + 1]) while(p.length && !(o = p.pop())[(i = p.pop()) + 1]);
}
return _o ? s : (i = (v = this._value).length, v[i] = v[--i], v[i] = s);
},
_set: function(s){return this._value[this._value.length - 1] = s;},
_get: function(){
var r = /([(){}|*+?.,^$\[\]\\])/g, f = function(s){return s.replace(r, "\\\$1")};
return this._value.join("").replace(new RegExp((f(_.oo) + ".*?" + f(_.oc) + "|" + f(_.co) + ".*?" + f(_.cc)), "gm"), "");
},
_reset: function(c){
if(c)
for(var a, o = this, t = _.tags, i = 0, l = t.length; i < l; i++)
((a = t[i]._parent) == o || a == this) && ((o = t[i])._value = [o._default]);
return this._value = [this._default];
},
_output: function(){return this._render(1);}
};
}
Template.prototype.parse = function(s){
var _ = this, p = 0, r = _.root, l = [[-1, s.length, r]], y = _.tags, i = _.oo,
e = _.co, $ = _.oc, d = _.cc, h = $.length, g, a, f, j, c, t, v;
while((p = s.indexOf(i, p)) > -1){
if(p == s.indexOf(e, p) && ++p) continue;
a = (a = p + i.length) + (t = s.slice(a, j = s.indexOf($, p))).length + h, f = s.indexOf(g = e + t + d, p);
while((j = s.indexOf(i + t + d, j + 1)) + 1 && j < f) f = s.indexOf(g, f + 1);
if(t.charAt() == "_") throw Error("Tag name not allowed [" + t + "]");
if(f < 0) throw Error("End of tag \"" + i + t + $ + "\" expected");
for(v = s.slice(a, f), j = l.length; j--;)
if((c = l[j])[2][t] && p > c[0] && f < c[1]) throw Error("Ambiguous tag name \"" + t + "\"");
else if(p > c[0] && f < c[1] && l.push([p++, f, (v = c[2][t] = _.node(t, v, c[2]))]) &&
(!(a = c[2]._parent) ? c[2] : a[c[2]._name])._children.push(v) && y.push(v)) break;
}
}
Example (Example)
<script type="text/javascript">
//<![CDATA[
s =
"<html>\n" +
"<head>[header]\n" +
'[js]<script src="[src][/src]" type="text/javascript"><\/script>\n[/js]' +
"[/header]</head>\n" +
"<body>\n" +
"[content]Default content[/content]\n" +
"</body>\n" +
"</html>";
var t = new Template(s, "[", "]", "[/", "]");
var js = t.root.header.js, content = t.root.content;
js.src._set("file.js");
js._render();
js.src._set("file2.js");
content._set("I Changed the content");
alert(t.root._output());
content._reset();
alert(t.root._output());
js._reset(true);
alert(t.root._output());
js._set("");
content._set("");
alert(t.root._output());
//]]>
</script>
Help
How it works
This class is similar to a xml parser, it searches for template tags on the text and make them easily to access through the class, adding special properties and methods to each node.
By default is recognized as a template tag the tags that follow this pattern:
<js:TagName>any content</js:TagName>
.
But this is configurable, example: [TagName]content[/TagName]
.
Template example:
<js:a>
<js:b>content</js:b>
</js:a>
After loading the template above, you could access the text "content" by using templateInstance.root.a.b._get()
There are two restrictions on the tag naming:
-
The tag name can't start with "_".
Wrong:[_myTag][/_myTag]
-
There must not be tags with the same name in the same indentation level.
Wrong:[a][/a][a][/a]
.
Correct:[a][/a][b][/b]
.
Wrong:[a][b][/b][b][/b][/a]
.
Correct:[a][b][/b][c][/c][/a]
.
Correct:[a][b][/b][a][b][/b]
(the second [b] is in another indentation level).
To get more details of how it works, look at the example.
Constructor
- Template(content: String, [beginOpen: String = "<js:"], [beginClose: String = ">"], [endOpen: String = "</js:"], [endClose: String = ">"])
-
Generates an instance of Template.
Restrictions:- beginOpen can't be equal to endOpen
- beginOpen can't be equal to beginClose
- beginOpen can't be equal to endClose
- endOpen can't be equal to beginClose
- endOpen can't be equal to endClose
- content
- text to be parsed by the class
- beginOpen
- defines the opening tag prefix
- beginClose
- defines the opening tag suffix
- endOpen
- defines the closing tag prefix
- endClose
- defines the closing tag suffix
Properties
- Template.root: Node
- It's the root node, must be used to obtain access to the other nodes through "JSON".
- Template.tags: Array
- Array containing references to all the nodes.
Node Methods
- Node._get(void): String
- Returns the node value. If there are template tags inside this text, they will be removed, keeping just the default text inside them.
- Node._set(value: String): String
-
Sets a new value to the node and, returns the own setted text. If the node have children, they will be ignored (but not really removed) on the output.
- value
- text to be setted
- Node._reset([deep: Boolean = false]): void
-
Undo all the modifications done on the node by the methods "_render" or "_set".
- deep
- if true, it will also reset the child nodes
- Node._render(void): String
-
The function returns the current node value and implements a kind of "content stack".
The current node value is pushed (you have access to it through the "_value" property), and the node starts to work on a copy of the value.
Obs: The unqueueing will occur automatically on the output. - Node._output(void): String
- Returns the text of the node and its child nodes. It's similar to "_get", but differently from "_get", that just returns the node value, it also gets the text of the child nodes.
Node Properties
- Node._default: String
- Keeps the initial node value.
- Node._parent: Node
- Reference to the parent node.
- Node._name: String
- Tag name.
- Node._children: Array
- Array containing the child nodes.
- Node._value: Array
- Keep the node text in a kind of queue structure.
Rank (Votes: 44)
3.00