Skip to content

Commit

Permalink
Add support for dynamic partial names
Browse files Browse the repository at this point in the history
Uses the subexpression syntax to allow for dynamic partial lookups. Ex:

```
{{> (helper) }}
```

Fixes #933
  • Loading branch information
kpdecker committed Jan 18, 2015
1 parent b0b522b commit cb51b82
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 9 deletions.
13 changes: 13 additions & 0 deletions docs/compiler-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
9 changes: 9 additions & 0 deletions lib/handlebars/compiler/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
10 changes: 8 additions & 2 deletions lib/handlebars/compiler/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ Compiler.prototype = {
},

PartialStatement: function(partial) {
var partialName = partial.sexpr.path.original;
this.usePartial = true;

var params = partial.sexpr.params;
Expand All @@ -155,14 +154,21 @@ 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 || '';
if (this.options.preventIndent && indent) {
this.opcode('appendContent', indent);
indent = '';
}
this.opcode('invokePartial', partialName, indent);

this.opcode('invokePartial', isDynamic, partialName, indent);
this.opcode('append');
},

Expand Down
13 changes: 11 additions & 2 deletions lib/handlebars/compiler/javascript-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion lib/handlebars/compiler/printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand Down
5 changes: 5 additions & 0 deletions lib/handlebars/compiler/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */) {},

Expand Down
15 changes: 12 additions & 3 deletions lib/handlebars/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down
26 changes: 26 additions & 0 deletions spec/partials.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}}";
Expand Down
8 changes: 7 additions & 1 deletion src/handlebars.yy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit cb51b82

Please sign in to comment.