4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to permit
10 | persons to whom the Software is furnished to do so, subject to the
11 | following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included
14 | in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
19 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
22 | USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | [![MIT License][license-image]][license-url]
2 |
3 | # Tangular
4 |
5 | [](https://european-union.europa.eu/)
6 |
7 | > A simple template engine like Angular.js for JavaScript or node.js
8 |
9 | - only __2.0 kB__ minified + gziped
10 | - syntax like __Angular.js__ templates
11 | - supports custom helpers
12 | - supports conditions (+ nested conditions)
13 | - supports loops (+ nested loops)
14 | - __supports two models__
15 | - no dependencies
16 | - IE `>= 9`
17 | - best of use with [www.totaljs.com - web framework for Node.js](http://www.totaljs.com)
18 | - Live example on [JSFiddle / Tangular](http://jsfiddle.net/petersirka/ftfvba65/2/)
19 | - __One of the fastest template engine in the world__
20 |
21 | __YOU MUST SEE:__
22 |
23 | - [jComponent - A component library for jQuery](https://github.com/petersirka/jComponent)
24 | - [jRouting - HTML 5 routing via History API](https://github.com/petersirka/jRouting)
25 | - [jQuery two way bindings](https://github.com/petersirka/jquery.bindings)
26 |
27 |
28 | ## Node.js
29 |
30 | ```bash
31 | npm install tangular
32 | ```
33 |
34 | ```javascript
35 | require('tangular');
36 | // Inits Tangular and registers "Tangular" keyword as a global variable
37 | // console.log(Tangular);
38 | ```
39 |
40 | ## Example
41 |
42 | ```javascript
43 | var output = Tangular.render('Hello {{name}} and {{name | raw}}!', { name: 'world' });
44 | // Hello <b>world</b> and world!
45 | ```
46 |
47 | ## Second model
48 |
49 | - very helpful, you don't have to change the base model
50 | - second model can be used in the template via `$` character, e.g. `{{ $.property_name }}`
51 |
52 | ```javascript
53 | var output = Tangular.render('Hello {{ name }} and {{ $.name }}!', { name: 'MODEL 1' }, { name: 'MODEL 2'});
54 | // Hello MODEL 1 and MODEL 2
55 | ```
56 |
57 |
58 | ## Best performance with pre-compile
59 |
60 | ```javascript
61 | // cache
62 | var template = Tangular.compile('Hello {{name}} and {{name | raw}}!');
63 |
64 | // render
65 | // template(model, [model2])
66 | var output = template({ name: 'Peter' });
67 | ```
68 |
69 | ## Conditions
70 |
71 | - supports `else if`
72 |
73 | ```html
74 | {{if name.length > 0}}
75 | OK
76 | {{else}}
77 | NO
78 | {{fi}}
79 | ```
80 |
81 | ```html
82 | {{if name !== null}}
83 | NOT NULL
84 | {{fi}}
85 | ```
86 |
87 | ## Looping
88 |
89 | ```html
90 | {{foreach m in orders}}
91 | Order num.{{m.number}} (current index: {{$index}})
92 | {{m.name}}
93 | {{end}}
94 | ```
95 |
96 | ## Custom helpers
97 |
98 | ```javascript
99 | Tangular.register('currency', function(value, decimals) {
100 | // this === MODEL/OBJECT
101 | // console.log(this);
102 | // example
103 | return value.format(decimals || 0);
104 | });
105 |
106 | Tangular.register('plus', function(value, count) {
107 | return value + (count || 1);
108 | });
109 |
110 | // Calling custom helper in JavaScript, e.g.:
111 | Tangular.helpers.currency(100, 2);
112 | ```
113 |
114 | ```html
115 | {{ amount | currency }}
116 | {{ amount | currency(2) }}
117 |
118 |
119 | {{ count | plus | plus(2) | plus | plus(3) }}
120 | ```
121 |
122 | ## Built-in helpers
123 |
124 | ```html
125 | {{ name }} = VALUE IS ENCODED BY DEFAULT
126 | {{ name | raw }} = VALUE IS NOT ENCODED
127 | ```
128 |
129 | ## Miracles
130 |
131 | ```javascript
132 | var template = Tangular.compile('Encoded value {{}} and raw value {{ | raw }}.');
133 | console.log(template('Tangular'));
134 | ```
135 |
136 | ## Alias: Tangular is too long as word
137 |
138 | ```javascript
139 | // use alias:
140 | // Ta === Tangular
141 | Ta.compile('');
142 | ```
143 |
144 | ## Contributors
145 |
146 | | Contributor | Type | E-mail |
147 | |-------------|------|--------|
148 | | [Peter Širka](https://www.petersirka.eu) | author | |
149 | | [Константин](https://github.com/bashkos) | contributor |
150 |
151 | [license-image]: http://img.shields.io/badge/license-MIT-blue.svg?style=flat
152 | [license-url]: license.txt
153 |
--------------------------------------------------------------------------------
/Tangular.min.js:
--------------------------------------------------------------------------------
1 | (function(e){if(e.Tangular)return;var t={};var v=t.helpers={};t.version='v5.0.5';t.cache={};e.Ta=e.Tangular=t;e.Thelpers=v;var s={null:true,undefined:true,true:true,false:true,Object:1,String:1,Number:1,Boolean:1,Date:1,Array:1,window:1,global:1,arguments:1,eval:1,Function:1,function:1,var:1,let:1,const:1,delete:1};var l=/\{\{.*?\}\}/g;var u=/\{\{|\}\}/g;var n=/[<>&"]/g;var o=/\n$/g;function w(e,r){var t='';var n=[];var i=0;for(var a=0;a47&&l<58||l>64&&l<91||l>96&&l<123||(l===95||l===36)){t+=e.charAt(a);continue}if(t){if(!s[t]&&n.indexOf(t)===-1&&(!r||r.indexOf(t)===-1))n.push(t);t=''}}if(l===46||l===124){i=l}else if((i===46||i===124)&&l===40){i=0}else if(l===96||l===34||l===39){if(l===i)i=0;else i=l}}if(t&&!s[t]&&n.indexOf(t)===-1&&(!r||r.indexOf(t)===-1))n.push(t);return n}t.toArray=function(e){var r=Object.keys(e);var t=[];for(var n=0,i=r.length;n':return'>';case'"':return'"'}return e})};v.raw=function(e){return e};t.render=function(e,r,t,n){var e=(new i).compile(e);return e(r==null?{}:r,t,n)};t.compile=function(e,r,t){return(new i).compile(e,r,t)};t.register=function(e,r){v[e]=r;return t}})(typeof window==='undefined'?global:window);
--------------------------------------------------------------------------------
/Tangular.js:
--------------------------------------------------------------------------------
1 | (function(W) {
2 |
3 | if (W.Tangular)
4 | return;
5 |
6 | var Tangular = {};
7 | var Thelpers = Tangular.helpers = {};
8 |
9 | Tangular.version = 'v5.0.5';
10 | Tangular.cache = {};
11 |
12 | W.Ta = W.Tangular = Tangular;
13 | W.Thelpers = Thelpers;
14 |
15 | var SKIP = { 'null': true, 'undefined': true, 'true': true, 'false': true, 'Object': 1, 'String': 1, 'Number': 1, 'Boolean': 1, 'Date': 1, 'Array': 1, 'window': 1, 'global': 1, 'arguments': 1, 'eval': 1, 'Function': 1, 'function': 1, 'var': 1, 'let': 1, 'const': 1, 'delete': 1 };
16 | var REG_CMDFIND = /\{\{.*?\}\}/g;
17 | var REG_CMDCLEAN = /\{\{|\}\}/g;
18 | var REG_ENCODE = /[<>&"]/g;
19 | var REG_TRIM = /\n$/g;
20 |
21 | function parseInlineVariables(line, blacklist) {
22 |
23 | var tmp = '';
24 | var variables = [];
25 | var skip = 0;
26 |
27 | for (var i = 0; i < line.length; i++) {
28 |
29 | var c = line.charCodeAt(i);
30 |
31 | if (!skip) {
32 |
33 | if ((tmp && c > 47 && c < 58) || (c > 64 && c < 91) || (c > 96 && c < 123) || (c === 95 || c === 36)) {
34 | tmp += line.charAt(i);
35 | continue;
36 | }
37 |
38 | if (tmp) {
39 | if (!SKIP[tmp] && variables.indexOf(tmp) === -1 && (!blacklist || blacklist.indexOf(tmp) === -1))
40 | variables.push(tmp);
41 | tmp = '';
42 | }
43 | }
44 |
45 | if (c === 46 || c === 124) { // "." or "|"
46 | skip = c;
47 | } else if ((skip === 46 || skip === 124) && c === 40) { // ("." or "|") and "("
48 | skip = 0;
49 | } else if (c === 96 || c === 34 || c === 39) { // "`" or "'" or "\""
50 | if (c === skip)
51 | skip = 0;
52 | else
53 | skip = c;
54 | }
55 | }
56 |
57 | if (tmp && !SKIP[tmp] && variables.indexOf(tmp) === -1 && (!blacklist || blacklist.indexOf(tmp) === -1))
58 | variables.push(tmp);
59 |
60 | return variables;
61 | }
62 |
63 | Tangular.toArray = function(obj) {
64 | var keys = Object.keys(obj);
65 | var arr = [];
66 | for (var i = 0, length = keys.length; i < length; i++)
67 | arr.push({ key: keys[i], value: obj[keys[i]] });
68 | return arr;
69 | };
70 |
71 | function Template() {
72 | this.commands;
73 | this.variables;
74 | this.builder;
75 | this.split = '\0';
76 | }
77 |
78 | Template.prototype.compile = function(template, tagbeg, tagend) {
79 |
80 | var self = this;
81 | var ifcount = 0;
82 | var loopcount = 0;
83 | var loops = [];
84 | var reg_find = REG_CMDFIND;
85 | var reg_clean = REG_CMDCLEAN;
86 | var tmp;
87 |
88 | if (tagbeg && tagend) {
89 | reg_find = new RegExp(tagbeg + '.*?' + tagend, 'g');
90 | reg_clean = new RegExp(tagbeg + '|' + tagend, 'g');
91 | }
92 |
93 | self.template = template;
94 |
95 | template = template.replace(/\|\|/g, '\1');
96 |
97 | self.variables = {};
98 | self.commands = [];
99 |
100 | self.builder = template.replace(reg_find, function(text) {
101 |
102 | var cmd = text.replace(reg_clean, '').trim();
103 | var variable = null;
104 | var helpers = null;
105 | var index;
106 | var isif = false;
107 | var isloop = false;
108 | var iscode = true;
109 |
110 | if (cmd === 'fi') {
111 | ifcount--;
112 | // end of condition
113 | } else if (cmd === 'end') {
114 | loopcount--;
115 | // end of loop
116 | loops.pop();
117 | } else if (cmd.substring(0, 3) === 'if ') {
118 | // condition
119 | ifcount++;
120 | variable = parseInlineVariables(cmd.substring(3), loops);
121 | if (variable.length) {
122 | for (var i = 0; i < variable.length; i++) {
123 | var name = variable[i];
124 | if (self.variables[name])
125 | self.variables[name]++;
126 | else
127 | self.variables[name] = 1;
128 | }
129 | } else
130 | variable = null;
131 | isif = true;
132 | iscode = true;
133 | } else if (cmd.substring(0, 8) === 'foreach ') {
134 |
135 | loopcount++;
136 | // loop
137 |
138 | tmp = cmd.substring(8).split(' ');
139 | loops.push(tmp[0].trim());
140 |
141 | index = tmp[2].indexOf('.');
142 | if (index !== -1)
143 | tmp[2] = tmp[2].substring(0, index);
144 |
145 | variable = tmp[2].trim();
146 |
147 | if (loops.indexOf(variable) === -1) {
148 | if (self.variables[variable])
149 | self.variables[variable]++;
150 | else
151 | self.variables[variable] = 1;
152 | variable = [variable];
153 | }
154 | else
155 | variable = null;
156 |
157 | isloop = true;
158 | } else if (cmd.substring(0, 8) === 'else if ') {
159 | // else if
160 | variable = parseInlineVariables(cmd.substring(8), loops);
161 | if (variable.length) {
162 | for (var i = 0; i < variable.length; i++) {
163 | var name = variable[i];
164 | if (self.variables[name])
165 | self.variables[name]++;
166 | else
167 | self.variables[name] = 1;
168 | }
169 | } else
170 | variable = null;
171 | isif = true;
172 | } else if (cmd !== 'continue' && cmd !== 'break' && cmd !== 'else') {
173 |
174 | variable = parseInlineVariables(cmd);
175 |
176 | var ishelper = false;
177 |
178 | for (var i = 0; i < variable.length; i++) {
179 | var v = variable[i];
180 |
181 | if (v + '(' === cmd.substring(0, v.length + 1)) {
182 | ishelper = true;
183 | continue;
184 | }
185 |
186 | if (self.variables[v])
187 | self.variables[v]++;
188 | else
189 | self.variables[v] = 1;
190 | }
191 |
192 | if (!variable.length)
193 | variable = null;
194 |
195 | var hindex = cmd.indexOf('|');
196 | var fnhelper = null;
197 |
198 | if (ishelper) {
199 | fnhelper = cmd.substring(0, hindex === -1 ? cmd.length : hindex);
200 | if (hindex === -1)
201 | cmd = '';
202 | else
203 | cmd = '' + cmd.substring(index);
204 | } else if (!ishelper && hindex === -1)
205 | cmd += ' | encode';
206 |
207 | helpers = cmd.split('|');
208 | cmd = helpers[0];
209 | helpers = helpers.slice(1);
210 |
211 | if (ishelper)
212 | helpers.unshift(fnhelper);
213 |
214 | if (helpers.length) {
215 | for (var i = 0; i < helpers.length; i++) {
216 | var helper = helpers[i].trim();
217 | var ishelperfirst = ishelper && !i;
218 | index = helper.indexOf('(');
219 | if (index === -1) {
220 | helper = 'Thelpers.$execute($helpers,model,\'' + helper + '\',' + (ishelperfirst ? '' : '\7)');
221 | } else
222 | helper = 'Thelpers.$execute($helpers,model,\'' + helper.substring(0, index) + '\',' + (ishelperfirst ? '' : '\7,') + helper.substring(index + 1);
223 | helpers[i] = helper;
224 | }
225 | } else
226 | helpers = null;
227 |
228 | cmd = self.safe(cmd.trim() || 'model');
229 | iscode = false;
230 | }
231 |
232 | self.commands.push({ index: self.commands.length, cmd: cmd, ifcount: ifcount, loopcount: loopcount, variable: variable, helpers: helpers, isloop: isloop, isif: isif, iscode: iscode });
233 |
234 | return self.split;
235 |
236 | }).split(self.split);
237 |
238 | for (var i = 0; i < self.builder.length; i++) {
239 | var m = self.builder[i];
240 | self.builder[i] = m ? m.replace(REG_TRIM, '') : m;
241 | }
242 |
243 | return self.make();
244 | };
245 |
246 | Template.prototype.safe = function(cmd) {
247 |
248 | var arr = cmd.split('.');
249 | var output = [];
250 |
251 | for (var i = 1; i < arr.length; i++) {
252 | var k = arr.slice(0, i).join('.');
253 | output.push(k + '==null?\'\':');
254 | }
255 | return output.join('') + arr.join('.');
256 | };
257 |
258 | Template.prototype.make = function() {
259 |
260 | var self = this;
261 | var builder = ['var $output=$text[0];var $tmp;var $index=0;'];
262 |
263 | for (var i = 0, length = self.commands.length; i < length; i++) {
264 |
265 | var cmd = self.commands[i];
266 | var tmp;
267 |
268 | i && builder.push('$output+=$text[' + i + '];');
269 |
270 | if (cmd.iscode) {
271 |
272 | if (cmd.isloop) {
273 |
274 | var name = '$i' + Math.random().toString(16).substring(3, 6);
275 | var namea = name + 'a';
276 | var index = cmd.cmd.lastIndexOf(' in ');
277 | if (index === -1)
278 | index = cmd.cmd.lastIndexOf(' of ');
279 | tmp = cmd.cmd.substring(index + 4).trim();
280 | tmp = namea + '=' + self.safe(tmp) + ';if(!(' + namea + ' instanceof Array)){if(' + namea + '&&typeof(' + namea + ')===\'object\')' + namea + '=Tangular.toArray(' + namea + ')}if(' + namea + ' instanceof Array&&' + namea + '.length){for(var ' + name + '=0,' + name + 'l=' + namea + '.length;' + name + '<' + name + 'l;' + name + '++){$index=' + name + ';var ' + cmd.cmd.split(' ')[1] + '=' + namea + '[' + name + '];';
281 | builder.push(tmp);
282 |
283 | } else if (cmd.isif) {
284 | if (cmd.cmd.substring(0, 8) === 'else if ')
285 | builder.push('}' + cmd.cmd.substring(0, 8).trim() + '(' + cmd.cmd.substring(8).trim() + '){');
286 | else
287 | builder.push(cmd.cmd.substring(0, 3).trim() + '(' + cmd.cmd.substring(3).trim() + '){');
288 | } else {
289 | switch (cmd.cmd) {
290 | case 'else':
291 | builder.push('}else{');
292 | break;
293 | case 'end':
294 | builder.push('}}');
295 | break;
296 | case 'fi':
297 | builder.push('}');
298 | break;
299 | case 'break':
300 | builder.push('break;');
301 | break;
302 | case 'continue':
303 | builder.push('continue;');
304 | break;
305 | }
306 | }
307 |
308 | } else {
309 | if (cmd.helpers) {
310 | var str = '';
311 | for (var j = 0; j < cmd.helpers.length; j++) {
312 | var helper = cmd.helpers[j];
313 | if (j === 0)
314 | str = helper.replace('\7', cmd.cmd.trim()).trim();
315 | else
316 | str = helper.replace('\7', str.trim());
317 | }
318 | builder.push('$tmp=' + str + ';if($tmp!=null)$output+=$tmp;');
319 | } else
320 | builder.push('if(' + cmd.cmd + '!=null)$output+=' + cmd.cmd + ';');
321 | }
322 | }
323 |
324 | builder.push((length ? ('$output+=$text[' + length + '];') : '') + 'return $output.charAt(0) === \'\\n\'?$output.substring(1):$output;');
325 | delete self.variables.$;
326 | var variables = Object.keys(self.variables);
327 | var names = ['$helpers||{}', '$||{}', 'model'];
328 |
329 | for (var i = 0; i < variables.length; i++)
330 | names.push('model.' + variables[i]);
331 |
332 | for (var i = 0; i < self.builder.length; i++)
333 | self.builder[i] = self.builder[i].replace(/\1/g, '||');
334 |
335 | var code = 'var tangular=function($helpers,$,model' + (variables.length ? (',' + variables.join(',')) : '') + '){' + builder.join('') + '};return function(model,$,$helpers){try{return tangular(' + names.join(',') + ')}catch(e){console.error(\'Tangular error:\',e + \'\',$template)}}';
336 | return (new Function('$text', '$template', code.replace(/\1/g, '||')))(self.builder, self.template);
337 | };
338 |
339 | Thelpers.$execute = function(helpers, model, name, a, b, c, d, e, f, g, h) {
340 |
341 | var fn = helpers[name] || Thelpers[name];
342 |
343 | if (!fn) {
344 | console && console.warn('Tangular: missing helper', '"' + name + '"');
345 | return a;
346 | }
347 |
348 | return fn.call(model, a, b, c, d, e, f, g, h);
349 | };
350 |
351 | Thelpers.encode = function(value, type) {
352 |
353 | if (type) {
354 |
355 | if (typeof(type) === 'function')
356 | return type(value);
357 |
358 | if (type === 'json')
359 | return JSON.stringify(value);
360 |
361 | if (type === 'json2')
362 | return JSON.stringify(value, null, '\t');
363 |
364 | if (type === 'url' || type === 'urlencoded')
365 | return encodeURIComponent(value);
366 |
367 | if (type === 'querify')
368 | return QUERIFY(value);
369 | }
370 |
371 | return value == null ? '' : (value + '').replace(REG_ENCODE, function(c) {
372 | switch (c) {
373 | case '&': return '&';
374 | case '<': return '<';
375 | case '>': return '>';
376 | case '"': return '"';
377 | }
378 | return c;
379 | });
380 | };
381 |
382 | Thelpers.raw = function(value) {
383 | return value;
384 | };
385 |
386 | Tangular.render = function(template, model, repository, helpers) {
387 | var template = new Template().compile(template);
388 | return template(model == null ? {} : model, repository, helpers);
389 | };
390 |
391 | Tangular.compile = function(template, tagbeg, tagend) {
392 | return new Template().compile(template, tagbeg, tagend);
393 | };
394 |
395 | Tangular.register = function(name, fn) {
396 | Thelpers[name] = fn;
397 | return Tangular;
398 | };
399 |
400 | })(typeof(window)==='undefined'?global:window);
--------------------------------------------------------------------------------