| @@ -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: | |||||
| <h2>Message of the Day</h2> | |||||
| {{> *dayOfWeek}} | |||||
| thursday.mustache: | |||||
| <span>This must be Thursday. I never could get the hang of Thursdays.</span> | |||||
| When loaded with the context: | |||||
| ```json | |||||
| { | |||||
| "dayOfWeek": "thursday" | |||||
| } | |||||
| ``` | |||||
| will be expanded to: | |||||
| ```html | |||||
| <h2>Message of the Day</h2> | |||||
| <span>This must be Thursday. I never could get the hang of Thursdays.</span> | |||||
| ``` | |||||
| ### Custom Delimiters | ### Custom Delimiters | ||||
| Custom delimiters can be used in place of `{{` and `}}` by setting the new values in JavaScript or in templates. | Custom delimiters can be used in place of `{{` and `}}` by setting the new values in JavaScript or in templates. | ||||
| @@ -79,6 +79,7 @@ var spaceRe = /\s+/; | |||||
| var equalsRe = /\s*=/; | var equalsRe = /\s*=/; | ||||
| var curlyRe = /\s*\}/; | var curlyRe = /\s*\}/; | ||||
| var tagRe = /#|\^|\/|>|\{|&|=|!/; | var tagRe = /#|\^|\/|>|\{|&|=|!/; | ||||
| var dynamicRe = /\*/; | |||||
| /** | /** | ||||
| * Breaks up the given `template` string into a tree of tokens. If the `tags` | * 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.scan(curlyRe); | ||||
| scanner.scanUntil(closingTagRe); | scanner.scanUntil(closingTagRe); | ||||
| type = '&'; | type = '&'; | ||||
| } else if (type === '>') { | |||||
| if (scanner.scan(dynamicRe) === '') { | |||||
| value = scanner.scanUntil(closingTagRe); | |||||
| } else { | |||||
| scanner.scan(whiteRe); | |||||
| type = '>*'; | |||||
| value = scanner.scanUntil(closingTagRe); | |||||
| } | |||||
| } else { | } else { | ||||
| value = scanner.scanUntil(closingTagRe); | value = scanner.scanUntil(closingTagRe); | ||||
| } | } | ||||
| @@ -210,7 +219,7 @@ function parseTemplate (template, tags) { | |||||
| if (!scanner.scan(closingTagRe)) | if (!scanner.scan(closingTagRe)) | ||||
| throw new Error('Unclosed tag at ' + scanner.pos); | throw new Error('Unclosed tag at ' + scanner.pos); | ||||
| if (type == '>') { | |||||
| if (type == '>' || type == '>*') { | |||||
| token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ]; | token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ]; | ||||
| } else { | } else { | ||||
| token = [ type, value, start, scanner.pos ]; | 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); | 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.renderInverted(token, context, partials, originalTemplate, config); | ||||
| else if (symbol === '>') value = this.renderPartial(token, context, partials, 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 === '&') value = this.unescapedValue(token, context); | ||||
| else if (symbol === 'name') value = this.escapedValue(token, context, config); | else if (symbol === 'name') value = this.escapedValue(token, context, config); | ||||
| else if (symbol === 'text') value = this.rawValue(token); | else if (symbol === 'text') value = this.rawValue(token); | ||||
| @@ -636,10 +646,27 @@ Writer.prototype.indentPartial = function indentPartial (partial, indentation, l | |||||
| return partialByNl.join('\n'); | 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; | if (!partials) return; | ||||
| var tags = this.getConfigTags(config); | 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]]; | var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; | ||||
| if (value != null) { | if (value != null) { | ||||
| var lineHasNonSpace = token[6]; | var lineHasNonSpace = token[6]; | ||||
| @@ -14,6 +14,9 @@ var skipTests = { | |||||
| inverted: [ | inverted: [ | ||||
| 'Standalone Without Newline' | 'Standalone Without Newline' | ||||
| ], | ], | ||||
| interpolation: [ | |||||
| 'Dotted Names - Context Precedence' | |||||
| ], | |||||
| partials: [ | partials: [ | ||||
| 'Standalone Without Previous Line', | 'Standalone Without Previous Line', | ||||
| 'Standalone Without Newline' | 'Standalone Without Newline' | ||||
| @@ -21,6 +24,34 @@ var skipTests = { | |||||
| sections: [ | sections: [ | ||||
| 'Standalone Without Newline' | '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': [ | '~lambdas': [ | ||||
| 'Interpolation', | 'Interpolation', | ||||
| 'Interpolation - Expansion', | 'Interpolation - Expansion', | ||||
| @@ -46,6 +46,10 @@ var expectations = { | |||||
| ' {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ] ], | ' {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ] ], | ||||
| ' {{> abc }} {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ], [ '>', 'abc', 13, 23, ' ', 1, false ] ], | ' {{> abc }} {{> abc }}\n' : [ [ '>', 'abc', 2, 12, ' ', 0, false ], [ '>', 'abc', 13, 23, ' ', 1, false ] ], | ||||
| '{{ > abc }}' : [ [ '>', 'abc', 0, 11, '', 0, 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, 11 ] ], | ||||
| '{{= <% %> =}}' : [ [ '=', '<% %>', 0, 13 ] ], | '{{= <% %> =}}' : [ [ '=', '<% %>', 0, 13 ] ], | ||||
| '{{=<% %>=}}<%={{ }}=%>' : [ [ '=', '<% %>', 0, 11 ], [ '=', '{{ }}', 11, 22 ] ], | '{{=<% %>=}}<%={{ }}=%>' : [ [ '=', '<% %>', 0, 11 ], [ '=', '{{ }}', 11, 22 ] ], | ||||
| @@ -172,4 +172,18 @@ describe('Partials spec', function () { | |||||
| var renderResult = Mustache.render(template, {}, partials, tags); | var renderResult = Mustache.render(template, {}, partials, tags); | ||||
| assert.equal(renderResult, expected); | 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); | |||||
| }); | |||||
| }); | |||||
| }); | }); | ||||
| @@ -1 +1 @@ | |||||
| Subproject commit 72233f3ffda9e33915fd3022d0a9ebbcce265acd | |||||
| Subproject commit 7138576e12ff0f05511de77807c2fb3959bb22e3 | |||||