can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
503 lines (502 loc) • 20.7 kB
JavaScript
/*!
* CanJS - 2.3.34
* http://canjs.com/
* Copyright (c) 2018 Bitovi
* Mon, 30 Apr 2018 20:56:51 GMT
* Licensed MIT
*/
/*can@2.3.34#view/mustache/mustache*/
define([
'can/util/library',
'can/view/scope',
'can/view',
'can/scanner',
'can/compute',
'can/render',
'can/view/bindings'
], function (can) {
can.view.ext = '.mustache';
var SCOPE = 'scope', HASH = '___h4sh', CONTEXT_OBJ = '{scope:' + SCOPE + ',options:options}', SPECIAL_CONTEXT_OBJ = '{scope:' + SCOPE + ',options:options, special: true}', ARG_NAMES = SCOPE + ',options', argumentsRegExp = /((([^'"\s]+?=)?('.*?'|".*?"))|.*?)\s/g, literalNumberStringBooleanRegExp = /^(('.*?'|".*?"|[0-9]+\.?[0-9]*|true|false|null|undefined)|((.+?)=(('.*?'|".*?"|[0-9]+\.?[0-9]*|true|false)|(.+))))$/, makeLookupLiteral = function (type) {
return '{get:"' + type.replace(/"/g, '\\"') + '"}';
}, isLookup = function (obj) {
return obj && typeof obj.get === 'string';
}, isObserveLike = function (obj) {
return obj instanceof can.Map || obj && !!obj._get;
}, isArrayLike = function (obj) {
return obj && obj.splice && typeof obj.length === 'number';
}, makeConvertToScopes = function (original, scope, options) {
var originalWithScope = function (ctx, opts) {
return original(ctx || scope, opts);
};
return function (updatedScope, updatedOptions) {
if (updatedScope !== undefined && !(updatedScope instanceof can.view.Scope)) {
updatedScope = scope.add(updatedScope);
}
if (updatedOptions !== undefined && !(updatedOptions instanceof can.view.Options)) {
updatedOptions = options.add(updatedOptions);
}
return originalWithScope(updatedScope, updatedOptions || options);
};
};
var Mustache = function (options, helpers) {
if (this.constructor !== Mustache) {
var mustache = new Mustache(options);
return function (data, options) {
return mustache.render(data, options);
};
}
if (typeof options === 'function') {
this.template = { fn: options };
return;
}
can.extend(this, options);
this.template = this.scanner.scan(this.text, this.name);
};
can.Mustache = can.global.Mustache = Mustache;
Mustache.prototype.render = function (data, options) {
if (!(data instanceof can.view.Scope)) {
data = new can.view.Scope(data || {});
}
if (!(options instanceof can.view.Options)) {
options = new can.view.Options(options || {});
}
options = options || {};
return this.template.fn.call(data, data, options);
};
can.extend(Mustache.prototype, {
scanner: new can.view.Scanner({
text: {
start: '',
scope: SCOPE,
options: ',options: options',
argNames: ARG_NAMES
},
tokens: [
[
'returnLeft',
'{{{',
'{{[{&]'
],
[
'commentFull',
'{{!}}',
'^[\\s\\t]*{{!.+?}}\\n'
],
[
'commentLeft',
'{{!',
'(\\n[\\s\\t]*{{!|{{!)'
],
[
'escapeFull',
'{{}}',
'(^[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}$)',
function (content) {
return {
before: /^\n.+?\n$/.test(content) ? '\n' : '',
content: content.match(/\{\{(.+?)\}\}/)[1] || ''
};
}
],
[
'escapeLeft',
'{{'
],
[
'returnRight',
'}}}'
],
[
'right',
'}}'
]
],
helpers: [
{
name: /^>[\s]*\w*/,
fn: function (content, cmd) {
var templateName = can.trim(content.replace(/^>\s?/, '')).replace(/["|']/g, '');
return 'can.Mustache.renderPartial(\'' + templateName + '\',' + ARG_NAMES + ')';
}
},
{
name: /^\s*data\s/,
fn: function (content, cmd) {
var attr = content.match(/["|'](.*)["|']/)[1];
return 'can.proxy(function(__){' + 'can.data(can.$(__),\'' + attr + '\', this.attr(\'.\')); }, ' + SCOPE + ')';
}
},
{
name: /\s*\(([\$\w]+)\)\s*->([^\n]*)/,
fn: function (content) {
var quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/, parts = content.match(quickFunc);
return 'can.proxy(function(__){var ' + parts[1] + '=can.$(__);with(' + SCOPE + '.attr(\'.\')){' + parts[2] + '}}, this);';
}
},
{
name: /^.*$/,
fn: function (content, cmd) {
var mode = false, result = {
content: '',
startTxt: false,
startOnlyTxt: false,
end: false
};
content = can.trim(content);
if (content.length && (mode = content.match(/^([#^/]|else$)/))) {
mode = mode[0];
switch (mode) {
case '#':
case '^':
if (cmd.specialAttribute) {
result.startOnlyTxt = true;
} else {
result.startTxt = true;
result.escaped = 0;
}
break;
case '/':
result.end = true;
result.content += 'return ___v1ew.join("");}}])';
return result;
}
content = content.substring(1);
}
if (mode !== 'else') {
var args = [], hashes = [], i = 0, m;
result.content += 'can.Mustache.txt(\n' + (cmd.specialAttribute ? SPECIAL_CONTEXT_OBJ : CONTEXT_OBJ) + ',\n' + (mode ? '"' + mode + '"' : 'null') + ',';
(can.trim(content) + ' ').replace(argumentsRegExp, function (whole, arg) {
if (i && (m = arg.match(literalNumberStringBooleanRegExp))) {
if (m[2]) {
args.push(m[0]);
} else {
hashes.push(m[4] + ':' + (m[6] ? m[6] : makeLookupLiteral(m[5])));
}
} else {
args.push(makeLookupLiteral(arg));
}
i++;
});
result.content += args.join(',');
if (hashes.length) {
result.content += ',{' + HASH + ':{' + hashes.join(',') + '}}';
}
}
if (mode && mode !== 'else') {
result.content += ',[\n\n';
}
switch (mode) {
case '^':
case '#':
result.content += '{fn:function(' + ARG_NAMES + '){var ___v1ew = [];';
break;
case 'else':
result.content += 'return ___v1ew.join("");}},\n{inverse:function(' + ARG_NAMES + '){\nvar ___v1ew = [];';
break;
default:
result.content += ')';
break;
}
if (!mode) {
result.startTxt = true;
result.end = true;
}
return result;
}
}
]
})
});
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 (scopeAndOptions, mode, name) {
var scope = scopeAndOptions.scope, options = scopeAndOptions.options, args = [], helperOptions = {
fn: function () {
},
inverse: function () {
}
}, hash, context = scope.attr('.'), getHelper = true, helper;
for (var i = 3; i < arguments.length; i++) {
var arg = arguments[i];
if (mode && can.isArray(arg)) {
helperOptions = can.extend.apply(can, [helperOptions].concat(arg));
} else if (arg && arg[HASH]) {
hash = arg[HASH];
for (var prop in hash) {
if (isLookup(hash[prop])) {
hash[prop] = Mustache.get(hash[prop].get, scopeAndOptions, false, true);
}
}
} else if (arg && isLookup(arg)) {
args.push(Mustache.get(arg.get, scopeAndOptions, false, true, true));
} else {
args.push(arg);
}
}
if (isLookup(name)) {
var get = name.get;
name = Mustache.get(name.get, scopeAndOptions, args.length, false);
getHelper = get === name;
}
helperOptions.fn = makeConvertToScopes(helperOptions.fn, scope, options);
helperOptions.inverse = makeConvertToScopes(helperOptions.inverse, scope, options);
if (mode === '^') {
var tmp = helperOptions.fn;
helperOptions.fn = helperOptions.inverse;
helperOptions.inverse = tmp;
}
if (helper = getHelper && (typeof name === 'string' && Mustache.getHelper(name, options)) || can.isFunction(name) && !name.isComputed && { fn: name }) {
can.extend(helperOptions, {
context: context,
scope: scope,
contexts: scope,
hash: hash
});
args.push(helperOptions);
return function () {
var result = helper.fn.apply(context, args);
return result == null ? '' : result;
};
}
return function () {
var value;
if (can.isFunction(name) && name.isComputed) {
value = name();
} else {
value = name;
}
var validArgs = args.length ? args : [value], valid = true, result = [], i, argIsObserve, arg;
if (mode) {
for (i = 0; i < validArgs.length; i++) {
arg = validArgs[i];
argIsObserve = typeof arg !== 'undefined' && isObserveLike(arg);
if (isArrayLike(arg)) {
if (mode === '#') {
valid = valid && !!(argIsObserve ? arg.attr('length') : arg.length);
} else if (mode === '^') {
valid = valid && !(argIsObserve ? arg.attr('length') : arg.length);
}
} else {
valid = mode === '#' ? valid && !!arg : mode === '^' ? valid && !arg : valid;
}
}
}
if (valid) {
if (mode === '#') {
if (isArrayLike(value)) {
var isObserveList = isObserveLike(value);
for (i = 0; i < value.length; i++) {
result.push(helperOptions.fn(isObserveList ? value.attr('' + i) : value[i]));
}
return result.join('');
} else {
return helperOptions.fn(value || {}) || '';
}
} else if (mode === '^') {
return helperOptions.inverse(value || {}) || '';
} else {
return '' + (value != null ? value : '');
}
}
return '';
};
};
Mustache.get = function (key, scopeAndOptions, isHelper, isArgument, isLookup) {
var context = scopeAndOptions.scope.attr('.'), options = scopeAndOptions.options || {};
if (isHelper) {
if (Mustache.getHelper(key, options)) {
return key;
}
if (scopeAndOptions.scope && can.isFunction(context[key])) {
return context[key];
}
}
var computeData = scopeAndOptions.scope.computeData(key, {
isArgument: isArgument,
args: [
context,
scopeAndOptions.scope
]
}), compute = computeData.compute;
can.compute.temporarilyBind(compute);
var initialValue = computeData.initialValue, helperObj = Mustache.getHelper(key, options);
if (!isLookup && (initialValue === undefined || computeData.scope !== scopeAndOptions.scope) && Mustache.getHelper(key, options)) {
return key;
}
if (!compute.computeInstance.hasDependencies) {
return initialValue;
} else {
return compute;
}
};
Mustache.resolve = function (value) {
if (isObserveLike(value) && isArrayLike(value) && value.attr('length')) {
return value;
} else if (can.isFunction(value)) {
return value();
} else {
return value;
}
};
Mustache._helpers = {};
Mustache.registerHelper = function (name, fn) {
this._helpers[name] = {
name: name,
fn: fn
};
};
Mustache.registerSimpleHelper = function (name, fn) {
Mustache.registerHelper(name, can.view.simpleHelper(fn));
};
Mustache.getHelper = function (name, options) {
var helper;
if (options) {
helper = options.get('helpers.' + name, { proxyMethods: false });
}
return helper ? { fn: helper } : this._helpers[name];
};
Mustache.render = function (partial, scope, options) {
if (!can.view.cached[partial]) {
can.__notObserve(function () {
var scopePartialName = scope.attr(partial);
if (scopePartialName) {
partial = scopePartialName;
}
})();
}
return can.view.render(partial, scope, options);
};
Mustache.safeString = function (str) {
return {
toString: function () {
return str;
}
};
};
Mustache.renderPartial = function (partialName, scope, options) {
var partial = options.get('partials.' + partialName, { proxyMethods: false });
if (partial) {
return partial.render ? partial.render(scope, options) : partial(scope, options);
} else {
return can.Mustache.render(partialName, scope, options);
}
};
can.each({
'if': function (expr, options) {
var value;
if (can.isFunction(expr)) {
value = can.compute.truthy(expr)();
} else {
value = !!Mustache.resolve(expr);
}
if (value) {
return options.fn(options.contexts || this);
} else {
return options.inverse(options.contexts || this);
}
},
'is': function () {
var lastValue, curValue, options = arguments[arguments.length - 1];
if (arguments.length - 2 <= 0) {
return options.inverse();
}
for (var i = 0; i < arguments.length - 1; i++) {
curValue = Mustache.resolve(arguments[i]);
curValue = can.isFunction(curValue) ? curValue() : curValue;
if (i > 0) {
if (curValue !== lastValue) {
return options.inverse();
}
}
lastValue = curValue;
}
return options.fn();
},
'eq': function () {
return Mustache._helpers.is.fn.apply(this, arguments);
},
'unless': function (expr, options) {
return Mustache._helpers['if'].fn.apply(this, [
expr,
can.extend({}, options, {
fn: options.inverse,
inverse: options.fn
})
]);
},
'each': function (expr, options) {
var resolved = Mustache.resolve(expr), result = [], keys, key, i;
if (can.view.lists && (resolved instanceof can.List || expr && expr.isComputed && resolved === undefined)) {
return can.view.lists(expr, function (item, index) {
return options.fn(options.scope.add({ '@index': index }).add(item));
});
}
expr = resolved;
if (!!expr && isArrayLike(expr)) {
for (i = 0; i < expr.length; i++) {
result.push(options.fn(options.scope.add({ '@index': i }).add(expr[i])));
}
return result.join('');
} else if (isObserveLike(expr)) {
keys = can.Map.keys(expr);
for (i = 0; i < keys.length; i++) {
key = keys[i];
result.push(options.fn(options.scope.add({ '@key': key }).add(expr[key])));
}
return result.join('');
} else if (expr instanceof Object) {
for (key in expr) {
result.push(options.fn(options.scope.add({ '@key': key }).add(expr[key])));
}
return result.join('');
}
},
'with': function (expr, options) {
var ctx = expr;
expr = Mustache.resolve(expr);
if (!!expr) {
return options.fn(ctx);
}
},
'log': function (expr, options) {
if (typeof console !== 'undefined' && console.log) {
if (!options) {
console.log(expr.context);
} else {
console.log(expr, options.context);
}
}
},
'@index': function (offset, options) {
if (!options) {
options = offset;
offset = 0;
}
var index = options.scope.read('@index', { isArgument: true }).value;
return '' + ((can.isFunction(index) ? index() : index) + offset);
}
}, function (fn, name) {
Mustache.registerHelper(name, fn);
});
can.view.register({
suffix: 'mustache',
contentType: 'x-mustache-template',
script: function (id, src) {
return 'can.Mustache(function(' + ARG_NAMES + ') { ' + new Mustache({
text: src,
name: id
}).template.out + ' })';
},
renderer: function (id, text) {
return Mustache({
text: text,
name: id
});
}
});
can.mustache.registerHelper = can.proxy(can.Mustache.registerHelper, can.Mustache);
can.mustache.safeString = can.Mustache.safeString;
return can;
});