From bc455be720c80eb3302977683b459e0d3a5a4d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 10 Dec 2019 15:03:14 +0100 Subject: [PATCH] [IMP] components: slots can now define default content closes #554 --- doc/reference/component.md | 13 ++++ src/qweb/extensions.ts | 8 ++- .../__snapshots__/component.test.ts.snap | 34 +++++++++ tests/component/component.test.ts | 70 +++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) diff --git a/doc/reference/component.md b/doc/reference/component.md index 67fdc6d7..63e1b32d 100644 --- a/doc/reference/component.md +++ b/doc/reference/component.md @@ -865,6 +865,19 @@ be considered the `default` slot. For example: ``` +Slots can define a default content, in case the parent did not define them: + +```xml +
+ +
+ + + default content + + +``` + ### Dynamic sub components It is not common, but sometimes we need a dynamic component name. In this case, diff --git a/src/qweb/extensions.ts b/src/qweb/extensions.ts index 0dab24f0..b7b95598 100644 --- a/src/qweb/extensions.ts +++ b/src/qweb/extensions.ts @@ -194,7 +194,7 @@ QWeb.addDirective({ QWeb.addDirective({ name: "slot", priority: 80, - atNodeEncounter({ ctx, value }): boolean { + atNodeEncounter({ ctx, value, node, qweb }): boolean { const slotKey = ctx.generateID(); ctx.addLine( `const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + '${value}'];` @@ -214,6 +214,12 @@ QWeb.addDirective({ if (!ctx.parentNode) { ctx.addLine(`utils.defineProxy(result, ${parentNode}[0]);`); } + if (node.hasChildNodes()) { + ctx.addElse(); + const nodeCopy = node.cloneNode(true); + nodeCopy.removeAttribute("t-slot"); + qweb._compileNode(nodeCopy, ctx); + } ctx.closeIf(); return true; } diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index e2cd9375..859fb0e5 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -1395,6 +1395,23 @@ exports[`t-slot directive content is the default slot 1`] = ` }" `; +exports[`t-slot directive dafault slots can define a default content 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__1\\" + var h = this.h; + let c5 = [], p5 = {key:5}; + var vn5 = h('span', p5, c5); + const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; + if (slot6) { + slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c5, parent: extra.parent || context})); + } else { + c5.push({text: \`default content\`}); + } + return vn5; +}" +`; + exports[`t-slot directive default slot work with text nodes 1`] = ` "function anonymous(context, extra ) { @@ -1439,6 +1456,23 @@ exports[`t-slot directive multiple roots are allowed in a named slot 1`] = ` }" `; +exports[`t-slot directive named slots can define a default content 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__1\\" + var h = this.h; + let c5 = [], p5 = {key:5}; + var vn5 = h('span', p5, c5); + const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'header']; + if (slot6) { + slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c5, parent: extra.parent || context})); + } else { + c5.push({text: \`default content\`}); + } + return vn5; +}" +`; + exports[`t-slot directive refs are properly bound in slots 1`] = ` "function anonymous(context, extra ) { diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index 6db797b0..dbcd6c1a 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -4032,6 +4032,76 @@ describe("t-slot directive", () => { expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); }); + test("named slots can define a default content", async () => { + class Dialog extends Component { + static template = xml` + + default content + `; + } + class Parent extends Component { + static template = xml`
`; + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
default content
"); + expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); + }); + + test("dafault slots can define a default content", async () => { + class Dialog extends Component { + static template = xml` + + default content + `; + } + class Parent extends Component { + static template = xml`
`; + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
default content
"); + expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); + }); + + test("default content is not rendered if slot is provided", async () => { + class Dialog extends Component { + static template = xml` + + default content + `; + } + class Parent extends Component { + static template = xml`
hey
`; + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
hey
"); + }); + + test("default content is not rendered if named slot is provided", async () => { + class Dialog extends Component { + static template = xml` + + default content + `; + } + class Parent extends Component { + static template = xml`
hey
`; + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
hey
"); + }); + test("slots are rendered with proper context", async () => { env.qweb.addTemplates(`