[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:
Géry Debongnie
2019-10-03 10:48:26 +02:00
parent 367725d194
commit 022b29b6a0
7 changed files with 5 additions and 161 deletions
-19
View File
@@ -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
-1
View File
@@ -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) |
+4 -6
View File
@@ -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
View File
@@ -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);
-36
View File
@@ -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
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
-23
View File
@@ -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);
-65
View File
@@ -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 {}