Add analytics
[bus.git] / busui / owa / modules / base / js / includes / jquery / jQote2 / external / jquery.tempest.js
blob:a/busui/owa/modules/base/js/includes/jquery/jQote2/external/jquery.tempest.js -> blob:b/busui/owa/modules/base/js/includes/jquery/jQote2/external/jquery.tempest.js
--- a/busui/owa/modules/base/js/includes/jquery/jQote2/external/jquery.tempest.js
+++ b/busui/owa/modules/base/js/includes/jquery/jQote2/external/jquery.tempest.js
@@ -1,1 +1,583 @@
-
+// Tempest jQuery Templating Plugin
+// ================================
+//
+// Copyright (c) 2009 Nick Fitzgerald - http://fitzgeraldnick.com/
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// JSLint
+"use strict";
+
+(function ($) {
+    // PRIVATE VARIABLES
+    var templateCache = {},
+
+        // TAG REGULAR EXPRESSIONS
+        // Overwrite these if you want, but don't blame me when stuff goes wrong.
+        OPEN_VAR_TAG = /\{\{[\s]*?/g,
+        CLOSE_VAR_TAG = /[\s]*?\}\}/g,
+        OPEN_BLOCK_TAG = /\{%[\s]*?/g,
+        CLOSE_BLOCK_TAG = /[\s]*?%\}/g,
+
+        // Probably, you don't want to mess with these, as they are built from
+        // the ones above.
+        VAR_TAG = new RegExp(OPEN_VAR_TAG.source +
+                             "[\\w\\-\\.]+?" +
+                             CLOSE_VAR_TAG.source, "g"),
+
+        BLOCK_TAG = new RegExp(OPEN_BLOCK_TAG.source +
+                               "[\\w]+?(?:[ ]+?[\\w\\-\\.]*?)*?" +
+                               CLOSE_BLOCK_TAG.source, "g"),
+
+        END_BLOCK_TAG = new RegExp(OPEN_BLOCK_TAG.source +
+                                   "end[\\w]*?" +
+                                   CLOSE_BLOCK_TAG.source, "g"),
+
+        // All block tags stored in here. Tags have a couple things to work
+        // with:
+        //
+        // * "args" property is set before render:
+        //     - Example: {% tag_type arg1 arg2 foo bar %}
+        //         * The "args" property would be set to
+        //               ["arg1", "arg2", "foo", "bar"]
+        //           in this example. The tag's render method could look them
+        //           up in the context object, or could do whatever it wanted
+        //           to do with it.
+        // * "subNodes" property which is an array of all the nodes between
+        //   the block tag and it's corresponding {% end... %} tag
+        //     - NOTE: This property is only set for a block if it has the
+        //       "expectsEndTag" property set to true.
+        // * Every block tag should have a "render" method that takes one
+        //   argument: a context object. It should return a string.
+        BLOCK_NODES = {
+            "for": {
+                expectsEndTag: true,
+                render: function (context) {
+                    var args = this.args,
+                    subNodes = this.subNodes,
+                    renderedNodes = [],
+                    i, itemName, arrName, arr, forContext, tmpObj;
+
+                    if (args.length === 3 && args[1] === "in") {
+                        itemName = args[0];
+                        arrName = args[2];
+                        arr = getValFromObj(arrName, context);
+
+                        for (i = 0; i < arr.length; i++) {
+                            tmpObj = {};
+                            tmpObj[itemName] = arr[i];
+                            tmpObj._index = i;
+                            forContext = $.extend(true, {}, context, tmpObj);
+
+                            $.each(subNodes, function (j, node) {
+                                renderedNodes.push(
+                                    node.render(forContext)
+                                );
+                            });
+                        }
+
+                        return renderedNodes.join("");
+                    }
+                    else {
+                        throw new TemplateSyntaxError(
+                            "Bad for tag syntax. Use {% for <item> in <array> %}"
+                        );
+                    }
+                }
+            },
+            "if": {
+                expectsEndTag: true,
+                render: function (context) {
+                    var rendered_nodes = [],
+                        subNodes = this.subNodes;
+
+                    // Check the truthiness of the argument.
+                    if (!!context[this.args[0]]) {
+                        $.each(subNodes, function (i, node) {
+                            rendered_nodes.push(node.render(context));
+                        });
+                    }
+
+                    return rendered_nodes.join("");
+                }
+            }
+        },
+
+        // Base text node object for prototyping.
+        baseTextNode = {
+            render: function (context) {
+                return this.text || "";
+            }
+        },
+
+        // Base variable node object for prototyping.
+        baseVarNode = {
+            render: function (context) {
+                var val = context[this.name] === undefined ?
+                    "" :
+                    context[this.name];
+                if (val === "" && this.name.search(/\./) !== -1) {
+                    return getValFromObj(this.name, context);
+                }
+                return cleanVal(val);
+            }
+        };
+
+    // CUSTOM ERRORS
+
+    function TemplateSyntaxError(message) {
+        if (!(this instanceof TemplateSyntaxError)) {
+            return new TemplateSyntaxError(message);
+        }
+        this.message = message;
+        return this;
+    }
+    TemplateSyntaxError.prototype = new SyntaxError();
+    TemplateSyntaxError.prototype.name = "TemplateSyntaxError";
+
+    // PRIVATE FUNCTIONS
+
+    // Some browsers don't return the grouped part of the RegExp with the array,
+    // so we must accomodate them.
+    var split = (function () {
+        if ("abc".split(/(b)/).length === 3) {
+            return function (str, delimiter) {
+                return String.prototype
+                             .split
+                             .call(str, delimiter);
+            };
+        } else {
+            return function (str, delimiter) {
+                if (Object.prototype
+                          .toString
+                          .call(delimiter) === "[object RegExp]") {
+                    var regex = delimiter.ignoreCase ?
+                        new RegExp(delimiter.source, "gi") :
+                        new RegExp(delimiter.source, "g"),
+                    match,
+                    match_str = "",
+                    arr = [],
+                    i,
+                    len = str.length;
+
+                    for (i = 0; i < len; i++) {
+                        match_str += str.charAt(i);
+                        match = match_str.match(regex);
+                        if (match !== null && match.length > 0) {
+                            arr.push(match_str.replace(match[0], ""));
+                            arr.push(match[0]);
+                            match_str = "";
+                        }
+                    }
+
+                    if (match_str !== "") {
+                        arr.push(match_str);
+                    }
+
+                    return arr;
+                } else {
+                    return String.prototype
+                                 .split
+                                 .call(str, delimiter);
+                }
+            };
+        }
+    }());
+
+    function isBlockTag(token) {
+        return token.search(BLOCK_TAG) !== -1;
+    }
+    function isEndTag(token) {
+        return token.search(END_BLOCK_TAG) !== -1;
+    }
+    function isVarTag(token) {
+        return token.search(VAR_TAG) !== -1;
+    }
+
+    function strip(str) {
+        return str.replace(/^[\s]+/, "").replace(/[\s]+$/, "");
+    }
+
+    // Clean the passed value the best we can.
+    function cleanVal(val) {
+        if (val instanceof $) {
+            return jQueryToString(val);
+        } else if (val !== null && !isArray(val) && typeof(val) === "object") {
+            if (typeof(val.toHTML) === "function") {
+                return cleanVal(val.toHTML());
+            } else {
+                return val.toString();
+            }
+        } else {
+            return val;
+        }
+    }
+
+    // Traverse a path of an obj from a string representation,
+    // for example "object.child.attr".
+    function getValFromObj(str, obj) {
+        var path = split(str, "."),
+            val = obj[path[0]],
+            i;
+        for (i = 1; i < path.length; i++) {
+            // Return an empty string if the lookup ever hits undefined.
+            if (val !== undefined) {
+                val = val[path[i]];
+            } else {
+                return "";
+            }
+        }
+
+        // Make sure the last piece did not end up undefined.
+        val = val === undefined ? "" : val;
+        return cleanVal(val);
+    }
+
+    // Hack to get the HTML of a jquery object as a string.
+    function jQueryToString(jq) {
+        return $(document.createElement("div")).append(jq).html();
+    }
+
+    // Make a new copy of a given object.
+    function makeObj(obj) {
+        if (obj === undefined) {
+            return obj;
+        }
+        var O = function () {};
+        O.prototype = obj;
+        return new O();
+    }
+
+    // Return an array of key/template pairs.
+    function storedTemplates() {
+        var cache = [];
+        $.each(templateCache, function (key, templ) {
+            cache.push([ key, templ ]);
+        });
+        return cache;
+    }
+
+    // Determine if the string is a key to a stored template or a
+    // one-time-use template.
+    function chooseTemplate(str) {
+        return typeof templateCache[str] === "string" ?
+            templateCache[str] :
+            str;
+    }
+
+    // Return true if (and only if) an object is an array.
+    function isArray(objToTest) {
+        return Object.prototype
+                     .toString
+                     .apply(objToTest) === "[object Array]";
+    }
+
+    // Call a rendering function on arrays of objects or just a single
+    // object seamlessly.
+    function renderEach(data, f) {
+        return isArray(data) ?
+            $.each(data, f) :
+            f(0, data);
+    }
+
+    // Split a template in to tokens which will eventually be converted to
+    // nodes and then rendered.
+    function tokenize(templ) {
+        return (function (arr) {
+            var tokens = [];
+            for (i = 0; i < arr.length; i++) {
+                (function (token) {
+                     return token === "" ?
+                        null :
+                        tokens.push(token);
+                }(arr[i]));
+            }
+            return tokens;
+        }(split(templ, new RegExp("(" + VAR_TAG.source + "|" +
+                                  BLOCK_TAG.source + "|" +
+                                  END_BLOCK_TAG.source + ")"))));
+    }
+
+    // "Lisp in C's clothing." - Douglas Crockford
+    function cdr(arr) {
+        return arr.slice(1);
+    }
+
+    // Array.push changes the original array in place and returns the new
+    // length of the array rather than the the actual array itself. This
+    // makes it unchainable, which is ridiculous.
+    function append(item, list) {
+        return list.concat([item]);
+    }
+
+    // Take a token and create a variable node from it.
+    function makeVarNode(token) {
+        var node = makeObj(baseVarNode);
+        node.name = strip(token.replace(OPEN_VAR_TAG, "")
+                               .replace(CLOSE_VAR_TAG, ""));
+        return node;
+    }
+
+    // Take a token and create a text node from it.
+    function makeTextNode(token) {
+        var node = makeObj(baseTextNode);
+        node.text = token;
+        return node;
+    }
+
+    // A recursive function that terminates either when all tokens have
+    // been converted to nodes or an end-block tag is found.
+    function makeNodes(tokens) {
+        return (function (nodes, tokens) {
+            var token = tokens[0];
+            return tokens.length === 0 ?
+                       [nodes, [], true] :
+                   isEndTag(token) ?
+                       [nodes, cdr(tokens)] :
+                   isVarTag(token) ?
+                       arguments.callee(append(makeVarNode(token), nodes), cdr(tokens)) :
+                   isBlockTag(token) ?
+                       makeBlockNode(nodes, tokens, arguments.callee) :
+                   // Else assume it is a text node.
+                       arguments.callee(append(makeTextNode(token), nodes), cdr(tokens));
+
+        }([], tokens));
+    }
+
+    // Split a block tags contents in to an array of bits that contains the
+    // type of block node, and any arguments that were passed to the block
+    // node if they exist.
+    function makeBits(blockToken) {
+        return (function (bits, split) {
+            // Remove empty strings and strip whitespace.
+            for (i = 0; i < split.length; i++) {
+                (function (bit) {
+                    return bit === "" ? null : bits.push(bit);
+                }(strip(split[i])));
+            }
+            return bits;
+        }([], split(blockToken.replace(OPEN_BLOCK_TAG, "")
+                              .replace(CLOSE_BLOCK_TAG, ""),
+                   /[\s]+?/)));
+    }
+
+    // Create a block tag's node by hijacking the "makeNodes" function
+    // until an end-block is found.
+    function makeBlockNode(nodes, tokens, f) {
+        // Remove the templating syntax and split the type of block tag and
+        // its arguments.
+        var bits = makeBits(tokens[0]),
+
+            // The type of block tag is the first of the bits, the rest
+            // (if present) are args
+            type = bits[0],
+            args = cdr(bits),
+
+            // Make the node from the set of block tags that Tempest knows
+            // about.
+            node = makeObj(BLOCK_NODES[type]),
+            resultsArray;
+
+        // Ensure that the type of block tag is one that is defined in
+        // BLOCK_NODES
+        if (node === undefined) {
+            throw new TemplateSyntaxError("Unknown Block Tag.");
+        }
+
+        node.args = args;
+        tokens = cdr(tokens);
+
+        if (node.expectsEndTag === true) {
+            resultsArray = makeNodes(tokens);
+
+            if (resultsArray[2] !== undefined) {
+                // The third item in the array returned by makeNodes is
+                // only defined if the last of the tokens was made in to a
+                // node and it wasn't an end-block tag.
+                throw new TemplateSyntaxError(
+                    "A block tag was expecting an ending tag but it was not found."
+                );
+            }
+            node.subNodes = resultsArray[0];
+            tokens = resultsArray[1];
+        }
+
+        // Add the newly created node to the nodes list.
+        nodes = append(node, nodes);
+
+        // Continue where we were before the block node.
+        return f(nodes, tokens);
+    }
+
+    // Return the template rendered with the given object(s) as a jQuery
+    // object.
+    function renderToJQ(str, objects) {
+        var template = chooseTemplate(str),
+            lines = [];
+
+        renderEach(objects, function (i, obj) {
+            var resultsArray = makeNodes(tokenize(template), obj),
+                nodes = resultsArray[0];
+
+            // Check for tokens left over in the results array, this means
+            // that not all tokens were rendered because there are more
+            // end-block tagss than block tags that expect an end.
+            if (resultsArray[1].length !== 0) {
+                throw new TemplateSyntaxError(
+                    "An unexpected end tag was found."
+                );
+            }
+
+            // Render each node and push it to the lines.
+            $.each(nodes, function (i, node) {
+                lines.push(node.render(obj));
+            });
+        });
+
+        // Return the joined templates as jQuery objects if it appears to start
+        // with an HTML tag, otherwise just return the string itself.
+        return (function (str) {
+            return str.charAt(0) === "<" ?
+                $(str) :
+                str;
+        }(strip(lines.join(""))));
+    }
+
+    // EXTEND JQUERY OBJECT
+    $.extend({
+        tempest: function () {
+            var args = arguments;
+
+            if (args.length === 0) {
+
+                // Return key/template pairs of all stored templates.
+                return storedTemplates();
+
+            } else if (args.length === 2 &&
+                       typeof(args[0]) === "string" &&
+                       typeof(args[1]) === "object") {
+
+                // Render the supplied template (args[0], template name of
+                // existing or one-time-use template) with the context data
+                // (args[1]).
+                return renderToJQ(args[0], args[1]);
+
+            } else if (args.length === 1 && typeof(args[0]) === "string") {
+
+                // Template getter.
+                return templateCache[args[0]];
+
+            } else if (args.length === 2 &&
+                       typeof(args[0]) === "string" &&
+                       typeof(args[1]) === "string") {
+
+                // Template setter.
+                templateCache[args[0]] = args[1].replace(/^\s+/g, "")
+                                                .replace(/\s+$/g, "")
+                                                .replace(/[\n\r]+/g, "");
+                return templateCache[args[0]];
+
+            } else {
+
+                // Raise an exception because the arguments did not match the
+                // API.
+                throw new TypeError(
+                    "jQuery.tempest can't handle the given arguments."
+                );
+
+            }
+        }
+    });
+
+    // Extend jQuery("selector").tempest using the existing jQuery.tempest API.
+    $.fn.tempest = function() {
+        var args = Array.prototype.slice.call(arguments, 0);
+        var f = null;
+
+        if (args.length == 2 &&
+            typeof args[0] == "string" &&
+            typeof args[1] == "object") {
+            // Inserts the result of rendering the specified template on the
+            // specified data into the set of matched elements.
+            f = function () {
+                $(this).html($.tempest(args[0], args[1]));
+            };
+        } else if (args.length == 3 &&
+                   typeof args[0] == "string" &&
+                   typeof args[1] == "string" &&
+                   typeof args[2] == "object") {
+            // Calls the appropriate jQuery function, passing it the result of
+            // rendering the given template on the data provided.
+            f = function () {
+                $(this)[args[0]]($.tempest(args[1], args[2]));
+            };
+        } else {
+            throw new TypeError([
+                "jQuery(selector).tempest was passed the wrong number or type",
+                "of arguments. Received " + args
+            ].join(" "));
+        }
+
+        return this.each(f);
+    };
+
+    // EXPOSE BLOCK_NODES OBJECT TO ALLOW EXTENSION WITH CUSTOM TAGS
+    $.tempest.tags = BLOCK_NODES;
+
+    // EXPOSE PRIVATE FUNCTIONS FOR TESTING
+    if (window.testTempestPrivates === true) {
+        $.tempest._test = {};
+
+        // Make it easier to attach the private methods methods to the public
+        // object.
+        function a(name, fn) {
+            $.tempest._test[name] = fn;
+        }
+        a("isBlockTag", isBlockTag);
+        a("isEndTag", isEndTag);
+        a("isVarTag", isVarTag);
+        a("cleanVal", cleanVal);
+        a("getValFromObj", getValFromObj);
+        a("jQueryToString", jQueryToString);
+        a("makeObj", makeObj);
+        a("storedTemplates", storedTemplates);
+        a("chooseTemplate", chooseTemplate);
+        a("isArray", isArray);
+        a("renderEach", renderEach);
+        a("tokenize", tokenize);
+        a("cdr", cdr);
+        a("append", append);
+        a("makeVarNode", makeVarNode);
+        a("makeTextNode", makeTextNode);
+        a("makeNodes", makeNodes);
+        a("makeBits", makeBits);
+        a("makeBlockNode", makeBlockNode);
+        a("renderToJQ", renderToJQ);
+        a("strip", strip);
+    }
+
+    // GET ALL TEXTAREA TEMPLATES ON READY
+    $(document).ready(function () {
+        $(".tempest-template").each(function (obj) {
+            templateCache[$(this).attr('title')] = strip(($(this).val() || $(this).html()).replace(/[\n\r]+/g, " "));
+            $(this).remove();
+        });
+    });
+}(jQuery));
+