diff --git a/doc/reference/slots.md b/doc/reference/slots.md index 59061fb3..ca201aa5 100644 --- a/doc/reference/slots.md +++ b/doc/reference/slots.md @@ -25,6 +25,8 @@ some sub template, but still be the owner. For example, a generic dialog compone will need to render some content, some footer, but with the parent as the rendering context. +Slots are inserted with the `t-slot` directive: + ```xml ``` -Default content: slots can define a default content, in case the parent did not define them: +### Default content + +Slots can define a default content, in case the parent did not define them: ```xml
@@ -94,3 +100,12 @@ Rendering context: the content of the slots is actually rendered with the rendering context corresponding to where it was defined, not where it is positioned. This allows the user to define event handlers that will be bound to the correct component (usually, the grandparent of the slot content). + +### Dynamic Slots + +The `t-slot` directive is actually able to use any expressions, using string +interplolation: + +```xml + +``` diff --git a/src/qweb/extensions.ts b/src/qweb/extensions.ts index 9e61bb44..f771e34f 100644 --- a/src/qweb/extensions.ts +++ b/src/qweb/extensions.ts @@ -1,4 +1,5 @@ import { VNode } from "../vdom/index"; +import { INTERP_REGEXP } from "./compilation_context"; import { QWeb } from "./qweb"; /** @@ -228,8 +229,9 @@ QWeb.addDirective({ priority: 80, atNodeEncounter({ ctx, value, node, qweb }): boolean { const slotKey = ctx.generateID(); + const valueExpr = value.match(INTERP_REGEXP) ? ctx.interpolate(value) : `'${value}'`; ctx.addLine( - `const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + '${value}'];` + `const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + ${valueExpr}];` ); ctx.addIf(`slot${slotKey}`); let parentNode = `c${ctx.parentNode}`; diff --git a/tests/component/__snapshots__/slots.test.ts.snap b/tests/component/__snapshots__/slots.test.ts.snap index a21f2a4d..932eb179 100644 --- a/tests/component/__snapshots__/slots.test.ts.snap +++ b/tests/component/__snapshots__/slots.test.ts.snap @@ -220,6 +220,25 @@ exports[`t-slot directive default slot work with text nodes 1`] = ` }" `; +exports[`t-slot directive dynamic t-slot call 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__1\\" + let utils = this.constructor.utils; + let scope = Object.create(context); + let h = this.h; + let c10 = [], p10 = {key:10,on:{}}; + let vn10 = h('button', p10, c10); + extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['toggle'](e);}; + p10.on['click'] = extra.handlers['click__11__']; + const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + (scope['current'].slot)]; + if (slot12) { + slot12.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context})); + } + return vn10; +}" +`; + exports[`t-slot directive multiple roots are allowed in a default slot 1`] = ` "function anonymous(context, extra ) { diff --git a/tests/component/slots.test.ts b/tests/component/slots.test.ts index de6b0221..61f25493 100644 --- a/tests/component/slots.test.ts +++ b/tests/component/slots.test.ts @@ -1,4 +1,4 @@ -import { Component, Env } from "../../src/component/component"; +import { Component, Env, mount } from "../../src/component/component"; import { QWeb } from "../../src/qweb/qweb"; import { xml } from "../../src/tags"; import { useState, useRef } from "../../src/hooks"; @@ -1085,4 +1085,37 @@ describe("t-slot directive", () => { expect(fixture.innerHTML).toBe("

Ablip

Bblip

"); }); + + test("dynamic t-slot call", async () => { + class Toggler extends Component { + static template = xml``; + current = useState({ slot: "slot1" }); + toggle() { + this.current.slot = this.current.slot === "slot1" ? "slot2" : "slot1"; + } + } + + class Parent extends Component { + static template = xml` +
+ +

slot1

content
+

slot2

+
+
`; + static components = { Toggler }; + } + await mount(Parent, { target: fixture }); + expect(fixture.innerHTML).toBe("
"); + + fixture.querySelector("button")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
"); + + fixture.querySelector("button")!.click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
"); + + expect(env.qweb.templates[Toggler.template].fn.toString()).toMatchSnapshot(); + }); });