mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] qweb: the t-slot directive is now dynamic
It is useful for some kind of components to be able to use a completely dynamic slot expression.
This commit is contained in:
+17
-2
@@ -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
|
||||
<div t-name="Dialog" class="modal">
|
||||
<div class="modal-title"><t t-esc="props.title"/></div>
|
||||
@@ -62,7 +64,9 @@ This is deprecated and should no longer be used in new code.
|
||||
|
||||
## Reference
|
||||
|
||||
Default slot: the first element inside the component which is not a named slot will
|
||||
### Default Slot
|
||||
|
||||
The first element inside the component which is not a named slot will
|
||||
be considered the `default` slot. For example:
|
||||
|
||||
```xml
|
||||
@@ -77,7 +81,9 @@ be considered the `default` slot. For example:
|
||||
</div>
|
||||
```
|
||||
|
||||
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
|
||||
<div t-name="Parent">
|
||||
@@ -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
|
||||
<t t-slot="{{current}}" />
|
||||
```
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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("<div><div><p>Ablip</p><div><p>Bblip</p></div></div></div>");
|
||||
});
|
||||
|
||||
test("dynamic t-slot call", async () => {
|
||||
class Toggler extends Component {
|
||||
static template = xml`<button t-on-click="toggle"><t t-slot="{{current.slot}}"/></button>`;
|
||||
current = useState({ slot: "slot1" });
|
||||
toggle() {
|
||||
this.current.slot = this.current.slot === "slot1" ? "slot2" : "slot1";
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Toggler>
|
||||
<t t-set-slot="slot1"><p>slot1</p><span>content</span></t>
|
||||
<t t-set-slot="slot2"><h1>slot2</h1></t>
|
||||
</Toggler>
|
||||
</div>`;
|
||||
static components = { Toggler };
|
||||
}
|
||||
await mount(Parent, { target: fixture });
|
||||
expect(fixture.innerHTML).toBe("<div><button><p>slot1</p><span>content</span></button></div>");
|
||||
|
||||
fixture.querySelector<HTMLElement>("button")!.click();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><button><h1>slot2</h1></button></div>");
|
||||
|
||||
fixture.querySelector<HTMLElement>("button")!.click();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><button><p>slot1</p><span>content</span></button></div>");
|
||||
|
||||
expect(env.qweb.templates[Toggler.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user