mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
@@ -932,25 +932,6 @@ 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.
|
||||||
|
|
||||||
Warning! Slots have a technical constraint: the result of the slot rendering
|
|
||||||
should have exactly one root node. So,
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<t t-set="content">
|
|
||||||
<div>A</div>
|
|
||||||
<div>B</div>
|
|
||||||
</t>
|
|
||||||
```
|
|
||||||
|
|
||||||
is not allowed. A workaround could be to wrap the content in a div:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div t-set="content">
|
|
||||||
<div>A</div>
|
|
||||||
<div>B</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
|
|||||||
+18
-6
@@ -268,9 +268,15 @@ export class QWeb extends EventBus {
|
|||||||
return template.fn.call(this, context, extra);
|
return template.fn.call(this, context, extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
_compile(name: string, elem: Element): CompiledTemplate {
|
_compile(name: string, elem: Element, parentNode?: number): CompiledTemplate {
|
||||||
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
||||||
const ctx = new Context(name);
|
const ctx = new Context(name);
|
||||||
|
if (parentNode) {
|
||||||
|
ctx.nextID = parentNode + 1;
|
||||||
|
ctx.parentNode = parentNode;
|
||||||
|
ctx.allowMultipleRoots = true;
|
||||||
|
ctx.addLine(`let c${parentNode} = extra.parentNode;`);
|
||||||
|
}
|
||||||
this._compileNode(elem, ctx);
|
this._compileNode(elem, ctx);
|
||||||
|
|
||||||
if (ctx.shouldProtectContext) {
|
if (ctx.shouldProtectContext) {
|
||||||
@@ -288,10 +294,12 @@ export class QWeb extends EventBus {
|
|||||||
ctx.code.unshift(" let utils = this.utils;");
|
ctx.code.unshift(" let utils = this.utils;");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx.rootNode) {
|
if (!parentNode) {
|
||||||
throw new Error("A template should have one root node");
|
if (!ctx.rootNode) {
|
||||||
|
throw new Error("A template should have one root node");
|
||||||
|
}
|
||||||
|
ctx.addLine(`return vn${ctx.rootNode};`);
|
||||||
}
|
}
|
||||||
ctx.addLine(`return vn${ctx.rootNode};`);
|
|
||||||
let template;
|
let template;
|
||||||
try {
|
try {
|
||||||
template = new Function(
|
template = new Function(
|
||||||
@@ -353,7 +361,6 @@ export class QWeb extends EventBus {
|
|||||||
// this is a component, we modify in place the xml document to change
|
// this is a component, we modify in place the xml document to change
|
||||||
// <SomeComponent ... /> to <t t-component="SomeComponent" ... />
|
// <SomeComponent ... /> to <t t-component="SomeComponent" ... />
|
||||||
node.setAttribute("t-component", node.tagName);
|
node.setAttribute("t-component", node.tagName);
|
||||||
node.nodeValue = "t";
|
|
||||||
}
|
}
|
||||||
const attributes = (<Element>node).attributes;
|
const attributes = (<Element>node).attributes;
|
||||||
|
|
||||||
@@ -648,6 +655,7 @@ export class Context {
|
|||||||
inLoop: boolean = false;
|
inLoop: boolean = false;
|
||||||
inPreTag: boolean = false;
|
inPreTag: boolean = false;
|
||||||
templateName: string;
|
templateName: string;
|
||||||
|
allowMultipleRoots: boolean = false;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
this.rootContext = this;
|
this.rootContext = this;
|
||||||
@@ -661,7 +669,11 @@ export class Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withParent(node: number): Context {
|
withParent(node: number): Context {
|
||||||
if (this === this.rootContext && (this.parentNode || this.parentTextNode)) {
|
if (
|
||||||
|
!this.allowMultipleRoots &&
|
||||||
|
this === this.rootContext &&
|
||||||
|
(this.parentNode || this.parentTextNode)
|
||||||
|
) {
|
||||||
throw new Error("A template should not have more than one root node");
|
throw new Error("A template should not have more than one root node");
|
||||||
}
|
}
|
||||||
if (!this.rootContext.rootNode) {
|
if (!this.rootContext.rootNode) {
|
||||||
|
|||||||
+29
-4
@@ -421,6 +421,18 @@ QWeb.addDirective({
|
|||||||
: ctx.inLoop
|
: ctx.inLoop
|
||||||
? `String(-${componentID} - i)`
|
? `String(-${componentID} - i)`
|
||||||
: String(componentID);
|
: String(componentID);
|
||||||
|
if (ctx.allowMultipleRoots) {
|
||||||
|
// necessary to prevent collisions
|
||||||
|
if (!key && ctx.inLoop) {
|
||||||
|
let id = ctx.generateID();
|
||||||
|
ctx.addLine(
|
||||||
|
`let template${id} = "_slot_" + String(-${componentID} - i)`
|
||||||
|
);
|
||||||
|
templateID = `template${id}`;
|
||||||
|
} else {
|
||||||
|
templateID = `"_slot_${templateID}"`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let ref = node.getAttribute("t-ref");
|
let ref = node.getAttribute("t-ref");
|
||||||
let refExpr = "";
|
let refExpr = "";
|
||||||
@@ -572,13 +584,24 @@ QWeb.addDirective({
|
|||||||
slotNode.parentElement!.removeChild(slotNode);
|
slotNode.parentElement!.removeChild(slotNode);
|
||||||
const key = slotNode.getAttribute("t-set")!;
|
const key = slotNode.getAttribute("t-set")!;
|
||||||
slotNode.removeAttribute("t-set");
|
slotNode.removeAttribute("t-set");
|
||||||
const slotFn = qweb._compile(`slot_${key}_template`, slotNode);
|
const slotFn = qweb._compile(
|
||||||
|
`slot_${key}_template`,
|
||||||
|
slotNode,
|
||||||
|
ctx.parentNode!
|
||||||
|
);
|
||||||
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
|
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (clone.childElementCount) {
|
if (clone.childElementCount) {
|
||||||
const content = clone.children[0];
|
const t = clone.ownerDocument!.createElement("t");
|
||||||
const slotFn = qweb._compile(`slot_default_template`, content);
|
for (let child of Object.values(clone.children)) {
|
||||||
|
t.appendChild(child);
|
||||||
|
}
|
||||||
|
const slotFn = qweb._compile(
|
||||||
|
`slot_default_template`,
|
||||||
|
t,
|
||||||
|
ctx.parentNode!
|
||||||
|
);
|
||||||
qweb.slots[`${slotId}_default`] = slotFn.bind(qweb);
|
qweb.slots[`${slotId}_default`] = slotFn.bind(qweb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -683,7 +706,9 @@ QWeb.addDirective({
|
|||||||
`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`
|
`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`
|
||||||
);
|
);
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`c${ctx.parentNode}.push(slot${slotKey}(context.__owl__.parent, extra));`
|
`slot${slotKey}(context.__owl__.parent, Object.assign({}, extra, {parentNode: c${
|
||||||
|
ctx.parentNode
|
||||||
|
}}));`
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1083,12 +1083,12 @@ exports[`t-slot directive can define and call slots 2`] = `
|
|||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
const slot3 = this.slots[context.__owl__.slotId + '_' + 'header'];
|
const slot3 = this.slots[context.__owl__.slotId + '_' + 'header'];
|
||||||
c2.push(slot3(context.__owl__.parent, extra));
|
slot3(context.__owl__.parent, Object.assign({}, extra, {parentNode: c2}));
|
||||||
let c4 = [], p4 = {key:4};
|
let c4 = [], p4 = {key:4};
|
||||||
var vn4 = h('div', p4, c4);
|
var vn4 = h('div', p4, c4);
|
||||||
c1.push(vn4);
|
c1.push(vn4);
|
||||||
const slot5 = this.slots[context.__owl__.slotId + '_' + 'footer'];
|
const slot5 = this.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||||
c4.push(slot5(context.__owl__.parent, extra));
|
slot5(context.__owl__.parent, Object.assign({}, extra, {parentNode: c4}));
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
+74
-21
@@ -2957,6 +2957,80 @@ describe("t-slot directive", () => {
|
|||||||
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("content is the default slot", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<Dialog>
|
||||||
|
<span>sts rocks</span>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
<div t-name="Dialog"><t t-slot="default"/></div>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
components = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>sts rocks</span></div></div>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple roots are allowed in a named slot", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<Dialog>
|
||||||
|
<t t-set="content">
|
||||||
|
<span>sts</span>
|
||||||
|
<span>rocks</span>
|
||||||
|
</t>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
<div t-name="Dialog"><t t-slot="content"/></div>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
components = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>sts</span><span>rocks</span></div></div>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple roots are allowed in a default slot", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<Dialog>
|
||||||
|
<span>sts</span>
|
||||||
|
<span>rocks</span>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
<div t-name="Dialog"><t t-slot="default"/></div>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
components = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>sts</span><span>rocks</span></div></div>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("t-model directive", () => {
|
describe("t-model directive", () => {
|
||||||
@@ -3190,28 +3264,7 @@ describe("t-model directive", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><input><span>invalid</span></div>");
|
expect(fixture.innerHTML).toBe("<div><input><span>invalid</span></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("content is the default slot", async () => {
|
|
||||||
env.qweb.addTemplates(`
|
|
||||||
<templates>
|
|
||||||
<div t-name="Parent">
|
|
||||||
<Dialog>
|
|
||||||
<span>sts rocks</span>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
<div t-name="Dialog"><t t-slot="default"/></div>
|
|
||||||
</templates>
|
|
||||||
`);
|
|
||||||
class Dialog extends Widget {}
|
|
||||||
class Parent extends Widget {
|
|
||||||
components = { Dialog };
|
|
||||||
}
|
|
||||||
const parent = new Parent(env);
|
|
||||||
await parent.mount(fixture);
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe(
|
|
||||||
"<div><div><span>sts rocks</span></div></div>"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("environment and plugins", () => {
|
describe("environment and plugins", () => {
|
||||||
|
|||||||
@@ -1132,10 +1132,10 @@ const SLOTS_XML = `<templates>
|
|||||||
<t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
|
<t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="'Title card B'">
|
<Card title="'Title card B'">
|
||||||
<div t-set="content">
|
<t t-set="content">
|
||||||
<div>Card 2... [<t t-esc="state.b"/>]</div>
|
<div>Card 2... [<t t-esc="state.b"/>]</div>
|
||||||
<Counter />
|
<Counter />
|
||||||
</div>
|
</t>
|
||||||
<t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
|
<t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user