Improve {{> foo}} lookup order

We now look for a helper named 'foo' (which can return
a dynamically chosen template object) before looking
for a global template named 'foo'. This is consistent
with the principle that adding a helper should generally
override other things with the same name (ie data properties)

This change was motivated by frontpage, which had both
a template and a helper named 'body'.
This commit is contained in:
Avital Oliver
2014-01-28 22:53:31 -08:00
parent f728523599
commit 6d0348da5d
5 changed files with 62 additions and 25 deletions

View File

@@ -106,7 +106,7 @@ Tinytest.add("spacebars - compiler output", function (test) {
function() {
var self = this;
return function() {
return Spacebars.include(Template.foo || self.lookup("foo"), {
return Spacebars.include(self.lookup("foo", {template: true}), {
__content: UI.block(function() {
var self = this;
return "abc";
@@ -137,7 +137,7 @@ Tinytest.add("spacebars - compiler output", function (test) {
function() {
var self = this;
return function() {
return Spacebars.include(Template.foo || self.lookup("foo"), {
return Spacebars.include(self.lookup("foo", {template: true}), {
data: self.lookup("bar")
});
};
@@ -147,7 +147,7 @@ Tinytest.add("spacebars - compiler output", function (test) {
function() {
var self = this;
return function() {
return Spacebars.include(Template.foo || self.lookup("foo"), {
return Spacebars.include(self.lookup("foo", {template: true}), {
x: self.lookup("bar")
});
};
@@ -157,7 +157,7 @@ Tinytest.add("spacebars - compiler output", function (test) {
function() {
var self = this;
return function() {
return Spacebars.include(Template.foo || self.lookup("foo"), {
return Spacebars.include(self.lookup("foo", {template: true}), {
data: function() {
return Spacebars.call(Spacebars.dot(self.lookup("bar"), "baz"));
}
@@ -169,7 +169,7 @@ Tinytest.add("spacebars - compiler output", function (test) {
function() {
var self = this;
return function() {
return Spacebars.include(Template.foo || self.lookup("foo"), {
return Spacebars.include(self.lookup("foo", {template: true}), {
x: function() {
return Spacebars.call(Spacebars.dot(self.lookup("bar"), "baz"));
}
@@ -184,7 +184,7 @@ Tinytest.add("spacebars - compiler output", function (test) {
function() {
var self = this;
return function() {
return Spacebars.include(Template.foo || self.lookup("foo"), {
return Spacebars.include(self.lookup("foo", {template: true}), {
__content: UI.block(function() {
var self = this;
return "aaa";
@@ -200,7 +200,7 @@ Tinytest.add("spacebars - compiler output", function (test) {
function() {
var self = this;
return function() {
return Spacebars.include(Template.foo || self.lookup("foo"), {
return Spacebars.include(self.lookup("foo", {template: true}), {
__content: UI.block(function() {
var self = this;
return "aaa";
@@ -274,4 +274,4 @@ Tinytest.add("spacebars - compiler errors", function (test) {
"Unexpected HTML close tag. <input> should have no close tag.");
assertStartsWith(getError("{{#each foo}}<input></input>{{/foo}}"),
"Unexpected HTML close tag. <input> should have no close tag.");
});
});

View File

@@ -170,21 +170,12 @@ var codeGenTemplateTag = function (tag) {
codeGenMustache(tag) + '); }');
} else if (tag.type === 'INCLUSION' || tag.type === 'BLOCKOPEN') {
var path = tag.path;
var compCode = codeGenPath(path);
var compCode;
if (path.length === 1) {
var compName = path[0];
if (builtInComponents.hasOwnProperty(compName)) {
compCode = builtInComponents[compName];
} else {
// toObjectLiteralKey returns `"foo"` or `foo` depending on
// whether `foo` is a safe JavaScript identifier.
var member = toObjectLiteralKey(path[0]);
var templateDotFoo = (member.charAt(0) === '"' ?
'Template[' + member + ']' :
'Template.' + member);
compCode = ('(' + templateDotFoo + ' || ' + compCode + ')');
}
if (path.length === 1 && builtInComponents.hasOwnProperty(path[0])) {
compCode = builtInComponents[path[0]];
} else {
compCode = codeGenPath(path, {lookupTemplate: true});
}
var includeArgs = codeGenInclusionArgs(tag);
@@ -305,7 +296,14 @@ var codeGenMustache = function (tag, mustacheType) {
// (i.e. it may invalidate the current computation).
//
// No code is generated to call the result if it's a function.
var codeGenPath = function (path) {
//
// Options:
//
// - lookupTemplate {Boolean} If true, generated code also looks in
// the list of templates. (After helpers, before data context).
// Used when generating code for `{{> foo}}` or `{{#foo}}`. Only
// used for non-dotted paths.
var codeGenPath = function (path, opts) {
// Let {{#if content}} check whether this template was invoked via
// inclusion or as a block helper.
if (builtInComponents.hasOwnProperty(path[0])) {
@@ -314,7 +312,10 @@ var codeGenPath = function (path) {
return builtInComponents[path[0]];
}
var code = 'self.lookup(' + toJSLiteral(path[0]) + ')';
var args = [toJSLiteral(path[0])];
if (opts && opts.lookupTemplate && path.length === 1)
args.push('{template: true}');
var code = 'self.lookup(' + args.join(', ') + ')';
if (path.length > 1) {
code = 'Spacebars.dot(' + code + ', ' +

View File

@@ -448,3 +448,16 @@ Hi there!
<template name="spacebars_template_test_double">
{{foo}}
</template>
<template name="spacebars_template_test_inclusion_lookup">
{{> spacebars_template_test_inclusion_lookup_subtmpl}}
</template>
<template name="spacebars_template_test_inclusion_lookup_subtmpl">
This is the template.
</template>
<template name="spacebars_template_test_inclusion_lookup_subtmpl2">
This is generated by a helper with the same name.
</template>

View File

@@ -1340,3 +1340,16 @@ Tinytest.add("spacebars - templates - double", function (test) {
run(null, '');
run(undefined, '');
});
Tinytest.add("spacebars - templates - inclusion lookup order", function (test) {
// test that {{> foo}} looks for a helper named 'foo' before a
// template named 'foo'
var tmpl = Template.spacebars_template_test_inclusion_lookup;
tmpl.spacebars_template_test_inclusion_lookup_subtmpl =
Template.spacebars_template_test_inclusion_lookup_subtmpl2;
var div = renderToDiv(tmpl);
test.equal(
stripComments(div.innerHTML),
"This is generated by a helper with the same name.");
});

View File

@@ -29,8 +29,13 @@ var builtInComponents = {
};
_extend(UI.Component, {
lookup: function (id) {
// Options:
//
// - template {Boolean} If true, look at the list of templates after
// helpers and before data context.
lookup: function (id, opts) {
var self = this;
var template = opts && opts.template;
var result;
var comp;
@@ -58,6 +63,7 @@ _extend(UI.Component, {
} else if (_.has(builtInComponents, id)) {
return builtInComponents[id];
// Code to search the global namespace for capitalized names
// like component classes, `Template`, `StringUtils.foo`,
// etc.
@@ -79,6 +85,10 @@ _extend(UI.Component, {
// for this? We should definitely not put it on the Handlebars
// namespace.
result = Handlebars._globalHelpers[id];
} else if (template && _.has(Template, id)) {
return Template[id];
} else {
// Resolve id `foo` as `data.foo` (with a "soft dot").
return function (/*arguments*/) {