{
                            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;" +

                                    "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]+\.?[0-9]*|true|false)|((.+?)=(('.*?'|".*?"|[0-9]+\.?[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.indexOf('\\.') == -1
  • Reference doesn't contain escaped periods

                ? ref.split('.')
  • Reference contains escaped periods (a.b\c.foo == `a["b.c"].foo)

                : (function() {
                    var names = [],
                        last = 0;
                    ref.replace(/(\\)?\./g, function($0, $1, index) {
                        if (!$1) {
                            names.push(ref.slice(last, index).replace(/\\\./g, '.'));
                            last = index + $0.length;
                        }
                    });
                    names.push(ref.slice(last).replace(/\\\./g, '.'));
                    return names;
                })(),
                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);
                };
            }
  • Support attributes on compute objects

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

            else if (lastValue && can.isFunction(lastValue[name])) {
                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 && isArrayLike(expr)) {
                        if (isObserve(expr) && expr.attr('length')) {
                            return can.view.lists && can.view.lists(expr, function(item) {
                                return options.fn(item);
                            });
                        } else {
                            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
                        });
                }
            });
    
        return can;
    })(can);