Implement partial blocks

This allows for failover for missing partials as well as limited templating ability through the `{{> @partial-block }}` partial special case.

Partial fix for #1018
This commit is contained in:
kpdecker
2015-08-14 15:18:52 -05:00
parent 2571dd8e8e
commit 91ffd32cad
12 changed files with 184 additions and 27 deletions
+12 -2
View File
@@ -1,3 +1,5 @@
/* eslint-disable new-cap */
import Exception from '../exception';
import {isArray, indexOf} from '../utils';
import AST from './ast';
@@ -157,6 +159,11 @@ Compiler.prototype = {
PartialStatement: function(partial) {
this.usePartial = true;
let program = partial.program;
if (program) {
program = this.compileProgram(partial.program);
}
let params = partial.params;
if (params.length > 1) {
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
@@ -170,7 +177,7 @@ Compiler.prototype = {
this.accept(partial.name);
}
this.setupFullMustacheParams(partial, undefined, undefined, true);
this.setupFullMustacheParams(partial, program, undefined, true);
let indent = partial.indent || '';
if (this.options.preventIndent && indent) {
@@ -181,9 +188,12 @@ Compiler.prototype = {
this.opcode('invokePartial', isDynamic, partialName, indent);
this.opcode('append');
},
PartialBlockStatement: function(partialBlock) {
this.PartialStatement(partialBlock);
},
MustacheStatement: function(mustache) {
this.SubExpression(mustache); // eslint-disable-line new-cap
this.SubExpression(mustache);
if (mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
+19
View File
@@ -185,3 +185,22 @@ export function prepareProgram(statements, loc) {
}
export function preparePartialBlock(openPartialBlock, program, close, locInfo) {
if (openPartialBlock.name.original !== close.path.original) {
let errorNode = {loc: openPartialBlock.name.loc};
throw new Exception(openPartialBlock.name.original + " doesn't match " + close.path.original, errorNode);
}
return {
type: 'PartialBlockStatement',
path: openPartialBlock.name,
params: openPartialBlock.params,
hash: openPartialBlock.hash,
program,
openStrip: openPartialBlock.strip,
closeStrip: close && close.strip,
loc: this.locInfo(locInfo)
};
}
+16
View File
@@ -84,6 +84,22 @@ PrintVisitor.prototype.PartialStatement = function(partial) {
}
return this.pad('{{> ' + content + ' }}');
};
PrintVisitor.prototype.PartialBlockStatement = function(partial) {
let content = 'PARTIAL BLOCK:' + partial.name.original;
if (partial.params[0]) {
content += ' ' + this.accept(partial.params[0]);
}
if (partial.hash) {
content += ' ' + this.accept(partial.hash);
}
content += ' ' + this.pad('PROGRAM:');
this.padding++;
content += this.accept(partial.program);
this.padding--;
return this.pad('{{> ' + content + ' }}');
};
PrintVisitor.prototype.ContentStatement = function(content) {
return this.pad("CONTENT[ '" + content.value + "' ]");
+24 -21
View File
@@ -75,35 +75,21 @@ Visitor.prototype = {
this.acceptArray(program.body);
},
MustacheStatement: function(mustache) {
this.acceptRequired(mustache, 'path');
this.acceptArray(mustache.params);
this.acceptKey(mustache, 'hash');
},
MustacheStatement: visitSubExpression,
BlockStatement: function(block) {
this.acceptRequired(block, 'path');
this.acceptArray(block.params);
this.acceptKey(block, 'hash');
BlockStatement: visitBlock,
this.acceptKey(block, 'program');
this.acceptKey(block, 'inverse');
},
PartialStatement: visitPartial,
PartialBlockStatement: function(partial) {
visitPartial.call(this, partial);
PartialStatement: function(partial) {
this.acceptRequired(partial, 'name');
this.acceptArray(partial.params);
this.acceptKey(partial, 'hash');
this.acceptKey(partial, 'program');
},
ContentStatement: function(/* content */) {},
CommentStatement: function(/* comment */) {},
SubExpression: function(sexpr) {
this.acceptRequired(sexpr, 'path');
this.acceptArray(sexpr.params);
this.acceptKey(sexpr, 'hash');
},
SubExpression: visitSubExpression,
PathExpression: function(/* path */) {},
@@ -121,4 +107,21 @@ Visitor.prototype = {
}
};
function visitSubExpression(mustache) {
this.acceptRequired(mustache, 'path');
this.acceptArray(mustache.params);
this.acceptKey(mustache, 'hash');
}
function visitBlock(block) {
visitSubExpression.call(this, block);
this.acceptKey(block, 'program');
this.acceptKey(block, 'inverse');
}
function visitPartial(partial) {
this.acceptRequired(partial, 'name');
this.acceptArray(partial.params);
this.acceptKey(partial, 'hash');
}
export default Visitor;
+14 -1
View File
@@ -194,7 +194,11 @@ export function wrapProgram(container, i, fn, data, declaredBlockParams, blockPa
export function resolvePartial(partial, context, options) {
if (!partial) {
partial = options.partials[options.name];
if (options.name === '@partial-block') {
partial = options.data['partial-block'];
} else {
partial = options.partials[options.name];
}
} else if (!partial.call && !options.name) {
// This is a dynamic partial that returned a string
options.name = partial;
@@ -209,6 +213,15 @@ export function invokePartial(partial, context, options) {
options.data.contextPath = options.ids[0] || options.data.contextPath;
}
let partialBlock;
if (options.fn && options.fn !== noop) {
partialBlock = options.data['partial-block'] = options.fn;
}
if (partial === undefined && partialBlock) {
partial = partialBlock;
}
if (partial === undefined) {
throw new Exception('The partial ' + options.name + ' could not be found');
} else if (partial instanceof Function) {