Add support for dynamic partial names
Uses the subexpression syntax to allow for dynamic partial lookups. Ex:
```
{{> (helper) }}
```
Fixes #933
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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 */) {},
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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}}";
|
||||
|
||||
+7
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user