Include name option for all helper calls
All helper calls will have access to `options.name` which is the first id value of the mustache operation. As part of this the helperMissing call has been simplified to remove the indexed name in order to optimize the call. This is a breaking change. Fixes #634
This commit is contained in:
@@ -49,11 +49,13 @@ HandlebarsEnvironment.prototype = {
|
||||
};
|
||||
|
||||
function registerDefaultHelpers(instance) {
|
||||
instance.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
if(arguments.length === 1) {
|
||||
// A missing field in a {{foo}} constuct.
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Exception("Missing helper: '" + arg + "'");
|
||||
// Someone is actually trying to call something, blow up.
|
||||
throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ Compiler.prototype = {
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
this.opcode('emptyHash');
|
||||
this.opcode('blockValue');
|
||||
this.opcode('blockValue', sexpr.id.original);
|
||||
} else {
|
||||
this.ambiguousSexpr(sexpr, program, inverse);
|
||||
|
||||
|
||||
@@ -224,11 +224,11 @@ JavaScriptCompiler.prototype = {
|
||||
// `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
|
||||
// replace it on the stack with the result of properly
|
||||
// invoking blockHelperMissing.
|
||||
blockValue: function() {
|
||||
blockValue: function(name) {
|
||||
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
||||
|
||||
var params = ["depth0"];
|
||||
this.setupParams(0, params);
|
||||
this.setupParams(name, 0, params);
|
||||
|
||||
this.replaceStack(function(current) {
|
||||
params.splice(1, 0, current);
|
||||
@@ -247,7 +247,7 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
// We're being a bit cheeky and reusing the options value from the prior exec
|
||||
var params = ["depth0"];
|
||||
this.setupParams(0, params, true);
|
||||
this.setupParams('', 0, params, true);
|
||||
|
||||
this.flushInline();
|
||||
|
||||
@@ -504,20 +504,15 @@ JavaScriptCompiler.prototype = {
|
||||
this.context.aliases.helperMissing = 'helpers.helperMissing';
|
||||
this.useRegister('helper');
|
||||
|
||||
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
|
||||
var helper = this.lastHelper = this.setupHelper(paramSize, name);
|
||||
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
|
||||
|
||||
var lookup = 'helper = ' + helper.name + ' || ' + nonHelper;
|
||||
var lookup = 'helper = ' + helper.name + ' || ' + nonHelper + ' || helperMissing';
|
||||
if (helper.paramsInit) {
|
||||
lookup += ',' + helper.paramsInit;
|
||||
}
|
||||
|
||||
this.push(
|
||||
'('
|
||||
+ lookup
|
||||
+ ',helper '
|
||||
+ '? helper.call(' + helper.callParams + ') '
|
||||
+ ': helperMissing.call(' + helper.helperMissingParams + '))');
|
||||
this.push('(' + lookup + ',helper.call(' + helper.callParams + '))');
|
||||
|
||||
// Always flush subexpressions. This is both to prevent the compounding size issue that
|
||||
// occurs when the code has to be duplicated for inlining and also to prevent errors
|
||||
@@ -826,7 +821,7 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
setupHelper: function(paramSize, name, missingParams) {
|
||||
var params = [],
|
||||
paramsInit = this.setupParams(paramSize, params, missingParams);
|
||||
paramsInit = this.setupParams(name, paramSize, params, missingParams);
|
||||
var foundHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
return {
|
||||
@@ -838,9 +833,10 @@ JavaScriptCompiler.prototype = {
|
||||
};
|
||||
},
|
||||
|
||||
setupOptions: function(paramSize, params) {
|
||||
setupOptions: function(helper, paramSize, params) {
|
||||
var options = [], contexts = [], types = [], param, inverse, program;
|
||||
|
||||
options.push("name:" + this.quotedString(helper));
|
||||
options.push("hash:" + this.popStack());
|
||||
|
||||
if (this.stringParams) {
|
||||
@@ -892,8 +888,8 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
// the params and contexts arguments are passed in arrays
|
||||
// to fill in
|
||||
setupParams: function(paramSize, params, useRegister) {
|
||||
var options = '{' + this.setupOptions(paramSize, params).join(',') + '}';
|
||||
setupParams: function(helperName, paramSize, params, useRegister) {
|
||||
var options = '{' + this.setupOptions(helperName, paramSize, params).join(',') + '}';
|
||||
|
||||
if (useRegister) {
|
||||
this.useRegister('options');
|
||||
|
||||
+40
-3
@@ -362,9 +362,9 @@ describe('helpers', function() {
|
||||
var context = { hello: "Hello", world: "world" };
|
||||
|
||||
var helpers = {
|
||||
helperMissing: function(helper, context) {
|
||||
if(helper === "link_to") {
|
||||
return new Handlebars.SafeString("<a>" + context + "</a>");
|
||||
helperMissing: function(mesg, options) {
|
||||
if(options.name === "link_to") {
|
||||
return new Handlebars.SafeString("<a>" + mesg + "</a>");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -436,6 +436,43 @@ describe('helpers', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('name field', function() {
|
||||
var context = {};
|
||||
var helpers = {
|
||||
blockHelperMissing: function() {
|
||||
return 'missing: ' + arguments[arguments.length-1].name;
|
||||
},
|
||||
helper: function() {
|
||||
return 'ran: ' + arguments[arguments.length-1].name;
|
||||
}
|
||||
};
|
||||
|
||||
it('should include in ambiguous mustache calls', function() {
|
||||
shouldCompileTo('{{helper}}', [context, helpers], 'ran: helper');
|
||||
});
|
||||
it('should include in helper mustache calls', function() {
|
||||
shouldCompileTo('{{helper 1}}', [context, helpers], 'ran: helper');
|
||||
});
|
||||
it('should include in ambiguous block calls', function() {
|
||||
shouldCompileTo('{{#helper}}{{/helper}}', [context, helpers], 'ran: helper');
|
||||
});
|
||||
it('should include in simple block calls', function() {
|
||||
shouldCompileTo('{{#./helper}}{{/./helper}}', [context, helpers], 'missing: ./helper');
|
||||
});
|
||||
it('should include in helper block calls', function() {
|
||||
shouldCompileTo('{{#helper 1}}{{/helper}}', [context, helpers], 'ran: helper');
|
||||
});
|
||||
it('should include in known helper calls', function() {
|
||||
var template = CompilerContext.compile("{{helper}}", {knownHelpers: {'helper': true}, knownHelpersOnly: true});
|
||||
|
||||
equal(template({}, {helpers: helpers}), 'ran: helper');
|
||||
});
|
||||
|
||||
it('should include full id', function() {
|
||||
shouldCompileTo('{{#foo.helper}}{{/foo.helper}}', [{foo: {}}, helpers], 'missing: foo.helper');
|
||||
});
|
||||
});
|
||||
|
||||
describe('name conflicts', function() {
|
||||
it("helpers take precedence over same-named context properties", function() {
|
||||
var template = CompilerContext.compile("{{goodbye}} {{cruel world}}");
|
||||
|
||||
Reference in New Issue
Block a user