{
                name: /^\s*data\s/,
                fn: function (content, cmd) {
                    var attr = content.match(/["|'](.*)["|']/)[1];
  • return a function which calls can.data on the element with the attribute name with the current context.

                        return "can.proxy(function(__){" +
  • "var context = this[this.length-1];" + "context = context." + STACKED + " ? context[context.length-2] : context; console.warn(this, context);" +

                        "can.data(can.$(__),'" + attr + "', this.pop()); }, " + CONTEXT_STACK + ")";
                    }
                },
  • Transformation (default)

    This transforms all content to its interpolated equivalent, including calls to the corresponding helpers as applicable. This outputs the render code for almost all cases.

    Definitions

                {
                    name: /^.*$/,
                    fn: function (content, cmd) {
                        var mode = false,
                            result = [];
  • Trim the content so we don't have any trailing whitespace.

                        content = can.trim(content);
  • Determine what the active mode is. # - Truthy section ^ - Falsey section / - Close the prior section else - Inverted section (only exists within a truthy/falsey section)

                        if (content.length && (mode = content.match(/^([#^/]|else$)/))) {
                            mode = mode[0];
                            switch (mode) {
  • Open a new section.

                            case '#':
                            case '^':
                                result.push(cmd.insert + 'can.view.txt(0,\'' + cmd.tagName + '\',' + cmd.status + ',this,function(){ return ');
                                break;
  • Close the prior section.

                            case '/':
                                return {
                                    raw: 'return ___v1ew.join("");}}])}));'
                                };
                                break;
                            }
  • Trim the mode off of the content.

                            content = content.substring(1);
                        }
  • else helpers are special and should be skipped since they don't have any logic aside from kicking off an inverse function.

                        if (mode != 'else') {
                            var args = [],
                                i = 0,
                                hashing = false,
                                arg, split, m;
  • Parse the helper arguments. This needs uses this method instead of a split(/\s/) so that strings with spaces can be correctly parsed.

                            (can.trim(content) + ' ').replace(/((([^\s]+?=)?('.*?'|".*?"))|.*?)\s/g, function (whole, part) {
                                args.push(part);
                            });
  • Start the content render block.

                            result.push('can.Mustache.txt(' + CONTEXT_OBJ + ',' + (mode ? '"' + mode + '"' : 'null') + ',');
  • Iterate through the helper arguments, if there are any.

                            for (; arg = args[i]; i++) {
                                i && result.push(',');
  • Check for special helper arguments (string/number/boolean/hashes).

                                if (i && (m = arg.match(/^(('.*?'|".*?"|[0-9.]+|true|false)|((.+?)=(('.*?'|".*?"|[0-9.]+|true|false)|(.+))))$/))) {
  • Found a native type like string/number/boolean.

                                    if (m[2]) {
                                        result.push(m[0]);
                                    }
  • Found a hash object.

                                    else {
  • Open the hash object.

                                        if (!hashing) {
                                            hashing = true;
                                            result.push('{' + HASH + ':{');
                                        }
  • Add the key/value.

                                        result.push(m[4], ':', m[6] ? m[6] : 'can.Mustache.get("' + m[5].replace(/"/g, '\\"') + '",' + CONTEXT_OBJ + ')');
  • Close the hash if this was the last argument.

                                        if (i == args.length - 1) {
                                            result.push('}}');
                                        }
                                    }
                                }
  • Otherwise output a normal interpolation reference.

                                else {
                                    result.push('can.Mustache.get("' +
  • Include the reference name.

                                    arg.replace(/"/g, '\\"') + '",' +
  • Then the stack of context.

                                    CONTEXT_OBJ +
  • Flag as a helper method to aid performance, if it is a known helper (anything with > 0 arguments).

                                    (i == 0 && args.length > 1 ? ',true' : ',false') + (i > 0 ? ',true' : ',false') + ')');
                                }
                            }
                        }
  • Create an option object for sections of code.

                        mode && mode != 'else' && result.push(',[{_:function(){');
                        switch (mode) {
  • Truthy section

                        case '#':
                            result.push('return ___v1ew.join("");}},{fn:function(' + CONTEXT + '){var ___v1ew = [];');
                            break;
  • If/else section Falsey section

                        case 'else':
                        case '^':
                            result.push('return ___v1ew.join("");}},{inverse:function(' + CONTEXT + '){var ___v1ew = [];');
                            break;
  • Not a section

                        default:
                            result.push(');');
                            break;
                        }
  • Return a raw result if there was a section, otherwise return the default string.

                        result = result.join('');
                        return mode ? {
                            raw: result
                        } : result;
                    }
                }]
            })
        });
  • Add in default scanner helpers first. We could probably do this differently if we didn't 'break' on every match.

        var helpers = can.view.Scanner.prototype.helpers;
        for (var i = 0; i < helpers.length; i++) {
            Mustache.prototype.scanner.helpers.unshift(helpers[i]);
        };
    
    
        Mustache.txt = function (context, mode, name) {
  • Grab the extra arguments to pass to helpers.

            var args = Array.prototype.slice.call(arguments, 3),
  • Create a default options object to pass to the helper.

                options = can.extend.apply(can, [{
                    fn: function () {},
                    inverse: function () {}
                }].concat(mode ? args.pop() : []));
    
    
            var extra = {};
            if (context.context) {
                extra = context.options;
                context = context.context;
            }
  • Check for a registered helper or a helper-like function.

            if (helper = (Mustache.getHelper(name, extra) || (can.isFunction(name) && !name.isComputed && {
                fn: name
            }))) {
  • Use the most recent context as this for the helper.

                var stack = context[STACKED] && context,
                    context = (stack && context[context.length - 1]) || context,
  • Update the options with a function/inverse (the inner templates of a section).

                    opts = {
                        fn: can.proxy(options.fn, context),
                        inverse: can.proxy(options.inverse, context)
                    },
                    lastArg = args[args.length - 1];
  • Store the context stack in the options if one exists

                if (stack) {
                    opts.contexts = stack;
                }
  • Add the hash to options if one exists

                if (lastArg && lastArg[HASH]) {
                    opts.hash = args.pop()[HASH];
                }
                args.push(opts);
  • Call the helper.

                return helper.fn.apply(context, args) || '';
            }
  • if a compute, get the value

            if (can.isFunction(name) && name.isComputed) {
                name = name();
            }
  • An array of arguments to check for truthyness when evaluating sections.

            var validArgs = args.length ? args : [name],
  • Whether the arguments meet the condition of the section.

                valid = true,
                result = [],
                i, helper, argIsObserve, arg;
  • Validate the arguments based on the section mode.

            if (mode) {
                for (i = 0; i < validArgs.length; i++) {
                    arg = validArgs[i];
                    argIsObserve = typeof arg !== 'undefined' && isObserve(arg);
  • Array-like objects are falsey if their length = 0.

                    if (isArrayLike(arg)) {
  • Use .attr to trigger binding on empty lists returned from function

                        if (mode == '#') {
                            valid = valid && !! (argIsObserve ? arg.attr('length') : arg.length);
                        } else if (mode == '^') {
                            valid = valid && !(argIsObserve ? arg.attr('length') : arg.length);
                        }
                    }
  • Otherwise just check if it is truthy or not.

                    else {
                        valid = mode == '#' ? valid && !! arg : mode == '^' ? valid && !arg : valid;
                    }
                }
            }
  • Otherwise interpolate like normal.

            if (valid) {
                switch (mode) {
  • Truthy section.

                case '#':
  • Iterate over arrays

                    if (isArrayLike(name)) {
                        var isObserveList = isObserve(name);
  • Add the reference to the list in the contexts.

                        for (i = 0; i < name.length; i++) {
                            result.push(options.fn.call(name[i], context) || '');
  • Ensure that live update works on observable lists

                            isObserveList && name.attr('' + i);
                        }
                        return result.join('');
                    }
  • Normal case.

                    else {
                        return options.fn.call(name || {}, context) || '';
                    }
                    break;
  • Falsey section.

                case '^':
                    return options.inverse.call(name || {}, context) || '';
                    break;
                default:
  • Add + '' to convert things like numbers to strings. This can cause issues if you are trying to eval on the length but this is the more common case.

                    return '' + (name !== undefined ? name : '');
                    break;
                }
            }
    
            return '';
        };
    
    
        Mustache.get = function (ref, contexts, isHelper, isArgument) {
            var options = contexts.options || {};
            contexts = contexts.context || contexts;
  • Assume the local object is the last context in the stack.

            var obj = contexts[contexts.length - 1],
  • Assume the parent context is the second to last context in the stack.

                context = contexts[contexts.length - 2],
  • Split the reference (like a.b.c) into an array of key names.

                names = ref.split('.'),
                namesLength = names.length,
                value, lastValue, name, i, j,
  • if we walk up and don't find a property, we default to listening on an undefined property of the first context that is an observe

                defaultObserve, defaultObserveName;
  • Handle this references for list iteration: {{.}} or {{this}}

            if (/^\.|this$/.test(ref)) {
  • If context isn't an object, then it was a value passed by a helper so use it as an override.

                if (!/^object|undefined$/.test(typeof context)) {
                    return context || '';
                }
  • Otherwise just return the closest object.

                else {
                    while (value = contexts.pop()) {
                        if (typeof value !== 'undefined') {
                            return value;
                        }
                    }
                    return '';
                }
            }
  • Handle object resolution (like a.b.c).

            else if (!isHelper) {
  • Reverse iterate through the contexts (last in, first out).

                for (i = contexts.length - 1; i >= 0; i--) {
  • Check the context for the reference

                    value = contexts[i];
  • Is the value a compute?

                    if (can.isFunction(value) && value.isComputed) {
                        value = value();
                    }
  • Make sure the context isn't a failed object before diving into it.

                    if (typeof value !== 'undefined' && value !== null) {
                        var isHelper = Mustache.getHelper(ref, options);
                        for (j = 0; j < namesLength; j++) {
  • Keep running up the tree while there are matches.

                            if (typeof value[names[j]] !== 'undefined' && value[names[j]] !== null) {
                                lastValue = value;
                                value = value[name = names[j]];
                            }
  • if there's a name conflict between property and helper property wins

                            else if (isHelper) {
                                return ref;
                            }
  • If it's undefined, still match if the parent is an Observe.

                            else if (isObserve(value)) {
                                defaultObserve = value;
                                defaultObserveName = names[j];
                                lastValue = value = undefined;
                                break;
                            }
                            else {
                                lastValue = value = undefined;
                                break;
                            }
                        }
                    }
  • Found a matched reference.

                    if (value !== undefined) {
                        return Mustache.resolve(value, lastValue, name, isArgument);
                    }
                }
            }
    
            if (defaultObserve &&
  • if there's not a helper by this name and no attribute with this name

            !(Mustache.getHelper(ref) && can.inArray(defaultObserveName, can.Observe.keys(defaultObserve)) === -1)) {
                return defaultObserve.compute(defaultObserveName);
            }
  • Support helpers without arguments, but only if there wasn't a matching data reference. Helpers have priority over local function, see https://github.com/bitovi/canjs/issues/258

            if (value = Mustache.getHelper(ref, options)) {
                return ref;
            } else if (typeof obj !== 'undefined' && obj !== null && can.isFunction(obj[ref])) {
  • Support helper-like functions as anonymous helpers

                return obj[ref];
            }
    
            return '';
        };
    
    
        Mustache.resolve = function (value, lastValue, name, isArgument) {
            if (lastValue && can.isFunction(lastValue[name]) && isArgument) {
                if (lastValue[name].isComputed) {
                    return lastValue[name];
                }
  • Don't execute functions if they are parameters for a helper and are not a can.compute Need to bind it to the original context so that that information doesn't get lost by the helper

                return function () {
                    return lastValue[name].apply(lastValue, arguments);
                };
            } else if (lastValue && can.isFunction(lastValue[name])) {
  • Support functions stored in objects.

                return lastValue[name]();
            }
  • Invoke the length to ensure that Observe.List events fire.

            else if (isObserve(value) && isArrayLike(value) && value.attr('length')) {
                return value;
            }
  • Add support for observes

            else if (lastValue && isObserve(lastValue)) {
                return lastValue.compute(name);
            }
            else if (can.isFunction(value)) {
                return value();
            }
            else {
                return value;
            }
        };
  • Helpers

    Helpers are functions that can be called from within a template. These helpers differ from the scanner helpers in that they execute at runtime instead of during compilation. Custom helpers can be added via can.Mustache.registerHelper, but there are also some built-in helpers included by default. Most of the built-in helpers are little more than aliases to actions that the base version of Mustache simply implies based on the passed in object. Built-in helpers: data - data is a special helper that is implemented via scanning helpers. It hooks up the active element to the active data object: <div {{data "key"}} /> if - Renders a truthy section: {{#if var}} render {{/if}} unless - Renders a falsey section: {{#unless var}} render {{/unless}} each - Renders an array: {{#each array}} render {{this}} {{/each}} * with - Opens a context section: {{#with var}} render {{/with}}

        Mustache._helpers = {};
    
        Mustache.registerHelper = function (name, fn) {
            this._helpers[name] = {
                name: name,
                fn: fn
            };
        };
    
    
        Mustache.getHelper = function (name, options) {
            return options && options.helpers && options.helpers[name] && {
                fn: options.helpers[name]
            } || this._helpers[name]
            for (var i = 0, helper; helper = [i]; i++) {
  • Find the correct helper

                if (helper.name == name) {
                    return helper;
                }
            }
            return null;
        };
    
    
        Mustache.render = function (partial, context) {
  • Make sure the partial being passed in isn't a variable like { partial: "foo.mustache" }

            if (!can.view.cached[partial] && context[partial]) {
                partial = context[partial];
            }
  • Call into can.view.render passing the partial and context.

            return can.view.render(partial, context);
        };
    
        Mustache.renderPartial = function (partial, context, options) {
            return partial.render ? partial.render(context, options) : partial(context, options);
        };
  • The built-in Mustache helpers.

        can.each({
  • Implements the if built-in helper.

            'if': function (expr, options) {
                if ( !! Mustache.resolve(expr)) {
                    return options.fn(options.contexts || this);
                }
                else {
                    return options.inverse(options.contexts || this);
                }
            },
  • Implements the unless built-in helper.

            'unless': function (expr, options) {
                if (!Mustache.resolve(expr)) {
                    return options.fn(options.contexts || this);
                }
            },
  • Implements the each built-in helper.

            'each': function (expr, options) {
                expr = Mustache.resolve(expr);
                if ( !! expr && expr.length) {
                    var result = [];
                    for (var i = 0; i < expr.length; i++) {
                        result.push(options.fn(expr[i]));
                    }
                    return result.join('');
                }
            },
  • Implements the with built-in helper.

            'with': function (expr, options) {
                var ctx = expr;
                expr = Mustache.resolve(expr);
                if ( !! expr) {
                    return options.fn(ctx);
                }
            }
    
        }, function (fn, name) {
            Mustache.registerHelper(name, fn);
        });
  • Registration

    Registers Mustache with can.view.

        can.view.register({
            suffix: "mustache",
    
            contentType: "x-mustache-template",
  • Returns a function that renders the view.

            script: function (id, src) {
                return "can.Mustache(function(_CONTEXT,_VIEW) { " + new Mustache({
                    text: src,
                    name: id
                }).template.out + " })";
            },
    
            renderer: function (id, text) {
                return Mustache({
                    text: text,
                    name: id
                });
            }
        });
    
    
    })(can, this);