1 /**
  2  * jTemplates 0.8.3 (http://jtemplates.tpython.com)
  3  * Copyright (c) 2007-2012 Tomasz Gloc (http://www.tpython.com)
  4  * 
  5  * Dual licensed under the MIT (MIT-LICENSE.txt)
  6  * and/or GPL (GPL-LICENSE.txt) licenses.
  7  *
  8  * Id: $Id: jquery-jtemplates_uncompressed.js 197 2012-07-29 08:20:01Z tom $
  9  */
 10 
 11  /**
 12  * @fileOverview Template engine in JavaScript.
 13  * @name jTemplates
 14  * @author Tomasz Gloc
 15  * @date $Date: 2012-07-29 10:20:01 +0200 (N, 29 lip 2012) $
 16  */
 17 
 18 if (window.jQuery && !window.jQuery.createTemplate) {(function (jQuery) {
 19 
 20 	/**
 21 	 * [abstract]
 22 	 * @name BaseNode
 23 	 * @class Abstract node. [abstract]
 24 	 */
 25 
 26 	/**
 27 	 * Process node and get the html string. [abstract]
 28 	 * @name get
 29 	 * @function
 30 	 * @param {object} d data
 31 	 * @param {object} param parameters
 32 	 * @param {Element} element a HTML element
 33 	 * @param {Number} deep
 34 	 * @return {String}
 35 	 * @memberOf BaseNode
 36 	 */
 37 
 38 	/**
 39 	 * [abstract]
 40 	 * @name BaseArray
 41 	 * @augments BaseNode
 42 	 * @class Abstract array/collection. [abstract]
 43 	 */
 44 
 45 	/**
 46 	 * Add node 'e' to array.
 47 	 * @name push
 48 	 * @function
 49 	 * @param {BaseNode} e a node
 50 	 * @memberOf BaseArray
 51 	 */
 52 
 53 	/**
 54 	 * See (http://jquery.com/).
 55 	 * @name jQuery
 56 	 * @class jQuery Library (http://jquery.com/)
 57 	 */
 58 
 59 	/**
 60 	 * See (http://jquery.com/)
 61 	 * @name fn
 62 	 * @class jQuery Library (http://jquery.com/)
 63 	 * @memberOf jQuery
 64 	 */
 65 
 66 	/**
 67 	 * Create new template from string s.
 68 	 * @name Template
 69 	 * @class A template or multitemplate.
 70 	 * @param {string} s A template string (like: "Text: {$T.txt}.").
 71 	 * @param {array} [includes] Array of included templates.
 72 	 * @param {object} [settings] Settings.
 73 	 * @config {boolean} [disallow_functions] Do not allow use function in data (default: true).
 74 	 * @config {boolean} [filter_data] Enable filter data using escapeHTML (default: true).
 75 	 * @config {boolean} [filter_params] Enable filter parameters using escapeHTML (default: false).
 76 	 * @config {boolean} [runnable_functions] Automatically run function (from data) inside {} [default: false].
 77 	 * @config {boolean} [clone_data] Clone input data [default: true]
 78 	 * @config {boolean} [clone_params] Clone input parameters [default: true]
 79 	 * @config {Function} [f_cloneData] Function used to data cloning
 80 	 * @config {Function} [f_escapeString] Function used to escape strings
 81 	 * @config {Function} [f_parseJSON] Function used to parse JSON
 82 	 * @augments BaseNode
 83 	 */
 84 	var Template = function (s, includes, settings) {
 85 		this._tree = [];
 86 		this._param = {};
 87 		this._includes = null;
 88 		this._templates = {};
 89 		this._templates_code = {};
 90 		
 91 		//default parameters
 92 		this.settings = jQuery.extend({
 93 			disallow_functions: false,
 94 			filter_data: true,
 95 			filter_params: false,
 96 			runnable_functions: false,
 97 			clone_data: true,
 98 			clone_params: true
 99 		}, settings);
100 		
101 		//set handlers
102 		this.f_cloneData = (this.settings.f_cloneData !== undefined) ? (this.settings.f_cloneData) : (TemplateUtils.cloneData);
103 		this.f_escapeString = (this.settings.f_escapeString !== undefined) ? (this.settings.f_escapeString) : (TemplateUtils.escapeHTML);
104 		this.f_parseJSON = (this.settings.f_parseJSON !== undefined) ? (this.settings.f_parseJSON) : ((this.settings.disallow_functions) ? (jQuery.parseJSON) : (TemplateUtils.parseJSON));
105 		
106 		if(s == null) {
107 			return;
108 		}
109 		
110 		//split multiteplate
111 		this.splitTemplates(s, includes);
112 		
113 		if(s) {
114 			//set main template
115 			this.setTemplate(this._templates_code['MAIN'], includes, this.settings);
116 		}
117 		
118 		this._templates_code = null;
119 	};
120 	
121 	/**
122 	 * jTemplates version
123 	 * @type string
124 	 */
125 	Template.version = '0.8.3';
126 	
127 	/**
128 	 * Debug mode (all errors are on), default: off
129 	 * @type Boolean
130 	 */
131 	Template.DEBUG_MODE = false;
132 	
133 	/**
134 	 * Foreach loop limit (enable only when DEBUG_MODE = true)
135 	 * @type integer
136 	 */
137 	Template.FOREACH_LOOP_LIMIT = 10000;
138 	
139 	/**
140 	 * Global guid
141 	 * @type integer
142 	 */
143 	Template.guid = 0;
144 	
145 	/**
146 	 * Split multitemplate into multiple templates.
147 	 * @param {string} s A template string (like: "Text: {$T.txt}.").
148 	 * @param {array} includes Array of included templates.
149 	 */
150 	Template.prototype.splitTemplates = function (s, includes) {
151 		var reg = /\{#template *(\w+) *(.*?) *\}/g, //split multitemplate into subtemplates
152 			iter, tname, se, lastIndex = null, _template_settings = [], i;
153 		
154 		//while find new subtemplate
155 		while((iter = reg.exec(s)) !== null) {
156 			lastIndex = reg.lastIndex;
157 			tname = iter[1];
158 			se = s.indexOf('{#/template ' + tname + '}', lastIndex);
159 			if(se === -1) {
160 				throw new Error('jTemplates: Template "' + tname + '" is not closed.');
161 			}
162 			//save a subtemplate and parse options
163 			this._templates_code[tname] = s.substring(lastIndex, se);
164 			_template_settings[tname] = TemplateUtils.optionToObject(iter[2]);
165 		}
166 		//when no subtemplates, use all as main template
167 		if(lastIndex === null) {
168 			this._templates_code['MAIN'] = s;
169 			return;
170 		}
171 		
172 		//create a new object for every subtemplates
173 		for(i in this._templates_code) {
174 			if(i !== 'MAIN') {
175 				this._templates[i] = new Template();
176 			}
177 		}
178 		for(i in this._templates_code) {
179 			if(i !== 'MAIN') {
180 				this._templates[i].setTemplate(this._templates_code[i],
181 					jQuery.extend({}, includes || {}, this._templates || {}),
182 					jQuery.extend({}, this.settings, _template_settings[i]));
183 				this._templates_code[i] = null;
184 			}
185 		}
186 	};
187 	
188 	/**
189 	 * Parse template. (should be template, not multitemplate).
190 	 * @param {string} s A template string (like: "Text: {$T.txt}.").
191 	 * @param {array} includes Array of included templates.
192 	 * @param {object} [settings] Settings.
193 	 */
194 	Template.prototype.setTemplate = function (s, includes, settings) {
195 		if(s == undefined) {
196 			this._tree.push(new TextNode('', 1, this));
197 			return;
198 		}
199 		s = s.replace(/[\n\r]/g, ''); //remove endlines
200 		s = s.replace(/\{\*.*?\*\}/g, ''); //remove comments
201 		this._includes = jQuery.extend({}, this._templates || {}, includes || {});
202 		this.settings = new Object(settings);
203 		var node = this._tree,
204 			op = s.match(/\{#.*?\}/g), //find operators
205 			ss = 0, se = 0, e, literalMode = 0, i, l;
206 		
207 		//loop operators
208 		for(i=0, l=(op)?(op.length):(0); i<l; ++i) {
209 			var this_op = op[i];
210 			
211 			//when literal mode is on, treat operator like a text
212 			if(literalMode) {
213 				se = s.indexOf('{#/literal}');
214 				if(se === -1) {
215 					throw new Error("jTemplates: No end of literal.");
216 				}
217 				if(se > ss) {
218 					node.push(new TextNode(s.substring(ss, se), 1, this));
219 				}
220 				ss = se + 11;
221 				literalMode = 0;
222 				i = jQuery.inArray('{#/literal}', op);
223 				continue;
224 			}
225 			
226 			se = s.indexOf(this_op, ss);
227 			if(se > ss) {
228 				node.push(new TextNode(s.substring(ss, se), literalMode, this));
229 			}
230 			this_op.match(/\{#([\w\/]+).*?\}/); //find operator name
231 			var op_ = RegExp.$1;
232 			switch(op_) {
233 				case 'elseif':
234 					node.addCond(this_op);
235 					break;
236 				case 'if':
237 					e = new opIF(node, this);
238 					e.addCond(this_op);
239 					node.push(e);
240 					node = e;
241 					break;
242 				case 'else':
243 					node.switchToElse();
244 					break;
245 				case '/if':
246 				case '/for':
247 				case '/foreach':
248 					node = node.getParent();
249 					break;
250 				case 'foreach':
251 					e = new opFOREACH(this_op, node, this);
252 					node.push(e);
253 					node = e;
254 					break;
255 				case 'for':
256 					e = opFORFactory(this_op, node, this);
257 					node.push(e);
258 					node = e;
259 					break;
260 				case 'continue':
261 				case 'break':
262 					node.push(new JTException(op_));
263 					break;
264 				case 'include':
265 					node.push(new Include(this_op, this._includes, this));
266 					break;
267 				case 'param':
268 					node.push(new UserParam(this_op, this));
269 					break;
270 				case 'var':
271 					node.push(new UserVariable(this_op, this));
272 					break;
273 				case 'cycle':
274 					node.push(new Cycle(this_op));
275 					break;
276 				case 'ldelim':
277 					node.push(new TextNode('{', 1, this));
278 					break;
279 				case 'rdelim':
280 					node.push(new TextNode('}', 1, this));
281 					break;
282 				case 'literal':
283 					literalMode = 1;
284 					break;
285 				case '/literal':
286 					if(Template.DEBUG_MODE) {
287 						throw new Error("jTemplates: Missing begin of literal.");
288 					}
289 					break;
290 				default:
291 					if(Template.DEBUG_MODE) {
292 						throw new Error('jTemplates: unknown tag: ' + op_ + '.');
293 					}
294 			}
295 	
296 			ss = se + this_op.length;
297 		}
298 	
299 		if(s.length > ss) {
300 			node.push(new TextNode(s.substr(ss), literalMode, this));
301 		}
302 	};
303 	
304 	/**
305 	 * Process template and get the html string.
306 	 * @param {object} d data
307 	 * @param {object} param parameters
308 	 * @param {Element} element a HTML element
309 	 * @param {Number} deep
310 	 * @return {String}
311 	 */
312 	Template.prototype.get = function (d, param, element, deep) {
313 		++deep;
314 		
315 		if (deep == 1 && element != undefined) {
316 			jQuery.removeData(element, "jTemplatesRef");
317 		}
318 		
319 		var $T = d, $P, ret = '';
320 		
321 		//create clone of data
322 		if(this.settings.clone_data) {
323 			$T = this.f_cloneData(d, {escapeData: (this.settings.filter_data && deep == 1), noFunc: this.settings.disallow_functions}, this.f_escapeString);
324 		}
325 		
326 		//create clone of parameters
327 		if(!this.settings.clone_params) {
328 			$P = jQuery.extend({}, this._param, param);
329 		} else {
330 			$P = jQuery.extend({},
331 				this.f_cloneData(this._param, {escapeData: (this.settings.filter_params), noFunc: false}, this.f_escapeString),
332 				this.f_cloneData(param, {escapeData: (this.settings.filter_params && deep == 1), noFunc: false}, this.f_escapeString));
333 		}
334 		
335 		for(var i=0, l=this._tree.length; i<l; ++i) {
336 			ret += this._tree[i].get($T, $P, element, deep);
337 		}
338 		
339 		this.EvalObj = null;
340 		
341 		--deep;
342 		return ret;
343 	};
344 	
345 	/**
346 	 * Create and return EvalClass object
347 	 * @return {EvalClass}
348 	 */
349 	Template.prototype.getBin = function () {
350 		if(this.EvalObj == null) {
351 			this.EvalObj = new EvalClass(this);
352 		}
353 		return this.EvalObj;
354 	};
355 	
356 	/**
357 	 * Set to parameter 'name' value 'value'.
358 	 * @param {string} name
359 	 * @param {object} value
360 	 */
361 	Template.prototype.setParam = function (name, value) {
362 		this._param[name] = value;
363 	};
364 
365 
366 	/**
367 	 * Template utilities.
368 	 * @namespace Template utilities.
369 	 */
370 	TemplateUtils = function () {
371 	};
372 	
373 	/**
374 	 * Replace chars &, >, <, ", ' with html entities.
375 	 * To disable function set settings: filter_data=false, filter_params=false
376 	 * @param {string} string
377 	 * @return {string}
378 	 * @static
379 	 * @memberOf TemplateUtils
380 	 */
381 	TemplateUtils.escapeHTML = function (txt) {
382 		return txt.replace(/&/g,'&').replace(/>/g,'>').replace(/</g,'<').replace(/"/g,'"').replace(/'/g,''');
383 	};
384 
385 	/**
386 	 * Make a copy od data 'd'. It also filters data (depend on 'filter').
387 	 * @param {object} d input data
388 	 * @param {object} filter a filters
389 	 * @config {boolean} [escapeData] Use escapeHTML on every string.
390 	 * @config {boolean} [noFunc] Do not allow to use function (throws exception).
391 	 * @param {Function} f_escapeString function using to filter string (usually: TemplateUtils.escapeHTML)
392 	 * @return {object} output data (filtered)
393 	 * @static
394 	 * @memberOf TemplateUtils
395 	 */
396 	TemplateUtils.cloneData = function (d, filter, f_escapeString) {
397 		if(d == null) {
398 			return d;
399 		}
400 		switch(d.constructor) {
401 			case Object:
402 				var o = {};
403 				for(var i in d) {
404 					o[i] = TemplateUtils.cloneData(d[i], filter, f_escapeString);
405 				}
406 				if(!filter.noFunc) {
407 					if(d.hasOwnProperty("toString")) {
408 						o.toString = d.toString;
409 					}
410 				}
411 				return o;
412 			case Array:
413 				var a = [];
414 				for(var i=0,l=d.length; i<l; ++i) {
415 					a[i] = TemplateUtils.cloneData(d[i], filter, f_escapeString);
416 				}
417 				return a;
418 			case String:
419 				return (filter.escapeData) ? (f_escapeString(d)) : (d);
420 			case Function:
421 				if(filter.noFunc) {
422 					if(Template.DEBUG_MODE) {
423 						throw new Error("jTemplates: Functions are not allowed.");
424 					}
425 					else {
426 						return undefined;
427 					}
428 				}
429 		}
430 		return d;
431 	};
432 	
433 	/**
434 	 * Convert text-based option string to Object
435 	 * @param {string} optionText text-based option string
436 	 * @return {Object}
437 	 * @static
438 	 * @memberOf TemplateUtils
439 	 */
440 	TemplateUtils.optionToObject = function (optionText) {
441 		if(optionText === null || optionText === undefined) {
442 			return {};
443 		}
444 		
445 		var o = optionText.split(/[= ]/);
446 		if(o[0] === '') {
447 			o.shift();
448 		}
449 		
450 		var obj = {};
451 		for(var i=0, l=o.length; i<l; i+=2) {
452 			obj[o[i]] = o[i+1];
453 		}
454 		
455 		return obj;
456 	};
457 	
458 	/**
459 	 * Parse JSON string into object
460 	 * @param {string} data Text JSON
461 	 * @return {Object}
462 	 * @static
463 	 */
464 	TemplateUtils.parseJSON = function (data) {
465 		if ( typeof data !== "string" || !data ) {
466 			return null;
467 		}
468 		try {
469 			return (new Function("return " + jQuery.trim(data)))();
470 		} catch(e) {
471 			if(Template.DEBUG_MODE) {
472 				throw new Error("jTemplates: Invalid JSON");
473 			}
474 			return {};
475 		}
476 	};
477 	
478 	/**
479 	 * Find parents nodes for a reference value and return it
480 	 * @param {Element} el html element
481 	 * @param {int} guid template process unique identificator
482 	 * @param {int} id index
483 	 * @return {object}
484 	 * @static
485 	 */
486 	TemplateUtils.ReturnRefValue = function (el, guid, id) {
487 		//search element with stored data
488 		while(el != null) {
489 			var d = jQuery.data(el, 'jTemplatesRef');
490 			if(d != undefined && d.guid == guid && d.d[id] != undefined) {
491 				return d.d[id];
492 			}
493 			el = el.parentNode;
494 		}
495 		return null;
496 	};
497 	
498 	/**
499 	 * Create a new text node.
500 	 * @name TextNode
501 	 * @class All text (block {..}) between control's block "{#..}".
502 	 * @param {string} val text string
503 	 * @param {boolean} literalMode When enable (true) template does not process blocks {..}.
504 	 * @param {Template} Template object
505 	 * @augments BaseNode
506 	 */
507 	var TextNode = function (val, literalMode, template) {
508 		this._value = val;
509 		this._literalMode = literalMode;
510 		this._template = template;
511 	};
512 	
513 	/**
514 	 * Get the html string for a text node.
515 	 * @param {object} d data
516 	 * @param {object} param parameters
517 	 * @param {Element} element a HTML element
518 	 * @param {Number} deep
519 	 * @return {String}
520 	 */
521 	TextNode.prototype.get = function (d, param, element, deep) {
522 		if(this._literalMode) {
523 			return this._value;
524 		}
525 		var s = this._value;
526 		var result = "";
527 		var i = -1;
528 		var nested = 0;
529 		var sText = -1;
530 		var sExpr = 0;
531 		while(true) {
532 			var lm = s.indexOf("{", i+1);
533 			var rm = s.indexOf("}", i+1);
534 			if(lm < 0 && rm < 0) {
535 				break;
536 			}
537 			if((lm != -1 && lm < rm) || (rm == -1)) {
538 				i = lm;
539 				if(++nested == 1) {
540 					sText = lm;
541 					result += s.substring(sExpr, i);
542 					sExpr = -1;
543 				}
544 			} else {
545 				i = rm;
546 				if(--nested === 0) {
547 					if(sText >= 0) {
548 						result += this._template.getBin().evaluateContent(d, param, element, s.substring(sText, rm+1));
549 						sText = -1;
550 						sExpr = i+1;
551 					}
552 				} else if(nested < 0) {
553 					nested = 0;
554 				}
555 			}
556 		}
557 		if(sExpr > -1) {
558 			result += s.substr(sExpr);
559 		}
560 		return result;
561 	};
562 	
563 	/**
564 	 * Virtual context for eval() (internal class)
565 	 * @name EvalClass
566 	 * @class Virtual bin for eval() evaluation
567 	 * @param {Template} t template
568 	 * @private
569 	 */
570 	EvalClass = function (t) {
571 		this.__templ = t;
572 	};
573 	
574 	/**
575 	 * Evaluate expression (template content)
576 	 * @param {object} $T data
577 	 * @param {object} $P parameters
578 	 * @param {object} $Q element
579 	 * @param {String} __value Template content
580 	 * @return {String}
581 	 */
582 	EvalClass.prototype.evaluateContent = function ($T, $P, $Q, __value) {
583 		try {
584 			var result = eval(__value);
585 			
586 			if(jQuery.isFunction(result)) {
587 				if(this.__templ.settings.disallow_functions || !this.__templ.settings.runnable_functions) {
588 					return '';
589 				}
590 				result = result($T, $P, $Q);
591 			}
592 			return (result === undefined) ? ("") : (String(result));
593 		} catch(e) {
594 			if(Template.DEBUG_MODE) {
595 				if(e instanceof JTException) {
596 					e.type = "subtemplate";
597 				}
598 				throw e;
599 			}
600 			return "";
601 		}
602 	};
603 	
604 	/**
605 	 * Evaluate expression (simple eval)
606 	 * @param {object} $T data
607 	 * @param {object} $P parameters
608 	 * @param {object} $Q element
609 	 * @param {String} __value content to evaluate
610 	 * @return {String}
611 	 */
612 	EvalClass.prototype.evaluate = function ($T, $P, $Q, __value) {
613 		return eval(__value);
614 	};
615 	
616 	/**
617 	 * Create a new conditional node.
618 	 * @name opIF
619 	 * @class A class represent: {#if}.
620 	 * @param {object} par parent node
621 	 * @param {Template} templ template
622 	 * @augments BaseArray
623 	 */
624 	var opIF = function (par, templ) {
625 		this._parent = par;
626 		this._templ = templ;
627 		this._cond = []; //conditions
628 		this._tree = []; //conditions subtree
629 		this._curr = null; //current subtree
630 	};
631 	
632 	/**
633 	 * Add node 'e' to array.
634 	 * @param {BaseNode} e a node
635 	 */
636 	opIF.prototype.push = function (e) {
637 		this._curr.push(e);
638 	};
639 	
640 	/**
641 	 * Get a parent node.
642 	 * @return {BaseNode}
643 	 */
644 	opIF.prototype.getParent = function () {
645 		return this._parent;
646 	};
647 	
648 	/**
649 	 * Add condition
650 	 * @param {string} oper content of operator {#..}
651 	 */
652 	opIF.prototype.addCond = function (oper) {
653 		oper.match(/\{#(?:else)*if (.*?)\}/);
654 		this._cond.push(RegExp.$1);
655 		this._curr = [];
656 		this._tree.push(this._curr);
657 	};
658 	
659 	/**
660 	 * Switch to else
661 	 */
662 	opIF.prototype.switchToElse = function () {
663 		this._cond.push(true); //else is the last condition and its always true
664 		this._curr = [];
665 		this._tree.push(this._curr);
666 	};
667 	
668 	/**
669 	 * Process node depend on conditional and get the html string.
670 	 * @param {object} d data
671 	 * @param {object} param parameters
672 	 * @param {Element} element a HTML element
673 	 * @param {Number} deep
674 	 * @return {String}
675 	 */
676 	opIF.prototype.get = function (d, param, element, deep) {
677 		var ret = ''; //result
678 		
679 		try {
680 			//foreach condition
681 			for(var ci=0, cl=this._cond.length; ci<cl; ++ci) {
682 				//if condition is true
683 				if(this._templ.getBin().evaluate(d, param, element, this._cond[ci])) {
684 					//execute and exit
685 					var t = this._tree[ci];
686 					for(var i=0, l=t.length; i<l; ++i) {
687 						ret += t[i].get(d, param, element, deep);
688 					}
689 					return ret;
690 				}
691 			}
692 		} catch(e) {
693 			if(Template.DEBUG_MODE || (e instanceof JTException)) {
694 				throw e;
695 			}
696 		}
697 		return ret;
698 	};
699 	
700 	/**
701 	 * Handler for a tag 'FOR'. Create new and return relative opFOREACH object.
702 	 * @name opFORFactory
703 	 * @class Handler for a tag 'FOR'. Create new and return relative opFOREACH object.
704 	 * @param {string} oper content of operator {#..}
705 	 * @param {object} par parent node
706 	 * @param {Template} template a pointer to Template object
707 	 * @return {opFOREACH}
708 	 */
709 	opFORFactory = function (oper, par, template) {
710 		//create operator FOREACH with function as iterator
711 		if(oper.match(/\{#for (\w+?) *= *(\S+?) +to +(\S+?) *(?:step=(\S+?))*\}/)) {
712 			var f = new opFOREACH(null, par, template);
713 			f._name = RegExp.$1;
714 			f._option = {'begin': (RegExp.$2 || 0), 'end': (RegExp.$3 || -1), 'step': (RegExp.$4 || 1), 'extData': '$T'};
715 			f._runFunc = (function (i){return i;});
716 			return f;
717 		} else {
718 			throw new Error('jTemplates: Operator failed "find": ' + oper);
719 		}
720 	};
721 	
722 	/**
723 	 * Create a new loop node.
724 	 * @name opFOREACH
725 	 * @class A class represent: {#foreach}.
726 	 * @param {string} oper content of operator {#..}
727 	 * @param {object} par parent node
728 	 * @param {Template} template a pointer to Template object
729 	 * @augments BaseArray
730 	 */
731 	var opFOREACH = function (oper, par, template) {
732 		this._parent = par;
733 		this._template = template;
734 		if(oper != null) {
735 			oper.match(/\{#foreach +(.+?) +as +(\w+?)( .+)*\}/);
736 			this._arg = RegExp.$1;
737 			this._name = RegExp.$2;
738 			this._option = RegExp.$3 || null;
739 			this._option = TemplateUtils.optionToObject(this._option);
740 		}
741 		
742 		this._onTrue = [];
743 		this._onFalse = [];
744 		this._currentState = this._onTrue;
745 		//this._runFunc = null;
746 	};
747 	
748 	/**
749 	 * Add node 'e' to array.
750 	 * @param {BaseNode} e
751 	 */
752 	opFOREACH.prototype.push = function (e) {
753 		this._currentState.push(e);
754 	};
755 	
756 	/**
757 	 * Get a parent node.
758 	 * @return {BaseNode}
759 	 */
760 	opFOREACH.prototype.getParent = function () {
761 		return this._parent;
762 	};
763 	
764 	/**
765 	 * Switch from collection onTrue to onFalse.
766 	 */
767 	opFOREACH.prototype.switchToElse = function () {
768 		this._currentState = this._onFalse;
769 	};
770 	
771 	/**
772 	 * Process loop and get the html string.
773 	 * @param {object} d data
774 	 * @param {object} param parameters
775 	 * @param {Element} element a HTML element
776 	 * @param {Number} deep
777 	 * @return {String}
778 	 */
779 	opFOREACH.prototype.get = function (d, param, element, deep) {
780 		try {
781 			//array of elements in foreach (or function)
782 			var fcount = (this._runFunc === undefined) ? (this._template.getBin().evaluate(d, param, element, this._arg)) : (this._runFunc);
783 			if(fcount === $) {
784 				throw new Error("jTemplate: Variable '$' cannot be used as loop-function");
785 			}
786 			var key = [];	//only for objects
787 			var mode = typeof fcount;
788 			if(mode == 'object') {
789 				//transform object to array
790 				var arr = [];
791 				jQuery.each(fcount, function (k, v) {
792 					key.push(k);
793 					arr.push(v);
794 				});
795 				fcount = arr;
796 			}
797 			//setup primary iterator, iterator can get data from options (using by operator FOR) or from data "$T"
798 			var extData = (this._option.extData !== undefined) ? (this._template.getBin().evaluate(d, param, element, this._option.extData)) : ((d != null) ? (d) : ({}));
799 			if(extData == null) {
800 				extData = {};
801 			}
802 			//start, end and step
803 			var s = Number(this._template.getBin().evaluate(d, param, element, this._option.begin) || 0), e;	//start, end
804 			var step = Number(this._template.getBin().evaluate(d, param, element, this._option.step) || 1);
805 			if(mode != 'function') {
806 				e = fcount.length;
807 			} else {
808 				if(this._option.end === undefined || this._option.end === null) {
809 					e = Number.MAX_VALUE;
810 				} else {
811 					e = Number(this._template.getBin().evaluate(d, param, element, this._option.end)) + ((step>0) ? (1) : (-1));
812 				}
813 			}
814 			var ret = '';	//result string
815 			var i,l;	//local iterators
816 			
817 			if(this._option.count) {
818 				//limit number of loops
819 				var tmp = s + Number(this._template.getBin().evaluate(d, param, element, this._option.count));
820 				e = (tmp > e) ? (e) : (tmp);
821 			}
822 			
823 			if((e>s && step>0) || (e<s && step<0)) {
824 				var iteration = 0;
825 				var _total = (mode != 'function') ? (Math.ceil((e-s)/step)) : undefined;
826 				var ckey, cval;	//current key, current value
827 				var loopCounter = 0;
828 				for(; ((step>0) ? (s<e) : (s>e)); s+=step, ++iteration, ++loopCounter) {
829 					if(Template.DEBUG_MODE && loopCounter > Template.FOREACH_LOOP_LIMIT) {
830 						throw new Error("jTemplate: Foreach loop limit was exceed");
831 					}
832 					ckey = key[s];
833 					if(mode != 'function') {
834 						cval = fcount[s];  //get value from array
835 					} else {
836 						cval = fcount(s);  //calc function
837 						//if no result from function then stop foreach
838 						if(cval === undefined || cval === null) {
839 							break;
840 						}
841 					}
842 					if((typeof cval == 'function') && (this._template.settings.disallow_functions || !this._template.settings.runnable_functions)) {
843 						continue;
844 					}
845 					if((mode == 'object') && (ckey in Object) && (cval === Object[ckey])) {
846 						continue;
847 					}
848 					//backup on value
849 					var prevValue = extData[this._name];
850 					//set iterator properties
851 					extData[this._name] = cval;
852 					extData[this._name + '$index'] = s;
853 					extData[this._name + '$iteration'] = iteration;
854 					extData[this._name + '$first'] = (iteration === 0);
855 					extData[this._name + '$last'] = (s+step >= e);
856 					extData[this._name + '$total'] = _total;
857 					extData[this._name + '$key'] = (ckey !== undefined && ckey.constructor == String) ? (this._template.f_escapeString(ckey)) : (ckey);
858 					extData[this._name + '$typeof'] = typeof cval;
859 					for(i=0, l=this._onTrue.length; i<l; ++i) {
860 						try {
861 							ret += this._onTrue[i].get(extData, param, element, deep);
862 						} catch(ex) {
863 							if(ex instanceof JTException) {
864 								switch(ex.type) {
865 									case 'continue':
866 										i = l; //force skip to next node
867 										break;
868 									case 'break':
869 										i = l;  //force skip to next node
870 										s = e;  //force skip outsite foreach
871 										break;
872 									default:
873 										throw ex;
874 								}
875 							} else {
876 								throw ex;
877 							}
878 						}
879 					}
880 					//restore values
881 					delete extData[this._name + '$index'];
882 					delete extData[this._name + '$iteration'];
883 					delete extData[this._name + '$first'];
884 					delete extData[this._name + '$last'];
885 					delete extData[this._name + '$total'];
886 					delete extData[this._name + '$key'];
887 					delete extData[this._name + '$typeof'];
888 					delete extData[this._name];
889 					extData[this._name] = prevValue;
890 				}
891 			} else {
892 				//no items to loop ("foreach->else")
893 				for(i=0, l=this._onFalse.length; i<l; ++i) {
894 					ret += this._onFalse[i].get(d, param, element, deep);
895 				}
896 			}
897 			return ret;
898 		} catch(e) {
899 			if(Template.DEBUG_MODE || (e instanceof JTException)) {
900 				throw e;
901 			}
902 			return "";
903 		}
904 	};
905 	
906 	/**
907 	 * Template-control exceptions
908 	 * @name JTException
909 	 * @class A class used internals for a template-control exceptions
910 	 * @param type {string} Type of exception
911 	 * @augments Error
912 	 * @augments BaseNode
913 	 */
914 	var JTException = function (type) {
915 		this.type = type;
916 	};
917 	JTException.prototype = Error;
918 	
919 	/**
920 	 * Throw a template-control exception
921 	 * @throws It throws itself
922 	 */
923 	JTException.prototype.get = function (d) {
924 		throw this;
925 	};
926 	
927 	/**
928 	 * Create a new entry for included template.
929 	 * @name Include
930 	 * @class A class represent: {#include}.
931 	 * @param {string} oper content of operator {#..}
932 	 * @param {array} includes
933 	 * @param {Template} templ template
934 	 * @augments BaseNode
935 	 */
936 	var Include = function (oper, includes, templ) {
937 		oper.match(/\{#include (.*?)(?: root=(.*?))?\}/);
938 		this._template = includes[RegExp.$1];
939 		if(this._template == undefined) {
940 			if(Template.DEBUG_MODE) {
941 				throw new Error('jTemplates: Cannot find include: ' + RegExp.$1);
942 			}
943 		}
944 		this._root = RegExp.$2;
945 		this._mainTempl = templ;
946 	};
947 	
948 	/**
949 	 * Run method get on included template.
950 	 * @param {object} d data
951 	 * @param {object} param parameters
952 	 * @param {Element} element a HTML element
953 	 * @param {Number} deep
954 	 * @return {String}
955 	 */
956 	Include.prototype.get = function (d, param, element, deep) {
957 		try {
958 			//run a subtemplates with a new root node
959 			return this._template.get(this._mainTempl.getBin().evaluate(d, param, element, this._root), param, element, deep);
960 		} catch(e) {
961 			if(Template.DEBUG_MODE || (e instanceof JTException)) {
962 				throw e;
963 			}
964 		}
965 		return '';
966 	};
967 	
968 	/**
969 	 * Create new node for {#param}.
970 	 * @name UserParam
971 	 * @class A class represent: {#param}.
972 	 * @param {string} oper content of operator {#..}
973 	 * @param {Template} templ template
974 	 * @augments BaseNode
975 	 */
976 	var UserParam = function (oper, templ) {
977 		oper.match(/\{#param name=(\w*?) value=(.*?)\}/);
978 		this._name = RegExp.$1;
979 		this._value = RegExp.$2;
980 		this._templ = templ;
981 	};
982 	
983 	/**
984 	 * Return value of selected parameter.
985 	 * @param {object} d data
986 	 * @param {object} param parameters
987 	 * @param {Element} element a HTML element
988 	 * @param {Number} deep
989 	 * @return {String} empty string
990 	 */
991 	UserParam.prototype.get = function (d, param, element, deep) {
992 		try {
993 			param[this._name] = this._templ.getBin().evaluate(d, param, element, this._value);
994 		} catch(e) {
995 			if(Template.DEBUG_MODE || (e instanceof JTException)) {
996 				throw e;
997 			}
998 			param[this._name] = undefined;
999 		}
1000 		return '';
1001 	};
1002 	
1003 	/**
1004 	 * Create new node for {#var}.
1005 	 * @name UserVariable
1006 	 * @class A class represent: {#var}.
1007 	 * @param {string} oper content of operator {#..}
1008 	 * @param {Template} templ template
1009 	 * @augments BaseNode
1010 	 */
1011 	var UserVariable = function (oper, templ) {
1012 		oper.match(/\{#var (.*?)\}/);
1013 		this._id = RegExp.$1;
1014 		this._templ = templ;
1015 	};
1016 	
1017 	/**
1018 	 * Return value of selected variable.
1019 	 * @param {object} d data
1020 	 * @param {object} param parameters
1021 	 * @param {Element} element a HTML element
1022 	 * @param {Number} deep
1023 	 * @return {String} calling of function ReturnRefValue (as text string)
1024 	 */
1025 	UserVariable.prototype.get = function (d, param, element, deep) {
1026 		try {
1027 			if(element == undefined) {
1028 				return "";
1029 			}
1030 			var obj = this._templ.getBin().evaluate(d, param, element, this._id);
1031 			var refobj = jQuery.data(element, "jTemplatesRef");
1032 			if(refobj == undefined) {
1033 				refobj = {guid:(++Template.guid), d:[]};
1034 			}
1035 			var i = refobj.d.push(obj);
1036 			jQuery.data(element, "jTemplatesRef", refobj);
1037 			return "(TemplateUtils.ReturnRefValue(this," + refobj.guid + "," + (i-1) + "))";
1038 		} catch(e) {
1039 			if(Template.DEBUG_MODE || (e instanceof JTException)) {
1040 				throw e;
1041 			}
1042 			return '';
1043 		}
1044 	};
1045 	
1046 	/**
1047 	 * Create a new cycle node.
1048 	 * @name Cycle
1049 	 * @class A class represent: {#cycle}.
1050 	 * @param {string} oper content of operator {#..}
1051 	 * @augments BaseNode
1052 	 */
1053 	var Cycle = function (oper) {
1054 		oper.match(/\{#cycle values=(.*?)\}/);
1055 		this._values = eval(RegExp.$1);
1056 		this._length = this._values.length;
1057 		if(this._length <= 0) {
1058 			throw new Error('jTemplates: no elements for cycle');
1059 		}
1060 		this._index = 0;
1061 		this._lastSessionID = -1;
1062 	};
1063 
1064 	/**
1065 	 * Do a step on cycle and return value.
1066 	 * @param {object} d data
1067 	 * @param {object} param parameters
1068 	 * @param {Element} element a HTML element
1069 	 * @param {Number} deep
1070 	 * @return {String}
1071 	 */
1072 	Cycle.prototype.get = function (d, param, element, deep) {
1073 		var sid = jQuery.data(element, 'jTemplateSID');
1074 		if(sid != this._lastSessionID) {
1075 			this._lastSessionID = sid;
1076 			this._index = 0;
1077 		}
1078 		var i = this._index++ % this._length;
1079 		return this._values[i];
1080 	};
1081 	
1082 	
1083 	/**
1084 	 * Add a Template to HTML Elements.
1085 	 * @param {Template/string} s a Template or a template string
1086 	 * @param {array} [includes] Array of included templates.
1087 	 * @param {object} [settings] Settings (see Template)
1088 	 * @return {jQuery} chainable jQuery class
1089 	 * @memberOf jQuery.fn
1090 	 */
1091 	jQuery.fn.setTemplate = function (s, includes, settings) {
1092 		return jQuery(this).each(function () {
1093 			var t = (s && s.constructor == Template) ? s : new Template(s, includes, settings);
1094 			jQuery.data(this, 'jTemplate', t);
1095 			jQuery.data(this, 'jTemplateSID', 0);
1096 		});
1097 	};
1098 	
1099 	/**
1100 	 * Add a Template (from URL) to HTML Elements.
1101 	 * @param {string} url_ URL to template
1102 	 * @param {array} [includes] Array of included templates.
1103 	 * @param {object} [settings] Settings (see Template)
1104 	 * @return {jQuery} chainable jQuery class
1105 	 * @memberOf jQuery.fn
1106 	 */
1107 	jQuery.fn.setTemplateURL = function (url_, includes, settings) {
1108 		var s = jQuery.ajax({
1109 			url: url_,
1110 			dataType: 'text',
1111 			async: false,
1112 			type: 'GET'
1113 		}).responseText;
1114 		
1115 		return jQuery(this).setTemplate(s, includes, settings);
1116 	};
1117 	
1118 	/**
1119 	 * Create a Template from element's content.
1120 	 * @param {string} elementName an ID of element
1121 	 * @param {array} [includes] Array of included templates.
1122 	 * @param {object} [settings] Settings (see Template)
1123 	 * @return {jQuery} chainable jQuery class
1124 	 * @memberOf jQuery.fn
1125 	 */
1126 	jQuery.fn.setTemplateElement = function (elementName, includes, settings) {
1127 		var s = jQuery('#' + elementName).val();
1128 		if(s == null) {
1129 			s = jQuery('#' + elementName).html();
1130 			s = s.replace(/</g, "<").replace(/>/g, ">");
1131 		}
1132 		
1133 		s = jQuery.trim(s);
1134 		s = s.replace(/^<\!\[CDATA\[([\s\S]*)\]\]>$/im, '$1');
1135 		s = s.replace(/^<\!--([\s\S]*)-->$/im, '$1');
1136 		
1137 		return jQuery(this).setTemplate(s, includes, settings);
1138 	};
1139 	
1140 	/**
1141 	 * Check it HTML Elements have a template. Return count of templates.
1142 	 * @return {number} Number of templates.
1143 	 * @memberOf jQuery.fn
1144 	 */
1145 	jQuery.fn.hasTemplate = function () {
1146 		var count = 0;
1147 		jQuery(this).each(function () {
1148 			if(jQuery.getTemplate(this)) {
1149 				++count;
1150 			}
1151 		});
1152 		return count;
1153 	};
1154 	
1155 	/**
1156 	 * Remote Template from HTML Element(s)
1157 	 * @return {jQuery} chainable jQuery class
1158 	 */
1159 	jQuery.fn.removeTemplate = function () {
1160 		jQuery(this).processTemplateStop();
1161 		return jQuery(this).each(function () {
1162 			jQuery.removeData(this, 'jTemplate');
1163 		});
1164 	};
1165 	
1166 	/**
1167 	 * Set to parameter 'name' value 'value'.
1168 	 * @param {string} name
1169 	 * @param {object} value
1170 	 * @return {jQuery} chainable jQuery class
1171 	 * @memberOf jQuery.fn
1172 	 */
1173 	jQuery.fn.setParam = function (name, value) {
1174 		return jQuery(this).each(function () {
1175 			var t = jQuery.getTemplate(this);
1176 			if(t != null) {
1177 				t.setParam(name, value);
1178 			} else if(Template.DEBUG_MODE) {
1179 				throw new Error('jTemplates: Template is not defined.');
1180 			}
1181 		});
1182 	};
1183 	
1184 	/**
1185 	 * Process template using data 'd' and parameters 'param'. Update HTML code.
1186 	 * @param {object} d data 
1187 	 * @param {object} [param] parameters
1188 	 * @option {object} [options] internal use only
1189 	 * @return {jQuery} chainable jQuery class
1190 	 * @memberOf jQuery.fn
1191 	 */
1192 	jQuery.fn.processTemplate = function (d, param, options) {
1193 		return jQuery(this).each(function () {
1194 			var t = jQuery.getTemplate(this);
1195 			if(t != null) {
1196 				if(options != undefined && options.StrToJSON) {
1197 					d = t.f_parseJSON(d);
1198 				}
1199 				jQuery.data(this, 'jTemplateSID', jQuery.data(this, 'jTemplateSID') + 1);
1200 				jQuery(this).html(t.get(d, param, this, 0));
1201 			} else if(Template.DEBUG_MODE) {
1202 				throw new Error('jTemplates: Template is not defined.');
1203 			}
1204 		});
1205 	};
1206 	
1207 	/**
1208 	 * Process template using data from URL 'url_' (only format JSON) and parameters 'param'. Update HTML code.
1209 	 * @param {string} url_ URL to data (in JSON)
1210 	 * @param {object} [param] parameters
1211 	 * @param {object} options options (over ajaxSettings) and callbacks
1212 	 * @return {jQuery} chainable jQuery class
1213 	 * @memberOf jQuery.fn
1214 	 */
1215 	jQuery.fn.processTemplateURL = function (url_, param, options) {
1216 		var that = this;
1217 		
1218 		var o = jQuery.extend({cache: false}, jQuery.ajaxSettings);
1219 		o = jQuery.extend(o, options);
1220 
1221 		jQuery.ajax({
1222 			url: url_,
1223 			type: o.type,
1224 			data: o.data,
1225 			dataFilter: o.dataFilter,
1226 			async: o.async,
1227 			cache: o.cache,
1228 			timeout: o.timeout,
1229 			dataType: 'text',
1230 			success: function (d) {
1231 				var r = jQuery(that).processTemplate(d, param, {StrToJSON:true});
1232 				if(o.on_success) {
1233 					o.on_success(r);
1234 				}
1235 			},
1236 			error: o.on_error,
1237 			complete: o.on_complete
1238 		});
1239 		return this;
1240 	};
1241 
1242 	/**
1243 	 * Create new Updater.
1244 	 * @name Updater
1245 	 * @class This class is used for 'Live Refresh!'.
1246 	 * @param {string} url A destination URL
1247 	 * @param {object} param Parameters (for template)
1248 	 * @param {number} interval Time refresh interval
1249 	 * @param {object} args Additional URL parameters (in URL alter ?) as assoc array.
1250 	 * @param {array} objs An array of HTMLElement which will be modified by Updater.
1251 	 * @param {object} options options and callbacks
1252 	 */
1253 	var Updater = function (url, param, interval, args, objs, options) {
1254 		this._url = url;
1255 		this._param = param;
1256 		this._interval = interval;
1257 		this._args = args;
1258 		this.objs = objs;
1259 		this.timer = null;
1260 		this._options = options || {};
1261 		
1262 		var that = this;
1263 		jQuery(objs).each(function () {
1264 			jQuery.data(this, 'jTemplateUpdater', that);
1265 		});
1266 		this.run();
1267 	};
1268 	
1269 	/**
1270 	 * Create new HTTP request to server, get data (as JSON) and send it to templates. Also check does HTMLElements still exists in Document.
1271 	 */
1272 	Updater.prototype.run = function () {
1273 		//remove deleted node
1274 		this.objs = jQuery.grep(this.objs, function (elem) {
1275 			return (jQuery.contains(document.body, elem.jquery ? elem[0] : elem));
1276 		});
1277 		//if no node then do nothing
1278 		if(this.objs.length === 0) {
1279 			return;
1280 		}
1281 		//ajax call
1282 		var that = this;
1283 		jQuery.ajax({
1284 			url: this._url,
1285 			dataType: 'text',
1286 			data: this._args,
1287 			cache: false,
1288 			success: function (d) {
1289 				try {
1290 					var r = jQuery(that.objs).processTemplate(d, that._param, {StrToJSON:true});
1291 					if(that._options.on_success) {
1292 						that._options.on_success(r); //callback
1293 					}
1294 				} catch(ex) {}
1295 			}
1296 		});
1297 		//schedule next run
1298 		this.timer = setTimeout(function (){that.run();}, this._interval);
1299 	};
1300 	
1301 	/**
1302 	 * Start 'Live Refresh!'.
1303 	 * @param {string} url A destination URL
1304 	 * @param {object} param Parameters (for template)
1305 	 * @param {number} interval Time refresh interval
1306 	 * @param {object} args Additional URL parameters (in URL alter ?) as assoc array.
1307 	 * @param {object} options options and callbacks
1308 	 * @return {Updater} an Updater object
1309 	 * @memberOf jQuery.fn
1310 	 */
1311 	jQuery.fn.processTemplateStart = function (url, param, interval, args, options) {
1312 		return new Updater(url, param, interval, args, this, options);
1313 	};
1314 	
1315 	/**
1316 	 * Stop 'Live Refresh!'.
1317 	 * @return {jQuery} chainable jQuery class
1318 	 * @memberOf jQuery.fn
1319 	 */
1320 	jQuery.fn.processTemplateStop = function () {
1321 		return jQuery(this).each(function () {
1322 			var updater = jQuery.data(this, 'jTemplateUpdater');
1323 			if(updater == null) {
1324 				return;
1325 			}
1326 			var that = this;
1327 			updater.objs = jQuery.grep(updater.objs, function (o) {
1328 				return o != that;
1329 			});
1330 			jQuery.removeData(this, 'jTemplateUpdater');
1331 		});
1332 	};
1333 	
1334 	jQuery.extend(/** @scope jQuery.prototype */{
1335 		/**
1336 		 * Create new Template.
1337 		 * @param {string} s A template string (like: "Text: {$T.txt}.").
1338 		 * @param {array} includes Array of included templates.
1339 		 * @param {object} settings Settings. (see Template)
1340 		 * @return {Template}
1341 		 */
1342 		createTemplate: function (s, includes, settings) {
1343 			return new Template(s, includes, settings);
1344 		},
1345 		
1346 		/**
1347 		 * Create new Template from URL.
1348 		 * @param {string} url_ URL to template
1349 		 * @param {array} includes Array of included templates.
1350 		 * @param {object} settings Settings. (see Template)
1351 		 * @return {Template}
1352 		 */
1353 		createTemplateURL: function (url_, includes, settings) {
1354 			var s = jQuery.ajax({
1355 				url: url_,
1356 				dataType: 'text',
1357 				async: false,
1358 				type: 'GET'
1359 			}).responseText;
1360 			
1361 			return new Template(s, includes, settings);
1362 		},
1363 		
1364 		/**
1365 		 * Get a Template for HTML node
1366 		 * @param {Element} HTML node
1367 		 * @return {Template} a Template or "undefined"
1368 		 */
1369 		getTemplate: function (element) {
1370 			return jQuery.data(element, 'jTemplate');
1371 		},
1372 		
1373 		/**
1374 		 * Process template and return text content.
1375 		 * @param {Template} template A Template
1376 		 * @param {object} data data
1377 		 * @param {object} param parameters
1378 		 * @return {string} Content of template
1379 		 */
1380 		processTemplateToText: function (template, data, parameter) {
1381 			return template.get(data, parameter, undefined, 0);
1382 		},
1383 		
1384 		/**
1385 		 * Set Debug Mode
1386 		 * @param {Boolean} value
1387 		 */
1388 		jTemplatesDebugMode: function (value) {
1389 			Template.DEBUG_MODE = value;
1390 		}
1391 	});
1392 	
1393 })(jQuery);};
1394