Skip to content

Commit

Permalink
[IMP] qweb: the t-slot directive is now dynamic
Browse files Browse the repository at this point in the history
It is useful for some kind of components to be able to use a completely
dynamic slot expression.
  • Loading branch information
ged-odoo authored and aab-odoo committed Oct 30, 2020
1 parent cb07c99 commit d83cfc1
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 4 deletions.
19 changes: 17 additions & 2 deletions doc/reference/slots.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand All @@ -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">
Expand All @@ -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}}" />
```
4 changes: 3 additions & 1 deletion src/qweb/extensions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { VNode } from "../vdom/index";
import { INTERP_REGEXP } from "./compilation_context";
import { QWeb } from "./qweb";

/**
Expand Down Expand Up @@ -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}`;
Expand Down
19 changes: 19 additions & 0 deletions tests/component/__snapshots__/slots.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand Down
35 changes: 34 additions & 1 deletion tests/component/slots.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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();
});
});

0 comments on commit d83cfc1

Please sign in to comment.