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
|
will need to render some content, some footer, but with the parent as the
|
||||||
rendering context.
|
rendering context.
|
||||||
|
|
||||||
|
Slots are inserted with the `t-slot` directive:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="Dialog" class="modal">
|
<div t-name="Dialog" class="modal">
|
||||||
<div class="modal-title"><t t-esc="props.title"/></div>
|
<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
|
## 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:
|
be considered the `default` slot. For example:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
@@ -77,7 +81,9 @@ be considered the `default` slot. For example:
|
|||||||
</div>
|
</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
|
```xml
|
||||||
<div t-name="Parent">
|
<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
|
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
|
positioned. This allows the user to define event handlers that will be bound
|
||||||
to the correct component (usually, the grandparent of the slot content).
|
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 { VNode } from "../vdom/index";
|
||||||
|
import { INTERP_REGEXP } from "./compilation_context";
|
||||||
import { QWeb } from "./qweb";
|
import { QWeb } from "./qweb";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,8 +229,9 @@ QWeb.addDirective({
|
|||||||
priority: 80,
|
priority: 80,
|
||||||
atNodeEncounter({ ctx, value, node, qweb }): boolean {
|
atNodeEncounter({ ctx, value, node, qweb }): boolean {
|
||||||
const slotKey = ctx.generateID();
|
const slotKey = ctx.generateID();
|
||||||
|
const valueExpr = value.match(INTERP_REGEXP) ? ctx.interpolate(value) : `'${value}'`;
|
||||||
ctx.addLine(
|
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}`);
|
ctx.addIf(`slot${slotKey}`);
|
||||||
let parentNode = `c${ctx.parentNode}`;
|
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`] = `
|
exports[`t-slot directive multiple roots are allowed in a default slot 1`] = `
|
||||||
"function anonymous(context, extra
|
"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 { QWeb } from "../../src/qweb/qweb";
|
||||||
import { xml } from "../../src/tags";
|
import { xml } from "../../src/tags";
|
||||||
import { useState, useRef } from "../../src/hooks";
|
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>");
|
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