From 3a461e1dd1ae0f19ccd2f63cee52d05bf76ad035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 4 Jun 2021 11:23:33 +0200 Subject: [PATCH] [IMP] qweb: add support for t-tag directive Very useful in some cases, when one needs to define a generic component. closes #721 --- doc/reference/qweb_templating_language.md | 18 +++++ src/qweb/qweb.ts | 12 +++- .../__snapshots__/qweb_t_tag.test.ts.snap | 71 +++++++++++++++++++ tests/qweb/qweb_t_tag.test.ts | 42 +++++++++++ 4 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 tests/qweb/__snapshots__/qweb_t_tag.test.ts.snap create mode 100644 tests/qweb/qweb_t_tag.test.ts diff --git a/doc/reference/qweb_templating_language.md b/doc/reference/qweb_templating_language.md index e468984d..5c6d17e8 100644 --- a/doc/reference/qweb_templating_language.md +++ b/doc/reference/qweb_templating_language.md @@ -13,6 +13,7 @@ - [Setting Variables](#setting-variables) - [Conditionals](#conditionals) - [Dynamic Attributes](#dynamic-attributes) + - [Dynamic Tag Names](#dynamic-tag-names) - [Loops](#loops) - [Rendering Sub Templates](#rendering-sub-templates) - [Dynamic Sub Templates](#dynamic-sub-templates) @@ -76,6 +77,7 @@ needs. Here is a list of all Owl specific directives: | `t-transition` | [Defining an animation](animations.md#css-transitions) | | `t-slot` | [Rendering a slot](slots.md) | | `t-model` | [Form input bindings](component.md#form-input-bindings) | +| `t-tag` | [Rendering nodes with dynamic tag name](#dynamic-tag-names) | ## Reference @@ -324,6 +326,22 @@ values) or a pair `[key, value]`. For example:
``` +### Dynamic tag names + +When writing generic components or templates, the specific concrete tag for an +HTML element is not known yet. In those situations, the `t-tag` directive is +useful. It simply evaluates dynamically an expression to use as a tag name. The +template: + +```xml + + content + +``` + +will be rendered as `
content
` if the `tag` context key +is set to `div`. + ### Loops QWeb has an iteration directive `t-foreach` which take an expression returning the diff --git a/src/qweb/qweb.ts b/src/qweb/qweb.ts index cf6cce9c..4222366a 100644 --- a/src/qweb/qweb.ts +++ b/src/qweb/qweb.ts @@ -203,6 +203,7 @@ export class QWeb extends EventBus { att: 1, attf: 1, translation: 1, + tag: 1, }; static DIRECTIVES: Directive[] = []; @@ -614,7 +615,7 @@ export class QWeb extends EventBus { } } - if (node.nodeName !== "t") { + if (node.nodeName !== "t" || node.hasAttribute("t-tag")) { let nodeID = this._compileGenericNode(node, ctx, withHandlers); ctx = ctx.withParent(nodeID); let nodeHooks = {}; @@ -841,7 +842,14 @@ export class QWeb extends EventBus { ctx.addLine(`}`); ctx.closeIf(); } - ctx.addLine(`let vn${nodeID} = h('${node.nodeName}', p${nodeID}, c${nodeID});`); + let nodeName = `'${node.nodeName}'`; + if ((node).hasAttribute("t-tag")) { + const tagExpr = (node).getAttribute("t-tag"); + (node).removeAttribute("t-tag"); + nodeName = `tag${ctx.generateID()}`; + ctx.addLine(`let ${nodeName} = ${ctx.formatExpression(tagExpr)};`); + } + ctx.addLine(`let vn${nodeID} = h(${nodeName}, p${nodeID}, c${nodeID});`); if (ctx.parentNode) { ctx.addLine(`c${ctx.parentNode}.push(vn${nodeID});`); } else if (ctx.loopNumber || ctx.hasKey0) { diff --git a/tests/qweb/__snapshots__/qweb_t_tag.test.ts.snap b/tests/qweb/__snapshots__/qweb_t_tag.test.ts.snap new file mode 100644 index 00000000..05ce570c --- /dev/null +++ b/tests/qweb/__snapshots__/qweb_t_tag.test.ts.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`qweb t-tag simple usecases 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let scope = Object.create(context); + let result; + let h = this.h; + let c1 = [], p1 = {key:1}; + let tag2 = 'div'; + let vn1 = h(tag2, p1, c1); + result = vn1; + return result; +}" +`; + +exports[`qweb t-tag simple usecases 2`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let scope = Object.create(context); + let result; + let h = this.h; + let c3 = [], p3 = {key:3}; + let tag4 = scope['tag']; + let vn3 = h(tag4, p3, c3); + result = vn3; + c3.push({text: \`text\`}); + return result; +}" +`; + +exports[`qweb t-tag with multiple attributes 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let scope = Object.create(context); + let result; + let h = this.h; + let _2 = {'blueberry':true}; + let _3 = 'raspberry'; + let c4 = [], p4 = {key:4,attrs:{taste: _3},class:_2}; + let tag5 = scope['tag']; + let vn4 = h(tag5, p4, c4); + result = vn4; + c4.push({text: \`gooseberry\`}); + return result; +}" +`; + +exports[`qweb t-tag with multiple child nodes 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let scope = Object.create(context); + let result; + let h = this.h; + let c1 = [], p1 = {key:1}; + let tag2 = scope['tag']; + let vn1 = h(tag2, p1, c1); + result = vn1; + c1.push({text: \` pear \`}); + let c3 = [], p3 = {key:3}; + let vn3 = h('span', p3, c3); + c1.push(vn3); + c3.push({text: \`apple\`}); + c1.push({text: \` strawberry \`}); + return result; +}" +`; diff --git a/tests/qweb/qweb_t_tag.test.ts b/tests/qweb/qweb_t_tag.test.ts new file mode 100644 index 00000000..e39d9400 --- /dev/null +++ b/tests/qweb/qweb_t_tag.test.ts @@ -0,0 +1,42 @@ +import { QWeb } from "../../src/qweb/index"; +import { renderToString } from "../helpers"; + +//------------------------------------------------------------------------------ +// Setup and helpers +//------------------------------------------------------------------------------ + +function render(template, context = {}) { + const qweb = new QWeb(); + qweb.addTemplate("test", template); + return renderToString(qweb, "test", context); +} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("qweb t-tag", () => { + test("simple usecases", () => { + expect(render(``)).toBe("
"); + expect(render(`text`, { tag: "span" })).toBe("text"); + }); + + test("with multiple child nodes", () => { + const template = ` + + pear + apple + strawberry + `; + expect(render(template, { tag: "div" })).toBe( + "
pear apple strawberry
" + ); + }); + + test("with multiple attributes", () => { + const template = ` + gooseberry`; + const expected = `
gooseberry
`; + expect(render(template, { tag: "div" })).toBe(expected); + }); +});