Skip to content

Commit cb51b82

Browse files
committed
Add support for dynamic partial names
Uses the subexpression syntax to allow for dynamic partial lookups. Ex: ``` {{> (helper) }} ``` Fixes #933
1 parent b0b522b commit cb51b82

File tree

9 files changed

+92
-9
lines changed

9 files changed

+92
-9
lines changed

docs/compiler-api.md

+13
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,17 @@ interface SubExpression <: Expression {
115115

116116
`isHelper` is not required and is used to disambiguate between cases such as `{{foo}}` and `(foo)`, which have slightly different call behaviors.
117117

118+
```java
119+
interface PartialExpression <: Expression {
120+
type: "PartialExpression";
121+
name: PathExpression | SubExpression;
122+
params: [ Expression ];
123+
hash: Hash;
124+
}
125+
```
126+
127+
`path` may be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`
128+
118129
##### Paths
119130

120131
```java
@@ -221,6 +232,8 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
221232
- `name` is the current path component
222233
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, or `partial`.
223234

235+
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.
236+
224237
- `depthedLookup(name)`
225238
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
226239

lib/handlebars/compiler/ast.js

+9
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ var AST = {
6363
this.hash = hash;
6464
},
6565

66+
PartialExpression: function(name, params, hash, locInfo) {
67+
this.loc = locInfo;
68+
69+
this.type = 'PartialExpression';
70+
this.name = name;
71+
this.params = params || [];
72+
this.hash = hash;
73+
},
74+
6675
PathExpression: function(data, depth, parts, original, locInfo) {
6776
this.loc = locInfo;
6877
this.type = 'PathExpression';

lib/handlebars/compiler/compiler.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ Compiler.prototype = {
145145
},
146146

147147
PartialStatement: function(partial) {
148-
var partialName = partial.sexpr.path.original;
149148
this.usePartial = true;
150149

151150
var params = partial.sexpr.params;
@@ -155,14 +154,21 @@ Compiler.prototype = {
155154
params.push({type: 'PathExpression', parts: [], depth: 0});
156155
}
157156

157+
var partialName = partial.sexpr.name.original,
158+
isDynamic = partial.sexpr.name.type === 'SubExpression';
159+
if (isDynamic) {
160+
this.accept(partial.sexpr.name);
161+
}
162+
158163
this.setupFullMustacheParams(partial.sexpr, undefined, undefined, true);
159164

160165
var indent = partial.indent || '';
161166
if (this.options.preventIndent && indent) {
162167
this.opcode('appendContent', indent);
163168
indent = '';
164169
}
165-
this.opcode('invokePartial', partialName, indent);
170+
171+
this.opcode('invokePartial', isDynamic, partialName, indent);
166172
this.opcode('append');
167173
},
168174

lib/handlebars/compiler/javascript-compiler.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -644,17 +644,26 @@ JavaScriptCompiler.prototype = {
644644
//
645645
// This operation pops off a context, invokes a partial with that context,
646646
// and pushes the result of the invocation back.
647-
invokePartial: function(name, indent) {
647+
invokePartial: function(isDynamic, name, indent) {
648648
var params = [],
649649
options = this.setupParams(name, 1, params, false);
650650

651+
if (isDynamic) {
652+
name = this.popStack();
653+
delete options.name;
654+
}
655+
651656
if (indent) {
652657
options.indent = JSON.stringify(indent);
653658
}
654659
options.helpers = 'helpers';
655660
options.partials = 'partials';
656661

657-
params.unshift(this.nameLookup('partials', name, 'partial'));
662+
if (!isDynamic) {
663+
params.unshift(this.nameLookup('partials', name, 'partial'));
664+
} else {
665+
params.unshift(name);
666+
}
658667

659668
if (this.options.compat) {
660669
options.depths = 'depths';

lib/handlebars/compiler/printer.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ PrintVisitor.prototype.BlockStatement = function(block) {
7575

7676
PrintVisitor.prototype.PartialStatement = function(partial) {
7777
var sexpr = partial.sexpr,
78-
content = 'PARTIAL:' + sexpr.path.original;
78+
content = 'PARTIAL:' + sexpr.name.original;
7979
if(sexpr.params[0]) {
8080
content += ' ' + this.accept(sexpr.params[0]);
8181
}

lib/handlebars/compiler/visitor.js

+5
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ Visitor.prototype = {
9292
this.acceptArray(sexpr.params);
9393
this.acceptKey(sexpr, 'hash');
9494
},
95+
PartialExpression: function(partial) {
96+
this.acceptRequired(partial, 'name');
97+
this.acceptArray(partial.params);
98+
this.acceptKey(partial, 'hash');
99+
},
95100

96101
PathExpression: function(/* path */) {},
97102

lib/handlebars/runtime.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,8 @@ export function template(templateSpec, env) {
3939
if (options.hash) {
4040
context = Utils.extend({}, context, options.hash);
4141
}
42-
if (!partial) {
43-
partial = options.partials[options.name];
44-
}
4542

43+
partial = env.VM.resolvePartial.call(this, partial, context, options);
4644
var result = env.VM.invokePartial.call(this, partial, context, options);
4745

4846
if (result == null && env.compile) {
@@ -187,6 +185,17 @@ export function program(container, i, fn, data, declaredBlockParams, blockParams
187185
return prog;
188186
}
189187

188+
export function resolvePartial(partial, context, options) {
189+
if (!partial) {
190+
partial = options.partials[options.name];
191+
} else if (!partial.call && !options.name) {
192+
// This is a dynamic partial that returned a string
193+
options.name = partial;
194+
partial = options.partials[partial];
195+
}
196+
return partial;
197+
}
198+
190199
export function invokePartial(partial, context, options) {
191200
options.partial = true;
192201

spec/partials.js

+26
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,32 @@ describe('partials', function() {
88
shouldCompileToWithPartials(string, [hash, {}, {dude: partial},,false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
99
});
1010

11+
it('dynamic partials', function() {
12+
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
13+
var partial = '{{name}} ({{url}}) ';
14+
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
15+
var helpers = {
16+
partial: function() {
17+
return 'dude';
18+
}
19+
};
20+
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
21+
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial},,false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
22+
});
23+
it('failing dynamic partials', function() {
24+
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
25+
var partial = '{{name}} ({{url}}) ';
26+
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
27+
var helpers = {
28+
partial: function() {
29+
return 'missing';
30+
}
31+
};
32+
shouldThrow(function() {
33+
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
34+
}, Handlebars.Exception, 'The partial missing could not be found');
35+
});
36+
1137
it("partials with context", function() {
1238
var string = "Dudes: {{>dude dudes}}";
1339
var partial = "{{#this}}{{name}} ({{url}}) {{/this}}";

src/handlebars.yy

+7-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,13 @@ mustache
7777
;
7878

7979
partial
80-
: OPEN_PARTIAL sexpr CLOSE -> new yy.PartialStatement($2, yy.stripFlags($1, $3), yy.locInfo(@$))
80+
: OPEN_PARTIAL partial_expr CLOSE -> new yy.PartialStatement($2, yy.stripFlags($1, $3), yy.locInfo(@$))
81+
;
82+
83+
partial_expr
84+
: helperName param* hash? -> new yy.PartialExpression($1, $2, $3, yy.locInfo(@$))
85+
| dataName -> new yy.PartialExpression($1, null, null, yy.locInfo(@$))
86+
| OPEN_SEXPR sexpr CLOSE_SEXPR param* hash? -> new yy.PartialExpression($2, $4, $5, yy.locInfo(@$))
8187
;
8288

8389
sexpr

0 commit comments

Comments
 (0)