[IMP] add support for t-call-context directive

This commit is contained in:
Géry Debongnie
2022-06-22 14:39:42 +02:00
parent 5772b4e9e4
commit c7459ef87b
8 changed files with 279 additions and 11 deletions
+9
View File
@@ -540,6 +540,15 @@ This can be used to define variables scoped to a sub template:
<!-- "var" does not exist here --> <!-- "var" does not exist here -->
``` ```
Note: by default, the rendering context for a sub template is simply the current
rendering context (so, the current component). However, it may be useful to be
able to specify a specific object as context. This can be done by using the
`t-call-context` directive:
```xml
<t t-call="other-template" t-call-context="obj"/>
```
### Dynamic sub templates ### Dynamic sub templates
The `t-call` directive can also be used to dynamically call a sub template, The `t-call` directive can also be used to dynamically call a sub template,
+14 -8
View File
@@ -140,6 +140,7 @@ interface Context {
tKeyExpr: string | null; tKeyExpr: string | null;
nameSpace?: string; nameSpace?: string;
tModelSelectedExpr?: string; tModelSelectedExpr?: string;
ctxVar?: string;
} }
function createContext(parentCtx: Context, params?: Partial<Context>) { function createContext(parentCtx: Context, params?: Partial<Context>) {
@@ -967,16 +968,21 @@ export class CodeGenerator {
compileTCall(ast: ASTTCall, ctx: Context) { compileTCall(ast: ASTTCall, ctx: Context) {
let { block, forceNewBlock } = ctx; let { block, forceNewBlock } = ctx;
let ctxVar = ctx.ctxVar || "ctx";
if (ast.context) {
ctxVar = generateId("ctx");
this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
}
if (ast.body) { if (ast.body) {
this.addLine(`ctx = Object.create(ctx);`); this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
this.addLine(`ctx[isBoundary] = 1;`); this.addLine(`${ctxVar}[isBoundary] = 1;`);
this.helpers.add("isBoundary"); this.helpers.add("isBoundary");
const nextId = BlockDescription.nextBlockId; const nextId = BlockDescription.nextBlockId;
const subCtx: Context = createContext(ctx, { preventRoot: true }); const subCtx: Context = createContext(ctx, { preventRoot: true, ctxVar });
this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx); this.compileAST({ type: ASTType.Multi, content: ast.body }, subCtx);
if (nextId !== BlockDescription.nextBlockId) { if (nextId !== BlockDescription.nextBlockId) {
this.helpers.add("zero"); this.helpers.add("zero");
this.addLine(`ctx[zero] = b${nextId};`); this.addLine(`${ctxVar}[zero] = b${nextId};`);
} }
} }
const isDynamic = INTERP_REGEXP.test(ast.name); const isDynamic = INTERP_REGEXP.test(ast.name);
@@ -994,7 +1000,7 @@ export class CodeGenerator {
} }
this.define(templateVar, subTemplate); this.define(templateVar, subTemplate);
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block!, { this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block!, {
...ctx, ...ctx,
forceNewBlock: !block, forceNewBlock: !block,
}); });
@@ -1002,13 +1008,13 @@ export class CodeGenerator {
const id = generateId(`callTemplate_`); const id = generateId(`callTemplate_`);
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` }); this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
block = this.createBlock(block, "multi", ctx); block = this.createBlock(block, "multi", ctx);
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block!, { this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block!, {
...ctx, ...ctx,
forceNewBlock: !block, forceNewBlock: !block,
}); });
} }
if (ast.body && !ctx.isLast) { if (ast.body && !ctx.isLast) {
this.addLine(`ctx = ctx.__proto__;`); this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
} }
} }
@@ -1046,7 +1052,7 @@ export class CodeGenerator {
value = expr; value = expr;
} }
this.helpers.add("setContextValue"); this.helpers.add("setContextValue");
this.addLine(`setContextValue(ctx, "${ast.name}", ${value});`); this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
} }
} }
+6 -2
View File
@@ -115,6 +115,7 @@ export interface ASTTCall {
type: ASTType.TCall; type: ASTType.TCall;
name: string; name: string;
body: AST[] | null; body: AST[] | null;
context: string | null;
} }
interface SlotDefinition { interface SlotDefinition {
@@ -557,11 +558,13 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
return null; return null;
} }
const subTemplate = node.getAttribute("t-call")!; const subTemplate = node.getAttribute("t-call")!;
const context = node.getAttribute("t-call-context");
node.removeAttribute("t-call"); node.removeAttribute("t-call");
node.removeAttribute("t-call-context");
if (node.tagName !== "t") { if (node.tagName !== "t") {
const ast = parseNode(node, ctx); const ast = parseNode(node, ctx);
const tcall: AST = { type: ASTType.TCall, name: subTemplate, body: null }; const tcall: AST = { type: ASTType.TCall, name: subTemplate, body: null, context };
if (ast && ast.type === ASTType.DomNode) { if (ast && ast.type === ASTType.DomNode) {
ast.content = [tcall]; ast.content = [tcall];
return ast; return ast;
@@ -579,6 +582,7 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
type: ASTType.TCall, type: ASTType.TCall,
name: subTemplate, name: subTemplate,
body: body.length ? body : null, body: body.length ? body : null,
context,
}; };
} }
@@ -617,6 +617,36 @@ exports[`t-call (template calling) t-call allowed on a non t node 2`] = `
}" }"
`; `;
exports[`t-call (template calling) t-call on a div with t-call-context 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const callTemplate_1 = app.getTemplate(\`sub\`);
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let ctx1 = ctx['obj'];
const b2 = callTemplate_1.call(this, ctx1, node, key + \`__1\`);
return block1([], [b2]);
}
}"
`;
exports[`t-call (template calling) t-call on a div with t-call-context 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['value'];
return block1([txt1]);
}
}"
`;
exports[`t-call (template calling) t-call with body content as root of a template 1`] = ` exports[`t-call (template calling) t-call with body content as root of a template 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -890,6 +920,67 @@ exports[`t-call (template calling) t-call, conditional and t-set in t-call body
}" }"
`; `;
exports[`t-call (template calling) t-call-context 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const callTemplate_1 = app.getTemplate(\`sub\`);
return function template(ctx, node, key = \\"\\") {
let ctx1 = ctx['obj'];
return callTemplate_1.call(this, ctx1, node, key + \`__1\`);
}
}"
`;
exports[`t-call (template calling) t-call-context 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['value'];
return block1([txt1]);
}
}"
`;
exports[`t-call (template calling) t-call-context and value in body 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { isBoundary, withDefault, setContextValue } = helpers;
const callTemplate_1 = app.getTemplate(\`sub\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
ctx[isBoundary] = 1
let ctx1 = ctx['obj'];
ctx1 = Object.create(ctx1);
ctx1[isBoundary] = 1;
setContextValue(ctx1, \\"value2\\", ctx['aaron']);
return callTemplate_1.call(this, ctx1, node, key + \`__1\`);
}
}"
`;
exports[`t-call (template calling) t-call-context and value in body 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let block1 = createBlock(\`<span><block-text-0/><block-text-1/></span>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['value1'];
let txt2 = ctx['value2'];
return block1([txt1, txt2]);
}
}"
`;
exports[`t-call (template calling) t-esc inside t-call, with t-set outside 1`] = ` exports[`t-call (template calling) t-esc inside t-call, with t-set outside 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
+15 -1
View File
@@ -1033,6 +1033,7 @@ describe("qweb parser", () => {
type: ASTType.TCall, type: ASTType.TCall,
name: "blap", name: "blap",
body: null, body: null,
context: null,
}, },
memo: "", memo: "",
hasNoFirst: false, hasNoFirst: false,
@@ -1070,6 +1071,7 @@ describe("qweb parser", () => {
type: ASTType.TCall, type: ASTType.TCall,
name: "blabla", name: "blabla",
body: null, body: null,
context: null,
}); });
}); });
@@ -1077,10 +1079,20 @@ describe("qweb parser", () => {
expect(parse(`<t t-call="sub">ok</t>`)).toEqual({ expect(parse(`<t t-call="sub">ok</t>`)).toEqual({
type: ASTType.TCall, type: ASTType.TCall,
name: "sub", name: "sub",
context: null,
body: [{ type: ASTType.Text, value: "ok" }], body: [{ type: ASTType.Text, value: "ok" }],
}); });
}); });
test("t-call expression with t-call-context", async () => {
expect(parse(`<t t-call="blabla" t-call-context="someContext"/>`)).toEqual({
type: ASTType.TCall,
name: "blabla",
body: null,
context: "someContext",
});
});
test("t-call on a div node", async () => { test("t-call on a div node", async () => {
expect(parse(`<div t-call="blabla" />`)).toEqual({ expect(parse(`<div t-call="blabla" />`)).toEqual({
type: ASTType.DomNode, type: ASTType.DomNode,
@@ -1096,6 +1108,7 @@ describe("qweb parser", () => {
type: ASTType.TCall, type: ASTType.TCall,
name: "blabla", name: "blabla",
body: null, body: null,
context: null,
}, },
], ],
}); });
@@ -1111,6 +1124,7 @@ describe("qweb parser", () => {
type: ASTType.TCall, type: ASTType.TCall,
name: "blabla", name: "blabla",
body: null, body: null,
context: null,
}, },
}); });
}); });
@@ -1547,7 +1561,7 @@ describe("qweb parser", () => {
on: null, on: null,
slots: { slots: {
default: { default: {
content: { body: null, name: "subTemplate", type: ASTType.TCall }, content: { body: null, name: "subTemplate", type: ASTType.TCall, context: null },
attrs: null, attrs: null,
scope: null, scope: null,
on: null, on: null,
+34
View File
@@ -445,4 +445,38 @@ describe("t-call (template calling)", () => {
const expected2 = "<div><bar>quux</bar></div>"; const expected2 = "<div><bar>quux</bar></div>";
expect(context.renderToString("main", { template: "bar", val: "quux" })).toBe(expected2); expect(context.renderToString("main", { template: "bar", val: "quux" })).toBe(expected2);
}); });
test("t-call-context", () => {
const context = new TestContext();
context.addTemplate("sub", `<span><t t-esc="value"/></span>`);
context.addTemplate("main", `<t t-call="sub" t-call-context="obj"/>`);
expect(context.renderToString("main", { obj: { value: 123 } })).toBe("<span>123</span>");
});
test("t-call on a div with t-call-context", () => {
const context = new TestContext();
context.addTemplate("sub", `<span><t t-esc="value"/></span>`);
context.addTemplate("main", `<div t-call="sub" t-call-context="obj"/>`);
expect(context.renderToString("main", { obj: { value: 123 } })).toBe(
"<div><span>123</span></div>"
);
});
test("t-call-context and value in body", () => {
const context = new TestContext();
context.addTemplate("sub", `<span><t t-esc="value1"/><t t-esc="value2"/></span>`);
context.addTemplate(
"main",
`
<t t-call="sub" t-call-context="obj">
<t t-set="value2" t-value="aaron" />
</t>`
);
expect(context.renderToString("main", { obj: { value1: 123 }, aaron: "lucas" })).toBe(
"<span>123lucas</span>"
);
});
}); });
@@ -402,3 +402,70 @@ exports[`t-call t-call in t-foreach and children component 3`] = `
} }
}" }"
`; `;
exports[`t-call t-call with t-call-context and subcomponent 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const callTemplate_1 = app.getTemplate(\`someTemplate\`);
return function template(ctx, node, key = \\"\\") {
let ctx1 = ctx['subctx'];
return callTemplate_1.call(this, ctx1, node, key + \`__1\`);
}
}"
`;
exports[`t-call t-call with t-call-context and subcomponent 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, false);
const comp2 = app.createComponent(\`Child\`, true, false, false, false);
return function template(ctx, node, key = \\"\\") {
const b2 = comp1({name: ctx['aab']}, key + \`__1\`, node, this, null);
const b3 = comp2({name: ctx['lpe']}, key + \`__2\`, node, this, null);
return multi([b2, b3]);
}
}"
`;
exports[`t-call t-call with t-call-context and subcomponent 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`child\`);
const b3 = text(ctx['props'].name);
return multi([b2, b3]);
}
}"
`;
exports[`t-call t-call with t-call-context, simple use 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const callTemplate_1 = app.getTemplate(\`someTemplate\`);
return function template(ctx, node, key = \\"\\") {
let ctx1 = ctx['subctx'];
return callTemplate_1.call(this, ctx1, node, key + \`__1\`);
}
}"
`;
exports[`t-call t-call with t-call-context, simple use 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const b2 = text(ctx['aab']);
const b3 = text(ctx['lpe']);
return multi([b2, b3]);
}
}"
`;
+43
View File
@@ -244,4 +244,47 @@ describe("t-call", () => {
} }
expect(clickCount).toBe(2); expect(clickCount).toBe(2);
}); });
test("t-call with t-call-context, simple use", async () => {
class Root extends Component {
static template = xml`
<t t-call="someTemplate" t-call-context="subctx"/>`;
subctx = { aab: "aaron", lpe: "lucas" };
}
await mount(Root, fixture, {
templates: `
<templates>
<t t-name="someTemplate"><t t-esc="aab"/><t t-esc="lpe"/></t>
</templates>`,
});
expect(fixture.innerHTML).toBe("aaronlucas");
});
test("t-call with t-call-context and subcomponent", async () => {
class Child extends Component {
static template = xml`child<t t-esc="props.name"/>`;
}
class Root extends Component {
static template = xml`
<t t-call="someTemplate" t-call-context="subctx"/>`;
static components = { Child };
subctx = { aab: "aaron", lpe: "lucas" };
}
await mount(Root, fixture, {
templates: `
<templates>
<t t-name="someTemplate">
<Child name="aab"/>
<Child name="lpe"/>
</t>
</templates>`,
});
expect(fixture.innerHTML).toBe("childaaronchildlucas");
});
}); });