{{> UI.dynamic }} for easier dynamic template picking.

See #2123.
This commit is contained in:
Emily Stark
2014-05-15 18:06:55 -07:00
parent b63328c4e9
commit bd4a8735bd
7 changed files with 160 additions and 5 deletions

View File

@@ -163,10 +163,22 @@ var builtInBlockHelpers = {
'each': 'UI.Each'
};
// These must be prefixed with `UI.` when you use them in a template.
var builtInLexicals = {
// Some `UI.*` paths are special in that they generate code that
// doesn't folow the normal lookup rules for dotted symbols. The
// following names must be prefixed with `UI.` when you use them in a
// template.
var builtInUIPaths = {
// `template` is a local variable defined in the generated render
// function for the template in which `UI.contentBlock` (or
// `UI.elseBlock`) is invoked. `template` is a reference to the
// template itself.
'contentBlock': 'template.__content',
'elseBlock': 'template.__elseContent'
'elseBlock': 'template.__elseContent',
// `Template` is the global template namespace. If you define a
// template named `foo` in Spacebars, it gets defined as
// `Template.foo` in JavaScript.
'dynamic': 'Template.__dynamic'
};
// A "reserved name" can't be used as a <template> name. This
@@ -288,11 +300,11 @@ var codeGenPath = function (path, opts) {
// inclusion or as a block helper, in addition to supporting
// `{{> UI.contentBlock}}`.
if (path.length >= 2 &&
path[0] === 'UI' && builtInLexicals.hasOwnProperty(path[1])) {
path[0] === 'UI' && builtInUIPaths.hasOwnProperty(path[1])) {
if (path.length > 2)
throw new Error("Unexpected dotted path beginning with " +
path[0] + '.' + path[1]);
return builtInLexicals[path[1]];
return builtInUIPaths[path[1]];
}
var args = [toJSLiteral(path[0])];

View File

@@ -0,0 +1 @@
.build*

View File

@@ -0,0 +1,19 @@
<template name="ui-dynamic-test">
{{> UI.dynamic templateName=templateName dataContext=templateData}}
</template>
<template name="ui-dynamic-test-no-data">
{{> UI.dynamic templateName=templateName}}
</template>
<template name="ui-dynamic-test-inherited-data">
{{#with context}}
{{> UI.dynamic templateName=templateName}}
{{else}}
{{> UI.dynamic templateName=templateName}}
{{/with}}
</template>
<template name="ui-dynamic-test-sub">
test{{foo}}
</template>

View File

@@ -0,0 +1,87 @@
// Copied from spacebars-tests
var renderToDiv = function (comp) {
var div = document.createElement("DIV");
UI.materialize(comp, div);
return div;
};
Tinytest.add(
"ui-dynamic-template - render template dynamically", function (test, expect) {
var tmpl = Template["ui-dynamic-test"];
var rvName = new ReactiveVar;
var rvData = new ReactiveVar;
tmpl.templateName = function () {
return rvName.get();
};
tmpl.templateData = function () {
return rvData.get();
};
// No template chosen
var div = renderToDiv(tmpl);
test.equal(div.innerHTML.trim(), "");
// Choose the "ui-dynamic-test-sub" template, with no data context
// passed in.
rvName.set("ui-dynamic-test-sub");
Deps.flush();
test.equal(div.innerHTML.trim(), "test");
// Set a data context.
rvData.set({ foo: "bar" });
Deps.flush();
test.equal(div.innerHTML.trim(), "testbar");
});
// Same test as above, but the {{> UI.dynamic}} inclusion has no
// `dataContext` argument.
Tinytest.add(
"ui-dynamic-template - render template dynamically, no data context",
function (test, expect) {
var tmpl = Template["ui-dynamic-test-no-data"];
var rvName = new ReactiveVar;
tmpl.templateName = function () {
return rvName.get();
};
var div = renderToDiv(tmpl);
test.equal(div.innerHTML.trim(), "");
rvName.set("ui-dynamic-test-sub");
Deps.flush();
test.equal(div.innerHTML.trim(), "test");
});
Tinytest.add(
"ui-dynamic-template - render template " +
"dynamically, data context gets inherited",
function (test, expect) {
var tmpl = Template["ui-dynamic-test-inherited-data"];
var rvName = new ReactiveVar();
var rvData = new ReactiveVar();
tmpl.templateName = function () {
return rvName.get();
};
tmpl.context = function () {
return rvData.get();
};
var div = renderToDiv(tmpl);
test.equal(div.innerHTML.trim(), "");
rvName.set("ui-dynamic-test-sub");
Deps.flush();
test.equal(div.innerHTML.trim(), "test");
// Set the top-level template's data context; this should be
// inherited by the dynamically-chosen template, since the {{>
// UI.dynamic}} inclusion didn't include a data argument.
rvData.set({ foo: "bar" });
Deps.flush();
test.equal(div.innerHTML.trim(), "testbar");
}
);

View File

@@ -0,0 +1,18 @@
<template name="__dynamicWithDataContext">
{{#with chooseTemplate templateName}}
{{#with ../dataContext}} {{! original 'dataContext' argument to __dynamic}}
{{> ..}} {{! return value from chooseTemplate(templateName) }}
{{else}} {{! if the 'dataContext' argument was falsey }}
{{> .}} {{! return value from chooseTemplate(templateName) }}
{{/with}}
{{/with}}
</template>
<template name="__dynamic">
{{#if dataContext}}
{{> __dynamicWithDataContext}}
{{else}}
{{! if there was no explicit dataContext argument, use the parent context}}
{{> __dynamicWithDataContext templateName=templateName dataContext=..}}
{{/if}}
</template>

View File

@@ -0,0 +1,3 @@
Template.__dynamicWithDataContext.chooseTemplate = function (name) {
return Template[name] || null;
};

View File

@@ -0,0 +1,15 @@
Package.describe({
summary: "Component for dynamically rendering templates",
internal: true
});
Package.on_use(function (api) {
api.use('templating');
api.add_files(['dynamic.html', 'dynamic.js'], 'client');
});
Package.on_test(function (api) {
api.use(["ui-dynamic-template", "tinytest", "test-helpers"]);
api.use("templating", "client");
api.add_files(["dynamic-tests.html", "dynamic-tests.js"], "client");
});