diff --git a/doc/reference/hooks.md b/doc/reference/hooks.md index ed9797fc..e486bf0b 100644 --- a/doc/reference/hooks.md +++ b/doc/reference/hooks.md @@ -290,6 +290,7 @@ class Parent extends Component { // here, if component is mounted, refs are active: // - this.divRef.el is the div HTMLElement // - this.subRef.comp is the instance of the sub component + // - this.subRef.el is the root HTML node of the sub component (i.e. this.subRef.comp.el) } } ``` @@ -297,8 +298,11 @@ class Parent extends Component { As shown by the example above, html elements are accessed by using the `el` key, and components references are accessed with `comp`. -Note: if used on a component, the reference will be set in the `refs` -variable between `willPatch` and `patched`. +Notes: + +- if used on a component, the reference will be set in the `refs` +variable between `willPatch` and `patched`, +- on a component, accessing `ref.el` will get the root node of the component. The `t-ref` directive also accepts dynamic values with string interpolation (like the [`t-attf-`](qweb.md#dynamic-attributes) and diff --git a/src/hooks.ts b/src/hooks.ts index b8145e19..99188b89 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -104,7 +104,12 @@ export function useRef(name: string): Ref { return { get el(): HTMLElement | null { const val = __owl__.refs && __owl__.refs[name]; - return val instanceof HTMLElement ? val : null; + if (val instanceof HTMLElement) { + return val; + } else if (val instanceof Component) { + return val.el; + } + return null; }, get comp(): Component | null { const val = __owl__.refs && __owl__.refs[name]; diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index d3b67687..c7acf00e 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -1078,94 +1078,6 @@ describe("composition", () => { console.error = consoleError; }); - test("t-refs on widget are components", async () => { - class WidgetC extends Widget { - static template = xml`
Hello
`; - static components = { WidgetB }; - widget = useRef("mywidgetb"); - } - - const widget = new WidgetC(); - await widget.mount(fixture); - expect(widget.widget.comp).toBeInstanceOf(WidgetB); - }); - - test("t-refs are bound at proper timing", async () => { - expect.assertions(2); - class ParentWidget extends Widget { - static template = xml` -
- -
- `; - static components = { Widget }; - state = useState({ list: [] }); - child = useRef("child"); - willPatch() { - expect(this.child.comp).toBeNull(); - } - patched() { - expect(this.child.comp).not.toBeNull(); - } - } - - const parent = new ParentWidget(); - await parent.mount(fixture); - parent.state.list.push(1); - await nextTick(); - }); - - test("t-refs are bound at proper timing (2)", async () => { - expect.assertions(10); - env.qweb.addTemplate( - "ParentWidget", - ` -
- - -
` - ); - class ParentWidget extends Widget { - static components = { Widget }; - state = useState({ child1: true, child2: false }); - child1 = useRef("child1"); - child2 = useRef("child2"); - count = 0; - mounted() { - expect(this.child1.comp).toBeDefined(); - expect(this.child2.comp).toBeNull(); - } - willPatch() { - if (this.count === 0) { - expect(this.child1.comp).toBeDefined(); - expect(this.child2.comp).toBeNull(); - } - if (this.count === 1) { - expect(this.child1.comp).toBeDefined(); - expect(this.child2.comp).toBeDefined(); - } - } - patched() { - if (this.count === 0) { - expect(this.child1.comp).toBeDefined(); - expect(this.child2.comp).toBeDefined(); - } - if (this.count === 1) { - expect(this.child1.comp).toBeNull(); - expect(this.child2.comp).toBeDefined(); - } - this.count++; - } - } - - const parent = new ParentWidget(); - await parent.mount(fixture); - parent.state.child2 = true; - await nextTick(); - parent.state.child1 = false; - await nextTick(); - }); - test("modifying a sub widget", async () => { env.qweb.addTemplate("ParentWidget", `
`); class ParentWidget extends Widget { diff --git a/tests/hooks.test.ts b/tests/hooks.test.ts index e8b9edc8..bc39bb87 100644 --- a/tests/hooks.test.ts +++ b/tests/hooks.test.ts @@ -228,8 +228,11 @@ describe("hooks", () => { } } const counter = new Counter(); + expect(counter.button.el).toBe(null); await counter.mount(fixture); expect(fixture.innerHTML).toBe("
"); + expect(counter.button.el).not.toBe(null); + expect(counter.button.el).toBe(fixture.querySelector("button")); counter.increment(); await nextTick(); expect(fixture.innerHTML).toBe("
"); @@ -256,6 +259,104 @@ describe("hooks", () => { expect(fixture.innerHTML).toBe("
"); }); + test("t-refs on widget are components", async () => { + class WidgetB extends Component { + static template = xml`
b
`; + } + class WidgetC extends Component { + static template = xml`
Hello
`; + static components = { WidgetB }; + ref = useRef("mywidgetb"); + } + + const widget = new WidgetC(); + expect(widget.ref.comp).toBe(null); + expect(widget.ref.el).toBe(null); + await widget.mount(fixture); + expect(widget.ref.comp).toBeInstanceOf(WidgetB); + expect(widget.ref.el).toEqual(fixture.querySelector(".outer-div > div")); + }); + + test("t-refs are bound at proper timing", async () => { + expect.assertions(2); + class Widget extends Component { + static template = xml`
widget
`; + } + + class ParentWidget extends Component { + static template = xml` +
+ +
+ `; + static components = { Widget }; + state = useState({ list: [] }); + child = useRef("child"); + willPatch() { + expect(this.child.comp).toBeNull(); + } + patched() { + expect(this.child.comp).not.toBeNull(); + } + } + + const parent = new ParentWidget(); + await parent.mount(fixture); + parent.state.list.push(1); + await nextTick(); + }); + + test("t-refs are bound at proper timing (2)", async () => { + expect.assertions(10); + class Widget extends Component { + static template = xml`
widget
`; + } + class ParentWidget extends Component { + static template = xml` +
+ + +
`; + static components = { Widget }; + state = useState({ child1: true, child2: false }); + child1 = useRef("child1"); + child2 = useRef("child2"); + count = 0; + mounted() { + expect(this.child1.comp).toBeDefined(); + expect(this.child2.comp).toBeNull(); + } + willPatch() { + if (this.count === 0) { + expect(this.child1.comp).toBeDefined(); + expect(this.child2.comp).toBeNull(); + } + if (this.count === 1) { + expect(this.child1.comp).toBeDefined(); + expect(this.child2.comp).toBeDefined(); + } + } + patched() { + if (this.count === 0) { + expect(this.child1.comp).toBeDefined(); + expect(this.child2.comp).toBeDefined(); + } + if (this.count === 1) { + expect(this.child1.comp).toBeNull(); + expect(this.child2.comp).toBeDefined(); + } + this.count++; + } + } + + const parent = new ParentWidget(); + await parent.mount(fixture); + parent.state.child2 = true; + await nextTick(); + parent.state.child1 = false; + await nextTick(); + }); + test("can use onPatched, onWillPatch", async () => { const steps: string[] = []; function useMyHook() {