mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] slots: add new t-set-slot directive
This new t-set-slot directive is meant to replace t-set when we need to define the content of a sub slot. All new code should use that directive. The old t-set directive is still supported for now, but this should be removed when we publish Owl 2.0.
This commit is contained in:
@@ -37,16 +37,16 @@ rendering context.
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
Slots are defined by the caller, with the `t-set` directive:
|
Slots are defined by the caller, with the `t-set-slot` directive:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="SomeComponent">
|
<div t-name="SomeComponent">
|
||||||
<div>some component</div>
|
<div>some component</div>
|
||||||
<Dialog title="Some Dialog">
|
<Dialog title="Some Dialog">
|
||||||
<t t-set="content">
|
<t t-set-slot="content">
|
||||||
<div>hey</div>
|
<div>hey</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-set="footer">
|
<t t-set-slot="footer">
|
||||||
<button t-on-click="doSomething">ok</button>
|
<button t-on-click="doSomething">ok</button>
|
||||||
</t>
|
</t>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -57,6 +57,9 @@ In this example, the component `Dialog` will render the slots `content` and `foo
|
|||||||
with its parent as rendering context. This means that clicking on the button
|
with its parent as rendering context. This means that clicking on the button
|
||||||
will execute the `doSomething` method on the parent, not on the dialog.
|
will execute the `doSomething` method on the parent, not on the dialog.
|
||||||
|
|
||||||
|
Note: Owl previously used the `t-set` directive to define the content of a slot.
|
||||||
|
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
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ change!
|
|||||||
|
|
||||||
### 2.x (2020? 2021? 2022?)
|
### 2.x (2020? 2021? 2022?)
|
||||||
|
|
||||||
|
- stop support for `t-set` directive to define the content of a slot
|
||||||
|
|
||||||
Maybe:
|
Maybe:
|
||||||
|
|
||||||
- reimplement vdom to use *block* system, like Vue 3, which should make Owl
|
- reimplement vdom to use *block* system, like Vue 3, which should make Owl
|
||||||
|
|||||||
@@ -400,7 +400,16 @@ QWeb.addDirective({
|
|||||||
|
|
||||||
if (hasSlots) {
|
if (hasSlots) {
|
||||||
const clone = <Element>node.cloneNode(true);
|
const clone = <Element>node.cloneNode(true);
|
||||||
const slotNodes: Element[] = [];
|
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
|
||||||
|
|
||||||
|
// The next code is a fallback for compatibility reason. It accepts t-set
|
||||||
|
// elements that are direct children with a non empty body as nodes defining
|
||||||
|
// the content of a slot.
|
||||||
|
//
|
||||||
|
// This is wrong, but is necessary to prevent breaking all existing Owl
|
||||||
|
// code using slots. This will be removed in v2.0 someday. Meanwhile,
|
||||||
|
// please use t-set-slot everywhere you need to set the content of a
|
||||||
|
// slot.
|
||||||
for (let el of clone.children) {
|
for (let el of clone.children) {
|
||||||
if (el.getAttribute("t-set") && el.hasChildNodes()) {
|
if (el.getAttribute("t-set") && el.hasChildNodes()) {
|
||||||
slotNodes.push(el);
|
slotNodes.push(el);
|
||||||
@@ -412,8 +421,15 @@ QWeb.addDirective({
|
|||||||
for (let i = 0, length = slotNodes.length; i < length; i++) {
|
for (let i = 0, length = slotNodes.length; i < length; i++) {
|
||||||
const slotNode = slotNodes[i];
|
const slotNode = slotNodes[i];
|
||||||
slotNode.parentElement!.removeChild(slotNode);
|
slotNode.parentElement!.removeChild(slotNode);
|
||||||
const key = slotNode.getAttribute("t-set")!;
|
let key = slotNode.getAttribute("t-set-slot")!;
|
||||||
slotNode.removeAttribute("t-set");
|
slotNode.removeAttribute("t-set-slot");
|
||||||
|
|
||||||
|
// here again, this code should be removed when we stop supporting
|
||||||
|
// using t-set to define the content of named slots.
|
||||||
|
if (!key) {
|
||||||
|
key = slotNode.getAttribute("t-set")!;
|
||||||
|
slotNode.removeAttribute("t-set");
|
||||||
|
}
|
||||||
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
||||||
QWeb.slots[`${slotId}_${key}`] = slotFn;
|
QWeb.slots[`${slotId}_${key}`] = slotFn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,96 @@ exports[`t-slot directive can define and call slots 4`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive can define and call slots using old t-set keyword 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"__template__2\\"
|
||||||
|
let utils = this.constructor.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
|
let parent = context;
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let h = this.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
let vn1 = h('div', p1, c1);
|
||||||
|
// Component 'Dialog'
|
||||||
|
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
|
||||||
|
let props2 = {};
|
||||||
|
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||||
|
w2.destroy();
|
||||||
|
w2 = false;
|
||||||
|
}
|
||||||
|
if (w2) {
|
||||||
|
w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope));
|
||||||
|
let pvnode = w2.__owl__.pvnode;
|
||||||
|
c1.push(pvnode);
|
||||||
|
} else {
|
||||||
|
let componentKey2 = \`Dialog\`;
|
||||||
|
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Dialog'];
|
||||||
|
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||||
|
w2 = new W2(parent, props2);
|
||||||
|
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
||||||
|
w2.__owl__.slotId = 1;
|
||||||
|
let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
|
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||||
|
c1.push(pvnode);
|
||||||
|
w2.__owl__.pvnode = pvnode;
|
||||||
|
}
|
||||||
|
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive can define and call slots using old t-set keyword 2`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"__template__1\\"
|
||||||
|
let h = this.h;
|
||||||
|
let c6 = [], p6 = {key:6};
|
||||||
|
let vn6 = h('div', p6, c6);
|
||||||
|
let c7 = [], p7 = {key:7};
|
||||||
|
let vn7 = h('div', p7, c7);
|
||||||
|
c6.push(vn7);
|
||||||
|
const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
|
||||||
|
if (slot8) {
|
||||||
|
slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c7, parent: extra.parent || context}));
|
||||||
|
}
|
||||||
|
let c9 = [], p9 = {key:9};
|
||||||
|
let vn9 = h('div', p9, c9);
|
||||||
|
c6.push(vn9);
|
||||||
|
const slot10 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||||
|
if (slot10) {
|
||||||
|
slot10.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c9, parent: extra.parent || context}));
|
||||||
|
}
|
||||||
|
return vn6;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive can define and call slots using old t-set keyword 3`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"slot_header_template\\"
|
||||||
|
let h = this.h;
|
||||||
|
let c1 = extra.parentNode;
|
||||||
|
let c4 = [], p4 = {key:4};
|
||||||
|
let vn4 = h('span', p4, c4);
|
||||||
|
c1.push(vn4);
|
||||||
|
c4.push({text: \`header\`});
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive can define and call slots using old t-set keyword 4`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"slot_footer_template\\"
|
||||||
|
let h = this.h;
|
||||||
|
let c1 = extra.parentNode;
|
||||||
|
let c5 = [], p5 = {key:5};
|
||||||
|
let vn5 = h('span', p5, c5);
|
||||||
|
c1.push(vn5);
|
||||||
|
c5.push({text: \`footer\`});
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-slot directive content is the default slot 1`] = `
|
exports[`t-slot directive content is the default slot 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ describe("t-slot directive", () => {
|
|||||||
<templates>
|
<templates>
|
||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<t t-set="header"><span>header</span></t>
|
<t t-set-slot="header"><span>header</span></t>
|
||||||
<t t-set="footer"><span>footer</span></t>
|
<t t-set-slot="footer"><span>footer</span></t>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
<div t-name="Dialog">
|
<div t-name="Dialog">
|
||||||
@@ -67,6 +67,38 @@ describe("t-slot directive", () => {
|
|||||||
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can define and call slots using old t-set keyword", async () => {
|
||||||
|
// NOTE: this test should be removed once we stop supporting the t-set directive
|
||||||
|
// for defining slot content.
|
||||||
|
class Dialog extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<div><t t-slot="header"/></div>
|
||||||
|
<div><t t-slot="footer"/></div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Dialog>
|
||||||
|
<t t-set="header"><span>header</span></t>
|
||||||
|
<t t-set="footer"><span>footer</span></t>
|
||||||
|
</Dialog>
|
||||||
|
</div>`;
|
||||||
|
static components = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><div><span>header</span></div><div><span>footer</span></div></div></div>"
|
||||||
|
);
|
||||||
|
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||||
|
expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
|
||||||
|
expect(QWeb.slots["1_header"].toString()).toMatchSnapshot();
|
||||||
|
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
test("named slots can define a default content", async () => {
|
test("named slots can define a default content", async () => {
|
||||||
class Dialog extends Component {
|
class Dialog extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
@@ -128,7 +160,7 @@ describe("t-slot directive", () => {
|
|||||||
</span>`;
|
</span>`;
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<div><Dialog><t t-set="header">hey</t></Dialog></div>`;
|
static template = xml`<div><Dialog><t t-set-slot="header">hey</t></Dialog></div>`;
|
||||||
static components = { Dialog };
|
static components = { Dialog };
|
||||||
}
|
}
|
||||||
const parent = new Parent();
|
const parent = new Parent();
|
||||||
@@ -143,7 +175,7 @@ describe("t-slot directive", () => {
|
|||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
<span class="counter"><t t-esc="state.val"/></span>
|
<span class="counter"><t t-esc="state.val"/></span>
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<t t-set="footer"><button t-on-click="doSomething">do something</button></t>
|
<t t-set-slot="footer"><button t-on-click="doSomething">do something</button></t>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
<span t-name="Dialog"><t t-slot="footer"/></span>
|
<span t-name="Dialog"><t t-slot="footer"/></span>
|
||||||
@@ -302,7 +334,7 @@ describe("t-slot directive", () => {
|
|||||||
<div>
|
<div>
|
||||||
<span class="counter"><t t-esc="state.val"/></span>
|
<span class="counter"><t t-esc="state.val"/></span>
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
|
<t t-set-slot="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -376,7 +408,7 @@ describe("t-slot directive", () => {
|
|||||||
<templates>
|
<templates>
|
||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<t t-set="content">
|
<t t-set-slot="content">
|
||||||
<span>sts</span>
|
<span>sts</span>
|
||||||
<span>rocks</span>
|
<span>rocks</span>
|
||||||
</t>
|
</t>
|
||||||
@@ -442,14 +474,14 @@ describe("t-slot directive", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><span><span>some content</span></span></div>");
|
expect(fixture.innerHTML).toBe("<div><span><span>some content</span></span></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-debug on a t-set (defining a slot)", async () => {
|
test("t-debug on a t-set-slot (defining a slot)", async () => {
|
||||||
const consoleLog = console.log;
|
const consoleLog = console.log;
|
||||||
console.log = jest.fn();
|
console.log = jest.fn();
|
||||||
|
|
||||||
env.qweb.addTemplates(`
|
env.qweb.addTemplates(`
|
||||||
<templates>
|
<templates>
|
||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
<Dialog><t t-set="content" t-debug="">abc</t></Dialog>
|
<Dialog><t t-set-slot="content" t-debug="">abc</t></Dialog>
|
||||||
</div>
|
</div>
|
||||||
<span t-name="Dialog">
|
<span t-name="Dialog">
|
||||||
<t t-slot="content"/>
|
<t t-slot="content"/>
|
||||||
@@ -623,8 +655,8 @@ describe("t-slot directive", () => {
|
|||||||
class A extends Component {
|
class A extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<B>
|
<B>
|
||||||
<t t-set="s1"><C val="1"/></t>
|
<t t-set-slot="s1"><C val="1"/></t>
|
||||||
<t t-set="s2"><C val="2"/></t>
|
<t t-set-slot="s2"><C val="2"/></t>
|
||||||
</B>`;
|
</B>`;
|
||||||
static components = { B, C };
|
static components = { B, C };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1169,7 +1169,7 @@ const RESPONSIVE_CSS = `body {
|
|||||||
const SLOTS = `// We show here how slots can be used to create generic components.
|
const SLOTS = `// We show here how slots can be used to create generic components.
|
||||||
// In this example, the Card component is basically only a container. It is not
|
// In this example, the Card component is basically only a container. It is not
|
||||||
// aware of its content. It just knows where it should be (with t-slot).
|
// aware of its content. It just knows where it should be (with t-slot).
|
||||||
// The parent component define the content with t-set.
|
// The parent component define the content with t-set-slot.
|
||||||
//
|
//
|
||||||
// Note that the t-on-click event, defined in the App template, is executed in
|
// Note that the t-on-click event, defined in the App template, is executed in
|
||||||
// the context of the App component, even though it is inside the Card component
|
// the context of the App component, even though it is inside the Card component
|
||||||
@@ -1235,15 +1235,15 @@ const SLOTS_XML = `<templates>
|
|||||||
|
|
||||||
<div t-name="App" class="main">
|
<div t-name="App" class="main">
|
||||||
<Card title="'Title card A'">
|
<Card title="'Title card A'">
|
||||||
<t t-set="content">Content of card 1... [<t t-esc="state.a"/>]</t>
|
<t t-set-slot="content">Content of card 1... [<t t-esc="state.a"/>]</t>
|
||||||
<t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
|
<t t-set-slot="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="'Title card B'">
|
<Card title="'Title card B'">
|
||||||
<t t-set="content">
|
<t t-set-slot="content">
|
||||||
<div>Card 2... [<t t-esc="state.b"/>]</div>
|
<div>Card 2... [<t t-esc="state.b"/>]</div>
|
||||||
<Counter />
|
<Counter />
|
||||||
</t>
|
</t>
|
||||||
<t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
|
<t t-set-slot="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
|
|||||||
Reference in New Issue
Block a user