Ver código fonte

feat: Allows to register global "Functions" to access from the Mustache instance

pull/801/head
jrafa1994@gmail.com 3 anos atrás
pai
commit
753acf3ddb
2 arquivos alterados com 97 adições e 37 exclusões
  1. +40
    -9
      mustache.js
  2. +57
    -28
      test/render-test.js

+ 40
- 9
mustache.js Ver arquivo

@@ -12,6 +12,16 @@ function isFunction (object) {
return typeof object === 'function';
}

function putFunctionsIntoView (view, functions) {
for (var fn in functions){
if (!view.hasOwnProperty(fn)){
view[fn] = functions[fn];
}
}

return view;
}

/**
* More correct typeof string handling array
* which normally returns typeof 'object'
@@ -38,10 +48,10 @@ function hasProperty (obj, propName) {
*/
function primitiveHasOwnProperty (primitive, propName) {
return (
primitive != null
&& typeof primitive !== 'object'
&& primitive.hasOwnProperty
&& primitive.hasOwnProperty(propName)
primitive != null
&& typeof primitive !== 'object'
&& primitive.hasOwnProperty
&& primitive.hasOwnProperty(propName)
);
}

@@ -425,8 +435,8 @@ Context.prototype.lookup = function lookup (name) {
while (intermediateValue != null && index < names.length) {
if (index === names.length - 1)
lookupHit = (
hasProperty(intermediateValue, names[index])
|| primitiveHasOwnProperty(intermediateValue, names[index])
hasProperty(intermediateValue, names[index])
|| primitiveHasOwnProperty(intermediateValue, names[index])
);

intermediateValue = intermediateValue[names[index++]];
@@ -703,6 +713,7 @@ var mustache = {
Scanner: undefined,
Context: undefined,
Writer: undefined,
globalFunctions: {},
/**
* Allows a user to override the default caching strategy, by providing an
* object with set, get and clear methods. This can also be used to disable
@@ -745,11 +756,31 @@ mustache.parse = function parse (template, tags) {
mustache.render = function render (template, view, partials, config) {
if (typeof template !== 'string') {
throw new TypeError('Invalid template! Template should be a "string" ' +
'but "' + typeStr(template) + '" was given as the first ' +
'argument for mustache#render(template, view, partials)');
'but "' + typeStr(template) + '" was given as the first ' +
'argument for mustache#render(template, view, partials)');
}

return defaultWriter.render(template, putFunctionsIntoView(view, mustache.globalFunctions), partials, config);
};

mustache.registerFunction = function registerFunction (name, fn) {
if (typeStr(name) !== 'string') {
throw new TypeError('String expected on first argument to mustache.registerFunction');
}

if (!isFunction(fn)){
throw new TypeError('Function expected on second argument to mustache.registerFunction');
}

return defaultWriter.render(template, view, partials, config);
if (hasProperty(mustache.globalFunctions, name)){
console.warn('Function "' + name + '" is already registered, it will be overridden');
}

mustache.globalFunctions[name] = function globalFunction () {
return function run (text, render) {
return fn.call(null, render(text));
};
};
};

// Export the escaping function so that the user may override it.


+ 57
- 28
test/render-test.js Ver arquivo

@@ -13,8 +13,8 @@ describe('Mustache.render', function () {
assert.throws(function () {
Mustache.render(['dummy template'], ['foo', 'bar']);
}, TypeError, 'Invalid template! Template should be a "string" but ' +
'"array" was given as the first argument ' +
'for mustache#render(template, view, partials)');
'"array" was given as the first argument ' +
'for mustache#render(template, view, partials)');
});

describe('custom tags', function () {
@@ -24,7 +24,7 @@ describe('Mustache.render', function () {
Mustache.tags = ['{{', '}}'];
assert.equal(Mustache.render(template, { placeholder: 'foo' }, {}, ['<<', '>>']), 'foobar{{placeholder}}');
});
it('uses config.tags argument instead of Mustache.tags when given', function () {
var template = '<<placeholder>>bar{{placeholder}}';

@@ -39,7 +39,7 @@ describe('Mustache.render', function () {
Mustache.render(template, { placeholder: 'foo' });
assert.equal(Mustache.render(template, { placeholder: 'foo' }, {}, ['((', '))']), 'foobar{{placeholder}}');
});
it('uses config.tags argument instead of Mustache.tags when given, even when it previously rendered the template using Mustache.tags', function () {
var template = '((placeholder))bar{{placeholder}}';

@@ -54,7 +54,7 @@ describe('Mustache.render', function () {
Mustache.render(template, { placeholder: 'foo' }, {}, ['<<', '>>']);
assert.equal(Mustache.render(template, { placeholder: 'foo' }, {}, ['[[', ']]']), 'foobar<<placeholder>>');
});
it('uses config.tags argument instead of Mustache.tags when given, even when it previously rendered the template using different tags', function () {
var template = '[[placeholder]]bar<<placeholder>>';

@@ -71,7 +71,7 @@ describe('Mustache.render', function () {
assert.equal(Mustache.tags, correctMustacheTags);
assert.deepEqual(Mustache.tags, ['{{', '}}']);
});
it('does not mutate Mustache.tags when given config.tags argument', function () {
var correctMustacheTags = ['{{', '}}'];
Mustache.tags = correctMustacheTags;
@@ -89,18 +89,18 @@ describe('Mustache.render', function () {

assert.equal(output, 'Santa Claus');
});
it('uses provided config.tags when rendering partials', function () {
var output = Mustache.render('<%> partial %>', { name: 'Santa Claus' }, {
partial: '<% name %>'
}, { tags: ['<%', '%>'] });
}, { tags: ['<%', '%>'] });

assert.equal(output, 'Santa Claus');
});
it('uses config.escape argument instead of Mustache.escape when given', function () {
var template = 'Hello, {{placeholder}}';
function escapeBang (text) {
return text + '!';
}
@@ -109,7 +109,7 @@ describe('Mustache.render', function () {

it('uses config.escape argument instead of Mustache.escape when given, even when it previously rendered the template using Mustache.escape', function () {
var template = 'Hello, {{placeholder}}';
function escapeQuestion (text) {
return text + '?';
}
@@ -119,7 +119,7 @@ describe('Mustache.render', function () {

it('uses config.escape argument instead of Mustache.escape when given, even when it previously rendered the template using a different escape function', function () {
var template = 'Hello, {{placeholder}}';
function escapeQuestion (text) {
return text + '?';
}
@@ -129,7 +129,7 @@ describe('Mustache.render', function () {
Mustache.render(template, { placeholder: 'foo' }, {}, { escape: escapeQuestion });
assert.equal(Mustache.render(template, { placeholder: 'foo' }, {}, { escape: escapeBang }), 'Hello, foo!');
});
it('does not mutate Mustache.escape when given config.escape argument', function () {
var correctMustacheEscape = Mustache.escape;

@@ -141,21 +141,21 @@ describe('Mustache.render', function () {
assert.equal(Mustache.escape, correctMustacheEscape);
assert.equal(Mustache.escape('>&'), '&gt;&amp;');
});
it('uses provided config.escape when rendering partials', function () {
function escapeDoubleAmpersand (text) {
return text.replace('&', '&&');
}
var output = Mustache.render('{{> partial }}', { name: 'Ampersand &' }, {
partial: '{{ name }}'
}, { escape: escapeDoubleAmpersand });
}, { escape: escapeDoubleAmpersand });

assert.equal(output, 'Ampersand &&');
});
it('uses config.tags and config.escape arguments instead of Mustache.tags and Mustache.escape when given', function () {
var template = 'Hello, {{placeholder}} [[placeholder]]';
function escapeTwoBangs (text) {
return text + '!!';
}
@@ -165,7 +165,7 @@ describe('Mustache.render', function () {
};
assert.equal(Mustache.render(template, { placeholder: 'world' }, {}, config), 'Hello, {{placeholder}} world!!');
});
it('uses provided config.tags and config.escape when rendering partials', function () {
function escapeDoubleAmpersand (text) {
return text.replace('&', '&&');
@@ -176,20 +176,20 @@ describe('Mustache.render', function () {
};
var output = Mustache.render('[[> partial ]]', { name: 'Ampersand &' }, {
partial: '[[ name ]]'
}, config);
}, config);

assert.equal(output, 'Ampersand &&');
});
it('uses provided config.tags and config.escape when rendering sections', function () {
var template = (
'<[[&value-raw]]: ' +
'[[#test-1]][[value-1]][[/test-1]]' +
'[[^test-2]][[value-2]][[/test-2]], ' +
'[[#test-lambda]][[value-lambda]][[/test-lambda]]' +
'>'
'<[[&value-raw]]: ' +
'[[#test-1]][[value-1]][[/test-1]]' +
'[[^test-2]][[value-2]][[/test-2]], ' +
'[[#test-lambda]][[value-lambda]][[/test-lambda]]' +
'>'
);
function escapeQuotes (text) {
return '"' + text + '"';
}
@@ -219,14 +219,43 @@ describe('Mustache.render', function () {
},
'value-lambda': 'bar'
};
var outputTrue = Mustache.render(template, viewTestTrue, {}, config);
var outputFalse = Mustache.render(template, viewTestFalse, {}, config);
var outputTrue = Mustache.render(template, viewTestTrue, {}, config);
var outputFalse = Mustache.render(template, viewTestFalse, {}, config);

assert.equal(outputTrue, '<foo: "abc", lambda: "bar">');
assert.equal(outputFalse, '<foo: "123", lambda: "bar">');
});
});

describe('globals functions', function () {
it('the name of the function must be a string', function () {
assert.throws(function () {
Mustache.registerFunction(['wrong name'], function () {});
}, TypeError, 'String expected on first argument to mustache.registerFunction');
});

it('the function parameter must be a Function', function () {
assert.throws(function () {
Mustache.registerFunction('wrong name', ['wrong function']);
}, TypeError, 'Function expected on second argument to mustache.registerFunction');
});

it('should register a function', function () {
Mustache.registerFunction('formatNumber', function (number) {
return Number(number).toLocaleString('en-US',{ maximumFractionDigits: 2 });
});

assert.ok(Mustache.globalFunctions.hasOwnProperty('formatNumber'));
});

it('should render a template with a registered function', function () {

var template = 'Nicaragua has formatted the number {{number}} to {{#formatNumber}}{{number}}{{/formatNumber}}';

assert.equal(Mustache.render(template, {number: 1500.6655}), 'Nicaragua has formatted the number 1500.6655 to 1,500.67');
});
});

tests.forEach(function (test) {
var view = eval(test.view);



Carregando…
Cancelar
Salvar