﻿/**
* 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);

