﻿/**
* jQuery Templates
*
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* Written by: Stan Lemon <stanlemon@mac.com>
*
* Based off of the Ext.Template library, available at:
* http://www.extjs.com
*
* This library provides basic templating functionality, allowing for macro-based
* templates within jQuery.
*
* Basic Usage:
*
* var t = $.template('<div id="foo">Hello ${name}, how are you ${question}?  I am ${me:substr(0,10)}</div>');
*
* $(selector).append( t , {
*     name: 'Stan',
*     question: 'feeling',
*     me: 'doing quite well myself, thank you very much!'
* });
*
* Requires: jQuery 1.2+
*
*
* @todo    Add callbacks to the DOM manipulation methods, so that events can be bound
*          to template nodes after creation.
*/
(function($) {

	/**
	* Create a New Template
	*/
	$.template = function(html, options) {
		return new $.template.instance(html, options);
	};

	/**
	* Template constructor - Creates a new template instance.
	*
	* @param 	html 	The string of HTML to be used for the template.
	* @param 	options An object of configurable options.  Currently
	* 			you can toggle compile as a boolean value and set a custom
	*          template regular expression on the property regx by
	*          specifying the key of the regx to use from the regx object.
	*/
	$.template.instance = function(html, options) {
		// If a custom regular expression has been set, grab it from the regx object
		if (options && options['regx']) options.regx = this.regx[options.regx];

		this.options = $.extend({
			compile: false,
			regx: this.regx.standard
		}, options || {});

		this.html = html;

		if (this.options.compile) {
			this.compile();
		}
		this.isTemplate = true;
	};

	/**
	* Regular Expression for Finding Variables
	*
	* The default pattern looks for variables in JSP style, the form of: ${variable}
	* There are also regular expressions available for ext-style variables and
	* jTemplate style variables.
	*
	* You can add your own regular expressions for variable ussage by doing.
	* $.extend({ $.template.re , {
	*     myvartype: /...../g
	* }
	*
	* Then when creating a template do:
	* var t = $.template("<div>...</div>", { regx: 'myvartype' });
	*/
	$.template.regx = $.template.instance.prototype.regx = {
		jsp: /\$\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
		ext: /\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
		jtemplates: /\{\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}\}/g
	};

	/**
	* Set the standard regular expression to be used.
	*/
	$.template.regx.standard = $.template.regx.jsp;

	/**
	* Variable Helper Methods
	*
	* This is a collection of methods which can be used within the variable syntax, ie:
	* ${variable:substr(0,30)} Which would only print a substring, 30 characters in length
	* begining at the first character for the variable named "variable".
	*
	* A basic substring helper is provided as an example of how you can define helpers.
	* To add more helpers simply do:
	* $.extend( $.template.helpers , {
	*	 sampleHelper: function() { ... }	
	* });
	*/
	$.template.helpers = $.template.instance.prototype.helpers = {
		substr: function(value, start, length) {
			return String(value).substr(start, length);
		}
	};


	/**
	* Template Instance Methods
	*/
	$.extend($.template.instance.prototype, {

		/**
		* Apply Values to a Template
		*
		* This is the macro-work horse of the library, it receives an object
		* and the properties of that objects are assigned to the template, where
		* the variables in the template represent keys within the object itself.
		*
		* @param 	values 	An object of properties mapped to template variables
		*/
		apply: function(values) {
			if (this.options.compile) {
				return this.compiled(values);
			} else {
				var tpl = this;
				var fm = this.helpers;

				var fn = function(m, name, format, args) {
					if (format) {
						if (format.substr(0, 5) == "this.") {
							return tpl.call(format.substr(5), values[name], values);
						} else {
							if (args) {
								// quoted values are required for strings in compiled templates, 
								// but for non compiled we need to strip them
								// quoted reversed for jsmin
								var re = /^\s*['"](.*)["']\s*$/;
								args = args.split(',');

								for (var i = 0, len = args.length; i < len; i++) {
									args[i] = args[i].replace(re, "$1");
								}
								args = [values[name]].concat(args);
							} else {
								args = [values[name]];
							}

							return fm[format].apply(fm, args);
						}
					} else {
						return values[name] !== undefined ? values[name] : "";
					}
				};

				return this.html.replace(this.options.regx, fn);
			}
		},

		/**
		* Compile a template for speedier usage
		*/
		compile: function() {
			var sep = $.browser.mozilla ? "+" : ",";
			var fm = this.helpers;

			var fn = function(m, name, format, args) {
				if (format) {
					args = args ? ',' + args : "";

					if (format.substr(0, 5) != "this.") {
						format = "fm." + format + '(';
					} else {
						format = 'this.call("' + format.substr(5) + '", ';
						args = ", values";
					}
				} else {
					args = ''; format = "(values['" + name + "'] == undefined ? '' : ";
				}
				return "'" + sep + format + "values['" + name + "']" + args + ")" + sep + "'";
			};

			var body;

			if ($.browser.mozilla) {
				body = "this.compiled = function(values){ return '" +
					   this.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.options.regx, fn) +
						"';};";
			} else {
				body = ["this.compiled = function(values){ return ['"];
				body.push(this.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.options.regx, fn));
				body.push("'].join('');};");
				body = body.join('');
			}
			eval(body);
			return this;
		}
	});


	/**
	* Save a reference in this local scope to the original methods which we're 
	* going to overload.
	**/
	var $_old = {
		domManip: $.fn.domManip,
		text: $.fn.text,
		html: $.fn.html
	};

	/**
	* Overwrite the domManip method so that we can use things like append() by passing a 
	* template object and macro parameters.
	*/
	$.fn.domManip = function(args, table, reverse, callback) {
		if (args[0].isTemplate) {
			// Apply the template and it's arguments...
			args[0] = args[0].apply(args[1]);
			// Get rid of the arguements, we don't want to pass them on
			delete args[1];
		}

		// Call the original method
		var r = $_old.domManip.apply(this, arguments);

		return r;
	};

	/**
	* Overwrite the html() method
	*/
	$.fn.html = function(value, o) {
		if (value && value.isTemplate) var value = value.apply(o);

		var r = $_old.html.apply(this, [value]);

		return r;
	};

	/**
	* Overwrite the text() method
	*/
	$.fn.text = function(value, o) {
		if (value && value.isTemplate) var value = value.apply(o);

		var r = $_old.text.apply(this, [value]);

		return r;
	};

})(jQuery);

