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