diff options
author | kpdecker <kpdecker@gmail.com> | 2015-01-18 13:23:45 -0600 |
---|---|---|
committer | kpdecker <kpdecker@gmail.com> | 2015-01-18 13:23:45 -0600 |
commit | cb51b82b8e8114aaa3e1b19c41e5c4eebf0539d5 (patch) | |
tree | 386038a4a43f85e19dfee0f996cce66f10c10d8f | |
parent | b0b522b4f81baf5ba4c190b59abd2b9cfe82bc77 (diff) | |
download | handlebars.js-cb51b82b8e8114aaa3e1b19c41e5c4eebf0539d5.zip handlebars.js-cb51b82b8e8114aaa3e1b19c41e5c4eebf0539d5.tar.gz handlebars.js-cb51b82b8e8114aaa3e1b19c41e5c4eebf0539d5.tar.bz2 |
Add support for dynamic partial names
Uses the subexpression syntax to allow for dynamic partial lookups. Ex:
```
{{> (helper) }}
```
Fixes #933
-rw-r--r-- | docs/compiler-api.md | 13 | ||||
-rw-r--r-- | lib/handlebars/compiler/ast.js | 9 | ||||
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 10 | ||||
-rw-r--r-- | lib/handlebars/compiler/javascript-compiler.js | 13 | ||||
-rw-r--r-- | lib/handlebars/compiler/printer.js | 2 | ||||
-rw-r--r-- | lib/handlebars/compiler/visitor.js | 5 | ||||
-rw-r--r-- | lib/handlebars/runtime.js | 15 | ||||
-rw-r--r-- | spec/partials.js | 26 | ||||
-rw-r--r-- | src/handlebars.yy | 8 |
9 files changed, 92 insertions, 9 deletions
diff --git a/docs/compiler-api.md b/docs/compiler-api.md index 74af672..4a0ec01 100644 --- a/docs/compiler-api.md +++ b/docs/compiler-api.md @@ -115,6 +115,17 @@ interface SubExpression <: Expression { `isHelper` is not required and is used to disambiguate between cases such as `{{foo}}` and `(foo)`, which have slightly different call behaviors. +```java +interface PartialExpression <: Expression { + type: "PartialExpression"; + name: PathExpression | SubExpression; + params: [ Expression ]; + hash: Hash; +} +``` + +`path` may be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}` + ##### Paths ```java @@ -221,6 +232,8 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c - `name` is the current path component - `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, or `partial`. + Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases. + - `depthedLookup(name)` Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode. diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 113c03e..c193460 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -63,6 +63,15 @@ var AST = { this.hash = hash; }, + PartialExpression: function(name, params, hash, locInfo) { + this.loc = locInfo; + + this.type = 'PartialExpression'; + this.name = name; + this.params = params || []; + this.hash = hash; + }, + PathExpression: function(data, depth, parts, original, locInfo) { this.loc = locInfo; this.type = 'PathExpression'; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 95fe101..524f9aa 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -145,7 +145,6 @@ Compiler.prototype = { }, PartialStatement: function(partial) { - var partialName = partial.sexpr.path.original; this.usePartial = true; var params = partial.sexpr.params; @@ -155,6 +154,12 @@ Compiler.prototype = { params.push({type: 'PathExpression', parts: [], depth: 0}); } + var partialName = partial.sexpr.name.original, + isDynamic = partial.sexpr.name.type === 'SubExpression'; + if (isDynamic) { + this.accept(partial.sexpr.name); + } + this.setupFullMustacheParams(partial.sexpr, undefined, undefined, true); var indent = partial.indent || ''; @@ -162,7 +167,8 @@ Compiler.prototype = { this.opcode('appendContent', indent); indent = ''; } - this.opcode('invokePartial', partialName, indent); + + this.opcode('invokePartial', isDynamic, partialName, indent); this.opcode('append'); }, diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 75f9960..a027edb 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -644,17 +644,26 @@ JavaScriptCompiler.prototype = { // // This operation pops off a context, invokes a partial with that context, // and pushes the result of the invocation back. - invokePartial: function(name, indent) { + invokePartial: function(isDynamic, name, indent) { var params = [], options = this.setupParams(name, 1, params, false); + if (isDynamic) { + name = this.popStack(); + delete options.name; + } + if (indent) { options.indent = JSON.stringify(indent); } options.helpers = 'helpers'; options.partials = 'partials'; - params.unshift(this.nameLookup('partials', name, 'partial')); + if (!isDynamic) { + params.unshift(this.nameLookup('partials', name, 'partial')); + } else { + params.unshift(name); + } if (this.options.compat) { options.depths = 'depths'; diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index ad276f1..448331c 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -75,7 +75,7 @@ PrintVisitor.prototype.BlockStatement = function(block) { PrintVisitor.prototype.PartialStatement = function(partial) { var sexpr = partial.sexpr, - content = 'PARTIAL:' + sexpr.path.original; + content = 'PARTIAL:' + sexpr.name.original; if(sexpr.params[0]) { content += ' ' + this.accept(sexpr.params[0]); } diff --git a/lib/handlebars/compiler/visitor.js b/lib/handlebars/compiler/visitor.js index 3fb37fb..03af915 100644 --- a/lib/handlebars/compiler/visitor.js +++ b/lib/handlebars/compiler/visitor.js @@ -92,6 +92,11 @@ Visitor.prototype = { this.acceptArray(sexpr.params); this.acceptKey(sexpr, 'hash'); }, + PartialExpression: function(partial) { + this.acceptRequired(partial, 'name'); + this.acceptArray(partial.params); + this.acceptKey(partial, 'hash'); + }, PathExpression: function(/* path */) {}, diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 2b9f474..4e8c33a 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -39,10 +39,8 @@ export function template(templateSpec, env) { if (options.hash) { context = Utils.extend({}, context, options.hash); } - if (!partial) { - partial = options.partials[options.name]; - } + partial = env.VM.resolvePartial.call(this, partial, context, options); var result = env.VM.invokePartial.call(this, partial, context, options); if (result == null && env.compile) { @@ -187,6 +185,17 @@ export function program(container, i, fn, data, declaredBlockParams, blockParams return prog; } +export function resolvePartial(partial, context, options) { + if (!partial) { + partial = options.partials[options.name]; + } else if (!partial.call && !options.name) { + // This is a dynamic partial that returned a string + options.name = partial; + partial = options.partials[partial]; + } + return partial; +} + export function invokePartial(partial, context, options) { options.partial = true; diff --git a/spec/partials.js b/spec/partials.js index b150942..0c9e0f6 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -8,6 +8,32 @@ describe('partials', function() { shouldCompileToWithPartials(string, [hash, {}, {dude: partial},,false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) '); }); + it('dynamic partials', function() { + var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}'; + var partial = '{{name}} ({{url}}) '; + var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; + var helpers = { + partial: function() { + return 'dude'; + } + }; + shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) '); + shouldCompileToWithPartials(string, [hash, helpers, {dude: partial},,false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) '); + }); + it('failing dynamic partials', function() { + var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}'; + var partial = '{{name}} ({{url}}) '; + var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; + var helpers = { + partial: function() { + return 'missing'; + } + }; + shouldThrow(function() { + shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) '); + }, Handlebars.Exception, 'The partial missing could not be found'); + }); + it("partials with context", function() { var string = "Dudes: {{>dude dudes}}"; var partial = "{{#this}}{{name}} ({{url}}) {{/this}}"; diff --git a/src/handlebars.yy b/src/handlebars.yy index 27985b8..6ca32d9 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -77,7 +77,13 @@ mustache ; partial - : OPEN_PARTIAL sexpr CLOSE -> new yy.PartialStatement($2, yy.stripFlags($1, $3), yy.locInfo(@$)) + : OPEN_PARTIAL partial_expr CLOSE -> new yy.PartialStatement($2, yy.stripFlags($1, $3), yy.locInfo(@$)) + ; + +partial_expr + : helperName param* hash? -> new yy.PartialExpression($1, $2, $3, yy.locInfo(@$)) + | dataName -> new yy.PartialExpression($1, null, null, yy.locInfo(@$)) + | OPEN_SEXPR sexpr CLOSE_SEXPR param* hash? -> new yy.PartialExpression($2, $4, $5, yy.locInfo(@$)) ; sexpr |