diff --git a/doc/reference/qweb_templating_language.md b/doc/reference/qweb_templating_language.md index 81cd6378..b1d910c5 100644 --- a/doc/reference/qweb_templating_language.md +++ b/doc/reference/qweb_templating_language.md @@ -15,6 +15,7 @@ - [Dynamic Attributes](#dynamic-attributes) - [Loops](#loops) - [Rendering Sub Templates](#rendering-sub-templates) + - [Dynamic Sub Templates](#dynamic-sub-templates) - [Translations](#translations) - [Debugging](#debugging) @@ -509,6 +510,22 @@ This can be used to define variables scoped to a sub template: ``` +### Dynamic sub templates + +The `t-call` directive can also be used to dynamically call a sub template, +using string interpolation. For example: + +```xml +
+ + content + +
+``` + +Here, the name of the template is obtained from the `template` value in the +template rendering context. + ### Translations By default, QWeb specify that templates should be translated. If this behaviour diff --git a/src/qweb/base_directives.ts b/src/qweb/base_directives.ts index b6f23e9c..aa14bf9a 100644 --- a/src/qweb/base_directives.ts +++ b/src/qweb/base_directives.ts @@ -1,4 +1,4 @@ -import { CompilationContext } from "./compilation_context"; +import { CompilationContext, INTERP_REGEXP } from "./compilation_context"; import { QWeb } from "./qweb"; import { htmlToVDOM } from "../vdom/html_to_vdom"; import { QWebVar } from "./expression_parser"; @@ -224,19 +224,35 @@ QWeb.addDirective({ ctx.rootContext.shouldDefineScope = true; ctx.rootContext.shouldDefineUtils = true; const subTemplate = node.getAttribute("t-call")!; + const isDynamic = INTERP_REGEXP.test(subTemplate); const nodeTemplate = qweb.templates[subTemplate]; - if (!nodeTemplate) { + if (!isDynamic && !nodeTemplate) { throw new Error(`Cannot find template "${subTemplate}" (t-call)`); } // Step 2: compile target template in sub templates // ------------------------------------------------ - let subId = qweb.subTemplates[subTemplate]; - if (!subId) { - subId = QWeb.nextId++; - qweb.subTemplates[subTemplate] = subId; - const subTemplateFn = qweb._compile(subTemplate, { hasParent: true, defineKey: true }); - QWeb.subTemplates[subId] = subTemplateFn; + let subIdstr: string; + if (isDynamic) { + const _id = ctx.generateID(); + ctx.addLine(`let tname${_id} = ${ctx.interpolate(subTemplate)};`); + ctx.addLine(`let tid${_id} = this.constructor.nextId++;`); + ctx.addIf(`!(tname${_id} in this.subTemplates)`); + ctx.addLine(`this.subTemplates[tname${_id}] = tid${_id};`); + ctx.addLine( + `this.constructor.subTemplates[tid${_id}] = this._compile(tname${_id}, {hasParent: true, defineKey: true});` + ); + ctx.closeIf(); + subIdstr = `tid${_id}`; + } else { + let subId = qweb.subTemplates[subTemplate]; + if (!subId) { + subId = QWeb.nextId++; + qweb.subTemplates[subTemplate] = subId; + const subTemplateFn = qweb._compile(subTemplate, { hasParent: true, defineKey: true }); + QWeb.subTemplates[subId] = subTemplateFn; + } + subIdstr = `'${subId}'`; } // Step 3: compile t-call body if necessary @@ -272,12 +288,12 @@ QWeb.addDirective({ const parentNode = ctx.parentNode ? `c${ctx.parentNode}` : "result"; const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}, key: ${key}})`; if (ctx.parentNode) { - ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, scope, ${extra});`); + ctx.addLine(`this.constructor.subTemplates[${subIdstr}].call(this, scope, ${extra});`); } else { // this is a t-call with no parentnode, we need to extract the result ctx.rootContext.shouldDefineResult = true; ctx.addLine(`result = []`); - ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, scope, ${extra});`); + ctx.addLine(`this.constructor.subTemplates[${subIdstr}].call(this, scope, ${extra});`); ctx.addLine(`result = result[0]`); } diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap index b7f33abf..4392f539 100644 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ b/tests/qweb/__snapshots__/qweb.test.ts.snap @@ -1391,6 +1391,54 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = ` }" `; +exports[`t-call (template calling dynamic t-call 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"main\\" + let utils = this.constructor.utils; + let scope = Object.create(context); + let h = this.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + let tname2 = (scope['template']); + let tid2 = this.constructor.nextId++; + if (!(tname2 in this.subTemplates)) { + this.subTemplates[tname2] = tid2; + this.constructor.subTemplates[tid2] = this._compile(tname2, {hasParent: true, defineKey: true}); + } + let _origScope3 = scope; + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; + this.constructor.subTemplates[tid2].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'})); + scope = _origScope3; + return vn1; +}" +`; + +exports[`t-call (template calling dynamic t-call 2`] = ` +"function anonymous(context, extra +) { + // Template name: \\"main\\" + let utils = this.constructor.utils; + let scope = Object.create(context); + let h = this.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + let tname2 = (scope['template']); + let tid2 = this.constructor.nextId++; + if (!(tname2 in this.subTemplates)) { + this.subTemplates[tname2] = tid2; + this.constructor.subTemplates[tid2] = this._compile(tname2, {hasParent: true, defineKey: true}); + } + let _origScope3 = scope; + scope = Object.create(scope); + scope.__access_mode__ = 'ro'; + this.constructor.subTemplates[tid2].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'})); + scope = _origScope3; + return vn1; +}" +`; + exports[`t-call (template calling inherit context 1`] = ` "function anonymous(context, extra ) { diff --git a/tests/qweb/qweb.test.ts b/tests/qweb/qweb.test.ts index 95c0d2c6..24b6908e 100644 --- a/tests/qweb/qweb.test.ts +++ b/tests/qweb/qweb.test.ts @@ -1095,6 +1095,16 @@ describe("t-call (template calling", () => { const expected = "

yip yip

"; expect(renderToString(qweb, "main")).toBe(expected); }); + + test("dynamic t-call", () => { + qweb.addTemplate("foo", ``); + qweb.addTemplate("bar", ``); + qweb.addTemplate("main", `
`); + const expected = "
foo
"; + expect(renderToString(qweb, "main", { template: "foo", val: "foo" })).toBe(expected); + const expected2 = "
quux
"; + expect(renderToString(qweb, "main", { template: "bar", val: "quux" })).toBe(expected2); + }); }); describe("foreach", () => {