diff --git a/doc/reference/templates.md b/doc/reference/templates.md
index f0af6699..1cd613ca 100644
--- a/doc/reference/templates.md
+++ b/doc/reference/templates.md
@@ -540,6 +540,15 @@ This can be used to define variables scoped to a sub template:
```
+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
+
+```
+
### Dynamic sub templates
The `t-call` directive can also be used to dynamically call a sub template,
diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts
index e0714226..6bb513ff 100644
--- a/src/compiler/code_generator.ts
+++ b/src/compiler/code_generator.ts
@@ -140,6 +140,7 @@ interface Context {
tKeyExpr: string | null;
nameSpace?: string;
tModelSelectedExpr?: string;
+ ctxVar?: string;
}
function createContext(parentCtx: Context, params?: Partial) {
@@ -967,16 +968,21 @@ export class CodeGenerator {
compileTCall(ast: ASTTCall, ctx: Context) {
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) {
- this.addLine(`ctx = Object.create(ctx);`);
- this.addLine(`ctx[isBoundary] = 1;`);
+ this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
+ this.addLine(`${ctxVar}[isBoundary] = 1;`);
this.helpers.add("isBoundary");
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);
if (nextId !== BlockDescription.nextBlockId) {
this.helpers.add("zero");
- this.addLine(`ctx[zero] = b${nextId};`);
+ this.addLine(`${ctxVar}[zero] = b${nextId};`);
}
}
const isDynamic = INTERP_REGEXP.test(ast.name);
@@ -994,7 +1000,7 @@ export class CodeGenerator {
}
this.define(templateVar, subTemplate);
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,
forceNewBlock: !block,
});
@@ -1002,13 +1008,13 @@ export class CodeGenerator {
const id = generateId(`callTemplate_`);
this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
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,
forceNewBlock: !block,
});
}
if (ast.body && !ctx.isLast) {
- this.addLine(`ctx = ctx.__proto__;`);
+ this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
}
}
@@ -1046,7 +1052,7 @@ export class CodeGenerator {
value = expr;
}
this.helpers.add("setContextValue");
- this.addLine(`setContextValue(ctx, "${ast.name}", ${value});`);
+ this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
}
}
diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts
index 2e315835..540e4a70 100644
--- a/src/compiler/parser.ts
+++ b/src/compiler/parser.ts
@@ -115,6 +115,7 @@ export interface ASTTCall {
type: ASTType.TCall;
name: string;
body: AST[] | null;
+ context: string | null;
}
interface SlotDefinition {
@@ -557,11 +558,13 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
return null;
}
const subTemplate = node.getAttribute("t-call")!;
-
+ const context = node.getAttribute("t-call-context");
node.removeAttribute("t-call");
+ node.removeAttribute("t-call-context");
+
if (node.tagName !== "t") {
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) {
ast.content = [tcall];
return ast;
@@ -579,6 +582,7 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
type: ASTType.TCall,
name: subTemplate,
body: body.length ? body : null,
+ context,
};
}
diff --git a/tests/compiler/__snapshots__/t_call.test.ts.snap b/tests/compiler/__snapshots__/t_call.test.ts.snap
index a370e1f9..148914da 100644
--- a/tests/compiler/__snapshots__/t_call.test.ts.snap
+++ b/tests/compiler/__snapshots__/t_call.test.ts.snap
@@ -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(\`
\`);
+
+ 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(\`\`);
+
+ 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`] = `
"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(\`\`);
+
+ 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(\`\`);
+
+ 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`] = `
"function anonymous(app, bdom, helpers
) {
diff --git a/tests/compiler/parser.test.ts b/tests/compiler/parser.test.ts
index 3402f89f..52178251 100644
--- a/tests/compiler/parser.test.ts
+++ b/tests/compiler/parser.test.ts
@@ -1033,6 +1033,7 @@ describe("qweb parser", () => {
type: ASTType.TCall,
name: "blap",
body: null,
+ context: null,
},
memo: "",
hasNoFirst: false,
@@ -1070,6 +1071,7 @@ describe("qweb parser", () => {
type: ASTType.TCall,
name: "blabla",
body: null,
+ context: null,
});
});
@@ -1077,10 +1079,20 @@ describe("qweb parser", () => {
expect(parse(`ok`)).toEqual({
type: ASTType.TCall,
name: "sub",
+ context: null,
body: [{ type: ASTType.Text, value: "ok" }],
});
});
+ test("t-call expression with t-call-context", async () => {
+ expect(parse(``)).toEqual({
+ type: ASTType.TCall,
+ name: "blabla",
+ body: null,
+ context: "someContext",
+ });
+ });
+
test("t-call on a div node", async () => {
expect(parse(``)).toEqual({
type: ASTType.DomNode,
@@ -1096,6 +1108,7 @@ describe("qweb parser", () => {
type: ASTType.TCall,
name: "blabla",
body: null,
+ context: null,
},
],
});
@@ -1111,6 +1124,7 @@ describe("qweb parser", () => {
type: ASTType.TCall,
name: "blabla",
body: null,
+ context: null,
},
});
});
@@ -1547,7 +1561,7 @@ describe("qweb parser", () => {
on: null,
slots: {
default: {
- content: { body: null, name: "subTemplate", type: ASTType.TCall },
+ content: { body: null, name: "subTemplate", type: ASTType.TCall, context: null },
attrs: null,
scope: null,
on: null,
diff --git a/tests/compiler/t_call.test.ts b/tests/compiler/t_call.test.ts
index e6bc371c..13b8c967 100644
--- a/tests/compiler/t_call.test.ts
+++ b/tests/compiler/t_call.test.ts
@@ -445,4 +445,38 @@ describe("t-call (template calling)", () => {
const expected2 = "quux
";
expect(context.renderToString("main", { template: "bar", val: "quux" })).toBe(expected2);
});
+
+ test("t-call-context", () => {
+ const context = new TestContext();
+ context.addTemplate("sub", ``);
+ context.addTemplate("main", ``);
+
+ expect(context.renderToString("main", { obj: { value: 123 } })).toBe("123");
+ });
+
+ test("t-call on a div with t-call-context", () => {
+ const context = new TestContext();
+ context.addTemplate("sub", ``);
+ context.addTemplate("main", ``);
+
+ expect(context.renderToString("main", { obj: { value: 123 } })).toBe(
+ "123
"
+ );
+ });
+
+ test("t-call-context and value in body", () => {
+ const context = new TestContext();
+ context.addTemplate("sub", ``);
+ context.addTemplate(
+ "main",
+ `
+
+
+ `
+ );
+
+ expect(context.renderToString("main", { obj: { value1: 123 }, aaron: "lucas" })).toBe(
+ "123lucas"
+ );
+ });
});
diff --git a/tests/components/__snapshots__/t_call.test.ts.snap b/tests/components/__snapshots__/t_call.test.ts.snap
index 1a3f8a0a..aa4521d2 100644
--- a/tests/components/__snapshots__/t_call.test.ts.snap
+++ b/tests/components/__snapshots__/t_call.test.ts.snap
@@ -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]);
+ }
+}"
+`;
diff --git a/tests/components/t_call.test.ts b/tests/components/t_call.test.ts
index 5f74ea66..4d5bd476 100644
--- a/tests/components/t_call.test.ts
+++ b/tests/components/t_call.test.ts
@@ -244,4 +244,47 @@ describe("t-call", () => {
}
expect(clickCount).toBe(2);
});
+
+ test("t-call with t-call-context, simple use", async () => {
+ class Root extends Component {
+ static template = xml`
+ `;
+
+ subctx = { aab: "aaron", lpe: "lucas" };
+ }
+
+ await mount(Root, fixture, {
+ templates: `
+
+
+ `,
+ });
+ expect(fixture.innerHTML).toBe("aaronlucas");
+ });
+
+ test("t-call with t-call-context and subcomponent", async () => {
+ class Child extends Component {
+ static template = xml`child`;
+ }
+
+ class Root extends Component {
+ static template = xml`
+ `;
+
+ static components = { Child };
+
+ subctx = { aab: "aaron", lpe: "lucas" };
+ }
+
+ await mount(Root, fixture, {
+ templates: `
+
+
+
+
+
+ `,
+ });
+ expect(fixture.innerHTML).toBe("childaaronchildlucas");
+ });
});