diff --git a/src/qweb/compiler.ts b/src/qweb/compiler.ts
index 15e92dd0..c76e4196 100644
--- a/src/qweb/compiler.ts
+++ b/src/qweb/compiler.ts
@@ -54,6 +54,7 @@ class BlockDescription {
varName: string;
blockName: string;
+ dynamicTagName: string | null = null;
isRoot: boolean = false;
hasDynamicChildren: boolean = false;
children: BlockDescription[] = [];
@@ -95,6 +96,9 @@ class BlockDescription {
if (hasChildren) {
params += ", [" + this.children.map((c) => c.varName).join(", ") + "]";
}
+ if (this.dynamicTagName) {
+ return `toggler(${this.dynamicTagName}, ${this.blockName}(${this.dynamicTagName})(${params}))`;
+ }
return `${this.blockName}(${params})`;
} else if (this.type === "list") {
return `list(c_block${this.id})`;
@@ -304,7 +308,14 @@ export class QWebCompiler {
this.addLine(``);
for (let block of this.blocks) {
if (block.dom) {
- this.addLine(`let ${block.blockName} = createBlock(\`${block.asXmlString()}\`);`);
+ let xmlString = block.asXmlString();
+ if (block.dynamicTagName) {
+ xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
+ xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
+ this.addLine(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
+ } else {
+ this.addLine(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
+ }
}
}
}
@@ -514,11 +525,19 @@ export class QWebCompiler {
compileTDomNode(ast: ASTDomNode, ctx: Context) {
let { block, forceNewBlock } = ctx;
- const isNewBlock = !block || forceNewBlock;
+ const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null;
let codeIdx = this.target.code.length;
if (isNewBlock) {
+ if (ast.dynamicTag && ctx.block) {
+ this.insertAnchor(ctx.block!);
+ }
block = this.createBlock(block, "block", ctx);
this.blocks.push(block);
+ if (ast.dynamicTag) {
+ const tagExpr = this.generateId("tag");
+ this.addLine(`let ${tagExpr} = ${compileExpr(ast.dynamicTag)};`);
+ block.dynamicTagName = tagExpr;
+ }
}
// attributes
const attrs: { [key: string]: string } = {};
@@ -989,12 +1008,6 @@ export class QWebCompiler {
propString = propVar;
}
- let keyArg = `key + \`${key}\``;
- if (ctx.tKeyExpr) {
- keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
- }
- let blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
-
// slots
const hasSlot = !!Object.keys(ast.slots).length;
let slotDef: string;
@@ -1034,6 +1047,12 @@ export class QWebCompiler {
// todo: check the forcenewblock condition
this.insertAnchor(block);
}
+
+ let keyArg = `key + \`${key}\``;
+ if (ctx.tKeyExpr) {
+ keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
+ }
+ const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
let blockExpr = `component(${blockArgs})`;
if (Object.keys(extraArgs).length) {
this.shouldDefineAssign = true;
diff --git a/src/qweb/inline_expressions.ts b/src/qweb/inline_expressions.ts
index 2474c414..24a533d8 100644
--- a/src/qweb/inline_expressions.ts
+++ b/src/qweb/inline_expressions.ts
@@ -25,9 +25,10 @@
// Misc types, constants and helpers
//------------------------------------------------------------------------------
-const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(
- ","
-);
+const RESERVED_WORDS =
+ "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(
+ ","
+ );
const WORD_REPLACEMENT: { [key: string]: string } = Object.assign(Object.create(null), {
and: "&&",
diff --git a/src/qweb/parser.ts b/src/qweb/parser.ts
index c185d0d9..e4401492 100644
--- a/src/qweb/parser.ts
+++ b/src/qweb/parser.ts
@@ -35,6 +35,7 @@ export interface ASTComment {
export interface ASTDomNode {
type: ASTType.DomNode;
tag: string;
+ dynamicTag: string | null;
attrs: { [key: string]: string };
content: AST[];
ref: string | null;
@@ -289,7 +290,12 @@ const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
const { tagName } = node;
- if (tagName === "t") {
+ let dynamicTag = null;
+ if (node.hasAttribute("t-tag")) {
+ dynamicTag = node.getAttribute("t-tag");
+ node.removeAttribute("t-tag");
+ }
+ if (tagName === "t" && !dynamicTag) {
return null;
}
const children: AST[] = [];
@@ -373,6 +379,7 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
return {
type: ASTType.DomNode,
tag: tagName,
+ dynamicTag,
attrs,
on,
ref,
@@ -404,13 +411,9 @@ function parseTEscNode(node: Element, ctx: ParsingContext): AST | null {
}
if (ast && ast.type === ASTType.DomNode) {
return {
- type: ASTType.DomNode,
- tag: ast.tag,
- attrs: ast.attrs,
- on: ast.on,
+ ...ast,
ref,
content: [tesc],
- model: ast.model,
};
}
if (ast && ast.type === ASTType.TComponent) {
@@ -443,13 +446,9 @@ function parseTRawNode(node: Element, ctx: ParsingContext): AST | null {
if (ast && ast.type === ASTType.DomNode) {
tRaw.body = ast.content.length ? ast.content : null;
return {
- type: ASTType.DomNode,
- tag: ast.tag,
- attrs: ast.attrs,
- on: ast.on,
+ ...ast,
ref,
content: [tRaw],
- model: ast.model,
};
}
diff --git a/tests/components/error_handling.test.ts b/tests/components/error_handling.test.ts
index 67ef11ef..963f5676 100644
--- a/tests/components/error_handling.test.ts
+++ b/tests/components/error_handling.test.ts
@@ -43,7 +43,8 @@ describe("basics", () => {
expect(fixture.innerHTML).toBe("");
expect(status(parent)).toBe("destroyed");
expect(error).toBeDefined();
- const regexp = /Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/g;
+ const regexp =
+ /Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/g;
expect(error.message).toMatch(regexp);
});
@@ -112,7 +113,8 @@ describe.skip("errors and promises", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp =
+ /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
expect(console.error).toBeCalledTimes(0);
@@ -225,7 +227,8 @@ describe.skip("errors and promises", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp =
+ /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
expect(console.error).toBeCalledTimes(0);
@@ -251,7 +254,8 @@ describe.skip("errors and promises", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp =
+ /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
expect(console.error).toBeCalledTimes(0);
@@ -274,7 +278,8 @@ describe.skip("errors and promises", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp = /Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
+ const regexp =
+ /Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
expect(error.message).toMatch(regexp);
});
diff --git a/tests/components/style_class.test.ts b/tests/components/style_class.test.ts
index e44a1262..2f75798f 100644
--- a/tests/components/style_class.test.ts
+++ b/tests/components/style_class.test.ts
@@ -341,7 +341,8 @@ describe("style and class handling", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp =
+ /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
expect(fixture.innerHTML).toBe("");
});
diff --git a/tests/misc/portal.test.ts b/tests/misc/portal.test.ts
index 6e7bee56..2ee53eba 100644
--- a/tests/misc/portal.test.ts
+++ b/tests/misc/portal.test.ts
@@ -405,7 +405,8 @@ describe("Portal", () => {
error = e;
}
expect(error).toBeDefined();
- const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
+ const regexp =
+ /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error.message).toMatch(regexp);
});
diff --git a/tests/qweb/__snapshots__/t_tag.test.ts.snap b/tests/qweb/__snapshots__/t_tag.test.ts.snap
new file mode 100644
index 00000000..3c95eecb
--- /dev/null
+++ b/tests/qweb/__snapshots__/t_tag.test.ts.snap
@@ -0,0 +1,128 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`qweb t-tag can fallback if falsy tag 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
+
+ let block1 = tag => createBlock(\`<\${tag || 'fallback'}/>\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let tag1 = ctx['tag'];
+ return toggler(tag1, block1(tag1)());
+ }
+}"
+`;
+
+exports[`qweb t-tag can update 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
+
+ let block1 = tag => createBlock(\`<\${tag || 't'}/>\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let tag1 = ctx['tag'];
+ return toggler(tag1, block1(tag1)());
+ }
+}"
+`;
+
+exports[`qweb t-tag simple usecases 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
+
+ let block1 = tag => createBlock(\`<\${tag || 't'}/>\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let tag1 = 'div';
+ return toggler(tag1, block1(tag1)());
+ }
+}"
+`;
+
+exports[`qweb t-tag simple usecases 2`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
+
+ let block1 = tag => createBlock(\`<\${tag || 't'}>text\${tag || 't'}>\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let tag1 = ctx['tag'];
+ return toggler(tag1, block1(tag1)());
+ }
+}"
+`;
+
+exports[`qweb t-tag with multiple attributes 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
+
+ let block1 = tag => createBlock(\`<\${tag || 't'} class=\\"blueberry\\" taste=\\"raspberry\\">gooseberry\${tag || 't'}>\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let tag1 = ctx['tag'];
+ return toggler(tag1, block1(tag1)());
+ }
+}"
+`;
+
+exports[`qweb t-tag with multiple child nodes 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
+
+ let block1 = tag => createBlock(\`<\${tag || 't'}> pear apple strawberry \${tag || 't'}>\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let tag1 = ctx['tag'];
+ return toggler(tag1, block1(tag1)());
+ }
+}"
+`;
+
+exports[`qweb t-tag with multiple t-tag in same template 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber } = helpers;
+
+ let block1 = tag => createBlock(\`<\${tag || 't'}>