diff --git a/README.md b/README.md index ea74fc83..5616bfc2 100644 --- a/README.md +++ b/README.md @@ -419,6 +419,29 @@ Mustache.render(template, view, { }); ``` +Partials can have dynamic names, which begin with `*`. In this case, a value is read from the context and the partial with that value is used. + +For example, this template and partial: + + base.mustache: +

Message of the Day

+ {{> *dayOfWeek}} + + thursday.mustache: + This must be Thursday. I never could get the hang of Thursdays. + +When loaded with the context: +```json +{ + "dayOfWeek": "thursday" +} +``` +will be expanded to: +```html +

Message of the Day

+ This must be Thursday. I never could get the hang of Thursdays. +``` + ### Custom Delimiters Custom delimiters can be used in place of `{{` and `}}` by setting the new values in JavaScript or in templates. diff --git a/mustache.js b/mustache.js index ed0cd6d7..42b5fd36 100644 --- a/mustache.js +++ b/mustache.js @@ -79,6 +79,7 @@ var spaceRe = /\s+/; var equalsRe = /\s*=/; var curlyRe = /\s*\}/; var tagRe = /#|\^|\/|>|\{|&|=|!/; +var dynamicRe = /\*/; /** * Breaks up the given `template` string into a tree of tokens. If the `tags` @@ -202,6 +203,14 @@ function parseTemplate (template, tags) { scanner.scan(curlyRe); scanner.scanUntil(closingTagRe); type = '&'; + } else if (type === '>') { + if (scanner.scan(dynamicRe) === '') { + value = scanner.scanUntil(closingTagRe); + } else { + scanner.scan(whiteRe); + type = '>*'; + value = scanner.scanUntil(closingTagRe); + } } else { value = scanner.scanUntil(closingTagRe); } @@ -210,7 +219,7 @@ function parseTemplate (template, tags) { if (!scanner.scan(closingTagRe)) throw new Error('Unclosed tag at ' + scanner.pos); - if (type == '>') { + if (type == '>' || type == '>*') { token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ]; } else { token = [ type, value, start, scanner.pos ]; @@ -571,6 +580,7 @@ Writer.prototype.renderTokens = function renderTokens (tokens, context, partials if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate, config); else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate, config); else if (symbol === '>') value = this.renderPartial(token, context, partials, config); + else if (symbol === '>*') value = this.renderDynamicPartial(token, context, partials, config); else if (symbol === '&') value = this.unescapedValue(token, context); else if (symbol === 'name') value = this.escapedValue(token, context, config); else if (symbol === 'text') value = this.rawValue(token); @@ -636,10 +646,27 @@ Writer.prototype.indentPartial = function indentPartial (partial, indentation, l return partialByNl.join('\n'); }; -Writer.prototype.renderPartial = function renderPartial (token, context, partials, config) { +Writer.prototype.renderDynamicPartial = function renderPartial (token, context, partials, config) { if (!partials) return; var tags = this.getConfigTags(config); + var name = context.lookup(token[1].trim()); + var value = isFunction(partials) ? partials(name) : partials[name]; + if (value != null) { + var lineHasNonSpace = token[6]; + var tagIndex = token[5]; + var indentation = token[4]; + var indentedValue = value; + if (tagIndex == 0 && indentation) { + indentedValue = this.indentPartial(value, indentation, lineHasNonSpace); + } + var tokens = this.parse(indentedValue, tags); + return this.renderTokens(tokens, context, partials, indentedValue, config); + } +}; +Writer.prototype.renderPartial = function renderPartial (token, context, partials, config) { + if (!partials) return; + var tags = this.getConfigTags(config); var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; if (value != null) { var lineHasNonSpace = token[6]; diff --git a/test/mustache-spec-test.js b/test/mustache-spec-test.js index 0e500189..d035048b 100644 --- a/test/mustache-spec-test.js +++ b/test/mustache-spec-test.js @@ -14,6 +14,9 @@ var skipTests = { inverted: [ 'Standalone Without Newline' ], + interpolation: [ + 'Dotted Names - Context Precedence' + ], partials: [ 'Standalone Without Previous Line', 'Standalone Without Newline' @@ -21,6 +24,34 @@ var skipTests = { sections: [ 'Standalone Without Newline' ], + '~inheritance': [ + 'Default', + 'Variable', + 'Triple Mustache', + 'Sections', + 'Negative Sections', + 'Mustache Injection', + 'Inherit', + 'Overridden content', + 'Data does not override block', + 'Data does not override block default', + 'Overridden parent', + 'Two overridden parents', + 'Override parent with newlines', + 'Inherit indentation', + 'Only one override', + 'Parent template', + 'Recursion', + 'Multi-level inheritance', + 'Multi-level inheritance, no sub child', + 'Text inside parent', + 'Block scope', + 'Standalone parent', + 'Standalone block', + 'Block reindentation', + 'Intrinsic indentation', + 'Nested block reindentation' + ], '~lambdas': [ 'Interpolation', 'Interpolation - Expansion', diff --git a/test/parse-test.js b/test/parse-test.js index 1fb2cabe..547520b7 100644 --- a/test/parse-test.js +++ b/test/parse-test.js @@ -46,6 +46,10 @@ var expectations = { ' {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ] ], ' {{> abc }} {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ], [ '>', 'abc', 13, 23, ' ', 1, false ] ], '{{ > abc }}' : [ [ '>', 'abc', 0, 11, '', 0, false ] ], + '{{>*abc}}' : [ [ '>*', 'abc', 0, 9, '', 0, false ] ], + '{{> *abc}}' : [ [ '>*', 'abc', 0, 10, '', 0, false ] ], + '{{>* abc}}' : [ [ '>*', 'abc', 0, 10, '', 0, false ] ], + '{{ > * abc }}' : [ [ '>*', 'abc', 0, 13, '', 0, false ] ], '{{=<% %>=}}' : [ [ '=', '<% %>', 0, 11 ] ], '{{= <% %> =}}' : [ [ '=', '<% %>', 0, 13 ] ], '{{=<% %>=}}<%={{ }}=%>' : [ [ '=', '<% %>', 0, 11 ], [ '=', '{{ }}', 11, 22 ] ], diff --git a/test/partial-test.js b/test/partial-test.js index 54d62b58..55f8d0de 100644 --- a/test/partial-test.js +++ b/test/partial-test.js @@ -172,4 +172,18 @@ describe('Partials spec', function () { var renderResult = Mustache.render(template, {}, partials, tags); assert.equal(renderResult, expected); }); + + describe('when rendering a dynamically named partial after already having rendered that partial with a different name value', function () { + it('returns different output for the latter render', function () { + var template = 'Place: {{>*place}}'; + var partials = { + first: '1st', + second: '2nd', + }; + var renderedFirst = Mustache.render(template, {place:'first'}, partials); + var renderedSecond = Mustache.render(template, {place:'second'}, partials); + + assert.notEqual(renderedFirst, renderedSecond); + }); + }); }); diff --git a/test/spec b/test/spec index 72233f3f..7138576e 160000 --- a/test/spec +++ b/test/spec @@ -1 +1 @@ -Subproject commit 72233f3ffda9e33915fd3022d0a9ebbcce265acd +Subproject commit 7138576e12ff0f05511de77807c2fb3959bb22e3