summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkpdecker <kpdecker@gmail.com>2015-01-18 13:23:45 -0600
committerkpdecker <kpdecker@gmail.com>2015-01-18 13:23:45 -0600
commitcb51b82b8e8114aaa3e1b19c41e5c4eebf0539d5 (patch)
tree386038a4a43f85e19dfee0f996cce66f10c10d8f
parentb0b522b4f81baf5ba4c190b59abd2b9cfe82bc77 (diff)
downloadhandlebars.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.md13
-rw-r--r--lib/handlebars/compiler/ast.js9
-rw-r--r--lib/handlebars/compiler/compiler.js10
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js13
-rw-r--r--lib/handlebars/compiler/printer.js2
-rw-r--r--lib/handlebars/compiler/visitor.js5
-rw-r--r--lib/handlebars/runtime.js15
-rw-r--r--spec/partials.js26
-rw-r--r--src/handlebars.yy8
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