Compare commits

...

10 Commits

Author SHA1 Message Date
Géry Debongnie 0290f63ba3 [FIX] component: various issues while mounting manually components
The initial problem solved by this commit is that it was possible to get
into a situation where a mounting/rendering was started, then the component was
updated, but then another mounting operation begins, and it tries to
reuse the previous rendering operation, which is no longer uptodate.

The underlying issue is that Owl did not track properly the various
internal state change of a component.  These issue should be solved by
the introduction of the status enum, which currently tracks 6 possible
states:

- CREATED
- WILLSTARTED
- RENDERED
- MOUNTED
- UNMOUNTED
- DESTROYED

This status number replaces the isMounted and isDestroyed boolean flags.
It has the advantage of making sure that the component is in a
consistent state (it is no longer possible to be destroyed and mounted,
for example)

Another advantage is that it gives us an easy way to track the fact that
a component has been rendered, but is not in the DOM.  This is a subtle
situation where some various events can happen, and we need to be able
to react to that case.

Note that there is a change of behaviour: if a component is mounted in a
specific target, then before the mounting is complete, the component is
mounted in another target, we no longer reject the first mounting
operation.
2021-02-08 10:59:28 +01:00
Géry Debongnie 370fae4e1a [FIX] package.json: rollback live-server from 1.2.3 to 1.2.1
No idea why, but it looks like the npm package 1.2.3 disappeared.
2021-02-04 09:07:26 +01:00
Géry Debongnie 76735410f8 [FIX] qweb: prevent issue with builtin object properties
The QWeb expression parser use an object as a mapping between some
strings and the desired output in the compiled template.  However, as we
should all know, objects are not Maps, they have some additional
properties, such as "constructor" or "hasOwnProperty".

The simple solution is to make sure the mapping object does not have
anything in its prototype chain to pollute its purpose.

closes #835
2021-02-04 09:07:26 +01:00
Géry Debongnie 19a47a7001 [IMP] component: add setup lifecycle hook 2021-02-03 15:32:38 +01:00
Géry Debongnie 398f9f4e53 [IMP] component: can trigger event handlers even if unmounted
From the beginning, Owl was designed to only call event handler on
components that are mounted in the DOM.  The main benefit is that if a
component is destroyed, we are guaranteed to not execute any useless (or
potentially dangerous code).

However, there is one downside: if a component tree is being mounted,
and a child component trigger an event in its mounted hook, then it
cannot be caught by the parent, since the parent is technically not yet
mounted.

This may not be a good situation, but the point is that Owl
unnecessarily prevent the handler to be called.

We can fix this issue by simply checking if the component is not destroyed
instead of checking that it is mounted.

closes #809
2021-02-03 14:46:18 +01:00
Géry Debongnie af426aa902 [DOC] misc small fixes
closes #827, #825, #822, #821
2021-02-03 13:44:04 +01:00
Géry Debongnie 490cf18079 [REL] v1.2.3
# v1.2.3

This is just another small bug fix release, because we need it in Odoo.

Fix: prevent crash when rendering a component before mounting it
2021-01-19 15:39:26 +01:00
Géry Debongnie dfc782599b [FIX] crash when rendering component before mounting
This commit makes sure that Owl does not crash when a component is
created, then updated (for example, with a (observed) state change), and
then, some moment later, mounted.

The initial render is not useful, because it is not linked to a mounting
action anyway.  And it caused issues such as a crash when Owl tried to
patch it to a non existing target
2021-01-19 15:26:15 +01:00
Géry Debongnie 71f545058b [REL] v1.2.2
# v1.2.2

This is just a small bug fix release, because we need it in Odoo.

Bug fix: allow renderings for detached components. Before this release, Owl
ignored renderings in some cases if a component is detached.  We now still render
it, because it is important in some cases.
2021-01-14 09:10:35 +01:00
Géry Debongnie 1a20cc57de [FIX] component can be updated while detached from the main DOM
This commit tries to improve the interactions involving unmounted
components, or components mounted in an htmelement which is detached
from the main DOM, and rendering actions.

The main example is mounting a component in detached div, to prepare all
children.  If we just mount the component, it will work as expected: the
full component tree is rendered in memory, and ready to be really
mounted at the desired target.

However, if before doing that, we update the component and call render
on it (for example, with a change in an observed state), then this
rendering will be ignored, and therefore, the full subcomponent tree is
not uptodate.

This commit will also solve another issue in the compatibility layer in
odoo: in the form renderer, we mount components with the adapter in a
div, which is not yet attached to the DOM. We then manually call the
mounted hook when on_attach_callback is called.  This means that before
this commit, any change to the components between the initial rendering
and the call to mounted will be ignored.

As a bonus, this commit has the effect of bringing closer the semantics
of render and mount operations, which is certainly good.

closes #823
2021-01-14 09:05:05 +01:00
27 changed files with 462 additions and 255 deletions
+1 -1
View File
@@ -120,7 +120,7 @@ npm install @odoo/owl
If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-1.2.1](https://github.com/odoo/owl/releases/tag/v1.2.1)
- [owl-1.2.3](https://github.com/odoo/owl/releases/tag/v1.2.3)
## License
+2 -2
View File
@@ -663,7 +663,7 @@ function makeStore() {
function setup() {
owl.config.mode = "dev";
const env = {store = makeStore()};
const env = { store: makeStore() };
mount(App, { target: document.body, env });
}
```
@@ -939,7 +939,7 @@ For reference, here is the final code:
function setup() {
owl.config.mode = "dev";
const env = {store = makeStore()};
const env = { store: makeStore() };
mount(App, { target: document.body, env });
}
+20 -2
View File
@@ -11,6 +11,7 @@
- [Methods](#methods)
- [Lifecycle](#lifecycle)
- [`constructor(parent, props)`](#constructorparent-props)
- [`setup()`](#setup)
- [`willStart()`](#willstart)
- [`mounted()`](#mounted)
- [`willUpdateProps(nextProps)`](#willupdatepropsnextprops)
@@ -324,7 +325,7 @@ a owl component:
| Method | Description |
| ------------------------------------------------ | ----------------------------------------------------------- |
| **[constructor](#constructorparent-props)** | constructor |
| **[setup](#setup)** | setup |
| **[willStart](#willstart)** | async, before first rendering |
| **[mounted](#mounted)** | just after component is rendered and added to the DOM |
| **[willUpdateProps](#willupdatepropsnextprops)** | async, before props update |
@@ -369,6 +370,23 @@ class ClickCounter extends owl.Component {
}
```
Hook functions can be called in the constructor.
#### `setup()`
_setup_ is run just after the component is constructed. It is a lifecycle method,
very similar to the _constructor_, except that it does not receive any argument.
It is a valid method to call hook functions. Note that one of the main reason to
have the `setup` hook in the component lifecycle is to make it possible to
monkey patch it. It is a common need in the Odoo ecosystem.
```javascript
setup() {
useSetupAutofocus();
}
```
#### `willStart()`
willStart is an asynchronous hook that can be implemented to
@@ -771,7 +789,7 @@ template rendered with `props`. In Owl, this can be done by
simply defining a template, that will access the `props` object:
```js
const Welcome = xml`<h1>Hello, {props.name}</h1>`;
const Welcome = xml`<h1>Hello, <t t-esc="props.name"/></h1>`;
class MyComponent extends Component {
static template = xml`
+8 -1
View File
@@ -131,7 +131,7 @@ class SomeComponent extends Component {
### One rule
There is only one rule: every hook for a component has to be called in the
constructor (or in class fields):
constructor, in the _setup_ method, or in class fields:
```js
// ok
@@ -147,6 +147,13 @@ class SomeComponent extends Component {
}
}
// also ok
class SomeComponent extends Component {
setup() {
this.state = useState({ value: 0 });
}
}
// not ok: this is executed after the constructor is called
class SomeComponent extends Component {
async willStart() {
+5 -5
View File
@@ -15,7 +15,7 @@ use cases, there is no need to directly instantiate an observer.
For example, this code will display `update` in the console:
```javascript
const observer = new owl.Observer();
const observer = new owl.core.Observer();
observer.notifyCB = () => console.log("update");
const obj = observer.observe({ a: { b: 1 } });
@@ -39,14 +39,14 @@ is incremented every time the value is observed. Sometimes, it can be useful
to obtain that number:
```js
const observer = new owl.Observer();
const observer = new owl.core.Observer();
const obj = observer.observe({ a: { b: 1 } });
observer.deepRevNumber(obj.a); // 1
observer.revNumber(obj.a); // 1
obj.a.b = 2;
observer.deepRevNumber(obj.a); // 2
observer.revNumber(obj.a); // 2
```
The `deepRevNumber` can also return 0, which indicates that the value is not
The `revNumber` can also return 0, which indicates that the value is not
observed.
+1 -1
View File
@@ -44,7 +44,7 @@ Slots are defined by the caller, with the `t-set-slot` directive:
```xml
<div t-name="SomeComponent">
<div>some component</div>
<Dialog title="Some Dialog">
<Dialog title="'Some Dialog'">
<t t-set-slot="content">
<div>hey</div>
</t>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "1.2.1",
"version": "1.2.3",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js",
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.2.1
- Current version: 1.2.3
- Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may
+96 -78
View File
@@ -44,6 +44,15 @@ interface MountOptions {
position?: MountPosition;
}
export const enum STATUS {
CREATED,
WILLSTARTED, // willstart has been called
RENDERED, // first render is completed (so, vnode is now defined)
MOUNTED, // is ready, and in DOM. It has a valid el
UNMOUNTED, // has a valid el, but is not in DOM
DESTROYED,
}
/**
* This is mostly an internal detail of implementation. The Meta interface is
* useful to typecheck and describe the internal keys used by Owl to manage the
@@ -56,8 +65,7 @@ interface Internal<T extends Env> {
depth: number;
vnode: VNode | null;
pvnode: VNode | null;
isMounted: boolean;
isDestroyed: boolean;
status: STATUS;
// parent and children keys are obviously useful to setup the parent-children
// relationship.
@@ -164,16 +172,18 @@ export class Component<Props extends {} = any, T extends Env = Env> {
this.env.browser = browser;
}
this.env.qweb.on("update", this, () => {
if (this.__owl__.isMounted) {
this.render(true);
}
if (this.__owl__.isDestroyed) {
// this is unlikely to happen, but if a root widget is destroyed,
// we want to remove our subscription. The usual way to do that
// would be to perform some check in the destroy method, but since
// it is very performance sensitive, and since this is a rare event,
// we simply do it lazily
this.env.qweb.off("update", this);
switch (this.__owl__.status) {
case STATUS.MOUNTED:
this.render(true);
break;
case STATUS.DESTROYED:
// this is unlikely to happen, but if a root widget is destroyed,
// we want to remove our subscription. The usual way to do that
// would be to perform some check in the destroy method, but since
// it is very performance sensitive, and since this is a rare event,
// we simply do it lazily
this.env.qweb.off("update", this);
break;
}
});
depth = 0;
@@ -186,8 +196,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
depth: depth,
vnode: null,
pvnode: null,
isMounted: false,
isDestroyed: false,
status: STATUS.CREATED,
parent: parent || null,
children: {},
cmap: {},
@@ -209,8 +218,20 @@ export class Component<Props extends {} = any, T extends Env = Env> {
if (constr.style) {
this.__applyStyles(constr);
}
this.setup();
}
/**
* setup is run just after the component is constructed. This is the standard
* location where the component can setup its hooks. It has some advantages
* over the constructor:
* - it can be patched (useful in odoo ecosystem)
* - it does not need to propagate the arguments to the super call
*
* Note: this method should not be called manually.
*/
setup() {}
/**
* willStart is an asynchronous hook that can be implemented to perform some
* action before the initial rendering of a component.
@@ -305,42 +326,49 @@ export class Component<Props extends {} = any, T extends Env = Env> {
* Note that a component can be mounted an unmounted several times
*/
async mount(target: HTMLElement | DocumentFragment, options: MountOptions = {}): Promise<void> {
const position = options.position || "last-child";
const __owl__ = this.__owl__;
if (__owl__.isMounted) {
if (position !== "self" && this.el!.parentNode !== target) {
// in this situation, we are trying to mount a component on a different
// target. In this case, we need to unmount first, otherwise it will
// not work.
this.unmount();
} else {
return Promise.resolve();
}
}
if (__owl__.isDestroyed) {
throw new Error("Cannot mount a destroyed component");
}
if (__owl__.currentFiber) {
const currentFiber = __owl__.currentFiber;
if (currentFiber.target === target && currentFiber.position === position) {
return scheduler.addFiber(currentFiber);
} else {
scheduler.rejectFiber(currentFiber, "Mounting operation cancelled");
}
}
if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) {
let message = `Component '${this.constructor.name}' cannot be mounted: the target is not a valid DOM node.`;
message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`;
throw new Error(message);
}
const fiber = new Fiber(null, this, true, target, position);
fiber.shouldPatch = false;
if (!__owl__.vnode) {
this.__prepareAndRender(fiber, () => {});
} else {
this.__render(fiber);
const position = options.position || "last-child";
const __owl__ = this.__owl__;
const currentFiber = __owl__.currentFiber;
switch (__owl__.status) {
case STATUS.CREATED: {
const fiber = new Fiber(null, this, true, target, position);
fiber.shouldPatch = false;
this.__prepareAndRender(fiber, () => {});
return scheduler.addFiber(fiber);
}
case STATUS.WILLSTARTED:
case STATUS.RENDERED:
currentFiber.target = target;
currentFiber.position = position;
return scheduler.addFiber(currentFiber);
case STATUS.UNMOUNTED: {
const fiber = new Fiber(null, this, true, target, position);
fiber.shouldPatch = false;
this.__render(fiber);
return scheduler.addFiber(fiber);
}
case STATUS.MOUNTED: {
if (position !== "self" && this.el!.parentNode !== target) {
const fiber = new Fiber(null, this, true, target, position);
fiber.shouldPatch = false;
this.__render(fiber);
return scheduler.addFiber(fiber);
} else {
return Promise.resolve();
}
}
case STATUS.DESTROYED:
throw new Error("Cannot mount a destroyed component");
}
return scheduler.addFiber(fiber);
}
/**
@@ -348,7 +376,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
* to call willUnmount calls and remove the component from the DOM.
*/
unmount() {
if (this.__owl__.isMounted) {
if (this.__owl__.status === STATUS.MOUNTED) {
this.__callWillUnmount();
this.el!.remove();
}
@@ -366,10 +394,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
async render(force: boolean = false): Promise<void> {
const __owl__ = this.__owl__;
const currentFiber = __owl__.currentFiber;
if (!__owl__.isMounted && !currentFiber) {
// if we get here, this means that the component was either never mounted,
// or was unmounted and some state change triggered a render. Either way,
// we do not want to actually render anything in this case.
if (!__owl__.vnode && !currentFiber) {
return;
}
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
@@ -378,15 +403,13 @@ export class Component<Props extends {} = any, T extends Env = Env> {
// if we aren't mounted at this point, it implies that there is a
// currentFiber that is already rendered (isRendered is true), so we are
// about to be mounted
const isMounted = __owl__.isMounted;
const status = __owl__.status;
const fiber = new Fiber(null, this, force, null, null);
Promise.resolve().then(() => {
if (__owl__.isMounted || !isMounted) {
if (fiber.isCompleted) {
if (__owl__.status === STATUS.MOUNTED || status !== STATUS.MOUNTED) {
if (fiber.isCompleted || fiber.isRendered) {
return;
}
// we are mounted (__owl__.isMounted), or if we are currently being
// mounted (!isMounted), so we call __render
this.__render(fiber);
} else {
// we were mounted when render was called, but we aren't anymore, so we
@@ -410,7 +433,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
*/
destroy() {
const __owl__ = this.__owl__;
if (!__owl__.isDestroyed) {
if (__owl__.status !== STATUS.DESTROYED) {
const el = this.el;
this.__destroy(__owl__.parent);
if (el) {
@@ -455,13 +478,12 @@ export class Component<Props extends {} = any, T extends Env = Env> {
*/
__destroy(parent: Component | null) {
const __owl__ = this.__owl__;
const isMounted = __owl__.isMounted;
if (isMounted) {
if (__owl__.status === STATUS.MOUNTED) {
if (__owl__.willUnmountCB) {
__owl__.willUnmountCB();
}
this.willUnmount();
__owl__.isMounted = false;
__owl__.status = STATUS.UNMOUNTED;
}
const children = __owl__.children;
for (let key in children) {
@@ -472,7 +494,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
delete parent.__owl__.children[id];
__owl__.parent = null;
}
__owl__.isDestroyed = true;
__owl__.status = STATUS.DESTROYED;
delete __owl__.vnode;
if (__owl__.currentFiber) {
__owl__.currentFiber.isCompleted = true;
@@ -482,7 +504,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
__callMounted() {
const __owl__ = this.__owl__;
__owl__.isMounted = true;
__owl__.status = STATUS.MOUNTED;
__owl__.currentFiber = null;
this.mounted();
if (__owl__.mountedCB) {
@@ -496,7 +518,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
__owl__.willUnmountCB();
}
this.willUnmount();
__owl__.isMounted = false;
__owl__.status = STATUS.UNMOUNTED;
if (__owl__.currentFiber) {
__owl__.currentFiber.isCompleted = true;
__owl__.currentFiber.root.counter = 0;
@@ -504,7 +526,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
const children = __owl__.children;
for (let id in children) {
const comp = children[id];
if (comp.__owl__.isMounted) {
if (comp.__owl__.status === STATUS.MOUNTED) {
comp.__callWillUnmount();
}
}
@@ -625,18 +647,25 @@ export class Component<Props extends {} = any, T extends Env = Env> {
}
return p._template;
}
async __prepareAndRender(fiber: Fiber, cb: CallableFunction) {
try {
await Promise.all([this.willStart(), this.__owl__.willStartCB && this.__owl__.willStartCB()]);
const proms = Promise.all([
this.willStart(),
this.__owl__.willStartCB && this.__owl__.willStartCB(),
]);
this.__owl__.status = STATUS.WILLSTARTED;
await proms;
if (this.__owl__.status === <any>STATUS.DESTROYED) {
return Promise.resolve();
}
} catch (e) {
fiber.handleError(e);
return Promise.resolve();
}
if (this.__owl__.isDestroyed) {
return Promise.resolve();
}
if (!fiber.isCompleted) {
this.__render(fiber);
this.__owl__.status = STATUS.RENDERED;
cb();
}
}
@@ -659,7 +688,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
for (let childKey in __owl__.children) {
const child = __owl__.children[childKey];
const childOwl = child.__owl__;
if (!childOwl.isMounted && childOwl.parentLastFiberId < fiber.id) {
if (childOwl.status !== STATUS.MOUNTED && childOwl.parentLastFiberId < fiber.id) {
// we only do here a "soft" destroy, meaning that we leave the child
// dom node alone, without removing it. Most of the time, it does not
// matter, because the child component is already unmounted. However,
@@ -706,17 +735,6 @@ export class Component<Props extends {} = any, T extends Env = Env> {
}
}
/**
* Only called by qweb t-component directive (when t-keepalive is set)
*/
__remount() {
const __owl__ = this.__owl__;
if (!__owl__.isMounted) {
__owl__.isMounted = true;
this.mounted();
}
}
/**
* Apply default props (only top level).
*
+2 -1
View File
@@ -1,6 +1,7 @@
import { QWeb } from "../qweb/index";
import { INTERP_REGEXP } from "../qweb/compilation_context";
import { makeHandlerCode, MODS_CODE } from "../qweb/extensions";
import { STATUS } from "./component";
//------------------------------------------------------------------------------
// t-component
@@ -361,7 +362,7 @@ QWeb.addDirective({
// need to update component
let styleCode = "";
if (tattStyle) {
styleCode = `.then(()=>{if (w${componentID}.__owl__.isDestroyed) {return};w${componentID}.el.style=${tattStyle};});`;
styleCode = `.then(()=>{if (w${componentID}.__owl__.status === ${STATUS.DESTROYED}) {return};w${componentID}.el.style=${tattStyle};});`;
}
ctx.addLine(
`w${componentID}.__updateProps(props${componentID}, extra.fiber, ${scope})${styleCode};`
+34 -20
View File
@@ -1,5 +1,5 @@
import { h, VNode } from "../vdom/index";
import { Component, MountPosition } from "./component";
import { Component, MountPosition, STATUS } from "./component";
import { scheduler } from "./scheduler";
/**
@@ -107,6 +107,8 @@ export class Fiber {
*/
_reuseFiber(oldFiber: Fiber) {
oldFiber.cancel(); // cancel children fibers
oldFiber.target = this.target || oldFiber.target;
oldFiber.position = this.position || oldFiber.position;
oldFiber.isCompleted = false; // keep the root fiber alive
oldFiber.isRendered = false; // the fiber has to be re-rendered
if (oldFiber.child) {
@@ -188,7 +190,8 @@ export class Fiber {
complete() {
let component = this.component;
this.isCompleted = true;
if (!this.target && !component.__owl__.isMounted) {
const status = component.__owl__.status;
if (status === STATUS.DESTROYED) {
return;
}
@@ -202,14 +205,16 @@ export class Fiber {
const patchLen = patchQueue.length;
// call willPatch hook on each fiber of patchQueue
for (let i = 0; i < patchLen; i++) {
const fiber = patchQueue[i];
if (fiber.shouldPatch) {
component = fiber.component;
if (component.__owl__.willPatchCB) {
component.__owl__.willPatchCB();
if (status === STATUS.MOUNTED) {
for (let i = 0; i < patchLen; i++) {
const fiber = patchQueue[i];
if (fiber.shouldPatch) {
component = fiber.component;
if (component.__owl__.willPatchCB) {
component.__owl__.willPatchCB();
}
component.willPatch();
}
component.willPatch();
}
}
@@ -250,8 +255,9 @@ export class Fiber {
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
}
}
if (fiber === component.__owl__.currentFiber) {
component.__owl__.currentFiber = null;
const compOwl = component.__owl__;
if (fiber === compOwl.currentFiber) {
compOwl.currentFiber = null;
}
}
@@ -271,16 +277,24 @@ export class Fiber {
}
// call patched/mounted hook on each fiber of (reversed) patchQueue
for (let i = patchLen - 1; i >= 0; i--) {
const fiber = patchQueue[i];
component = fiber.component;
if (fiber.shouldPatch && !this.target) {
component.patched();
if (component.__owl__.patchedCB) {
component.__owl__.patchedCB();
if (status === STATUS.MOUNTED || inDOM) {
for (let i = patchLen - 1; i >= 0; i--) {
const fiber = patchQueue[i];
component = fiber.component;
if (fiber.shouldPatch && !this.target) {
component.patched();
if (component.__owl__.patchedCB) {
component.__owl__.patchedCB();
}
} else {
component.__callMounted();
}
} else if (this.target ? inDOM : true) {
component.__callMounted();
}
} else {
for (let i = patchLen - 1; i >= 0; i--) {
const fiber = patchQueue[i];
component = fiber.component;
component.__owl__.status = STATUS.UNMOUNTED;
}
}
}
+4 -4
View File
@@ -29,14 +29,14 @@ const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in
","
);
const WORD_REPLACEMENT = {
const WORD_REPLACEMENT = Object.assign(Object.create(null), {
and: "&&",
or: "||",
gt: ">",
gte: ">=",
lt: "<",
lte: "<=",
};
});
export interface QWebVar {
id: string; // foo
@@ -69,7 +69,7 @@ interface Token {
varName?: string;
}
const STATIC_TOKEN_MAP: { [key: string]: TKind } = {
const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), {
"{": "LEFT_BRACE",
"}": "RIGHT_BRACE",
"[": "LEFT_BRACKET",
@@ -78,7 +78,7 @@ const STATIC_TOKEN_MAP: { [key: string]: TKind } = {
",": "COMMA",
"(": "LEFT_PAREN",
")": "RIGHT_PAREN",
};
});
// note that the space after typeof is relevant. It makes sure that the formatted
// expression has a space after typeof
+2 -1
View File
@@ -1,3 +1,4 @@
import { STATUS } from "../component/component";
import { VNode } from "../vdom/index";
import { INTERP_REGEXP } from "./compilation_context";
import { QWeb } from "./qweb";
@@ -76,7 +77,7 @@ export function makeHandlerCode(
code = ctx.captureExpression(value);
}
const modCode = mods.map((mod) => modcodes[mod]).join("");
let handler = `function (e) {if (!context.__owl__.isMounted){return}${modCode}${code}}`;
let handler = `function (e) {if (context.__owl__.status === ${STATUS.DESTROYED}){return}${modCode}${code}}`;
if (putInCache) {
const key = ctx.generateTemplateKey(event);
ctx.addLine(`extra.handlers[${key}] = extra.handlers[${key}] || ${handler};`);
@@ -20,7 +20,7 @@ exports[`class and style attributes with t-component dynamic t-att-style is prop
w2 = false;
}
if (w2) {
w2.__updateProps(props2, extra.fiber, undefined).then(()=>{if (w2.__owl__.isDestroyed) {return};w2.el.style=_4;});;
w2.__updateProps(props2, extra.fiber, undefined).then(()=>{if (w2.__owl__.status === 5) {return};w2.el.style=_4;});;
let pvnode = w2.__owl__.pvnode;
c1.push(pvnode);
} else {
@@ -444,7 +444,7 @@ exports[`composition t-ref on a node, and t-on-click 2`] = `
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -579,7 +579,7 @@ exports[`other directives with t-component t-on expression captured in t-foreach
c6.push(vn7);
const otherState_8 = scope['otherState'];
const iter_8 = scope.iter;
p7.on['click'] = function (e) {if (!context.__owl__.isMounted){return}otherState_8.vals.push(iter_8+'_'+iter_8)};
p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_8.vals.push(iter_8+'_'+iter_8)};
c7.push({text: \`expr\`});
utils.getScope(scope, 'iter').iter = scope.iter+1;
}
@@ -630,7 +630,7 @@ exports[`other directives with t-component t-on expression in t-foreach 1`] = `
c6.push(vn9);
const otherState_10 = scope['otherState'];
const val_10 = scope['val'];
p9.on['click'] = function (e) {if (!context.__owl__.isMounted){return}otherState_10.vals.push(val_10)};
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_10.vals.push(val_10)};
c9.push({text: \`Expr\`});
}
scope = _origScope5;
@@ -684,7 +684,7 @@ exports[`other directives with t-component t-on expression in t-foreach with t-s
const otherState_10 = scope['otherState'];
const val_10 = scope['val'];
const bossa_10 = scope.bossa;
p9.on['click'] = function (e) {if (!context.__owl__.isMounted){return}otherState_10.vals.push(val_10+'_'+bossa_10)};
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_10.vals.push(val_10+'_'+bossa_10)};
c9.push({text: \`Expr\`});
}
scope = _origScope5;
@@ -734,7 +734,7 @@ exports[`other directives with t-component t-on method call in t-foreach 1`] = `
let vn9 = h('button', p9, c9);
c6.push(vn9);
let args10 = [scope['val']];
p9.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['addVal'](...args10, e);};
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['addVal'](...args10, e);};
c9.push({text: \`meth call\`});
}
scope = _origScope5;
@@ -770,7 +770,7 @@ exports[`other directives with t-component t-on with .capture modifier 1`] = `
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['capture'](e);}, true);}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['capture'](e);}, true);}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -812,7 +812,7 @@ exports[`other directives with t-component t-on with getter as handler 1`] = `
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['handler'](e);});}});});
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handler'](e);});}});});
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode);
w3.__owl__.pvnode = pvnode;
@@ -851,7 +851,7 @@ exports[`other directives with t-component t-on with handler bound to argument 1
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -890,7 +890,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -929,7 +929,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -968,7 +968,7 @@ exports[`other directives with t-component t-on with handler bound to object 1`]
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -1011,7 +1011,7 @@ exports[`other directives with t-component t-on with inline statement 1`] = `
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}state_5.counter++});}});});
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}state_5.counter++});}});});
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode);
w3.__owl__.pvnode = pvnode;
@@ -1049,7 +1049,7 @@ exports[`other directives with t-component t-on with no handler (only modifiers)
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -1087,7 +1087,7 @@ exports[`other directives with t-component t-on with prevent and self modifiers
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv'](e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}e.preventDefault();if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -1125,7 +1125,7 @@ exports[`other directives with t-component t-on with self and prevent modifiers
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}if (e.target !== vn.elm) {return}e.preventDefault();utils.getComponent(context)['onEv'](e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}if (e.target !== vn.elm) {return}e.preventDefault();utils.getComponent(context)['onEv'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -1163,7 +1163,7 @@ exports[`other directives with t-component t-on with self modifier 1`] = `
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (!context.__owl__.isMounted){return}if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv2'](e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (context.__owl__.status === 5){return}if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv2'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -1201,7 +1201,7 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (!context.__owl__.isMounted){return}e.stopPropagation();utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();utils.getComponent(context)['onEv2'](e);});vn.elm.addEventListener('ev-3', function (e) {if (!context.__owl__.isMounted){return}e.stopPropagation();e.preventDefault();utils.getComponent(context)['onEv3'](e);});}});});
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onEv2'](e);});vn.elm.addEventListener('ev-3', function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();e.preventDefault();utils.getComponent(context)['onEv3'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
@@ -1555,7 +1555,7 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')}
w6 = new W6(parent, props6);
parent.__owl__.cmap[k7] = w6.__owl__.id;
let fiber = w6.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args8, e);});}});});
let fiber = w6.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args8, e);});}});});
let pvnode = h('dummy', {key: k7, hook: {remove() {},destroy(vn) {w6.destroy();}}});
c1.push(pvnode);
w6.__owl__.pvnode = pvnode;
@@ -1580,7 +1580,7 @@ exports[`t-call handlers are properly bound through a t-call 1`] = `
let vn3 = h('p', p3, c3);
c2.push(vn3);
let k4 = \`click__4__\${key0}__\`;
extra.handlers[k4] = extra.handlers[k4] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['update'](e);};
extra.handlers[k4] = extra.handlers[k4] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['update'](e);};
p3.on['click'] = extra.handlers[k4];
c3.push({text: \`lucas\`});
}"
@@ -1599,7 +1599,7 @@ exports[`t-call handlers with arguments are properly bound through a t-call 1`]
let vn3 = h('p', p3, c3);
c2.push(vn3);
let args4 = [scope['a']];
p3.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['update'](...args4, e);};
p3.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['update'](...args4, e);};
c3.push({text: \`lucas\`});
}"
`;
@@ -229,7 +229,7 @@ exports[`t-slot directive dynamic t-slot call 1`] = `
let h = this.h;
let c10 = [], p10 = {key:10,on:{}};
let vn10 = h('button', p10, c10);
extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['toggle'](e);};
extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['toggle'](e);};
p10.on['click'] = extra.handlers['click__11__'];
const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + (scope['current'].slot)];
if (slot12) {
@@ -301,7 +301,7 @@ exports[`t-slot directive refs are properly bound in slots 1`] = `
let c9 = [], p9 = {key:9,on:{}};
let vn9 = h('button', p9, c9);
c8.push(vn9);
extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);};
p9.on['click'] = extra.handlers['click__10__'];
const ref11 = \`myButton\`;
p9.hook = {
@@ -326,7 +326,7 @@ exports[`t-slot directive slots are rendered with proper context 1`] = `
let c9 = [], p9 = {key:9,on:{}};
let vn9 = h('button', p9, c9);
c8.push(vn9);
extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);};
p9.on['click'] = extra.handlers['click__10__'];
c9.push({text: \`do something\`});
}"
+5 -5
View File
@@ -1,4 +1,4 @@
import { Component, Env } from "../../src/component/component";
import { Component, Env, STATUS } from "../../src/component/component";
import { useState } from "../../src/hooks";
import { xml } from "../../src/tags";
import { makeDeferred, makeTestEnv, makeTestFixture, nextMicroTick, nextTick } from "../helpers";
@@ -40,14 +40,14 @@ describe("async rendering", () => {
}
}
const w = new W();
expect(w.__owl__.status).toBe(STATUS.CREATED);
w.mount(fixture);
expect(w.__owl__.isDestroyed).toBe(false);
expect(w.__owl__.isMounted).toBe(false);
expect(w.__owl__.status).toBe(STATUS.WILLSTARTED);
w.destroy();
expect(w.__owl__.status).toBe(STATUS.DESTROYED);
def.resolve();
await nextTick();
expect(w.__owl__.isDestroyed).toBe(true);
expect(w.__owl__.isMounted).toBe(false);
expect(w.__owl__.status).toBe(STATUS.DESTROYED);
});
test("destroying/recreating a subwidget with different props (if start is not over)", async () => {
+105 -28
View File
@@ -1,4 +1,4 @@
import { Component, Env, mount } from "../../src/component/component";
import { Component, Env, mount, STATUS } from "../../src/component/component";
import { EventBus } from "../../src/core/event_bus";
import { useRef, useState } from "../../src/hooks";
import { QWeb } from "../../src/qweb/qweb";
@@ -163,7 +163,7 @@ describe("basic widget properties", () => {
expect(fixture.innerHTML).toBe("<div><span></span></div>");
});
test("cannot be clicked on and updated if not in DOM", async () => {
test("can be clicked on and updated if not in DOM", async () => {
class Counter extends Component {
static template = xml`
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
@@ -178,8 +178,8 @@ describe("basic widget properties", () => {
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
button.click();
await nextTick();
expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>");
expect(counter.state.counter).toBe(0);
expect(target.innerHTML).toBe("<div>1<button>Inc</button></div>");
expect(counter.state.counter).toBe(1);
});
test("widget style and classname", async () => {
@@ -661,9 +661,8 @@ describe("lifecycle hooks", () => {
class ChildWidget extends Component {
static template = xml`<div/>`;
constructor(parent) {
super(parent);
steps.push("init");
setup() {
steps.push("setup");
}
async willStart() {
steps.push("willstart");
@@ -687,10 +686,10 @@ describe("lifecycle hooks", () => {
const widget = new ParentWidget();
await widget.mount(fixture);
expect(steps).toEqual(["init", "willstart", "mounted"]);
expect(steps).toEqual(["setup", "willstart", "mounted"]);
widget.state.ok = false;
await nextTick();
expect(steps).toEqual(["init", "willstart", "mounted", "willunmount"]);
expect(steps).toEqual(["setup", "willstart", "mounted", "willunmount"]);
});
test("components are unmounted and destroyed if no longer in DOM, even after updateprops", async () => {
@@ -738,8 +737,7 @@ describe("lifecycle hooks", () => {
class ChildWidget extends Component {
static template = xml`<div/>`;
constructor(parent) {
super(parent);
setup() {
steps.push("c init");
}
async willStart() {
@@ -755,8 +753,7 @@ describe("lifecycle hooks", () => {
class ParentWidget extends Component {
static template = xml`<div><t t-component="child"/></div>`;
static components = { child: ChildWidget };
constructor(parent?) {
super(parent);
setup() {
steps.push("p init");
}
async willStart() {
@@ -962,6 +959,69 @@ describe("lifecycle hooks", () => {
"parent:patched",
]);
});
test("willPatch/patched hook is not called if not mounted in DOM", async () => {
const steps: string[] = [];
class ChildWidget extends Component {
static template = xml`<div/>`;
constructor(parent, props) {
super(parent, props);
steps.push("child:constructor");
}
mounted() {
steps.push("child:mounted");
}
willPatch() {
steps.push("child:willPatch");
}
patched() {
steps.push("child:patched");
}
}
class ParentWidget extends Component {
static template = xml`
<div>
<t t-component="child" v="state.n"/>
</div>
`;
static components = { child: ChildWidget };
state = useState({ n: 1 });
constructor() {
super();
steps.push("parent:constructor");
}
mounted() {
steps.push("parent:mounted");
}
willPatch() {
steps.push("parent:willPatch");
}
patched() {
steps.push("parent:patched");
}
}
const div = document.createElement("div");
const widget = new ParentWidget();
await widget.mount(div);
expect(steps).toEqual(["parent:constructor", "child:constructor"]);
widget.state.n = 2;
await nextTick();
expect(steps).toEqual(["parent:constructor", "child:constructor"]);
// then we remount the component in the dom
await widget.mount(fixture);
expect(steps).toEqual([
"parent:constructor",
"child:constructor",
"child:mounted",
"parent:mounted",
]);
});
});
describe("destroy method", () => {
@@ -975,8 +1035,7 @@ describe("destroy method", () => {
expect(document.contains(widget.el)).toBe(true);
widget.destroy();
expect(document.contains(widget.el)).toBe(false);
expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(true);
expect(widget.__owl__.status).toBe(STATUS.DESTROYED);
});
test("destroying a parent also destroys its children", async () => {
@@ -985,9 +1044,9 @@ describe("destroy method", () => {
const child = children(parent)[0];
expect(child.__owl__.isDestroyed).toBe(false);
expect(child.__owl__.status).toBe(STATUS.MOUNTED);
parent.destroy();
expect(child.__owl__.isDestroyed).toBe(true);
expect(child.__owl__.status).toBe(STATUS.DESTROYED);
});
test("destroy remove the parent/children link", async () => {
@@ -1013,17 +1072,15 @@ describe("destroy method", () => {
}
expect(fixture.innerHTML).toBe("");
const widget = new DelayedWidget();
expect(widget.__owl__.status).toBe(STATUS.CREATED);
widget.mount(fixture);
expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(false);
expect(widget.__owl__.status).toBe(STATUS.WILLSTARTED);
widget.destroy();
expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(true);
expect(widget.__owl__.status).toBe(STATUS.DESTROYED);
def.resolve();
await nextTick();
expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(true);
expect(widget.__owl__.status).toBe(STATUS.DESTROYED);
expect(widget.__owl__.vnode).toBe(undefined);
expect(fixture.innerHTML).toBe("");
expect(isRendered).toBe(false);
@@ -1479,7 +1536,7 @@ describe("composition", () => {
parent.state.flag = true;
await nextTick();
expect(children(parent)[0]).toBe(child);
expect(child.__owl__.isDestroyed).toBe(false);
expect(child.__owl__.status).toBe(STATUS.MOUNTED);
expect(normalize(fixture.innerHTML)).toBe(
normalize(`
<div>
@@ -2227,9 +2284,29 @@ describe("other directives with t-component", () => {
el.click();
expect(steps).toEqual(["click"]);
parent.unmount();
expect(child.__owl__.isMounted).toBe(false);
expect(child.__owl__.status).toBe(STATUS.UNMOUNTED);
el.click();
expect(steps).toEqual(["click"]);
expect(steps).toEqual(["click", "click"]);
});
test("triggering custom event on mounted components", async () => {
let value = false;
class Child extends Component {
static template = xml`<div/>`;
mounted() {
this.trigger("coucou");
}
}
class Parent extends Component {
static template = xml`<Child t-on-coucou="doSomething"/>`;
static components = { Child };
doSomething() {
value = true;
}
}
const parent = new Parent();
await parent.mount(fixture);
expect(value).toBe(true);
});
test("t-on with .capture modifier", async () => {
@@ -2281,7 +2358,7 @@ describe("other directives with t-component", () => {
expect(steps).toEqual(["click"]);
parent.state.flag = false;
await nextTick();
expect(child.__owl__.isDestroyed).toBe(true);
expect(child.__owl__.status).toBe(STATUS.DESTROYED);
el.click();
expect(steps).toEqual(["click"]);
});
@@ -2316,7 +2393,7 @@ describe("other directives with t-component", () => {
expect(steps).toEqual(["click"]);
parent.state.flag = false;
await nextTick();
expect(child.__owl__.isDestroyed).toBe(true);
expect(child.__owl__.status).toBe(STATUS.DESTROYED);
el.click();
expect(steps).toEqual(["click"]);
});
+2 -2
View File
@@ -1,4 +1,4 @@
import { Component, Env } from "../../src/component/component";
import { Component, Env, STATUS } from "../../src/component/component";
import { useState } from "../../src/hooks";
import { xml } from "../../src/tags";
import { makeTestEnv, makeTestFixture, nextTick } from "../helpers";
@@ -108,7 +108,7 @@ describe("component error handling (catchError)", () => {
expect(console.error).toBeCalledTimes(0);
console.error = consoleError;
expect(app.__owl__.isDestroyed).toBe(true);
expect(app.__owl__.status).toBe(STATUS.DESTROYED);
expect(handler).toBeCalledTimes(1);
});
+86 -4
View File
@@ -323,6 +323,54 @@ describe("unmounting and remounting", () => {
expect(steps).toEqual([2, 2, 3]);
});
test("change state and render while mounted in detached dom", async () => {
class App extends Component {
static template = xml`<div><t t-esc="state.val"/></div>`;
state = useState({ val: 1 });
}
const detachedDiv = document.createElement("div");
const app = await mount(App, { target: detachedDiv });
expect(detachedDiv.innerHTML).toBe("<div>1</div>");
app.state.val = 2;
await nextTick();
expect(detachedDiv.innerHTML).toBe("<div>2</div>");
});
test("change state and render while not mounted ", async () => {
class App extends Component {
static template = xml`<div><t t-esc="state.val"/></div>`;
state = useState({ val: 1 });
}
const app = new App(null);
app.state.val = 2; // will call the render method (before being mounted)
await nextTick();
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div>2</div>");
});
test("destroy and change state after mounted in detached dom", async () => {
class App extends Component {
static template = xml`<div><t t-esc="state.val"/></div>`;
state = useState({ val: 1 });
}
const detachedDiv = document.createElement("div");
const app = await mount(App, { target: detachedDiv });
expect(detachedDiv.innerHTML).toBe("<div>1</div>");
app.destroy();
app.state.val = 2;
await nextTick();
expect(detachedDiv.innerHTML).toBe("");
});
test("change state while component is unmounted", async () => {
let child;
class Child extends Component {
@@ -356,6 +404,40 @@ describe("unmounting and remounting", () => {
expect(fixture.innerHTML).toBe("<div>P2<span>C2</span></div>");
});
test("change state while component is mounted in a fragment", async () => {
class Child1 extends Component {
static template = xml`<span>C1</span>`;
}
class Child2 extends Component {
static template = xml`<span>C2</span>`;
}
class Parent extends Component {
static components = { Child1, Child2 };
static template = xml`
<div>
<Child1 t-if="child == 'c1'"/>
<Child2 t-if="child == 'c2'"/>
</div>`;
child: string | false = false;
}
const fragment = document.createDocumentFragment();
const parent = new Parent();
await parent.mount(fragment);
expect(parent.el.outerHTML).toBe("<div></div>");
parent.child = "c1";
parent.render();
await Promise.resolve();
parent.child = "c2";
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>C2</span></div>");
});
test("unmount component during a re-rendering", async () => {
const def = makeDeferred();
class Child extends Component {
@@ -442,17 +524,17 @@ describe("unmounting and remounting", () => {
// one full tick.
await nextMicroTick();
await nextMicroTick();
expect(steps).toEqual(["1 catch"]);
expect(steps).toEqual([]);
await nextTick();
expect(fixture.innerHTML).toBe("<div></div><span></span>");
def.resolve();
await nextTick();
expect(steps).toEqual(["1 catch", "2 resolved"]);
expect(steps).toEqual(["2 resolved"]);
expect(fixture.innerHTML).toBe("<div></div><span><div>Hey</div></span>");
});
test("widget can be mounted on same target, another situation", async () => {
test("component can be mounted on same target, another situation", async () => {
const def = makeDeferred();
const steps: string[] = [];
@@ -480,8 +562,8 @@ describe("unmounting and remounting", () => {
def.resolve();
await nextTick();
expect(steps).toEqual(["1 resolved", "2 resolved"]);
expect(fixture.innerHTML).toBe("<div>Hey</div>");
expect(steps).toEqual(["1 resolved", "2 resolved"]);
});
test("mounting a destroyed widget", async () => {
+2 -2
View File
@@ -1,4 +1,4 @@
import { Env, Component } from "../src/component/component";
import { Env, Component, STATUS } from "../src/component/component";
import { scheduler } from "../src/component/scheduler";
import { EvalContext, QWeb } from "../src/qweb/qweb";
import { CompilationContext } from "../src/qweb/compilation_context";
@@ -92,7 +92,7 @@ export function renderToDOM(
if (!context.__owl__) {
// we add `__owl__` to better simulate a component as context. This is
// particularly important for event handlers added with the `t-on` directive.
context.__owl__ = { isMounted: true };
context.__owl__ = { status: STATUS.MOUNTED };
}
const vnode = qweb.render(template, context, extra);
+26 -26
View File
@@ -2727,7 +2727,7 @@ exports[`t-on can bind event handler 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](e);};
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](e);};
p1.on['click'] = extra.handlers['click__2__'];
c1.push({text: \`Click\`});
return vn1;
@@ -2744,7 +2744,7 @@ exports[`t-on can bind handlers with arguments 1`] = `
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
let args2 = [5];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](...args2, e);};
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](...args2, e);};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2760,7 +2760,7 @@ exports[`t-on can bind handlers with empty object (with non empty inner string)
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
let args2 = [{}];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](...args2, e);};
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](...args2, e);};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2776,7 +2776,7 @@ exports[`t-on can bind handlers with empty object 1`] = `
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
let args2 = [{}];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](...args2, e);};
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](...args2, e);};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2815,7 +2815,7 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
let vn7 = h('a', p7, c7);
c6.push(vn7);
let args8 = [scope['action']];
p7.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['activate'](...args8, e);};
p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['activate'](...args8, e);};
c7.push({text: \`link\`});
}
scope = _origScope5;
@@ -2833,7 +2833,7 @@ exports[`t-on can bind handlers with object arguments 1`] = `
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
let args2 = [{val:5}];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](...args2, e);};
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](...args2, e);};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2847,9 +2847,9 @@ exports[`t-on can bind two event handlers 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['handleClick'](e);};
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handleClick'](e);};
p1.on['click'] = extra.handlers['click__2__'];
extra.handlers['dblclick__3__'] = extra.handlers['dblclick__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['handleDblClick'](e);};
extra.handlers['dblclick__3__'] = extra.handlers['dblclick__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handleDblClick'](e);};
p1.on['dblclick'] = extra.handlers['dblclick__3__'];
c1.push({text: \`Click\`});
return vn1;
@@ -2864,7 +2864,7 @@ exports[`t-on handler is bound to proper owner 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](e);};
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](e);};
p1.on['click'] = extra.handlers['click__2__'];
c1.push({text: \`Click\`});
return vn1;
@@ -2883,7 +2883,7 @@ exports[`t-on t-on combined with t-esc 1`] = `
let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onClick'](e);};
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
let _4 = scope['text'];
if (_4 != null) {
@@ -2905,7 +2905,7 @@ exports[`t-on t-on combined with t-raw 1`] = `
let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onClick'](e);};
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
let _4 = scope['html'];
if (_4 != null) {
@@ -2923,12 +2923,12 @@ exports[`t-on t-on with .capture modifier 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('div', p1, c1);
extra.handlers['!click__2__'] = extra.handlers['!click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onCapture'](e);};
extra.handlers['!click__2__'] = extra.handlers['!click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onCapture'](e);};
p1.on['!click'] = extra.handlers['!click__2__'];
let c3 = [], p3 = {key:3,on:{}};
let vn3 = h('button', p3, c3);
c1.push(vn3);
extra.handlers['click__4__'] = extra.handlers['click__4__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
extra.handlers['click__4__'] = extra.handlers['click__4__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);};
p3.on['click'] = extra.handlers['click__4__'];
c3.push({text: \`Button\`});
return vn1;
@@ -2946,7 +2946,7 @@ exports[`t-on t-on with empty handler (only modifiers) 1`] = `
let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2);
c1.push(vn2);
p2.on['click'] = function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();};
p2.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();};
c2.push({text: \`Button\`});
return vn1;
}"
@@ -2961,7 +2961,7 @@ exports[`t-on t-on with inline statement (function call) 1`] = `
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
const state_2 = scope['state'];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.incrementCounter(2)};
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.incrementCounter(2)};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2976,7 +2976,7 @@ exports[`t-on t-on with inline statement 1`] = `
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
const state_2 = scope['state'];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.counter++};
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.counter++};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2991,7 +2991,7 @@ exports[`t-on t-on with inline statement, part 2 1`] = `
let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1);
const state_2 = scope['state'];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.flag=!state_2.flag};
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.flag=!state_2.flag};
c1.push({text: \`Toggle\`});
return vn1;
}"
@@ -3007,7 +3007,7 @@ exports[`t-on t-on with inline statement, part 3 1`] = `
let vn1 = h('button', p1, c1);
const state_2 = scope['state'];
const someFunction_2 = scope['someFunction'];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.n=someFunction_2(3)};
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.n=someFunction_2(3)};
c1.push({text: \`Toggle\`});
return vn1;
}"
@@ -3024,7 +3024,7 @@ exports[`t-on t-on with prevent and self modifiers (order matters) 1`] = `
let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();if (e.target !== this.elm) {return}utils.getComponent(context)['onClick'](e);};
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();if (e.target !== this.elm) {return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
let c4 = [], p4 = {key:4};
let vn4 = h('span', p4, c4);
@@ -3045,19 +3045,19 @@ exports[`t-on t-on with prevent and/or stop modifiers 1`] = `
let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();utils.getComponent(context)['onClickPrevented'](e);};
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onClickPrevented'](e);};
p2.on['click'] = extra.handlers['click__3__'];
c2.push({text: \`Button 1\`});
let c4 = [], p4 = {key:4,on:{}};
let vn4 = h('button', p4, c4);
c1.push(vn4);
extra.handlers['click__5__'] = extra.handlers['click__5__'] || function (e) {if (!context.__owl__.isMounted){return}e.stopPropagation();utils.getComponent(context)['onClickStopped'](e);};
extra.handlers['click__5__'] = extra.handlers['click__5__'] || function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();utils.getComponent(context)['onClickStopped'](e);};
p4.on['click'] = extra.handlers['click__5__'];
c4.push({text: \`Button 2\`});
let c6 = [], p6 = {key:6,on:{}};
let vn6 = h('button', p6, c6);
c1.push(vn6);
extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();e.stopPropagation();utils.getComponent(context)['onClickPreventedAndStopped'](e);};
extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();e.stopPropagation();utils.getComponent(context)['onClickPreventedAndStopped'](e);};
p6.on['click'] = extra.handlers['click__7__'];
c6.push({text: \`Button 3\`});
return vn1;
@@ -3097,7 +3097,7 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = `
let vn7 = h('a', p7, c7);
c1.push(vn7);
let args8 = [scope['project'].id];
p7.on['click'] = function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();utils.getComponent(context)['onEdit'](...args8, e);};
p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onEdit'](...args8, e);};
c7.push({text: \` Edit \`});
let _9 = scope['project'].name;
if (_9 != null) {
@@ -3121,7 +3121,7 @@ exports[`t-on t-on with self and prevent modifiers (order matters) 1`] = `
let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}if (e.target !== this.elm) {return}e.preventDefault();utils.getComponent(context)['onClick'](e);};
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}if (e.target !== this.elm) {return}e.preventDefault();utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
let c4 = [], p4 = {key:4};
let vn4 = h('span', p4, c4);
@@ -3142,7 +3142,7 @@ exports[`t-on t-on with self modifier 1`] = `
let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onClick'](e);};
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
let c4 = [], p4 = {key:4};
let vn4 = h('span', p4, c4);
@@ -3151,7 +3151,7 @@ exports[`t-on t-on with self modifier 1`] = `
let c5 = [], p5 = {key:5,on:{}};
let vn5 = h('button', p5, c5);
c1.push(vn5);
extra.handlers['click__6__'] = extra.handlers['click__6__'] || function (e) {if (!context.__owl__.isMounted){return}if (e.target !== this.elm) {return}utils.getComponent(context)['onClickSelf'](e);};
extra.handlers['click__6__'] = extra.handlers['click__6__'] || function (e) {if (context.__owl__.status === 5){return}if (e.target !== this.elm) {return}utils.getComponent(context)['onClickSelf'](e);};
p5.on['click'] = extra.handlers['click__6__'];
let c7 = [], p7 = {key:7};
let vn7 = h('span', p7, c7);
+4
View File
@@ -196,4 +196,8 @@ describe("expression evaluation", () => {
expect(compileExpr("f(...state.list)", {})).toBe("scope['f'](...scope['state'].list)");
expect(compileExpr("f([...list])", {})).toBe("scope['f']([...scope['list']])");
});
test("works with builtin properties", () => {
expect(compileExpr("state.constructor.name", {})).toBe("scope['state'].constructor.name");
});
});
+1 -1
View File
@@ -11,7 +11,7 @@ exports[`Link component can render simple cases 1`] = `
let _6 = scope['href'];
let c7 = [], p7 = {key:7,attrs:{href: _6},class:_5,on:{}};
let vn7 = h('a', p7, c7);
extra.handlers['click__8__'] = extra.handlers['click__8__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['navigate'](e);};
extra.handlers['click__8__'] = extra.handlers['click__8__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['navigate'](e);};
p7.on['click'] = extra.handlers['click__8__'];
const slot9 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
if (slot9) {
+6 -2
View File
@@ -8,7 +8,7 @@ import * as owl from "../../src/index";
import { Component, Env } from "../../src/component/component";
import { xml } from "../../src/tags";
import { makeTestFixture, makeTestEnv } from "../helpers";
import { makeTestFixture, makeTestEnv, nextTick } from "../helpers";
let fixture: HTMLElement = makeTestFixture();
let env: Env = makeTestEnv();
@@ -31,6 +31,7 @@ test("log a specific message for render method calls if component is not mounted
parent.unmount();
parent.state.value = 2;
await nextTick();
expect(steps).toEqual([
"[OWL_DEBUG] Parent<id=1> constructor, props={}",
"[OWL_DEBUG] Parent<id=1> mount",
@@ -40,7 +41,10 @@ test("log a specific message for render method calls if component is not mounted
"[OWL_DEBUG] Parent<id=1> mounted",
"[OWL_DEBUG] scheduler: stop running tasks queue",
"[OWL_DEBUG] Parent<id=1> willUnmount",
"[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted, this render has no effect)",
"[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted)",
"[OWL_DEBUG] scheduler: start running tasks queue",
"[OWL_DEBUG] Parent<id=1> rendering template",
"[OWL_DEBUG] scheduler: stop running tasks queue",
]);
console.log = log;
});
+2 -2
View File
@@ -101,8 +101,8 @@
component.render = function(...args) {
const __owl__ = component.__owl__;
let msg = `render`;
if (!__owl__.isMounted && !__owl__.currentFiber) {
msg += ` (warning: component is not mounted, this render has no effect)`;
if (__owl__.status !== 3 /* mounted */ && !__owl__.currentFiber) {
msg += ` (warning: component is not mounted)`;
}
log(msg);
return render(...args);
+22 -41
View File
@@ -2,8 +2,7 @@ const COMPONENTS = `// In this example, we show how components can be defined an
const { Component, useState, mount } = owl;
class Greeter extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ word: 'Hello' });
}
@@ -14,10 +13,9 @@ class Greeter extends Component {
// Main root component
class App extends Component {
constructor() {
super(...arguments);
this.state = useState({ name: 'World'});
}
setup() {
this.state = useState({ name: 'World'});
}
}
App.components = { Greeter };
@@ -52,8 +50,7 @@ const ANIMATION = `// The goal of this component is to see how the t-transition
const { Component, useState, mount } = owl;
class Counter extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ value: 0 });
}
@@ -63,8 +60,7 @@ class Counter extends Component {
}
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ flag: false, componentFlag: false, numbers: [] });
}
@@ -194,10 +190,9 @@ const LIFECYCLE_DEMO = `// This example shows all the possible lifecycle hooks
const { Component, useState, mount } = owl;
class DemoComponent extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ n: 0 });
console.log("constructor");
console.log("setup");
}
async willStart() {
console.log("willstart");
@@ -223,8 +218,7 @@ class DemoComponent extends Component {
}
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ n: 0, flag: true });
}
@@ -295,8 +289,7 @@ function useMouse() {
// Main root component
class App extends owl.Component {
constructor() {
super(...arguments);
setup() {
// simple state hook (reactive object)
this.counter = useState({ value: 0 });
@@ -333,8 +326,7 @@ const { Component, Context, mount } = owl;
const { useContext } = owl.hooks;
class ToolbarButton extends Component {
constructor() {
super(...arguments);
setup() {
this.theme = useContext(this.env.themeContext);
}
@@ -468,8 +460,7 @@ const actions = {
// TodoItem
//------------------------------------------------------------------------------
class TodoItem extends Component {
constructor() {
super(...arguments);
setup() {
useAutofocus("input");
this.state = useState({ isEditing: false });
this.dispatch = useDispatch();
@@ -499,8 +490,7 @@ class TodoItem extends Component {
// TodoApp
//------------------------------------------------------------------------------
class TodoApp extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ filter: "all" });
this.todos = useStore(state => state.todos);
this.dispatch = useDispatch();
@@ -1026,8 +1016,7 @@ class FormView extends owl.Component {}
FormView.components = { AdvancedComponent };
class Chatter extends owl.Component {
constructor() {
super(...arguments);
setup() {
this.messages = Array.from(Array(100).keys());
}
}
@@ -1170,8 +1159,7 @@ const SLOTS = `// We show here how slots can be used to create generic component
const { Component, useState, mount } = owl;
class Card extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ showContent: true });
}
@@ -1181,8 +1169,7 @@ class Card extends Component {
}
class Counter extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({val: 1});
}
@@ -1193,8 +1180,7 @@ class Counter extends Component {
// Main root component
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({a: 1, b: 3});
}
@@ -1306,8 +1292,7 @@ class SlowComponent extends Component {
class NotificationList extends Component {}
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ value: 0, notifs: [] });
}
@@ -1381,8 +1366,7 @@ const FORM = `// This example illustrate how the t-model directive can be used t
const { Component, useState, mount } = owl;
class Form extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({
text: "",
othertext: "",
@@ -1556,8 +1540,7 @@ const { useRef } = owl.hooks;
class HelloWorld extends Component {}
class Counter extends Component {
constructor() {
super(...arguments);
setup() {
this.state = useState({ value: 0 });
}
@@ -1610,8 +1593,7 @@ class Window extends Component {
}
class WindowManager extends Component {
constructor() {
super(...arguments);
setup() {
this.windows = [];
this.nextId = 1;
this.currentZindex = 1;
@@ -1661,8 +1643,7 @@ class WindowManager extends Component {
WindowManager.components = { Window };
class App extends Component {
constructor() {
super(...arguments);
setup() {
this.wmRef = useRef("wm");
}