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