﻿/*
* jQuery Templating Plugin
*   NOTE: Created for demonstration purposes.
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
*/
(function (jQuery) {
    // Override the DOM manipulation function
    var oldManip = jQuery.fn.domManip,
		htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$/;
    jQuery.fn.extend({
        render: function (data, options) {
            return this.map(function (i, tmpl) {
                return jQuery.render(tmpl, data, options);
            });
        },

        // This will allow us to do: .append( "template", dataObject )
        domManip: function (args) {
            // This appears to be a bug in the appendTo, etc. implementation
            // it should be doing .call() instead of .apply(). See #6227
            if (args.length > 1 && args[0].nodeType) {
                arguments[0] = [jQuery.makeArray(args)];
            }

            if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] !== "string") {
                arguments[0] = [jQuery.render(args[0], args[1], args[2])];
            }

            return oldManip.apply(this, arguments);
        }
    });

    jQuery.extend({
        render: function (tmpl, data, options) {
            var fn, node;

            if (typeof tmpl === "string") {
                // Use a pre-defined template, if available
                fn = jQuery.templates[tmpl];
                if (!fn && !htmlExpr.test(tmpl)) {
                    // it is a selector
                    node = jQuery(tmpl).get(0);
                }
            } else if (tmpl instanceof jQuery) {
                node = tmpl.get(0);
            } else if (tmpl.nodeType) {
                node = tmpl;
            }

            if (!fn && node) {
                var elemData = jQuery.data(node);
                fn = elemData.tmpl || (elemData.tmpl = jQuery.tmpl(node.innerHTML));
            }

            // We assume that if the template string is being passed directly
            // in the user doesn't want it cached. They can stick it in
            // jQuery.templates to cache it.

            fn = fn || jQuery.tmpl(tmpl);

            var rendering,
				rendered,
				context = {
				    data: data,
				    index: 0,
				    dataItem: data,
				    options: options || {}
				};
            if (options) {
                rendering = options.rendering;
                rendered = options.rendered;
            }

            function renderItem() {
                var dom = null;
                if (!rendering || rendering(context) !== false) {
                    var dom = fn(jQuery, context);
                    if (rendered)
                        rendered(context, dom);
                }
                return dom;
            }

            if (jQuery.isArray(data)) {
                return jQuery.map(data, function (data, i) {
                    context.index = i;
                    context.dataItem = data;
                    return renderItem();
                });

            } else {
                return renderItem();
            }
        },

        // You can stick pre-built template functions here
        templates: {},

        /*
        * For example, someone could do:
        *   jQuery.templates.foo = jQuery.tmpl("some long templating string");
        *   $("#test").append("foo", data);
        */

        // Some easy-to-use pre-built functions
        // You can extend it with your own methods here (like $id, for example)
        tmplFn: {
            html: function () {
                // access to context: jQuery._.context === $context (data, dataItem, index, options)
                // TODO: jQuery._.context seems backwards.
                // Perhaps jQuery.tmplContext would make more sense
                // (e.g. jQuery.tmplContext.html.push("foo"))
                jQuery._.push.apply(jQuery._, arguments);
            },
            text: function () {
                jQuery._.push.apply(jQuery._, jQuery.map(arguments, function (str) {
                    return document.createTextNode(str).nodeValue;
                }));
            }
        },

        // A store for the templating string being built
        // NOTE: How will this work if we're doing a template in a template?
        // NOTE: Not actually a problem when using rendered() callback to create
        //       a nested template since outer template is complete by then.
        //       Only a problem if code within a template renders one, but that
        //       is hard to imagine since that code wouldn't have anywhere to 
        //       append the resulting nodes.
        _: null,

        tmpl: function tmpl(str, data, i, options) {
            // Generate a reusable function that will serve as a template
            // generator (and which will be cached).

            var fn = new Function("jQuery", "$context",
				"var $=jQuery,$data=$context.dataItem,$i=$context.index,_=$._=[];_.context=$context;" +

            // Introduce the data as local variables using with(){}
				"with($.tmplFn){with($data){_.push('" +

            // Convert the template into pure JavaScript
				str.replace(/[\r\t\n]/g, " ")
            // protect single quotes that are within expressions
            // BUG: Fails to protect the first quote in: <%= foo + '%' %>
            // No regex solution I can think of -- may require manual parsing
            // using indexOf, etc (which may be just as fast?)
					.replace(/'(?=[^%]*%})/g, "\t")
            // escape other single quotes
					.split("'").join("\\'")
            // put back protected quotes
					.split("\t").join("'")
            // convert inline expressions into inline parameters
					.replace(/{%=(.+?)%}/g, "',($1),'")
            // convert start of code blocks into end of push()
					.split("{%").join("');")
            // and end of code blocks into start of push()
					.split("%}").join("_.push('")

				+ "');}}return $(_.join('')).get();");

            // Provide some basic currying to the user
            // TODO: When currying, the fact that only the dataItem and index are passed
            // in means we cannot know the value of 'data' although we know 'dataItem' and 'index'
            // Ok? Or, if this api took the array and index, we could know all 3 values.
            // e.g. instead of this:
            //  tmpl(tmpl, foo[i], i)
            // this:
            //  tmpl(tmpl, foo, i)
            // If you intend data to be as is,
            //  tmpl(tmpl, foo) or tmpl(tmpl, foo, null, options)
            return data ? fn(jQuery, { data: null, dataItem: data, index: i, options: options }) : fn;
        }
    });
})(jQuery);


