mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] component/qweb: remove t-mounted directive
The t-mounted directive main goals can be achieved with hooks, in a better and more intuitive way. closes #308
This commit is contained in:
@@ -16,7 +16,6 @@
|
|||||||
- [Event Handling](#event-handling)
|
- [Event Handling](#event-handling)
|
||||||
- [Form Input Bindings](#form-input-bindings)
|
- [Form Input Bindings](#form-input-bindings)
|
||||||
- [`t-key` Directive](#t-key-directive)
|
- [`t-key` Directive](#t-key-directive)
|
||||||
- [`t-mounted` Directive](#t-mounted-directive)
|
|
||||||
- [Semantics](#semantics)
|
- [Semantics](#semantics)
|
||||||
- [Props Validation](#props-validation)
|
- [Props Validation](#props-validation)
|
||||||
- [References](#references)
|
- [References](#references)
|
||||||
@@ -756,24 +755,6 @@ There are three main use cases:
|
|||||||
- _animations_: give a different identity to a component. Ex: thread id with
|
- _animations_: give a different identity to a component. Ex: thread id with
|
||||||
animations on add/remove message.
|
animations on add/remove message.
|
||||||
|
|
||||||
### `t-mounted` Directive
|
|
||||||
|
|
||||||
The `t-mounted` directive allows to register a callback to execute whenever the node
|
|
||||||
is inserted into the DOM.
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div><input t-ref="someInput" t-mounted="focusMe"/></div>
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
class MyComponent extends owl.Component {
|
|
||||||
...
|
|
||||||
focusMe() {
|
|
||||||
this.refs.someInput.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Semantics
|
### Semantics
|
||||||
|
|
||||||
We give here an informal description of the way components are created/updated
|
We give here an informal description of the way components are created/updated
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ needs. Here is a list of all Owl specific directives:
|
|||||||
| `t-key` | [Defining a key (to help virtual dom reconciliation)](component.md#t-key-directive) |
|
| `t-key` | [Defining a key (to help virtual dom reconciliation)](component.md#t-key-directive) |
|
||||||
| `t-on-*` | [Event handling](component.md#event-handling) |
|
| `t-on-*` | [Event handling](component.md#event-handling) |
|
||||||
| `t-transition` | [Defining an animation](animations.md#css-transitions) |
|
| `t-transition` | [Defining an animation](animations.md#css-transitions) |
|
||||||
| `t-mounted` | [Callback when a node or component is mounted](component.md#t-mounted-directive) |
|
|
||||||
| `t-slot` | [Rendering a slot](component.md#slots) |
|
| `t-slot` | [Rendering a slot](component.md#slots) |
|
||||||
| `t-model` | [Form input bindings](component.md#form-input-bindings) |
|
| `t-model` | [Form input bindings](component.md#form-input-bindings) |
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ interface Internal<T extends Env, Props> {
|
|||||||
boundHandlers: { [key: number]: any };
|
boundHandlers: { [key: number]: any };
|
||||||
observer: Observer | null;
|
observer: Observer | null;
|
||||||
render: CompiledTemplate | null;
|
render: CompiledTemplate | null;
|
||||||
mountedHandlers: { [key: number]: Function };
|
mountedCB: Function | null;
|
||||||
willUnmountCB: Function | null;
|
willUnmountCB: Function | null;
|
||||||
willPatchCB: Function | null;
|
willPatchCB: Function | null;
|
||||||
patchedCB: Function | null;
|
patchedCB: Function | null;
|
||||||
@@ -186,7 +186,7 @@ export class Component<T extends Env, Props extends {}> {
|
|||||||
cmap: {},
|
cmap: {},
|
||||||
currentFiber: null,
|
currentFiber: null,
|
||||||
boundHandlers: {},
|
boundHandlers: {},
|
||||||
mountedHandlers: {},
|
mountedCB: null,
|
||||||
willUnmountCB: null,
|
willUnmountCB: null,
|
||||||
willPatchCB: null,
|
willPatchCB: null,
|
||||||
patchedCB: null,
|
patchedCB: null,
|
||||||
@@ -480,11 +480,10 @@ export class Component<T extends Env, Props extends {}> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
__owl__.isMounted = true;
|
__owl__.isMounted = true;
|
||||||
const handlers = __owl__.mountedHandlers;
|
|
||||||
try {
|
try {
|
||||||
this.mounted();
|
this.mounted();
|
||||||
for (let key in handlers) {
|
if (__owl__.mountedCB) {
|
||||||
handlers[key]();
|
__owl__.mountedCB()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorHandler(e, this);
|
errorHandler(e, this);
|
||||||
@@ -601,7 +600,6 @@ export class Component<T extends Env, Props extends {}> {
|
|||||||
vnode = __owl__.render!(this, {
|
vnode = __owl__.render!(this, {
|
||||||
promises,
|
promises,
|
||||||
handlers: __owl__.boundHandlers,
|
handlers: __owl__.boundHandlers,
|
||||||
mountedHandlers: __owl__.mountedHandlers,
|
|
||||||
fiber: fiber
|
fiber: fiber
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
+1
-11
@@ -29,17 +29,6 @@ export function useState<T>(state: T): T {
|
|||||||
return __owl__.observer.observe(state);
|
return __owl__.observer.observe(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mounted hook. The callback will be called when the current component is
|
|
||||||
* mounted. Note that the component mounted method is called first.
|
|
||||||
*/
|
|
||||||
let nextID = 1;
|
|
||||||
|
|
||||||
export function onMounted(cb) {
|
|
||||||
const component: Component<any, any> = Component._current;
|
|
||||||
component.__owl__.mountedHandlers[`h${nextID++}`] = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeLifecycleHook(method: string, reverse: boolean = false) {
|
function makeLifecycleHook(method: string, reverse: boolean = false) {
|
||||||
return function(cb) {
|
return function(cb) {
|
||||||
const component: Component<any, any> = Component._current;
|
const component: Component<any, any> = Component._current;
|
||||||
@@ -66,6 +55,7 @@ function makeLifecycleHook(method: string, reverse: boolean = false) {
|
|||||||
* willUnmount hook. The callback will be called when the current component is
|
* willUnmount hook. The callback will be called when the current component is
|
||||||
* willUnmounted. Note that the component mounted method is called last.
|
* willUnmounted. Note that the component mounted method is called last.
|
||||||
*/
|
*/
|
||||||
|
export const onMounted = makeLifecycleHook("mountedCB", true);
|
||||||
export const onWillUnmount = makeLifecycleHook("willUnmountCB");
|
export const onWillUnmount = makeLifecycleHook("willUnmountCB");
|
||||||
export const onWillPatch = makeLifecycleHook("willPatchCB");
|
export const onWillPatch = makeLifecycleHook("willPatchCB");
|
||||||
export const onPatched = makeLifecycleHook("patchedCB", true);
|
export const onPatched = makeLifecycleHook("patchedCB", true);
|
||||||
|
|||||||
@@ -183,42 +183,6 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// t-mounted
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
QWeb.addDirective({
|
|
||||||
name: "mounted",
|
|
||||||
priority: 97,
|
|
||||||
atNodeCreation({ ctx, fullName, value, nodeID, addNodeHook }) {
|
|
||||||
ctx.rootContext.shouldDefineOwner = true;
|
|
||||||
const eventName = fullName.slice(5);
|
|
||||||
if (!eventName) {
|
|
||||||
throw new Error("Missing event name with t-on directive");
|
|
||||||
}
|
|
||||||
let extraArgs;
|
|
||||||
let handler = value.replace(/\(.*\)/, function(args) {
|
|
||||||
extraArgs = args.slice(1, -1);
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
let error = `(function () {throw new Error('Missing handler \\'' + '${handler}' + \`\\' when evaluating template '${ctx.templateName.replace(
|
|
||||||
/`/g,
|
|
||||||
"'"
|
|
||||||
)}'\`)})()`;
|
|
||||||
if (extraArgs) {
|
|
||||||
ctx.addLine(
|
|
||||||
`extra.mountedHandlers[${nodeID}] = (context['${handler}'] || ${error}).bind(owner, ${ctx.formatExpression(
|
|
||||||
extraArgs
|
|
||||||
)});`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ctx.addLine(
|
|
||||||
`extra.mountedHandlers[${nodeID}] = extra.mountedHandlers[${nodeID}] || (context['${handler}'] || ${error}).bind(owner);`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
addNodeHook("insert", `if (context.__owl__.isMounted) { extra.mountedHandlers[${nodeID}](); }`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// t-slot
|
// t-slot
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
makeDeferred,
|
makeDeferred,
|
||||||
makeTestFixture,
|
makeTestFixture,
|
||||||
makeTestEnv,
|
makeTestEnv,
|
||||||
nextTick,
|
|
||||||
patchNextFrame,
|
patchNextFrame,
|
||||||
renderToDOM,
|
renderToDOM,
|
||||||
unpatchNextFrame
|
unpatchNextFrame
|
||||||
@@ -265,28 +264,6 @@ describe("animations", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div></div>");
|
expect(fixture.innerHTML).toBe("<div></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-transition combined with t-mounted", async () => {
|
|
||||||
env.qweb.addTemplate(
|
|
||||||
"TestWidget",
|
|
||||||
`<div><span t-if="state.flag" t-mounted="f" t-transition="chimay">blue</span></div>`
|
|
||||||
);
|
|
||||||
class TestWidget extends Widget {
|
|
||||||
state = useState({ flag: false });
|
|
||||||
f() {}
|
|
||||||
}
|
|
||||||
const widget = new TestWidget(env);
|
|
||||||
widget.f = jest.fn();
|
|
||||||
await widget.mount(fixture);
|
|
||||||
|
|
||||||
patchNextFrame(cb => cb());
|
|
||||||
expect(widget.f).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
widget.state.flag = true;
|
|
||||||
await nextTick();
|
|
||||||
expect(widget.f).toHaveBeenCalledTimes(1);
|
|
||||||
unpatchNextFrame();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("t-transition, remove and re-add before transitionend", async () => {
|
test("t-transition, remove and re-add before transitionend", async () => {
|
||||||
expect.assertions(11);
|
expect.assertions(11);
|
||||||
|
|
||||||
|
|||||||
@@ -2959,71 +2959,6 @@ describe("widget and observable state", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("t-mounted directive", () => {
|
|
||||||
test("callback is not called when not in DOM", async () => {
|
|
||||||
env.qweb.addTemplate("TestWidget", `<div><input t-mounted="f"/></div>`);
|
|
||||||
class TestWidget extends Widget {
|
|
||||||
f() {}
|
|
||||||
}
|
|
||||||
const widget = new TestWidget(env);
|
|
||||||
widget.f = jest.fn();
|
|
||||||
await widget.mount(document.createElement("div"));
|
|
||||||
expect(widget.f).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("callback is called when in DOM", async () => {
|
|
||||||
env.qweb.addTemplate("TestWidget", `<div><input t-mounted="f"/></div>`);
|
|
||||||
class TestWidget extends Widget {
|
|
||||||
f() {}
|
|
||||||
}
|
|
||||||
const widget = new TestWidget(env);
|
|
||||||
widget.f = jest.fn();
|
|
||||||
await widget.mount(fixture);
|
|
||||||
expect(widget.f).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("callback with args is called when in DOM", async () => {
|
|
||||||
env.qweb.addTemplate("TestWidget", `<div><input t-mounted="f(2)"/></div>`);
|
|
||||||
class TestWidget extends Widget {
|
|
||||||
f() {}
|
|
||||||
}
|
|
||||||
const widget = new TestWidget(env);
|
|
||||||
widget.f = jest.fn();
|
|
||||||
await widget.mount(fixture);
|
|
||||||
expect(widget.f).toHaveBeenCalledTimes(1);
|
|
||||||
expect(widget.f).toHaveBeenCalledWith(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("combined with a t-if", async () => {
|
|
||||||
env.qweb.addTemplate("TestWidget", `<div><input t-if="state.flag" t-mounted="f"/></div>`);
|
|
||||||
class TestWidget extends Widget {
|
|
||||||
state = useState({ flag: false });
|
|
||||||
f() {}
|
|
||||||
}
|
|
||||||
const widget = new TestWidget(env);
|
|
||||||
widget.f = jest.fn();
|
|
||||||
await widget.mount(fixture);
|
|
||||||
expect(widget.f).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
widget.state.flag = true;
|
|
||||||
await nextTick();
|
|
||||||
expect(widget.f).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("combined with a t-ref", async () => {
|
|
||||||
env.qweb.addTemplate("TestWidget", `<div><input t-ref="input" t-mounted="f"/></div>`);
|
|
||||||
class TestWidget extends Widget {
|
|
||||||
input = useRef("input");
|
|
||||||
f() {}
|
|
||||||
}
|
|
||||||
const widget = new TestWidget(env);
|
|
||||||
widget.f = jest.fn();
|
|
||||||
await widget.mount(fixture);
|
|
||||||
expect(widget.input.el).toBeDefined();
|
|
||||||
expect(widget.f).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("can deduce template from name", () => {
|
describe("can deduce template from name", () => {
|
||||||
test("can find template if name of component", async () => {
|
test("can find template if name of component", async () => {
|
||||||
class ABC extends Widget {}
|
class ABC extends Widget {}
|
||||||
|
|||||||
Reference in New Issue
Block a user