diff --git a/mustache.js b/mustache.js index ed0cd6d..9dc51a6 100644 --- a/mustache.js +++ b/mustache.js @@ -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. diff --git a/test/render-test.js b/test/render-test.js index d055523..7a38ed0 100644 --- a/test/render-test.js +++ b/test/render-test.js @@ -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 = '<>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<>'); }); - + 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<>'; @@ -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('>&'), '>&'); }); - + 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, ''); assert.equal(outputFalse, ''); }); }); + 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);