diff --git a/doc/component.md b/doc/component.md
index 192763c4..308d2b0e 100644
--- a/doc/component.md
+++ b/doc/component.md
@@ -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
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
-
-
A
-
B
-
-```
-
-is not allowed. A workaround could be to wrap the content in a div:
-
-```xml
-
-
A
-
B
-
-```
-
Default slot: the first element inside the component which is not a named slot will
be considered the `default` slot. For example:
diff --git a/src/qweb_core.ts b/src/qweb_core.ts
index 5d0b536b..db8935e7 100644
--- a/src/qweb_core.ts
+++ b/src/qweb_core.ts
@@ -268,9 +268,15 @@ export class QWeb extends EventBus {
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 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);
if (ctx.shouldProtectContext) {
@@ -288,10 +294,12 @@ export class QWeb extends EventBus {
ctx.code.unshift(" let utils = this.utils;");
}
- if (!ctx.rootNode) {
- throw new Error("A template should have one root node");
+ if (!parentNode) {
+ 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;
try {
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
// to
node.setAttribute("t-component", node.tagName);
- node.nodeValue = "t";
}
const attributes = (node).attributes;
@@ -648,6 +655,7 @@ export class Context {
inLoop: boolean = false;
inPreTag: boolean = false;
templateName: string;
+ allowMultipleRoots: boolean = false;
constructor(name?: string) {
this.rootContext = this;
@@ -661,7 +669,11 @@ export class 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");
}
if (!this.rootContext.rootNode) {
diff --git a/src/qweb_extensions.ts b/src/qweb_extensions.ts
index 1bccf35a..f9de2b0a 100644
--- a/src/qweb_extensions.ts
+++ b/src/qweb_extensions.ts
@@ -421,6 +421,18 @@ QWeb.addDirective({
: ctx.inLoop
? `String(-${componentID} - i)`
: 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 refExpr = "";
@@ -572,13 +584,24 @@ QWeb.addDirective({
slotNode.parentElement!.removeChild(slotNode);
const key = slotNode.getAttribute("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);
}
}
if (clone.childElementCount) {
- const content = clone.children[0];
- const slotFn = qweb._compile(`slot_default_template`, content);
+ const t = clone.ownerDocument!.createElement("t");
+ 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);
}
}
@@ -683,7 +706,9 @@ QWeb.addDirective({
`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`
);
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;
}
diff --git a/tests/__snapshots__/component.test.ts.snap b/tests/__snapshots__/component.test.ts.snap
index ec0d302d..ab361803 100644
--- a/tests/__snapshots__/component.test.ts.snap
+++ b/tests/__snapshots__/component.test.ts.snap
@@ -1083,12 +1083,12 @@ exports[`t-slot directive can define and call slots 2`] = `
var vn2 = h('div', p2, c2);
c1.push(vn2);
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};
var vn4 = h('div', p4, c4);
c1.push(vn4);
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;
}"
`;
diff --git a/tests/component.test.ts b/tests/component.test.ts
index 590b113d..a52b95fc 100644
--- a/tests/component.test.ts
+++ b/tests/component.test.ts
@@ -2957,6 +2957,80 @@ describe("t-slot directive", () => {
'
1
'
);
});
+
+ test("content is the default slot", async () => {
+ env.qweb.addTemplates(`
+
+