msenkpiel
11/17/2011 - 2:17 PM

icanhaz updated with mustache 0.4.0-dev

icanhaz updated with mustache 0.4.0-dev

/*!
  ICanHaz.js version 0.9 -- by @HenrikJoreteg
  More info at: http://icanhazjs.com
*/
(function($) {
    /*
      mustache.js — Logic-less templates in JavaScript

      See http://mustache.github.com/ for more info.
    */
    var Mustache = function() {
        var regexCache = {};
        var Renderer = function() {};
        Renderer.prototype = {
            otag: "{{",
            ctag: "}}",
            pragmas: {},
            buffer: [],
            pragmas_implemented: {
                "IMPLICIT-ITERATOR": true
            },
            context: {},
            render: function(template, context, partials, in_recursion) {
                // reset buffer & set context
                if (!in_recursion) {
                    this.context = context;
                    this.buffer = []; // TODO: make this non-lazy
                }
                // fail fast
                if (!this.includes("", template)) {
                    if (in_recursion) {
                        return template;
                    } else {
                        this.send(template);
                        return;
                    }
                }
                // get the pragmas together
                template = this.render_pragmas(template);
                // render the template
                var html = this.render_section(template, context, partials);
                // render_section did not find any sections, we still need to render the tags
                if (html === false) {
                    html = this.render_tags(template, context, partials, in_recursion);
                }
                if (in_recursion) {
                    return html;
                } else {
                    this.sendLines(html);
                }
            },

            /*
              Sends parsed lines
            */
            send: function(line) {
                if (line !== "") {
                    this.buffer.push(line);
                }
            },
            sendLines: function(text) {
                if (text) {
                    var lines = text.split("\n");
                    for (var i = 0; i < lines.length; i++) {
                        this.send(lines[i]);
                    }
                }
            },

            /*
              Looks for %PRAGMAS
            */
            render_pragmas: function(template) {
                // no pragmas
                if (!this.includes("%", template)) {
                    return template;
                }
                var that = this;
                var regex = this.getCachedRegex("render_pragmas", function(otag, ctag) {
                    return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
                });
                return template.replace(regex, function(match, pragma, options) {
                    if (!that.pragmas_implemented[pragma]) {
                        throw ({
                            message: "This implementation of mustache doesn't understand the '" + pragma + "' pragma"
                        });
                    }
                    that.pragmas[pragma] = {};
                    if (options) {
                        var opts = options.split("=");
                        that.pragmas[pragma][opts[0]] = opts[1];
                    }
                    return "";
                    // ignore unknown pragmas silently
                });
            },
            /*
              Tries to find a partial in the curent scope and render it
            */
            render_partial: function(name, context, partials) {
                name = this.trim(name);
                if (!partials || partials[name] === undefined) {
                    throw ({
                        message: "unknown_partial '" + name + "'"
                    });
                }
                if (typeof(context[name]) != "object") {
                    return this.render(partials[name], context, partials, true);
                }
                return this.render(partials[name], context[name], partials, true);
            },
            /*
              Renders inverted (^) and normal (#) sections
            */
            render_section: function(template, context, partials) {
                if (!this.includes("#", template) && !this.includes("^", template)) {
                    // did not render anything, there were no sections
                    return false;
                }
                var that = this;
                var regex = this.getCachedRegex("render_section", function(otag, ctag) {
                    // This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
                    return new RegExp("^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1)
                    otag + // {{
                    "(\\^|\\#)\\s*(.+)\\s*" + //  #foo (# == $2, foo == $3)
                    ctag + // }}
                    "\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped
                    otag + // {{
                    "\\/\\s*\\3\\s*" + //  /foo (backreference to the opening tag).
                    ctag + // }}
                    "\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped.
                    "g");
                });
                // for each {{#foo}}{{/foo}} section do...
                return template.replace(regex, function(match, before, type, name, content, after) {
                    // before contains only tags, no sections
                    var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",


                        // after may contain both sections and tags, so use full rendering function
                        renderedAfter = after ? that.render(after, context, partials, true) : "",


                        // will be computed below
                        renderedContent, value = that.find(name, context);
                    if (type === "^") { // inverted section
                        if (!value || that.is_array(value) && value.length === 0) {
                            // false or empty list, render it
                            renderedContent = that.render(content, context, partials, true);
                        } else {
                            renderedContent = "";
                        }
                    } else if (type === "#") { // normal section
                        if (that.is_array(value)) { // Enumerable, Let's loop!
                            renderedContent = that.map(value, function(row) {
                                return that.render(content, that.create_context(row), partials, true);
                            }).join("");
                        } else if (that.is_object(value)) { // Object, Use it as subcontext!
                            renderedContent = that.render(content, that.create_context(value), partials, true);
                        } else if (typeof value === "function") {
                            // higher order section
                            renderedContent = value.call(context, content, function(text) {
                                return that.render(text, context, partials, true);
                            });
                        } else if (value) { // boolean section
                            renderedContent = that.render(content, context, partials, true);
                        } else {
                            renderedContent = "";
                        }
                    }
                    return renderedBefore + renderedContent + renderedAfter;
                });
            },
            /*
              Replace {{foo}} and friends with values from our view
            */
            render_tags: function(template, context, partials, in_recursion) {
                // tit for tat
                var that = this;
                var new_regex = function() {
                    return that.getCachedRegex("render_tags", function(otag, ctag) {
                        return new RegExp(otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + ctag + "+", "g");
                    });
                };
                var regex = new_regex();
                var tag_replace_callback = function(match, operator, name) {
                    switch (operator) {
                    case "!":
                        // ignore comments
                        return "";
                    case "=":
                        // set new delimiters, rebuild the replace regexp
                        that.set_delimiters(name);
                        regex = new_regex();
                        return "";
                    case ">":
                        // render partial
                        return that.render_partial(name, context, partials);
                    case "{":
                        // the triple mustache is unescaped
                        return that.find(name, context);
                    default:
                        // escape the value
                        return that.escape(that.find(name, context));
                    }
                };
                var lines = template.split("\n");
                for (var i = 0; i < lines.length; i++) {
                    lines[i] = lines[i].replace(regex, tag_replace_callback, this);
                    if (!in_recursion) {
                        this.send(lines[i]);
                    }
                }
                if (in_recursion) {
                    return lines.join("\n");
                }
            },
            set_delimiters: function(delimiters) {
                var dels = delimiters.split(" ");
                this.otag = this.escape_regex(dels[0]);
                this.ctag = this.escape_regex(dels[1]);
            },
            escape_regex: function(text) {
                // thank you Simon Willison
                if (!arguments.callee.sRE) {
                    var specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
                    arguments.callee.sRE = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
                }
                return text.replace(arguments.callee.sRE, '\\$1');
            },
            /*
              find `name` in current `context`. That is find me a value
              from the view object
            */
            find: function(name, context) {
                name = this.trim(name);
                // Checks whether a value is thruthy or false or 0

                function is_kinda_truthy(bool) {
                    return bool === false || bool === 0 || bool;
                }
                var value;
                // check for dot notation eg. foo.bar
                if (name.match(/([a-z_]+)\./ig)) {
                    var childValue = this.walk_context(name, context);
                    if (is_kinda_truthy(childValue)) {
                        value = childValue;
                    }
                } else {
                    if (is_kinda_truthy(context[name])) {
                        value = context[name];
                    } else if (is_kinda_truthy(this.context[name])) {
                        value = this.context[name];
                    }
                }
                if (typeof value === "function") {
                    return value.apply(context);
                }
                if (value !== undefined) {
                    return value;
                }
                // silently ignore unkown variables
                return "";
            },
            walk_context: function(name, context) {
                var path = name.split('.');
                // if the var doesn't exist in current context, check the top level context
                var value_context = (context[path[0]] != undefined) ? context : this.context;
                var value = value_context[path.shift()];
                while (value != undefined && path.length > 0) {
                    value_context = value;
                    value = value[path.shift()];
                }
                // if the value is a function, call it, binding the correct context
                if (typeof value === "function") {
                    return value.apply(value_context);
                }
                return value;
            },
            // Utility methods
            /* includes tag */
            includes: function(needle, haystack) {
                return haystack.indexOf(this.otag + needle) != -1;
            },
            /*
              Does away with nasty characters
            */
            escape: function(s) {
                s = String(s === null ? "" : s);
                return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
                    switch (s) {
                    case "&":
                        return "&amp;";
                    case '"':
                        return '&quot;';
                    case "'":
                        return '&#39;';
                    case "<":
                        return "&lt;";
                    case ">":
                        return "&gt;";
                    default:
                        return s;
                    }
                });
            },
            // by @langalex, support for arrays of strings
            create_context: function(_context) {
                if (this.is_object(_context)) {
                    return _context;
                } else {
                    var iterator = ".";
                    if (this.pragmas["IMPLICIT-ITERATOR"]) {
                        iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
                    }
                    var ctx = {};
                    ctx[iterator] = _context;
                    return ctx;
                }
            },
            is_object: function(a) {
                return a && typeof a == "object";
            },
            is_array: function(a) {
                return Object.prototype.toString.call(a) === '[object Array]';
            },
            /*
              Gets rid of leading and trailing whitespace
            */
            trim: function(s) {
                return s.replace(/^\s*|\s*$/g, "");
            },
            /*
              Why, why, why? Because IE. Cry, cry cry.
            */
            map: function(array, fn) {
                if (typeof array.map == "function") {
                    return array.map(fn);
                } else {
                    var r = [];
                    var l = array.length;
                    for (var i = 0; i < l; i++) {
                        r.push(fn(array[i]));
                    }
                    return r;
                }
            },
            getCachedRegex: function(name, generator) {
                var byOtag = regexCache[this.otag];
                if (!byOtag) {
                    byOtag = regexCache[this.otag] = {};
                }
                var byCtag = byOtag[this.ctag];
                if (!byCtag) {
                    byCtag = byOtag[this.ctag] = {};
                }
                var regex = byCtag[name];
                if (!regex) {
                    regex = byCtag[name] = generator(this.otag, this.ctag);
                }
                return regex;
            }
        };
        return ({
            name: "mustache.js",
            version: "0.4.0-dev",
            /*
              Turns a template and view into HTML
            */
            to_html: function(template, view, partials, send_fun) {
                var renderer = new Renderer();
                if (send_fun) {
                    renderer.send = send_fun;
                }
                renderer.render(template, view || {}, partials);
                if (!send_fun) {
                    return renderer.buffer.join("\n");
                }
            }
        });
    }();

    /*!
      ICanHaz.js -- by @HenrikJoreteg
    */
    /*global jQuery  */

    function ICanHaz() {
        var self = this;
        self.VERSION = "0.9";
        self.templates = {};
        self.partials = {};

        // public function for adding templates
        // We're enforcing uniqueness to avoid accidental template overwrites.
        // If you want a different template, it should have a different name.
        self.addTemplate = function(name, templateString) {
            if (self[name]) throw "Invalid name: " + name + ".";
            if (self.templates[name]) throw "Template \" + name + \" exists";

            self.templates[name] = templateString;
            self[name] = function(data, raw) {
                data = data || {};
                var result = Mustache.to_html(self.templates[name], data, self.partials);
                return raw ? result : $(result);
            };
        };

        // public function for adding partials
        self.addPartial = function(name, templateString) {
            if (self.partials[name]) {
                throw "Partial \" + name + \" exists";
            } else {
                self.partials[name] = templateString;
            }
        };

        // grabs templates from the DOM and caches them.
        // Loop through and add templates.
        // Whitespace at beginning and end of all templates inside <script> tags will
        // be trimmed. If you want whitespace around a partial, add it in the parent,
        // not the partial. Or do it explicitly using <br/> or &nbsp;
        self.grabTemplates = function() {
            $('script[type="text/html"]').each(function(a, b) {
                var script = $((typeof a === 'number') ? b : a),
                    // Zepto doesn't bind this
                    text = (''.trim) ? script.html().trim() : $.trim(script.html());

                self[script.hasClass('partial') ? 'addPartial' : 'addTemplate'](script.attr('id'), text);
                script.remove();
            });
        };

        // clears all retrieval functions and empties caches
        self.clearAll = function() {
            for (var key in self.templates) {
                delete self[key];
            }
            self.templates = {};
            self.partials = {};
        };

        self.refresh = function() {
            self.clearAll();
            self.grabTemplates();
        };
    }

    window.ich = new ICanHaz();

    // init itself on document ready
    $(function() {
        ich.grabTemplates();
    });
})(window.jQuery || window.Zepto);