mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 609c108607 | |||
| a56939f13a | |||
| 5ff501f8b8 | |||
| 60917bb102 | |||
| b201ef8162 | |||
| 9cacc78ad7 | |||
| 0d933b8570 | |||
| 72f6daa695 | |||
| 567d78a9ac | |||
| f013c57050 | |||
| b9fae87273 | |||
| 1f0457f69b | |||
| c481a73a76 | |||
| e64e415b3b | |||
| aa406b53e2 | |||
| 57027bd0fa | |||
| 671c662fee | |||
| 0ae70b1d63 | |||
| 2701a7d861 | |||
| 3ebe985c3c | |||
| 1c0d4b5832 | |||
| 2840794fa2 | |||
| 710f42d4e4 | |||
| 45a2b0122d | |||
| 4c41f62364 | |||
| c2ab9774fb | |||
| 22e48e3be0 | |||
| 1ff3c32fe4 | |||
| 9a3e1ed99e | |||
| 438e2fbc2e | |||
| b3db085745 | |||
| b948c59c6d | |||
| 6426fe98f9 | |||
| 4dd9de349e | |||
| e1bcea77b5 | |||
| 2a19aeb6a3 | |||
| b53a34b7d1 | |||
| f881640d3d | |||
| 54c3dd76e4 | |||
| 7b887447da | |||
| 438b21df3f | |||
| d1094f647a | |||
| 49baf0de6e | |||
| b14d069ee7 | |||
| e14a4fe338 | |||
| 1f9ec46236 | |||
| 1464a3631b |
+1
-1
@@ -23,4 +23,4 @@ package-lock.json
|
|||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# Extras temp file
|
# Extras temp file
|
||||||
/extras/owl.js
|
/tools/owl.js
|
||||||
@@ -10,7 +10,7 @@ other Odoo related projects. OWL's main feature is a _declarative component syst
|
|||||||
|
|
||||||
**Try it online!** An online playground is available at [https://odoo.github.io/owl/playground](https://odoo.github.io/owl/playground) to let you experiment with the OWL framework.
|
**Try it online!** An online playground is available at [https://odoo.github.io/owl/playground](https://odoo.github.io/owl/playground) to let you experiment with the OWL framework.
|
||||||
|
|
||||||
## OWL's design principles
|
## OWL's Design Principles
|
||||||
|
|
||||||
OWL is designed to be used in highly dynamic applications where changing
|
OWL is designed to be used in highly dynamic applications where changing
|
||||||
requirements are common, code needs to be maintained by large teams.
|
requirements are common, code needs to be maintained by large teams.
|
||||||
@@ -30,12 +30,20 @@ Owl is not designed to be fast or small (even though it is quite good on those
|
|||||||
two topics). If you are interested in a comparison with React or Vue, you will
|
two topics). If you are interested in a comparison with React or Vue, you will
|
||||||
find some more information [here](doc/comparison.md).
|
find some more information [here](doc/comparison.md).
|
||||||
|
|
||||||
# Example
|
## Example
|
||||||
|
|
||||||
Here is a short example to illustrate interactive widgets:
|
Here is a short example to illustrate interactive widgets:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<templates>
|
||||||
|
<button t-name="Counter" t-on-click="increment">
|
||||||
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
|
</button>
|
||||||
|
</templates>
|
||||||
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class ClickCounter extends owl.Component {
|
class Counter extends owl.Component {
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
@@ -43,36 +51,33 @@ class ClickCounter extends owl.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TEMPLATES = `
|
|
||||||
<button t-name="ClickCounter" t-on-click="increment">
|
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
|
||||||
</button>`;
|
|
||||||
|
|
||||||
const qweb = new owl.QWeb(TEMPLATES);
|
const qweb = new owl.QWeb(TEMPLATES);
|
||||||
const counter = new ClickCounter({ qweb });
|
const counter = new Counter({ qweb });
|
||||||
counter.mount(document.body);
|
counter.mount(document.body);
|
||||||
```
|
```
|
||||||
|
|
||||||
More interesting examples can be found on the [playground](https://odoo.github.io/owl/playground) application.
|
Note that we assume here that the xml templates are available in the `TEMPLATES`
|
||||||
|
string. More interesting examples can be found on the
|
||||||
|
[playground](https://odoo.github.io/owl/playground) application.
|
||||||
|
|
||||||
## Installing/Building
|
## Installing/Building
|
||||||
|
|
||||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||||
|
|
||||||
- [owl-0.13.0.js](https://github.com/odoo/owl/releases/download/v0.13.0/owl.js)
|
- [owl-0.15.0.js](https://github.com/odoo/owl/releases/download/v0.15.0/owl.js)
|
||||||
- [owl-0.13.0.min.js](https://github.com/odoo/owl/releases/download/v0.13.0/owl.min.js)
|
- [owl-0.15.0.min.js](https://github.com/odoo/owl/releases/download/v0.15.0/owl.min.js)
|
||||||
|
|
||||||
Some npm scripts are available:
|
Some npm scripts are available:
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| ---------------------- | ------------------------------------------------------------------------------- |
|
| --------------------- | ---------------------------------------------------------------------------- |
|
||||||
| `npm install` | install every dependency required for this project |
|
| `npm install` | install every dependency required for this project |
|
||||||
| `npm run build` | build a bundle of _owl_ in the _/dist/_ folder |
|
| `npm run build` | build a bundle of _owl_ in the _/dist/_ folder |
|
||||||
| `npm run minify` | minify the prebuilt owl.js file |
|
| `npm run minify` | minify the prebuilt owl.js file |
|
||||||
| `npm run test` | run all tests |
|
| `npm run test` | run all tests |
|
||||||
| `npm run test:watch` | run all tests, and keep a watcher |
|
| `npm run test:watch` | run all tests, and keep a watcher |
|
||||||
| `npm run extras` | build extras applications, start a static server (see [here](extras/readme.md)) |
|
| `npm run tools` | build tools applications, start a static server (see [here](doc/tooling.md)) |
|
||||||
| `npm run extras:watch` | same as `extras`, but with a watcher to rebuild owl |
|
| `npm run tools:watch` | same as `tools`, but with a watcher to rebuild owl |
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -38,7 +38,8 @@ keyboard) the button.
|
|||||||
A more complex situation occurs when we want to transition an element in or out
|
A more complex situation occurs when we want to transition an element in or out
|
||||||
of the page. For example, we may want a fade-in and fade-out effect.
|
of the page. For example, we may want a fade-in and fade-out effect.
|
||||||
|
|
||||||
The `t-transition` directive is here to help us (see [QWeb documentation](qweb.md#t-transition-directive)).
|
The `t-transition` directive is here to help us. It works on html elements and
|
||||||
|
on components, by adding and removing some css classes.
|
||||||
|
|
||||||
To perform useful transition effects, whenever an element appears or disappears,
|
To perform useful transition effects, whenever an element appears or disappears,
|
||||||
it is necessary to add/remove some css style or class at some precise moment in
|
it is necessary to add/remove some css style or class at some precise moment in
|
||||||
|
|||||||
+8
-6
@@ -7,6 +7,8 @@ In this page, we try to highlight some of these differences. Obviously, some
|
|||||||
effort was done to be fair. However, if you disagree with some of the points
|
effort was done to be fair. However, if you disagree with some of the points
|
||||||
discussed, feel free to open an issue/submit a PR to correct this text.
|
discussed, feel free to open an issue/submit a PR to correct this text.
|
||||||
|
|
||||||
|
## Content
|
||||||
|
|
||||||
- [Size](#size)
|
- [Size](#size)
|
||||||
- [Tooling/Build Step](#toolingbuild-step)
|
- [Tooling/Build Step](#toolingbuild-step)
|
||||||
- [Templating](#templating)
|
- [Templating](#templating)
|
||||||
@@ -14,7 +16,7 @@ discussed, feel free to open an issue/submit a PR to correct this text.
|
|||||||
- [Reactiveness](#reactiveness)
|
- [Reactiveness](#reactiveness)
|
||||||
- [State Management](#state-management)
|
- [State Management](#state-management)
|
||||||
|
|
||||||
### Size
|
## Size
|
||||||
|
|
||||||
OWL is intended to be small and to work at a slightly lower level of abstraction
|
OWL is intended to be small and to work at a slightly lower level of abstraction
|
||||||
than React and Vue. Also, jQuery is not the same kind of framework, but it is interesting to compare.
|
than React and Vue. Also, jQuery is not the same kind of framework, but it is interesting to compare.
|
||||||
@@ -26,7 +28,7 @@ than React and Vue. Also, jQuery is not the same kind of framework, but it is in
|
|||||||
| React + ReactDOM + Redux | | 40kb |
|
| React + ReactDOM + Redux | | 40kb |
|
||||||
| jQuery | 86kb | 30kb |
|
| jQuery | 86kb | 30kb |
|
||||||
|
|
||||||
### Tooling/Build step
|
## Tooling/Build step
|
||||||
|
|
||||||
OWL is designed to be easy to use in a standalone way. For various reasons,
|
OWL is designed to be easy to use in a standalone way. For various reasons,
|
||||||
Odoo does not want to rely on standard web tools (such as webpack), and OWL can
|
Odoo does not want to rely on standard web tools (such as webpack), and OWL can
|
||||||
@@ -84,7 +86,7 @@ This has the advantage of having the full power of Javascript, but is less
|
|||||||
structured than a template language. Note that the tooling is quite impressive:
|
structured than a template language. Note that the tooling is quite impressive:
|
||||||
there is a syntax highlighter for jsx here on github!
|
there is a syntax highlighter for jsx here on github!
|
||||||
|
|
||||||
### Asynchronous rendering
|
## Asynchronous Rendering
|
||||||
|
|
||||||
This is actually a big difference between OWL and React/Vue: components in OWL
|
This is actually a big difference between OWL and React/Vue: components in OWL
|
||||||
are totally asynchronous. They have two asynchronous hooks in their lifecycle:
|
are totally asynchronous. They have two asynchronous hooks in their lifecycle:
|
||||||
@@ -115,7 +117,7 @@ extremely powerful as well, as demonstrated by the Odoo Web Client.
|
|||||||
Lazy loading static libraries can obviously be done with React/Vue, but it is
|
Lazy loading static libraries can obviously be done with React/Vue, but it is
|
||||||
more convoluted.
|
more convoluted.
|
||||||
|
|
||||||
### Reactiveness
|
## Reactiveness
|
||||||
|
|
||||||
React has a simple model: whenever the state changes, it is
|
React has a simple model: whenever the state changes, it is
|
||||||
replaced with a new state (via the setState method). Then, the DOM is patched.
|
replaced with a new state (via the setState method). Then, the DOM is patched.
|
||||||
@@ -129,7 +131,7 @@ Owl is closer to vue: it also tracks magically the state properties, but it does
|
|||||||
only increment a counter whenever it changes (and a _deep_ counter for each of
|
only increment a counter whenever it changes (and a _deep_ counter for each of
|
||||||
its parents). This assumes that the state is actually a tree.
|
its parents). This assumes that the state is actually a tree.
|
||||||
|
|
||||||
### State Management
|
## State Management
|
||||||
|
|
||||||
Managing the state of an application is a tricky issue. Many solutions have
|
Managing the state of an application is a tricky issue. Many solutions have
|
||||||
been proposed these last few years. It also depends on the kind of application we
|
been proposed these last few years. It also depends on the kind of application we
|
||||||
@@ -190,5 +192,5 @@ keeps track of who get data, and retrigger a render when it was changed.
|
|||||||
|
|
||||||
Owl store is a little bit like a mix of redux and vuex: it has mutations and
|
Owl store is a little bit like a mix of redux and vuex: it has mutations and
|
||||||
actions, like VueX, it keeps track of the state changes, but it does not notify
|
actions, like VueX, it keeps track of the state changes, but it does not notify
|
||||||
a component when the state changes. Instead, components need to connect to the
|
a component when the state changes. Instead, components need to connect to the
|
||||||
store like in redux, with a function that will listen to the relevant state.
|
store like in redux, with a function that will listen to the relevant state.
|
||||||
|
|||||||
+226
-35
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Example](#example)
|
- [Example](#example)
|
||||||
|
- [Root Widget And Environment](#root-widget-and-environment)
|
||||||
- [Reference](#reference)
|
- [Reference](#reference)
|
||||||
- [Properties](#properties)
|
- [Properties](#properties)
|
||||||
- [Static Properties](#static-properties)
|
- [Static Properties](#static-properties)
|
||||||
@@ -11,11 +12,14 @@
|
|||||||
- [Lifecycle](#lifecycle)
|
- [Lifecycle](#lifecycle)
|
||||||
- [Composition](#composition)
|
- [Composition](#composition)
|
||||||
- [Event Handling](#event-handling)
|
- [Event Handling](#event-handling)
|
||||||
- [`t-key` directive](#t-key-directive)
|
- [Form Input Bindings](#form-input-bindings)
|
||||||
- [`t-mounted` directive](#t-mounted-directive)
|
- [`t-key` Directive](#t-key-directive)
|
||||||
|
- [`t-mounted` Directive](#t-mounted-directive)
|
||||||
|
- [Semantics](#semantics)
|
||||||
- [Props Validation](#props-validation)
|
- [Props Validation](#props-validation)
|
||||||
- [Keeping References](#keeping-references])
|
- [References](#references)
|
||||||
- [Asynchronous rendering](#asynchronous-rendering)
|
- [Slots](#slots)
|
||||||
|
- [Asynchronous Rendering](#asynchronous-rendering)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -72,6 +76,30 @@ a state object is defined. It is not mandatory to use the state object, but it
|
|||||||
is certainly encouraged. The state object is [observed](observer.md), and any
|
is certainly encouraged. The state object is [observed](observer.md), and any
|
||||||
change to it will cause a rerendering.
|
change to it will cause a rerendering.
|
||||||
|
|
||||||
|
## Root Widget And Environment
|
||||||
|
|
||||||
|
Most of the time, Owl widget will be created automatically by the `t-widget`
|
||||||
|
directive in a template. There is however an obvious exception: the root widget
|
||||||
|
of an Owl application has to be created manually:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class App extends owl.Component { ... }
|
||||||
|
|
||||||
|
const qweb = new owl.QWeb(TEMPLATES);
|
||||||
|
const env = { qweb: qweb };
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
```
|
||||||
|
|
||||||
|
The root widget needs an environment. In Owl, an environment is an object with
|
||||||
|
a `qweb` key, which has to be a [QWeb](qweb.md) instance. This qweb instance will
|
||||||
|
be used to render everything.
|
||||||
|
|
||||||
|
The environment will be given to each child, unchanged, in the `env` property.
|
||||||
|
This can be very useful to share common information/methods. For example, all
|
||||||
|
rpcs can be made through a `rpc` method in the environment. This makes it very
|
||||||
|
easy to test a component.
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
An Owl component is a small class which represent a widget or some UI element.
|
An Owl component is a small class which represent a widget or some UI element.
|
||||||
@@ -146,11 +174,6 @@ find a template with the component name (or one of its ancestor).
|
|||||||
if we have a `isMobile` key in the environment, to decide if we want a mobile
|
if we have a `isMobile` key in the environment, to decide if we want a mobile
|
||||||
interface or a destkop one.
|
interface or a destkop one.
|
||||||
|
|
||||||
- **`set(target, key, value)`**. This method is necessary in some cases when we
|
|
||||||
need to modify the state of the component in a way that is not visible to the
|
|
||||||
observer (see [observer's technical limitations](observer.md#technical-limitations)).
|
|
||||||
For example, if we need to add a key to the state.
|
|
||||||
|
|
||||||
- **`destroy()`**. As its name suggests, this method will remove the component,
|
- **`destroy()`**. As its name suggests, this method will remove the component,
|
||||||
and perform all necessary cleanup, such as unmounting the component, its children,
|
and perform all necessary cleanup, such as unmounting the component, its children,
|
||||||
removing the parent/children relationship. This method should almost never be
|
removing the parent/children relationship. This method should almost never be
|
||||||
@@ -165,13 +188,13 @@ a owl component:
|
|||||||
|
|
||||||
| Method | Description |
|
| Method | Description |
|
||||||
| ------------------------------------------------ | ----------------------------------------------------- |
|
| ------------------------------------------------ | ----------------------------------------------------- |
|
||||||
| **[constructor](#constructor)** | constructor |
|
| **[constructor](#constructorparent-props)** | constructor |
|
||||||
| **[willStart](#willStart)** | async, before first rendering |
|
| **[willStart](#willstart)** | async, before first rendering |
|
||||||
| **[mounted](#mounted)** | just after component is rendered and added to the DOM |
|
| **[mounted](#mounted)** | just after component is rendered and added to the DOM |
|
||||||
| **[willUpdateProps](#willupdatepropsnextprops)** | async, before props update |
|
| **[willUpdateProps](#willupdatepropsnextprops)** | async, before props update |
|
||||||
| **[willPatch](#willpatch)** | just before the DOM is patched |
|
| **[willPatch](#willpatch)** | just before the DOM is patched |
|
||||||
| **[patched](#patchedsnapshot)** | just after the DOM is patched |
|
| **[patched](#patchedsnapshot)** | just after the DOM is patched |
|
||||||
| **[willUnmount](#willUnmount)** | just before removing component from DOM |
|
| **[willUnmount](#willunmount)** | just before removing component from DOM |
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
@@ -312,7 +335,7 @@ the DOM. This is a good place to remove some listeners, for example.
|
|||||||
|
|
||||||
This is the opposite method of `mounted`.
|
This is the opposite method of `mounted`.
|
||||||
|
|
||||||
## Composition
|
### Composition
|
||||||
|
|
||||||
The example above shows a QWeb template with a `t-on-click` directive. Widget
|
The example above shows a QWeb template with a `t-on-click` directive. Widget
|
||||||
templates are standard [QWeb](qweb.md) templates, but with an extra directive:
|
templates are standard [QWeb](qweb.md) templates, but with an extra directive:
|
||||||
@@ -375,11 +398,11 @@ Note that there are some restrictions on prop names: `class`, `style` and any
|
|||||||
string which starts with `t-` are not allowed.
|
string which starts with `t-` are not allowed.
|
||||||
|
|
||||||
The `t-widget` directive also accepts dynamic values with string interpolation
|
The `t-widget` directive also accepts dynamic values with string interpolation
|
||||||
(like the [`t-attf-`](#dynamic-attributes-t-att-and-t-attf-directives) directive):
|
(like the [`t-attf-`](qweb.md#dynamic-attributes) directive):
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="ParentWidget">
|
<div t-name="ParentWidget">
|
||||||
<t t-widget="ChildWidget#{id}"/>
|
<t t-widget="ChildWidget{{id}}"/>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -390,14 +413,6 @@ class ParentWidget {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Similarly to `t-attf-`, there is an alternate form of string interpolation:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<div t-name="ParentWidget">
|
|
||||||
<t t-widget="ChildWidget{{id}}"/>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**CSS and style:** there is some specific support to allow the parent to declare
|
**CSS and style:** there is some specific support to allow the parent to declare
|
||||||
additional css classes or style for the sub widget: css declared in `class`, `style`, `t-att-class` or `t-att-style` will be added to the
|
additional css classes or style for the sub widget: css declared in `class`, `style`, `t-att-class` or `t-att-style` will be added to the
|
||||||
root widget element.
|
root widget element.
|
||||||
@@ -419,7 +434,7 @@ with a class object:
|
|||||||
<t t-widget="MyWidget" t-att-class="{a: state.flagA, b: state.flagB}" />
|
<t t-widget="MyWidget" t-att-class="{a: state.flagA, b: state.flagB}" />
|
||||||
```
|
```
|
||||||
|
|
||||||
### Event handling
|
### Event Handling
|
||||||
|
|
||||||
In a component's template, it is useful to be able to register handlers on some
|
In a component's template, it is useful to be able to register handlers on some
|
||||||
elements to some specific events. This is what makes a template _alive_. There
|
elements to some specific events. This is what makes a template _alive_. There
|
||||||
@@ -505,7 +520,99 @@ The `t-on` directive also allows to prebind some arguments. For example,
|
|||||||
Here, `expr` is a valid Owl expression, so it could be `true` or some variable
|
Here, `expr` is a valid Owl expression, so it could be `true` or some variable
|
||||||
from the rendering context.
|
from the rendering context.
|
||||||
|
|
||||||
### `t-key` directive
|
### Form Input Bindings
|
||||||
|
|
||||||
|
It is very common to need to be able to read the value out of an html `input` (or
|
||||||
|
`textarea`, or `select`) in order to use it (note: it does not need to be in a
|
||||||
|
form!). A possible way to do this is to do it by hand:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Form extends owl.Component {
|
||||||
|
state = { text: "" };
|
||||||
|
|
||||||
|
_updateInputValue(event) {
|
||||||
|
this.state.text = event.target.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<input t-on-input="_updateInputValue"/>
|
||||||
|
<span t-esc="state.text"/>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
This works. However, this requires a little bit of _plumbing_ code. Also, the
|
||||||
|
plumbing code is slightly different if you need to interact with a checkbox,
|
||||||
|
or with radio buttons, or with select tags.
|
||||||
|
|
||||||
|
To help with this situation, Owl has a builtin directive `t-model`: its value
|
||||||
|
is the (top-level) name in the state object. With the `t-model` directive, we
|
||||||
|
can write a shorter code, equivalent to the previous example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Form extends owl.Component {
|
||||||
|
state = { text: "" };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<input t-model="text"/>
|
||||||
|
<span t-esc="state.text"/>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `t-model` directive works with `<input>`, `<input type="checkbox">`,
|
||||||
|
`<input type="radio">`, `<textarea>` and `<select>`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<div>Text in an input: <input t-model="someVal"/></div>
|
||||||
|
<div>Textarea: <textarea t-model="otherVal"/></div>
|
||||||
|
<div>Boolean value: <input type="checkbox" t-model="someFlag"/></div>
|
||||||
|
<div>Selection:
|
||||||
|
<select t-model="color">
|
||||||
|
<option value="">Select a color</option>
|
||||||
|
<option value="red">Red</option>
|
||||||
|
<option value="blue">Blue</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Selection with radio buttons:
|
||||||
|
<span>
|
||||||
|
<input type="radio" name="color" id="red" value="red" t-model="color"/>
|
||||||
|
<label for="red">Red</label>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<input type="radio" name="color" id="blue" value="blue" t-model="color"/>
|
||||||
|
<label for="blue">Blue</label>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Like event handling, the `t-model` directive accepts some modifiers:
|
||||||
|
|
||||||
|
| Modifier | Description |
|
||||||
|
| --------- | -------------------------------------------------------------------- |
|
||||||
|
| `.lazy` | update the value on the `change` event (default is on `input` event) |
|
||||||
|
| `.number` | tries to parse the value to a number (using `parseFloat`) |
|
||||||
|
| `.trim` | trim the resulting value |
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<input t-model.lazy="someVal"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
These modifiers can be combined. For instance, `t-model.lazy.number` will only
|
||||||
|
update a number whenever the change is done.
|
||||||
|
|
||||||
|
Note: the online playground has an example to show how it works.
|
||||||
|
|
||||||
|
### `t-key` Directive
|
||||||
|
|
||||||
Even though Owl tries to be as declarative as possible, some DOM state is still
|
Even though Owl tries to be as declarative as possible, some DOM state is still
|
||||||
locked inside the DOM: for example, the scrolling state, the current user selection,
|
locked inside the DOM: for example, the scrolling state, the current user selection,
|
||||||
@@ -529,7 +636,7 @@ 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
|
### `t-mounted` Directive
|
||||||
|
|
||||||
The `t-mounted` directive allows to register a callback to execute whenever the node
|
The `t-mounted` directive allows to register a callback to execute whenever the node
|
||||||
is inserted into the DOM.
|
is inserted into the DOM.
|
||||||
@@ -721,7 +828,7 @@ Examples:
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Keeping references
|
### References
|
||||||
|
|
||||||
The `t-ref` directive helps a component keep reference to some inside part of it.
|
The `t-ref` directive helps a component keep reference to some inside part of it.
|
||||||
Like the `t-on` directive, it can work either on a DOM node, or on a component:
|
Like the `t-on` directive, it can work either on a DOM node, or on a component:
|
||||||
@@ -749,26 +856,94 @@ Note: if used on a component, the reference will be set in the `refs`
|
|||||||
variable between `willPatch` and `patched`.
|
variable between `willPatch` and `patched`.
|
||||||
|
|
||||||
The `t-ref` directive also accepts dynamic values with string interpolation
|
The `t-ref` directive also accepts dynamic values with string interpolation
|
||||||
(like the [`t-attf-`](#dynamic-attributes-t-att-and-t-attf-directives) and
|
(like the [`t-attf-`](qweb.md#dynamic-attributes) and
|
||||||
[`t-widget-`](#component-t-widget) directives). For example, if we have
|
`t-widget` directives). For example, if we have
|
||||||
`id` set to 44 in the rendering context,
|
`id` set to 44 in the rendering context,
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-ref="widget_#{id}"/>
|
<div t-ref="widget_{{id}}"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
this.refs.widget_44;
|
this.refs.widget_44;
|
||||||
```
|
```
|
||||||
|
|
||||||
Similarly to `t-attf-` and `t-widget`, there is an alternate form of string
|
### Slots
|
||||||
interpolation:
|
|
||||||
|
To make generic components, it is useful to be able for a parent widget to _inject_
|
||||||
|
some sub template, but still be the owner. For example, a generic dialog widget
|
||||||
|
will need to render some content, some footer, but with the parent as the
|
||||||
|
rendering context.
|
||||||
|
|
||||||
|
This is what _slots_ are for.
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-ref="widget_{{id}}"/>
|
<div t-name="Dialog" class="modal">
|
||||||
|
<div class="modal-title"><t t-esc="props.title"/></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<t t-slot="content"/>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<t t-slot="footer"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Asynchronous rendering
|
Slots are defined by the caller, with the `t-set` directive:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-name="SomeWidget">
|
||||||
|
<div>some widget</div>
|
||||||
|
<t t-widget="Dialog" title="Some Dialog">
|
||||||
|
<t t-set="content">
|
||||||
|
<div>hey</div>
|
||||||
|
</t>
|
||||||
|
<t t-set="footer">
|
||||||
|
<button t-on-click="doSomething">ok</button>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, the widget `Dialog` will render the slots `content` and `footer`
|
||||||
|
with its parent as rendering context. This means that clicking on the button
|
||||||
|
will execute the `doSomething` method on the parent, not on the dialog.
|
||||||
|
|
||||||
|
Warning! Slots have a technical constraint: the result of the slot rendering
|
||||||
|
should have exactly one root node. So,
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<t t-set="content">
|
||||||
|
<div>A</div>
|
||||||
|
<div>B</div>
|
||||||
|
</t>
|
||||||
|
```
|
||||||
|
|
||||||
|
is not allowed. A workaround could be to wrap the content in a div:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-set="content">
|
||||||
|
<div>A</div>
|
||||||
|
<div>B</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Default slot: the first element inside the widget which is not a named slot will
|
||||||
|
be considered the `default` slot. For example:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-name="Parent">
|
||||||
|
<t t-widget="Child">
|
||||||
|
<span>some content</span>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Child">
|
||||||
|
<t t-slot="default"/>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asynchronous Rendering
|
||||||
|
|
||||||
Working with asynchronous code always adds a lot of complexity to a system. Whenever
|
Working with asynchronous code always adds a lot of complexity to a system. Whenever
|
||||||
different parts of a system are active at the same time, one needs to think
|
different parts of a system are active at the same time, one needs to think
|
||||||
@@ -786,9 +961,25 @@ There are two different common problems with Owl asynchronous rendering model:
|
|||||||
|
|
||||||
Here are a few tips on how to work with asynchronous widgets:
|
Here are a few tips on how to work with asynchronous widgets:
|
||||||
|
|
||||||
1. minimize the use of asynchronous widgets!
|
1. Minimize the use of asynchronous widgets!
|
||||||
2. Maybe move the asynchronous logic in a store, which then triggers (mostly)
|
2. Maybe move the asynchronous logic in a store, which then triggers (mostly)
|
||||||
synchronous renderings
|
synchronous renderings
|
||||||
3. Lazy loading external libraries is a good use case for async rendering. This
|
3. Lazy loading external libraries is a good use case for async rendering. This
|
||||||
is mostly fine, because we can assume that it will only takes a fraction of a
|
is mostly fine, because we can assume that it will only takes a fraction of a
|
||||||
second, and only once (see `owl.utils.loadJS`)
|
second, and only once (see `owl.utils.loadJS`)
|
||||||
|
4. For all the other cases, the `t-asyncroot` directive (to use alongside
|
||||||
|
`t-widget`) is there to help you. When this directive is met, a new rendering
|
||||||
|
sub tree is created, such that the rendering of that component (and its
|
||||||
|
children) is not tied to the rendering of the rest of the interface. It can
|
||||||
|
be used on an asynchronous component, to prevent it from delaying the
|
||||||
|
rendering of the whole interface, or on a synchronous one, such that its
|
||||||
|
rendering isn't delayed by other (asynchronous) components. Note that this
|
||||||
|
directive has no effect on the first rendering, but only on subsequent ones
|
||||||
|
(triggered by state or props changes).
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-name="ParentWidget">
|
||||||
|
<t t-widget="SyncChild"/>
|
||||||
|
<t t-widget="AsyncChild" t-asyncroot="1"/>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 🦉 Event Bus 🦉
|
||||||
|
|
||||||
|
It is sometimes useful to use a `Bus` to communicate informations between various
|
||||||
|
parts of the code. Owl has a very simple bus class, which manages subscriptions,
|
||||||
|
triggering events, and callbacks.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const bus = new owl.EventBus();
|
||||||
|
|
||||||
|
bus.on("some-event", null, function(...args) {
|
||||||
|
console.log(...args);
|
||||||
|
});
|
||||||
|
|
||||||
|
bus.trigger("some-event", 1, 2, 3);
|
||||||
|
// [1,2,3] will be logged to the console
|
||||||
|
```
|
||||||
|
|
||||||
|
Its API is:
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
| -------------------------------- | --------------------------------- |
|
||||||
|
| `on(eventType, owner, callback)` | add a listener |
|
||||||
|
| `off(eventType, owner)` | remove all listeners for an owner |
|
||||||
|
| `trigger(eventType, ...args)` | trigger an event |
|
||||||
|
| `clear` | remove all subscriptions |
|
||||||
|
|
||||||
|
Note that the [`Store`](store.md) is an example of an `EventBus`.
|
||||||
+23
-27
@@ -20,35 +20,31 @@ obj.a.b = 2;
|
|||||||
## Technical Limitations
|
## Technical Limitations
|
||||||
|
|
||||||
Since the observer uses getters and setters, it is actually unable to react to
|
Since the observer uses getters and setters, it is actually unable to react to
|
||||||
changes in two situations:
|
changes in three situations:
|
||||||
|
|
||||||
- adding a key to an object:
|
- adding a key to an object
|
||||||
|
- deleting a key from an object
|
||||||
|
- modifying an array by setting a new value at a given index
|
||||||
|
|
||||||
```javascript
|
In those situations, we need a way to tell the observer that something happened.
|
||||||
const observer = new owl.Observer();
|
This can be done by using the `set` and `delete` (only for objects) static
|
||||||
const obj = { a: 1 };
|
methods of the `Observer`.
|
||||||
observer.observe(obj);
|
|
||||||
obj.b = 2; // will do nothing
|
|
||||||
```
|
|
||||||
|
|
||||||
In that case, we need a way to tell the observer that something happened.
|
```javascript
|
||||||
This can be done by using the `set` method:
|
const observer = new owl.Observer();
|
||||||
|
const obj = { a: 1 };
|
||||||
|
observer.observe(obj);
|
||||||
|
obj.b = 2; // won't notify the change
|
||||||
|
owl.Observer.set(obj, "b", 2); // will notify the change
|
||||||
|
|
||||||
```javascript
|
delete obj.b; // won't notify the change
|
||||||
observer.set(obj, "b", 2);
|
owl.Observer.delete(obj, "b"); // will notify the change
|
||||||
```
|
```
|
||||||
|
|
||||||
- modifying an array by setting a new value at a given index:
|
```javascript
|
||||||
|
const observer = new owl.Observer();
|
||||||
```javascript
|
const arr = ["a"];
|
||||||
const observer = new owl.Observer();
|
observer.observe(arr);
|
||||||
const obj = { todos: [{ id: 1, text: "todo" }] };
|
arr[0] = "b"; // won't notify the change
|
||||||
observer.observe(obj);
|
owl.Observer.set(arr, 0, "b"); // will notify the change
|
||||||
obj[0] = { id: 2, text: "othertodo" }; // will do nothing, and obj[0] is not observed
|
```
|
||||||
```
|
|
||||||
|
|
||||||
In that case, the solution is the same, we can simply use the `set` method:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
observer.set(obj, 0, { id: 2, text: "othertodo" });
|
|
||||||
```
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# 🦉 Quick Start 🦉
|
# 🦉 Quick Start 🦉
|
||||||
|
|
||||||
## Static server
|
## Static Server
|
||||||
|
|
||||||
Let us assume that we have a static server running somewhere. We could then
|
Let us assume that we have a static server running somewhere. We could then
|
||||||
simply add an html page with a few extra files.
|
simply add an html page with a few extra files.
|
||||||
|
|||||||
+35
-37
@@ -5,15 +5,15 @@
|
|||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Directives](#directives)
|
- [Directives](#directives)
|
||||||
- [QWeb Engine](#qweb-engine)
|
- [QWeb Engine](#qweb-engine)
|
||||||
- [Reference](#qweb-specification)
|
- [Reference](#reference)
|
||||||
- [White spaces](#white-spaces)
|
- [White Spaces](#white-spaces)
|
||||||
- [Root nodes](#root-nodes)
|
- [Root Nodes](#root-nodes)
|
||||||
- [Expression evaluation](#expression-evaluation)
|
- [Expression Evaluation](#expression-evaluation)
|
||||||
- [Static html nodes](#static-html-nodes)
|
- [Static html Nodes](#static-html-nodes)
|
||||||
- [Outputting data](#outputting-data)
|
- [Outputting Data](#outputting-data)
|
||||||
- [Setting Variables](#setting-variables)
|
- [Setting Variables](#setting-variables)
|
||||||
- [Conditionals](#conditionals)
|
- [Conditionals](#conditionals)
|
||||||
- [Dynamic attributes](#dynamic-attributes)
|
- [Dynamic Attributes](#dynamic-attributes)
|
||||||
- [Loops](#loops)
|
- [Loops](#loops)
|
||||||
- [Rendering Sub Templates](#rendering-sub-templates)
|
- [Rendering Sub Templates](#rendering-sub-templates)
|
||||||
- [Debugging](#debugging)
|
- [Debugging](#debugging)
|
||||||
@@ -65,14 +65,16 @@ We present here a list of all standard QWeb directives:
|
|||||||
The component system in Owl requires additional directives, to express various
|
The component system in Owl requires additional directives, to express various
|
||||||
needs. Here is a list of all Owl specific directives:
|
needs. Here is a list of all Owl specific directives:
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| ------------------------- | --------------------------------------------------------------------------------------- |
|
| -------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||||
| `t-widget`, `t-keepalive` | [Defining a sub component](component.md#composition) |
|
| `t-widget`, `t-keepalive`, `t-asyncroot` | [Defining a sub component](component.md#composition) |
|
||||||
| `t-ref` | [Setting a reference to a dom node or a sub component](component.md#keeping-references) |
|
| `t-ref` | [Setting a reference to a dom node or a sub component](component.md#references) |
|
||||||
| `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-mounted` | [Callback when a node or component is mounted](component.md#t-mounted-directive) |
|
||||||
|
| `t-slot` | [Rendering a slot](component.md#slots) |
|
||||||
|
| `t-model` | [Form input bindings](component.md#form-input-bindings) |
|
||||||
|
|
||||||
## QWeb Engine
|
## QWeb Engine
|
||||||
|
|
||||||
@@ -139,7 +141,7 @@ We define in this section the specification of how `QWeb` templates should be
|
|||||||
rendered. Note that we only document here the standard QWeb specification. Owl
|
rendered. Note that we only document here the standard QWeb specification. Owl
|
||||||
specific extensions are documented in various other parts of the documentation.
|
specific extensions are documented in various other parts of the documentation.
|
||||||
|
|
||||||
### White spaces
|
### White Spaces
|
||||||
|
|
||||||
White spaces in a templates are handled in a special way:
|
White spaces in a templates are handled in a special way:
|
||||||
|
|
||||||
@@ -147,7 +149,7 @@ White spaces in a templates are handled in a special way:
|
|||||||
- if a whitespace-only text node contains a linebreak, it is ignored
|
- if a whitespace-only text node contains a linebreak, it is ignored
|
||||||
- the previous rules do not apply if we are in a `<pre>` tag
|
- the previous rules do not apply if we are in a `<pre>` tag
|
||||||
|
|
||||||
### Root nodes
|
### Root Nodes
|
||||||
|
|
||||||
For many reasons, Owl QWeb templates should have a single root node. More
|
For many reasons, Owl QWeb templates should have a single root node. More
|
||||||
precisely, the result of a template rendering should have a single root node:
|
precisely, the result of a template rendering should have a single root node:
|
||||||
@@ -173,11 +175,17 @@ Note: this does not apply to subtemplates (see the `t-call` directive). In that
|
|||||||
case, they will be inlined in the main template, and can actually have many
|
case, they will be inlined in the main template, and can actually have many
|
||||||
root nodes.
|
root nodes.
|
||||||
|
|
||||||
### Expression evaluation
|
### Expression Evaluation
|
||||||
|
|
||||||
It is useful to explain the various rules that applies on QWeb expressions. These
|
QWeb expressions are strings that will be processed at compile time. Each variable in
|
||||||
expressions are strings that will be converted to a javascript expression at
|
the javascript expression will be replaced by a lookup in the context (so, the
|
||||||
compile time.
|
widget). For example, `a + b.c(d)` will be converted into:
|
||||||
|
|
||||||
|
```js
|
||||||
|
context["a"] + context["b"].c(context["d"]);
|
||||||
|
```
|
||||||
|
|
||||||
|
It is useful to explain the various rules that applies on these expressions:
|
||||||
|
|
||||||
1. it should be a simple expression which returns a value. It cannot be a statement.
|
1. it should be a simple expression which returns a value. It cannot be a statement.
|
||||||
|
|
||||||
@@ -191,10 +199,10 @@ compile time.
|
|||||||
<div><p t-if="console.log(1)">NOT valid</p></div>
|
<div><p t-if="console.log(1)">NOT valid</p></div>
|
||||||
```
|
```
|
||||||
|
|
||||||
2. it can use anything in the rendering context:
|
2. it can use anything in the rendering context (typically, the component):
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<p t-if="user.birthday == today()">Happy bithday!</p>
|
<p t-if="user.birthday === today()">Happy bithday!</p>
|
||||||
```
|
```
|
||||||
|
|
||||||
is valid, and will read the `user` object from the context, and call the
|
is valid, and will read the `user` object from the context, and call the
|
||||||
@@ -337,7 +345,7 @@ Extra conditional branching directives `t-elif` and `t-else` are also available:
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dynamic attributes
|
### Dynamic Attributes
|
||||||
|
|
||||||
One can use the `t-att-` directive to add dynamic attributes. Its main use is to
|
One can use the `t-att-` directive to add dynamic attributes. Its main use is to
|
||||||
evaluate an expression (at rendering time) and bind an attribute to its result:
|
evaluate an expression (at rendering time) and bind an attribute to its result:
|
||||||
@@ -357,13 +365,6 @@ If an expression evaluates to a falsy value, it will not be set at all:
|
|||||||
There is another way to format a string attribute: the `t-attf-` directive. With
|
There is another way to format a string attribute: the `t-attf-` directive. With
|
||||||
it, you get string interpolation:
|
it, you get string interpolation:
|
||||||
|
|
||||||
```xml
|
|
||||||
<div t-attf-foo="a #{value1} is #{value2} of #{value3} ]"/>
|
|
||||||
<!-- result if values are set to 1,2 and 3: <div foo="a 0 is 1 of 2 ]"></div> -->
|
|
||||||
```
|
|
||||||
|
|
||||||
For historical reason, there is an alternate form of string interpolation:
|
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-attf-foo="a {{value1}} is {{value2}} of {{value3}} ]"/>
|
<div t-attf-foo="a {{value1}} is {{value2}} of {{value3}} ]"/>
|
||||||
<!-- result if values are set to 1,2 and 3: <div foo="a 0 is 1 of 2 ]"></div> -->
|
<!-- result if values are set to 1,2 and 3: <div foo="a 0 is 1 of 2 ]"></div> -->
|
||||||
@@ -399,9 +400,8 @@ Like conditions, `t-foreach` applies to the element bearing the directive’s at
|
|||||||
|
|
||||||
is equivalent to the previous example.
|
is equivalent to the previous example.
|
||||||
|
|
||||||
`t-foreach` can iterate on an array (the current item will be the current value),
|
`t-foreach` can iterate on an array (the current item will be the current value)
|
||||||
an object (the current item will be the current key) or an integer (equivalent
|
or an object (the current item will be the current key).
|
||||||
to iterating on an array between 0 inclusive and the provided integer exclusive).
|
|
||||||
|
|
||||||
In addition to the name passed via t-as, `t-foreach` provides a few other
|
In addition to the name passed via t-as, `t-foreach` provides a few other
|
||||||
variables for various data points (note: `$as` will be replaced by the name
|
variables for various data points (note: `$as` will be replaced by the name
|
||||||
@@ -415,8 +415,6 @@ passed to `t-as`):
|
|||||||
- `$as_last`: whether the current item is the last of the iteration
|
- `$as_last`: whether the current item is the last of the iteration
|
||||||
(equivalent to `$as_index + 1 == $as_size`), requires the iteratee’s size be
|
(equivalent to `$as_index + 1 == $as_size`), requires the iteratee’s size be
|
||||||
available
|
available
|
||||||
- `$as_parity` (deprecated): either "even" or "odd", the parity of the current
|
|
||||||
iteration round
|
|
||||||
|
|
||||||
These extra variables provided and all new variables created into the `t-foreach`
|
These extra variables provided and all new variables created into the `t-foreach`
|
||||||
are only available in the scope of the `t-foreach`. If the variable exists outside
|
are only available in the scope of the `t-foreach`. If the variable exists outside
|
||||||
@@ -427,7 +425,7 @@ into the global context.
|
|||||||
<t t-set="existing_variable" t-value="False"/>
|
<t t-set="existing_variable" t-value="False"/>
|
||||||
<!-- existing_variable now False -->
|
<!-- existing_variable now False -->
|
||||||
|
|
||||||
<p t-foreach="[1, 2, 3]" t-as="i">
|
<p t-foreach="Array(3)" t-as="i">
|
||||||
<t t-set="existing_variable" t-value="True"/>
|
<t t-set="existing_variable" t-value="True"/>
|
||||||
<t t-set="new_variable" t-value="True"/>
|
<t t-set="new_variable" t-value="True"/>
|
||||||
<!-- existing_variable and new_variable now True -->
|
<!-- existing_variable and new_variable now True -->
|
||||||
|
|||||||
+10
-6
@@ -1,17 +1,21 @@
|
|||||||
# 🦉 OWL Documentation 🦉
|
# 🦉 OWL Documentation 🦉
|
||||||
|
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
|
- [Animations](animations.md)
|
||||||
- [Component](component.md)
|
- [Component](component.md)
|
||||||
|
- [Event Bus](event_bus.md)
|
||||||
|
- [Observer](observer.md)
|
||||||
- [QWeb](qweb.md)
|
- [QWeb](qweb.md)
|
||||||
- [Store](store.md)
|
- [Store](store.md)
|
||||||
- [Observer](observer.md)
|
|
||||||
- [Virtual DOM](vdom.md)
|
|
||||||
- [Utils](utils.md)
|
- [Utils](utils.md)
|
||||||
|
- [Virtual DOM](vdom.md)
|
||||||
|
|
||||||
|
## Learning Resources
|
||||||
|
|
||||||
|
- [Quick Start](quick_start.md)
|
||||||
|
|
||||||
## Miscellaneous
|
## Miscellaneous
|
||||||
- [Quick Start](quick_start.md)
|
|
||||||
- [Animations](animations.md)
|
|
||||||
- [Tooling](tooling.md)
|
|
||||||
- [Comparison with React/Vue](comparison.md)
|
- [Comparison with React/Vue](comparison.md)
|
||||||
|
- [Tooling](tooling.md)
|
||||||
|
|||||||
+108
-48
@@ -9,7 +9,7 @@
|
|||||||
- [Mutations](#mutations)
|
- [Mutations](#mutations)
|
||||||
- [Actions](#actions)
|
- [Actions](#actions)
|
||||||
- [Getters](#getters)
|
- [Getters](#getters)
|
||||||
- [Connecting a component](#connecting-a-component)
|
- [Connecting a Component](#connecting-a-component)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -28,50 +28,49 @@ Note: Owl's store is inspired by React Redux and VueX.
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Here is what a simple store look like:
|
Here is what a simple store looks like:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const actions = {
|
const actions = {
|
||||||
addTodo({commit}, message) {
|
addTodo({ commit }, message) {
|
||||||
commit('addTodo', message);
|
commit("addTodo", message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
addTodo({state}, message) {
|
addTodo({ state }, message) {
|
||||||
const todo = {
|
const todo = {
|
||||||
id: state.nextId++,
|
id: state.nextId++,
|
||||||
message,
|
message,
|
||||||
isCompleted: false,
|
isCompleted: false
|
||||||
};
|
};
|
||||||
state.todos.push(todo);
|
state.todos.push(todo);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
todos: [],
|
todos: [],
|
||||||
nextId: 1,
|
nextId: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new owl.Store({state, actions, mutations});
|
const store = new owl.Store({ state, actions, mutations });
|
||||||
store.on('update', () => console.log(store.state));
|
store.on("update", () => console.log(store.state));
|
||||||
|
|
||||||
// updating the state
|
// updating the state
|
||||||
store.dispatch('addTodo', 'fix all bugs');
|
store.dispatch("addTodo", "fix all bugs");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
The store is a simple `owl.EventBus` that triggers `update` events whenever its
|
The store is a simple [`owl.EventBus`](event_bus.md) that triggers `update` events whenever its
|
||||||
state is changed. Note that these events are triggered only after a microtask
|
state is changed. Note that these events are triggered only after a microtask
|
||||||
tick, so only one event will be triggered for any number of state changes in a
|
tick, so only one event will be triggered for any number of state changes in a
|
||||||
call stack.
|
call stack.
|
||||||
|
|
||||||
Also, it is important to mention that the state is observed (with a `owl.Observer`),
|
Also, it is important to mention that the state is observed (with an `owl.Observer`),
|
||||||
which is the reason why it is able to know if it was changed. This implies that
|
which is the reason why it is able to know if it was changed. This implies that
|
||||||
state changes need to be done carefully in some cases (adding a new key to an
|
state changes need to be done carefully in some cases (adding a new key to an
|
||||||
object, or modifying an array with the `arr[i] = newValue` syntax). See the
|
object, or modifying an array with the `arr[i] = newValue` syntax). See the
|
||||||
[Observer](observer.md)'s documentation for more details.
|
[Observer](observer.md)'s documentation for more details.
|
||||||
|
|
||||||
### Public API
|
### Public API
|
||||||
@@ -82,26 +81,26 @@ object, or modifying an array with the `arr[i] = newValue` syntax). See the
|
|||||||
|
|
||||||
### Mutations
|
### Mutations
|
||||||
|
|
||||||
Mutations are the only way to modify the state. Changing the state outside a
|
Mutations are the only way to modify the state. Changing the state outside a
|
||||||
mutation is not allowed (and should throw an error). Mutations are synchronous.
|
mutation is not allowed (and should throw an error). Mutations are synchronous.
|
||||||
|
|
||||||
### Actions
|
### Actions
|
||||||
|
|
||||||
Actions are used to coordinate state changes. It is also useful whenever some
|
Actions are used to coordinate state changes. It is also useful whenever some
|
||||||
asynchronous logic is necessary. For example, fetching data should be done
|
asynchronous logic is necessary. For example, fetching data should be done
|
||||||
in an action.
|
in an action.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const actions = {
|
const actions = {
|
||||||
async login({commit}) {
|
async login({ commit }) {
|
||||||
commit('setLoginState', 'pending');
|
commit("setLoginState", "pending");
|
||||||
try {
|
try {
|
||||||
const loginInfo = await doSomeRPC('/login/', 'someinfo');
|
const loginInfo = await doSomeRPC("/login/", "someinfo");
|
||||||
commit('setLoginState', loginInfo);
|
commit("setLoginState", loginInfo);
|
||||||
} catch {
|
} catch {
|
||||||
commit('setLoginState', 'error');
|
commit("setLoginState", "error");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -128,22 +127,83 @@ transform the data contained in the store.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const getters = {
|
const getters = {
|
||||||
getPost({state}, id) {
|
getPost({ state }, id) {
|
||||||
const post = state.posts.find(p => p.id === id);
|
const post = state.posts.find(p => p.id === id);
|
||||||
const author = state.authors.find(a => a.id = post.id);
|
const author = state.authors.find(a => (a.id = post.id));
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
author,
|
author,
|
||||||
content: post.content
|
content: post.content
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// somewhere else
|
// somewhere else
|
||||||
const post = store.getters.getPost(id);
|
const post = store.getters.getPost(id);
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Connecting a component
|
Getters take *at most* one argument.
|
||||||
|
|
||||||
Todo
|
|
||||||
|
Note that getters are cached if they don't take any argument, or their argument
|
||||||
|
is a string or a number.
|
||||||
|
|
||||||
|
### Connecting a Component
|
||||||
|
|
||||||
|
By default, an Owl `Component` is not connected to any store. The `connect`
|
||||||
|
function is there to create sub Components that are connected versions of
|
||||||
|
Components.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const actions = {
|
||||||
|
increment({ commit }) {
|
||||||
|
commit("increment", 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mutations = {
|
||||||
|
increment({ state }, val) {
|
||||||
|
state.counter += val;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const state = {
|
||||||
|
counter: 0
|
||||||
|
};
|
||||||
|
const store = new owl.Store({ state, actions, mutations });
|
||||||
|
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
increment() {
|
||||||
|
this.env.store.dispatch("increment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function mapStoreToProps(state) {
|
||||||
|
return {
|
||||||
|
value: state.counter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const ConnectedCounter = owl.connect(Counter, mapStoreToProps);
|
||||||
|
|
||||||
|
const counter = new ConnectedCounter({ store, qweb });
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<button t-name="Counter" t-on-click="increment">
|
||||||
|
Click Me! [<t t-esc="props.value"/>]
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
The arguments of `connect` are:
|
||||||
|
|
||||||
|
- `Counter`: an owl `Component` to connect
|
||||||
|
- `mapStoreToProps`: a function that extracts the `props` of the Component
|
||||||
|
from the `state` of the `Store` and returns them as a dict
|
||||||
|
- `options`: dictionary of optional parameters that may contain
|
||||||
|
- `getStore`: a function that takes the `env` in arguments and returns an
|
||||||
|
instance of `Store` to connect to (if not given, connects to `env.store`)
|
||||||
|
- `hashFunction`: the function to use to detect changes in the state (if not
|
||||||
|
given, generates a function that uses revision numbers, incremented at
|
||||||
|
each state change)
|
||||||
|
- `deep`: [only useful if no hashFunction is given] if false, only watch
|
||||||
|
for top level state changes (true by default)
|
||||||
|
|
||||||
|
The `connect` function returns a sub class of the given `Component` which is
|
||||||
|
connected to the `store`.
|
||||||
|
|||||||
+43
-6
@@ -1,18 +1,55 @@
|
|||||||
# 🦉 Tooling 🦉
|
# 🦉 Tooling 🦉
|
||||||
|
|
||||||
## Development mode
|
## Content
|
||||||
|
|
||||||
By default, Owl is in *production* mode, this means that it will try to do its
|
- [Overview](#overview)
|
||||||
job fast, and skip some expensive operations. However, in some cases, it is
|
- [Development Mode](#development-mode)
|
||||||
|
- [Playground](#playground)
|
||||||
|
- [Benchmarks](#benchmarks)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
To help work with/improve/learn OWL, there are a few extras tools/settings.
|
||||||
|
|
||||||
|
- development mode: enable better error reporting for the developer
|
||||||
|
- a playground application: a space to experiment and learn Owl.
|
||||||
|
- a benchmarks application: allow comparison with a few common frameworks
|
||||||
|
|
||||||
|
The two applications are available in the `tools/` folder, and can be accessed
|
||||||
|
by using a static http server. A simple python
|
||||||
|
server is available in `server.py`. There is also a npm script to start it:
|
||||||
|
`npm run tools` (and its version with a watcher: `npm run tools:watch`).
|
||||||
|
|
||||||
|
## Development Mode
|
||||||
|
|
||||||
|
By default, Owl is in _production_ mode, this means that it will try to do its
|
||||||
|
job fast, and skip some expensive operations. However, in some cases, it is
|
||||||
convenient to have better information on what is going on, this is the purpose
|
convenient to have better information on what is going on, this is the purpose
|
||||||
of the dev mode.
|
of the dev mode.
|
||||||
|
|
||||||
Owl has a mode flag, in `owl.__info__.mode`. Its default value is `prod`, but
|
Owl has a mode flag, in `owl.__info__.mode`. Its default value is `prod`, but
|
||||||
it can be set to `dev`:
|
it can be set to `dev`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
owl.__info__.mode = 'dev';
|
owl.__info__.mode = "dev";
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that templates compiled with the `prod` settings will not be recompiled.
|
Note that templates compiled with the `prod` settings will not be recompiled.
|
||||||
So, changing this setting is best done at startup.
|
So, changing this setting is best done at startup.
|
||||||
|
|
||||||
|
## Playground
|
||||||
|
|
||||||
|
The playground is an important application designed to help learning and
|
||||||
|
experimenting with Owl. The last published version of Owl can be tested [online](https://odoo.github.io/owl/playground/).
|
||||||
|
|
||||||
|
It is an application similar to `jsFiddle`, but specialized for Owl: there are
|
||||||
|
three tabs (`js`, `css` and `xml`), and a simple button `Run` to execute that
|
||||||
|
code in an iframe.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Note: This is more an internal tool, useful for people working on Owl.
|
||||||
|
|
||||||
|
The benchmarks application is a very small application, implemented in different
|
||||||
|
frameworks, and in different versions of Owl. This is a simple internal tool,
|
||||||
|
useful to compare various performance metrics on some tasks.
|
||||||
|
|||||||
+43
-17
@@ -5,35 +5,61 @@ functions are all available in the `owl.utils` namespace.
|
|||||||
|
|
||||||
## Content
|
## Content
|
||||||
|
|
||||||
- [`whenReady`](#whenready)
|
- [`whenReady`](#whenready): executing code when DOM is ready
|
||||||
- [`loadJS`](#loadjs)
|
- [`loadJS`](#loadjs): loading script files
|
||||||
- [`loadTemplates`](#loadtemplates)
|
- [`loadTemplates`](#loadtemplates): loading xml files
|
||||||
- [`escape`](#escape)
|
- [`escape`](#escape): sanitizing strings
|
||||||
- [`debounce`](#debounce)
|
- [`debounce`](#debounce): limiting rate of function calls
|
||||||
|
|
||||||
## `whenReady`
|
## `whenReady`
|
||||||
|
|
||||||
The function `whenReady` is useful to register some code that need to be executed
|
The function `whenReady` returns a `Promise` resolved when the DOM is ready (if
|
||||||
as soon as the document (page) is ready:
|
not ready yet, resolved directly otherwise). If called with a callback as
|
||||||
|
argument, it executes it as soon as the DOM ready (or directly).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
owl.utils.whenReady(function () {
|
Promise.all([loadTemplates(), owl.utils.whenReady()]).then(function([
|
||||||
const qweb = new owl.QWeb();
|
templates
|
||||||
const app = new App({ qweb });
|
]) {
|
||||||
app.mount(document.body);
|
const qweb = new owl.QWeb(templates);
|
||||||
|
const app = new App({ qweb });
|
||||||
|
app.mount(document.body);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
owl.utils.whenReady(function() {
|
||||||
|
const qweb = new owl.QWeb();
|
||||||
|
const app = new App({ qweb });
|
||||||
|
app.mount(document.body);
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## `loadJS`
|
## `loadJS`
|
||||||
|
|
||||||
|
`loadJS` takes a url (string) for a javascript resource, and loads it. It returns
|
||||||
|
a promise, so the caller can properly react when it is ready. Also, it is smart:
|
||||||
|
it maintains a list of urls previously loaded (or currently being loaded), and
|
||||||
|
prevent doing twice the work.
|
||||||
|
|
||||||
|
```js
|
||||||
|
class MyComponent extends owl.Component {
|
||||||
|
willStart() {
|
||||||
|
return owl.utils.loadJS("/static/libs/someLib.js");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## `loadTemplates`
|
## `loadTemplates`
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function makeEnv() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const qweb = new owl.QWeb(templates);
|
||||||
|
return { qweb };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## `escape`
|
## `escape`
|
||||||
|
|
||||||
## `debounce`
|
## `debounce`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+16
-1
@@ -1,3 +1,18 @@
|
|||||||
# 🦉 VDom 🦉
|
# 🦉 VDom 🦉
|
||||||
|
|
||||||
Owl's virtual dom is a fork of [snabbdom](https://github.com/snabbdom/snabbdom).
|
Owl is a declarative component system: we declare the structure of the component
|
||||||
|
tree, and Owl will translate that to a list of imperative operations. This
|
||||||
|
translation is done by a virtual dom. This is the low level layer of Owl, most
|
||||||
|
developer will not need to call directly the virtual dom functions.
|
||||||
|
|
||||||
|
The main idea behind a virtual dom is to keep a in-memory representation of the
|
||||||
|
DOM (called a virtual node), and whenever some change is needed, to regenerate
|
||||||
|
a new representation, compute the difference between the old and the new, then
|
||||||
|
apply the changes.
|
||||||
|
|
||||||
|
`vdom` exports two functions:
|
||||||
|
|
||||||
|
- `h`: create a new virtual node
|
||||||
|
- `patch`: compare two virtual nodes, and apply the difference.
|
||||||
|
|
||||||
|
Note: Owl's virtual dom is a fork of [snabbdom](https://github.com/snabbdom/snabbdom).
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<templates>
|
|
||||||
<div class="main" t-name="root">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div class="message_count">
|
|
||||||
Number of msg: 0
|
|
||||||
</div>
|
|
||||||
<button class="o_btn_msg 100">Add 100 messages</button>
|
|
||||||
<button class="o_btn_msg 1000">Add 1000 messages</button>
|
|
||||||
<button class="o_btn_msg 10000">Add 10000 messages</button>
|
|
||||||
<button class="o_btn_msg 50000">Add 50000 messages</button>
|
|
||||||
<button class="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button class="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
|
||||||
<span class="author"><t t-esc="widget.author"/></span>
|
|
||||||
<span class="msg"><t t-esc="widget.msg"/></span>
|
|
||||||
<button class="remove" >Remove</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="counter">
|
|
||||||
<button class="o_increment"></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<templates>
|
|
||||||
<div class="main" t-name="root">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div class="message_count">
|
|
||||||
Number of msg: 0
|
|
||||||
</div>
|
|
||||||
<button class="o_btn_msg 100">Add 100 messages</button>
|
|
||||||
<button class="o_btn_msg 1000">Add 1000 messages</button>
|
|
||||||
<button class="o_btn_msg 10000">Add 10000 messages</button>
|
|
||||||
<button class="o_btn_msg 50000">Add 50000 messages</button>
|
|
||||||
<button class="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button class="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
|
||||||
<span class="author"><t t-esc="widget.author"/></span>
|
|
||||||
<span class="msg"><t t-esc="widget.msg"/></span>
|
|
||||||
<button class="remove" >Remove</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="counter">
|
|
||||||
<button class="o_increment"></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Likes Counter Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Counter extends owl.Component {
|
|
||||||
template = "counter";
|
|
||||||
state = { counter: 0 };
|
|
||||||
|
|
||||||
increment() {
|
|
||||||
this.state.counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Message Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Message extends owl.Component {
|
|
||||||
template = "message";
|
|
||||||
widgets = { Counter };
|
|
||||||
|
|
||||||
shouldUpdate(nextProps) {
|
|
||||||
return nextProps !== this.props;
|
|
||||||
}
|
|
||||||
removeMessage() {
|
|
||||||
this.trigger("remove_message", {
|
|
||||||
id: this.props.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Root Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class App extends owl.Component {
|
|
||||||
template = "root";
|
|
||||||
widgets = { Message };
|
|
||||||
state = { messages: [] };
|
|
||||||
|
|
||||||
addMessages(n) {
|
|
||||||
startMeasure("add " + n);
|
|
||||||
const newMessages = buildData(n);
|
|
||||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
startMeasure("clear");
|
|
||||||
this.state.messages = [];
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSomeMessages() {
|
|
||||||
startMeasure("update every 10th");
|
|
||||||
const messages = this.state.messages;
|
|
||||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
|
||||||
const msg = Object.assign({}, messages[i]);
|
|
||||||
msg.author += '!!!';
|
|
||||||
this.set(messages, i, msg);
|
|
||||||
}
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage(data) {
|
|
||||||
startMeasure("remove message");
|
|
||||||
const index = this.state.messages.findIndex(m => m.id === data.id);
|
|
||||||
this.state.messages.splice(index, 1);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Application initialization
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
async function start() {
|
|
||||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
|
||||||
const env = {
|
|
||||||
qweb: new owl.QWeb(templates)
|
|
||||||
};
|
|
||||||
const app = new App(env);
|
|
||||||
app.mount(document.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<templates>
|
|
||||||
<div t-name="root" class="main">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div>Number of msg: <t t-esc="state.messages.length"/></div>
|
|
||||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
|
||||||
<button t-on-click="addMessages(1000)">Add 1000 messages</button>
|
|
||||||
<button t-on-click="addMessages(10000)">Add 10000 messages</button>
|
|
||||||
<button t-on-click="addMessages(50000)">Add 50000 messages</button>
|
|
||||||
<button t-on-click="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button t-on-click="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
<t t-foreach="state.messages" t-as="message">
|
|
||||||
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
|
||||||
<t t-widget="Counter"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="counter">
|
|
||||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Likes Counter Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Counter extends owl.Component {
|
|
||||||
template = "counter";
|
|
||||||
state = { counter: 0 };
|
|
||||||
|
|
||||||
increment() {
|
|
||||||
this.state.counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Message Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Message extends owl.Component {
|
|
||||||
template = "message";
|
|
||||||
widgets = { Counter };
|
|
||||||
|
|
||||||
shouldUpdate(nextProps) {
|
|
||||||
return nextProps !== this.props;
|
|
||||||
}
|
|
||||||
removeMessage() {
|
|
||||||
this.trigger("remove_message", {
|
|
||||||
id: this.props.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Root Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class App extends owl.Component {
|
|
||||||
template = "root";
|
|
||||||
widgets = { Message };
|
|
||||||
state = { messages: [] };
|
|
||||||
|
|
||||||
addMessages(n) {
|
|
||||||
startMeasure("add " + n);
|
|
||||||
const newMessages = buildData(n);
|
|
||||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
startMeasure("clear");
|
|
||||||
this.state.messages = [];
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSomeMessages() {
|
|
||||||
startMeasure("update every 10th");
|
|
||||||
const messages = this.state.messages;
|
|
||||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
|
||||||
const msg = Object.assign({}, messages[i]);
|
|
||||||
msg.author += '!!!';
|
|
||||||
this.set(messages, i, msg);
|
|
||||||
}
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage(data) {
|
|
||||||
startMeasure("remove message");
|
|
||||||
const index = this.state.messages.findIndex(m => m.id === data.id);
|
|
||||||
this.state.messages.splice(index, 1);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Application initialization
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
async function start() {
|
|
||||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
|
||||||
const env = {
|
|
||||||
qweb: new owl.QWeb(templates)
|
|
||||||
};
|
|
||||||
const app = new App(env);
|
|
||||||
app.mount(document.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<templates>
|
|
||||||
<div t-name="root" class="main">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div>Number of msg: <t t-esc="state.messages.length"/></div>
|
|
||||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
|
||||||
<button t-on-click="addMessages(1000)">Add 1000 messages</button>
|
|
||||||
<button t-on-click="addMessages(10000)">Add 10000 messages</button>
|
|
||||||
<button t-on-click="addMessages(50000)">Add 50000 messages</button>
|
|
||||||
<button t-on-click="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button t-on-click="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
<t t-foreach="state.messages" t-as="message">
|
|
||||||
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
|
||||||
<t t-widget="Counter"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="counter">
|
|
||||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Likes Counter Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Counter extends owl.Component {
|
|
||||||
template = "counter";
|
|
||||||
state = { counter: 0 };
|
|
||||||
|
|
||||||
increment() {
|
|
||||||
this.state.counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Message Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Message extends owl.Component {
|
|
||||||
template = "message";
|
|
||||||
widgets = { Counter };
|
|
||||||
|
|
||||||
shouldUpdate(nextProps) {
|
|
||||||
return nextProps !== this.props;
|
|
||||||
}
|
|
||||||
removeMessage() {
|
|
||||||
this.trigger("remove_message", {
|
|
||||||
id: this.props.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Root Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class App extends owl.Component {
|
|
||||||
template = "root";
|
|
||||||
widgets = { Message };
|
|
||||||
state = { messages: [] };
|
|
||||||
|
|
||||||
addMessages(n) {
|
|
||||||
startMeasure("add " + n);
|
|
||||||
const newMessages = buildData(n);
|
|
||||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
startMeasure("clear");
|
|
||||||
this.state.messages = [];
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSomeMessages() {
|
|
||||||
startMeasure("update every 10th");
|
|
||||||
const messages = this.state.messages;
|
|
||||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
|
||||||
const msg = Object.assign({}, messages[i]);
|
|
||||||
msg.author += '!!!';
|
|
||||||
this.set(messages, i, msg);
|
|
||||||
}
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage(data) {
|
|
||||||
startMeasure("remove message");
|
|
||||||
const index = this.state.messages.findIndex(m => m.id === data.id);
|
|
||||||
this.state.messages.splice(index, 1);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Application initialization
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
async function start() {
|
|
||||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
|
||||||
const env = {
|
|
||||||
qweb: new owl.QWeb(templates)
|
|
||||||
};
|
|
||||||
const app = new App(env);
|
|
||||||
app.mount(document.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<templates>
|
|
||||||
<div t-name="root" class="main">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div>Number of msg: <t t-esc="state.messages.length"/></div>
|
|
||||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
|
||||||
<button t-on-click="addMessages(1000)">Add 1000 messages</button>
|
|
||||||
<button t-on-click="addMessages(10000)">Add 10000 messages</button>
|
|
||||||
<button t-on-click="addMessages(50000)">Add 50000 messages</button>
|
|
||||||
<button t-on-click="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button t-on-click="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
<t t-foreach="state.messages" t-as="message">
|
|
||||||
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
|
||||||
<t t-widget="Counter"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="counter">
|
|
||||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Likes Counter Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Counter extends owl.Component {
|
|
||||||
template = "counter";
|
|
||||||
state = { counter: 0 };
|
|
||||||
|
|
||||||
increment() {
|
|
||||||
this.state.counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Message Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Message extends owl.Component {
|
|
||||||
template = "message";
|
|
||||||
widgets = { Counter };
|
|
||||||
|
|
||||||
shouldUpdate(nextProps) {
|
|
||||||
return nextProps !== this.props;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage() {
|
|
||||||
this.trigger("remove_message", {
|
|
||||||
id: this.props.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Root Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class App extends owl.Component {
|
|
||||||
template = "root";
|
|
||||||
widgets = { Message };
|
|
||||||
state = { messages: [] };
|
|
||||||
|
|
||||||
addMessages(n) {
|
|
||||||
startMeasure("add " + n);
|
|
||||||
const newMessages = buildData(n);
|
|
||||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
startMeasure("clear");
|
|
||||||
this.state.messages = [];
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSomeMessages() {
|
|
||||||
startMeasure("update every 10th");
|
|
||||||
const messages = this.state.messages;
|
|
||||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
|
||||||
const msg = Object.assign({}, messages[i]);
|
|
||||||
msg.author += '!!!';
|
|
||||||
this.set(messages, i, msg);
|
|
||||||
}
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage(data) {
|
|
||||||
startMeasure('remove message');
|
|
||||||
const index = this.state.messages.findIndex(m => m.id === data.id);
|
|
||||||
this.state.messages.splice(index, 1);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Application initialization
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
async function start() {
|
|
||||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
|
||||||
const env = {
|
|
||||||
qweb: new owl.QWeb(templates)
|
|
||||||
};
|
|
||||||
const app = new App(env);
|
|
||||||
app.mount(document.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<templates>
|
|
||||||
<div t-name="root" class="main">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div>Number of msg: <t t-esc="state.messages.length"/></div>
|
|
||||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
|
||||||
<button t-on-click="addMessages(1000)">Add 1000 messages</button>
|
|
||||||
<button t-on-click="addMessages(10000)">Add 10000 messages</button>
|
|
||||||
<button t-on-click="addMessages(50000)">Add 50000 messages</button>
|
|
||||||
<button t-on-click="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button t-on-click="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
<t t-foreach="state.messages" t-as="message">
|
|
||||||
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
|
||||||
<t t-widget="Counter"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="counter">
|
|
||||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Likes Counter Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Counter extends owl.Component {
|
|
||||||
template = "counter";
|
|
||||||
state = { counter: 0 };
|
|
||||||
|
|
||||||
increment() {
|
|
||||||
this.state.counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Message Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Message extends owl.Component {
|
|
||||||
template = "message";
|
|
||||||
widgets = { Counter };
|
|
||||||
|
|
||||||
shouldUpdate(nextProps) {
|
|
||||||
return nextProps !== this.props;
|
|
||||||
}
|
|
||||||
removeMessage() {
|
|
||||||
this.trigger("remove_message", {
|
|
||||||
id: this.props.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Root Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class App extends owl.Component {
|
|
||||||
template = "root";
|
|
||||||
widgets = { Message };
|
|
||||||
state = { messages: [] };
|
|
||||||
|
|
||||||
addMessages(n) {
|
|
||||||
startMeasure("add " + n);
|
|
||||||
const newMessages = buildData(n);
|
|
||||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
startMeasure("clear");
|
|
||||||
this.state.messages = [];
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSomeMessages() {
|
|
||||||
startMeasure("update every 10th");
|
|
||||||
const messages = this.state.messages;
|
|
||||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
|
||||||
const msg = Object.assign({}, messages[i]);
|
|
||||||
msg.author += '!!!';
|
|
||||||
this.set(messages, i, msg);
|
|
||||||
}
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage(data) {
|
|
||||||
startMeasure("remove message");
|
|
||||||
const index = this.state.messages.findIndex(m => m.id === data.id);
|
|
||||||
this.state.messages.splice(index, 1);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Application initialization
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
async function start() {
|
|
||||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
|
||||||
const env = {
|
|
||||||
qweb: new owl.QWeb(templates)
|
|
||||||
};
|
|
||||||
const app = new App(env);
|
|
||||||
app.mount(document.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<templates>
|
|
||||||
<div t-name="root" class="main">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div>Number of msg: <t t-esc="state.messages.length"/></div>
|
|
||||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
|
||||||
<button t-on-click="addMessages(1000)">Add 1000 messages</button>
|
|
||||||
<button t-on-click="addMessages(10000)">Add 10000 messages</button>
|
|
||||||
<button t-on-click="addMessages(50000)">Add 50000 messages</button>
|
|
||||||
<button t-on-click="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button t-on-click="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
<t t-foreach="state.messages" t-as="message">
|
|
||||||
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
|
||||||
<t t-widget="Counter"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="counter">
|
|
||||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Likes Counter Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Counter extends owl.Component {
|
|
||||||
template = "counter";
|
|
||||||
state = { counter: 0 };
|
|
||||||
|
|
||||||
increment() {
|
|
||||||
this.state.counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Message Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Message extends owl.Component {
|
|
||||||
template = "message";
|
|
||||||
widgets = { Counter };
|
|
||||||
|
|
||||||
shouldUpdate(nextProps) {
|
|
||||||
return nextProps !== this.props;
|
|
||||||
}
|
|
||||||
removeMessage() {
|
|
||||||
this.trigger("remove_message", {
|
|
||||||
id: this.props.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Root Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class App extends owl.Component {
|
|
||||||
template = "root";
|
|
||||||
widgets = { Message };
|
|
||||||
state = { messages: [] };
|
|
||||||
|
|
||||||
addMessages(n) {
|
|
||||||
startMeasure("add " + n);
|
|
||||||
const newMessages = buildData(n);
|
|
||||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
startMeasure("clear");
|
|
||||||
this.state.messages = [];
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSomeMessages() {
|
|
||||||
startMeasure("update every 10th");
|
|
||||||
const messages = this.state.messages;
|
|
||||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
|
||||||
const msg = Object.assign({}, messages[i]);
|
|
||||||
msg.author += '!!!';
|
|
||||||
this.set(messages, i, msg);
|
|
||||||
}
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage(data) {
|
|
||||||
startMeasure("remove message");
|
|
||||||
const index = this.state.messages.findIndex(m => m.id === data.id);
|
|
||||||
this.state.messages.splice(index, 1);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Application initialization
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
async function start() {
|
|
||||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
|
||||||
const env = {
|
|
||||||
qweb: new owl.QWeb(templates)
|
|
||||||
};
|
|
||||||
const app = new App(env);
|
|
||||||
app.mount(document.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<templates>
|
|
||||||
<div t-name="root" class="main">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div>Number of msg: <t t-esc="state.messages.length"/></div>
|
|
||||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
|
||||||
<button t-on-click="addMessages(1000)">Add 1000 messages</button>
|
|
||||||
<button t-on-click="addMessages(10000)">Add 10000 messages</button>
|
|
||||||
<button t-on-click="addMessages(50000)">Add 50000 messages</button>
|
|
||||||
<button t-on-click="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button t-on-click="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
<t t-foreach="state.messages" t-as="message">
|
|
||||||
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
|
||||||
<t t-widget="Counter"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="counter">
|
|
||||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Likes Counter Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Counter extends owl.Component {
|
|
||||||
state = { counter: 0 };
|
|
||||||
|
|
||||||
increment() {
|
|
||||||
this.state.counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Message Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Message extends owl.Component {
|
|
||||||
widgets = { Counter };
|
|
||||||
|
|
||||||
shouldUpdate(nextProps) {
|
|
||||||
return nextProps !== this.props;
|
|
||||||
}
|
|
||||||
removeMessage() {
|
|
||||||
this.trigger("remove_message", {
|
|
||||||
id: this.props.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Root Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class App extends owl.Component {
|
|
||||||
widgets = { Message };
|
|
||||||
state = { messages: [] };
|
|
||||||
|
|
||||||
addMessages(n) {
|
|
||||||
startMeasure("add " + n);
|
|
||||||
const newMessages = buildData(n);
|
|
||||||
this.state.messages.push.apply(this.state.messages, newMessages);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
startMeasure("clear");
|
|
||||||
this.state.messages = [];
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSomeMessages() {
|
|
||||||
startMeasure("update every 10th");
|
|
||||||
const messages = this.state.messages;
|
|
||||||
for (let i = 0; i < this.state.messages.length; i += 10) {
|
|
||||||
const msg = Object.assign({}, messages[i]);
|
|
||||||
msg.author += '!!!';
|
|
||||||
this.set(messages, i, msg);
|
|
||||||
}
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage(data) {
|
|
||||||
startMeasure("remove message");
|
|
||||||
const index = this.state.messages.findIndex(m => m.id === data.id);
|
|
||||||
this.state.messages.splice(index, 1);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Application initialization
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
async function start() {
|
|
||||||
const templates = await owl.utils.loadTemplates("templates.xml");
|
|
||||||
const env = {
|
|
||||||
qweb: new owl.QWeb(templates)
|
|
||||||
};
|
|
||||||
const app = new App(env);
|
|
||||||
app.mount(document.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<templates>
|
|
||||||
<div t-name="App" class="main">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div>Number of msg: <t t-esc="state.messages.length"/></div>
|
|
||||||
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
|
||||||
<button t-on-click="addMessages(1000)">Add 1000 messages</button>
|
|
||||||
<button t-on-click="addMessages(10000)">Add 10000 messages</button>
|
|
||||||
<button t-on-click="addMessages(50000)">Add 50000 messages</button>
|
|
||||||
<button t-on-click="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button t-on-click="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
<t t-foreach="state.messages" t-as="message">
|
|
||||||
<t t-widget="Message" t-key="message.id" message="message" t-on-remove_message="removeMessage"/>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="Message" class="message">
|
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
|
||||||
<t t-widget="Counter"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div t-name="Counter">
|
|
||||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</templates>
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Counter Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Counter extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
counter: 0
|
|
||||||
};
|
|
||||||
this.increment = this.increment.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return React.createElement(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
React.createElement(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: this.increment
|
|
||||||
},
|
|
||||||
"Value: ",
|
|
||||||
this.state.counter
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
increment() {
|
|
||||||
this.setState({
|
|
||||||
counter: this.state.counter + 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Message Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Message extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.removeMessage = this.removeMessage.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return React.createElement(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: "message"
|
|
||||||
},
|
|
||||||
React.createElement(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
className: "author"
|
|
||||||
},
|
|
||||||
this.props.message.author
|
|
||||||
),
|
|
||||||
React.createElement(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
className: "msg"
|
|
||||||
},
|
|
||||||
this.props.message.msg
|
|
||||||
),
|
|
||||||
React.createElement(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
className: "remove",
|
|
||||||
onClick: this.removeMessage
|
|
||||||
},
|
|
||||||
"Remove"
|
|
||||||
),
|
|
||||||
React.createElement(Counter, null)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage() {
|
|
||||||
this.props.removeCB(this.props.message.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Root Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
class Main extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
messages: []
|
|
||||||
};
|
|
||||||
this.removeMessage = this.removeMessage.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const msgList = this.state.messages.map(m =>
|
|
||||||
React.createElement(Message, {
|
|
||||||
key: m.id,
|
|
||||||
message: m,
|
|
||||||
removeCB: this.removeMessage
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return React.createElement(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: "main"
|
|
||||||
},
|
|
||||||
React.createElement(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: "left-thing"
|
|
||||||
},
|
|
||||||
React.createElement(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
"Number of msg: ",
|
|
||||||
this.state.messages.length
|
|
||||||
),
|
|
||||||
React.createElement(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: _ => this.addMessages(100)
|
|
||||||
},
|
|
||||||
"Add 100 messages"
|
|
||||||
),
|
|
||||||
React.createElement(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: _ => this.addMessages(1000)
|
|
||||||
},
|
|
||||||
"Add 1000 messages"
|
|
||||||
),
|
|
||||||
React.createElement(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: _ => this.addMessages(10000)
|
|
||||||
},
|
|
||||||
"Add 10000 messages"
|
|
||||||
),
|
|
||||||
React.createElement(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: _ => this.addMessages(50000)
|
|
||||||
},
|
|
||||||
"Add 50000 messages"
|
|
||||||
),
|
|
||||||
React.createElement(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: _ => this.updateSomeMessages()
|
|
||||||
},
|
|
||||||
"Update every 10th messags"
|
|
||||||
),
|
|
||||||
React.createElement(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: _ => this.clear()
|
|
||||||
},
|
|
||||||
"Clear"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
React.createElement(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: "right-thing"
|
|
||||||
},
|
|
||||||
React.createElement(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: "content"
|
|
||||||
},
|
|
||||||
msgList
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addMessages(n) {
|
|
||||||
startMeasure("add " + n);
|
|
||||||
const newMessages = this.state.messages.concat(buildData(n));
|
|
||||||
this.setState({
|
|
||||||
messages: newMessages
|
|
||||||
});
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
startMeasure("clear");
|
|
||||||
this.setState({
|
|
||||||
messages: []
|
|
||||||
});
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSomeMessages() {
|
|
||||||
startMeasure("update every 10th");
|
|
||||||
const messages = this.state.messages;
|
|
||||||
for (let i = 0; i < messages.length; i += 10) {
|
|
||||||
messages[i].author += "!!!";
|
|
||||||
}
|
|
||||||
this.forceUpdate();
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMessage(id) {
|
|
||||||
startMeasure("remove message");
|
|
||||||
const index = this.state.messages.findIndex(m => m.id === id);
|
|
||||||
const messages = this.state.messages.slice();
|
|
||||||
messages.splice(index, 1);
|
|
||||||
this.setState({
|
|
||||||
messages
|
|
||||||
});
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
} //-----------------------------
|
|
||||||
// INIT
|
|
||||||
//-----------------------------
|
|
||||||
|
|
||||||
ReactDOM.render(
|
|
||||||
React.createElement(Main, null),
|
|
||||||
document.getElementById("main")
|
|
||||||
);
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
.main {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 220px 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-thing {
|
|
||||||
background-color: gray;
|
|
||||||
padding: 20px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-thing button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-thing {
|
|
||||||
padding: 20px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Message widget */
|
|
||||||
.message .author {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
width: 400px;
|
|
||||||
background-color: lightblue;
|
|
||||||
margin: 10px 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Counter Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
Vue.component("likes-counter", {
|
|
||||||
data: function() {
|
|
||||||
return {
|
|
||||||
counter: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
increment() {
|
|
||||||
this.counter++;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<div>
|
|
||||||
<button @click="increment">Value: {{counter}}</button>
|
|
||||||
</div>`
|
|
||||||
});
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Message Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
Vue.component("my-message", {
|
|
||||||
props: ["msg"],
|
|
||||||
methods: {
|
|
||||||
removeMessage() {
|
|
||||||
this.$emit("removeMessage", this.msg.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<div class="message">
|
|
||||||
<span class="author">{{msg.author}}</span>
|
|
||||||
<span class="msg">{{msg.msg}}</span>
|
|
||||||
<button class="remove" @click="removeMessage">Remove</button>
|
|
||||||
<likes-counter></likes-counter>
|
|
||||||
</div>`
|
|
||||||
});
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Root Widget
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
const App = {
|
|
||||||
name: "App",
|
|
||||||
data() {
|
|
||||||
return { messages: [] };
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addMessages(n) {
|
|
||||||
startMeasure("add " + n);
|
|
||||||
const newMessages = buildData(n);
|
|
||||||
this.messages.push.apply(this.messages, newMessages);
|
|
||||||
stopMeasure();
|
|
||||||
},
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
startMeasure("clear");
|
|
||||||
this.messages = [];
|
|
||||||
stopMeasure();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSomeMessages() {
|
|
||||||
startMeasure("update every 10th");
|
|
||||||
const messages = this.messages;
|
|
||||||
for (let i = 0; i < this.messages.length; i += 10) {
|
|
||||||
messages[i].author += "!!!";
|
|
||||||
}
|
|
||||||
stopMeasure();
|
|
||||||
},
|
|
||||||
|
|
||||||
removeMessage(id) {
|
|
||||||
startMeasure("remove message");
|
|
||||||
const index = this.messages.findIndex(m => m.id === id);
|
|
||||||
this.messages.splice(index, 1);
|
|
||||||
stopMeasure();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<div class="main">
|
|
||||||
<div class="left-thing">
|
|
||||||
<div>Number of msg: {{messages.length}}</div>
|
|
||||||
<button @click="addMessages(100)">Add 100 messages</button>
|
|
||||||
<button @click="addMessages(1000)">Add 1000 messages</button>
|
|
||||||
<button @click="addMessages(10000)">Add 10000 messages</button>
|
|
||||||
<button @click="addMessages(50000)">Add 50000 messages</button>
|
|
||||||
<button @click="updateSomeMessages">Update every 10th message</button>
|
|
||||||
<button @click="clear">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="right-thing">
|
|
||||||
<div class="content">
|
|
||||||
<my-message v-for="msg in messages" v-bind:key="msg.id" v-bind:msg="msg" @removeMessage="removeMessage"></my-message>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
};
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Application initialization
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
render: h => h(App)
|
|
||||||
}).$mount(`#main`);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# 🦉 Extra Stuff 🦉
|
|
||||||
|
|
||||||
To help work/improve/learn with OWL, we have here:
|
|
||||||
|
|
||||||
- a benchmarks application
|
|
||||||
- a playground application
|
|
||||||
|
|
||||||
Both of them can be accessed by using a static http server. A simple python
|
|
||||||
server is available in `server.py`. There is also a npm script to start it:
|
|
||||||
`npm run extras` (and its version with a watcher: `npm run extras:watch`).
|
|
||||||
|
|
||||||
## Benchmarks
|
|
||||||
|
|
||||||
The benchmarks application is a very small application, implemented in different
|
|
||||||
frameworks, and in different versions of Owl. This is a simple internal tool,
|
|
||||||
useful to compare various performance metrics on some tasks.
|
|
||||||
|
|
||||||
## Playground
|
|
||||||
|
|
||||||
The playground is an important application designed to help learning and
|
|
||||||
experimenting with Owl. It is available [online](https://odoo.github.io/owl/playground/).
|
|
||||||
+5
-5
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "owl",
|
"name": "owl",
|
||||||
"version": "0.13.0",
|
"version": "0.15.0",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -10,10 +10,10 @@
|
|||||||
"minify": "uglifyjs dist/owl.js -o dist/owl.min.js --compress --mangle",
|
"minify": "uglifyjs dist/owl.js -o dist/owl.min.js --compress --mangle",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"extras:serve": "python3 extras/server.py || python extras/server.py",
|
"tools:serve": "python3 tools/server.py || python tools/server.py",
|
||||||
"extras": "npm run build && npm run extras:serve",
|
"tools": "npm run build && npm run tools:serve",
|
||||||
"preextras:watch": "npm run build",
|
"pretools:watch": "npm run build",
|
||||||
"extras:watch": "npm-run-all --parallel extras:serve \"build:* -- --watch\""
|
"tools:watch": "npm-run-all --parallel tools:serve \"build:* -- --watch\""
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
+27
-23
@@ -274,20 +274,7 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
|||||||
if (shouldPatch && __owl__.isMounted && renderId === __owl__.renderId) {
|
if (shouldPatch && __owl__.isMounted && renderId === __owl__.renderId) {
|
||||||
// we only update the vnode and the actual DOM if no other rendering
|
// we only update the vnode and the actual DOM if no other rendering
|
||||||
// occurred between now and when the render method was initially called.
|
// occurred between now and when the render method was initially called.
|
||||||
const patchLen = patchQueue!.length;
|
this._applyPatchQueue(<any[]>patchQueue);
|
||||||
for (let i = 0; i < patchLen; i++) {
|
|
||||||
const patch = patchQueue![i];
|
|
||||||
patch.push(patch[0].willPatch());
|
|
||||||
}
|
|
||||||
for (let i = 0; i < patchLen; i++) {
|
|
||||||
const patch = patchQueue![i];
|
|
||||||
patch[0]._patch(patch[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = patchLen - 1; i >= 0; i--) {
|
|
||||||
const patch = patchQueue![i];
|
|
||||||
patch[0].patched(patch[2]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,14 +327,6 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a key (from the state) to a specific value. This is mostly useful to
|
|
||||||
* work around the limitation in observed value with new keys.
|
|
||||||
*/
|
|
||||||
set(target: any, key: string | number, value: any) {
|
|
||||||
this.__owl__.observer!.set(target, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit a custom event of type 'eventType' with the given 'payload' on the
|
* Emit a custom event of type 'eventType' with the given 'payload' on the
|
||||||
* component's el, if it exists. However, note that the event will only bubble
|
* component's el, if it exists. However, note that the event will only bubble
|
||||||
@@ -479,7 +458,9 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
|||||||
p = p.__proto__;
|
p = p.__proto__;
|
||||||
}
|
}
|
||||||
if (p === Component) {
|
if (p === Component) {
|
||||||
this.template = "default";
|
throw new Error(
|
||||||
|
`Could not find template for component "${this.constructor.name}"`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
tmap[name] = template;
|
tmap[name] = template;
|
||||||
this.template = template;
|
this.template = template;
|
||||||
@@ -576,6 +557,29 @@ export class Component<T extends Env, Props extends {}, State extends {}> {
|
|||||||
return <Props>props;
|
return <Props>props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the given patch queue. A patch is a pair [c, vn], where c is a
|
||||||
|
* Component instance and vn a VNode.
|
||||||
|
* 1) Call 'willPatch' on the component of each patch
|
||||||
|
* 2) Call '_patch' on the component of each patch
|
||||||
|
* 3) Call 'patched' on the component of each patch, in inverse order
|
||||||
|
*/
|
||||||
|
_applyPatchQueue(patchQueue: any[]) {
|
||||||
|
const patchLen = patchQueue.length;
|
||||||
|
for (let i = 0; i < patchLen; i++) {
|
||||||
|
const patch = patchQueue[i];
|
||||||
|
patch.push(patch[0].willPatch());
|
||||||
|
}
|
||||||
|
for (let i = 0; i < patchLen; i++) {
|
||||||
|
const patch = patchQueue[i];
|
||||||
|
patch[0]._patch(patch[1]);
|
||||||
|
}
|
||||||
|
for (let i = patchLen - 1; i >= 0; i--) {
|
||||||
|
const patch = patchQueue[i];
|
||||||
|
patch[0].patched(patch[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the component props (or next props) against the (static) props
|
* Validate the component props (or next props) against the (static) props
|
||||||
* description. This is potentially an expensive operation: it may needs to
|
* description. This is potentially an expensive operation: it may needs to
|
||||||
|
|||||||
+37
-2
@@ -76,6 +76,24 @@ export class Observer {
|
|||||||
allowMutations: boolean = true;
|
allowMutations: boolean = true;
|
||||||
dirty: boolean = false;
|
dirty: boolean = false;
|
||||||
|
|
||||||
|
static set(target: any, key: number | string, value: any) {
|
||||||
|
if (!target.__owl__) {
|
||||||
|
throw Error(
|
||||||
|
"`Observer.set()` can only be called with observed Objects or Arrays"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
target.__owl__.observer.set(target, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static delete(target: any, key: number | string) {
|
||||||
|
if (!target.__owl__) {
|
||||||
|
throw Error(
|
||||||
|
"`Observer.delete()` can only be called with observed Objects"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
target.__owl__.observer.delete(target, key);
|
||||||
|
}
|
||||||
|
|
||||||
notifyCB() {}
|
notifyCB() {}
|
||||||
notifyChange() {
|
notifyChange() {
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
@@ -120,8 +138,19 @@ export class Observer {
|
|||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(target: any, key: number | string) {
|
||||||
|
delete target[key];
|
||||||
|
this._updateRevNumber(target);
|
||||||
|
this.notifyChange();
|
||||||
|
}
|
||||||
|
|
||||||
_observeObj<T extends { __owl__?: any }>(obj: T, parent?: any) {
|
_observeObj<T extends { __owl__?: any }>(obj: T, parent?: any) {
|
||||||
obj.__owl__ = { rev: this.rev, deepRev: this.rev, parent };
|
obj.__owl__ = {
|
||||||
|
rev: this.rev,
|
||||||
|
deepRev: this.rev,
|
||||||
|
parent,
|
||||||
|
observer: this
|
||||||
|
};
|
||||||
Object.defineProperty(obj, "__owl__", { enumerable: false });
|
Object.defineProperty(obj, "__owl__", { enumerable: false });
|
||||||
for (let key in obj) {
|
for (let key in obj) {
|
||||||
this._addProp(obj, key, obj[key]);
|
this._addProp(obj, key, obj[key]);
|
||||||
@@ -129,7 +158,12 @@ export class Observer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_observeArr(arr: Array<any>, parent?: any) {
|
_observeArr(arr: Array<any>, parent?: any) {
|
||||||
(<any>arr).__owl__ = { rev: this.rev, deepRev: this.rev, parent };
|
(<any>arr).__owl__ = {
|
||||||
|
rev: this.rev,
|
||||||
|
deepRev: this.rev,
|
||||||
|
parent,
|
||||||
|
observer: this
|
||||||
|
};
|
||||||
Object.defineProperty(arr, "__owl__", { enumerable: false });
|
Object.defineProperty(arr, "__owl__", { enumerable: false });
|
||||||
(<any>arr).__proto__ = Object.create(ModifiedArrayProto);
|
(<any>arr).__proto__ = Object.create(ModifiedArrayProto);
|
||||||
(<any>arr).__proto__.__observer__ = this;
|
(<any>arr).__proto__.__observer__ = this;
|
||||||
@@ -145,6 +179,7 @@ export class Observer {
|
|||||||
) {
|
) {
|
||||||
var self = this;
|
var self = this;
|
||||||
Object.defineProperty(obj, key, {
|
Object.defineProperty(obj, key, {
|
||||||
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get() {
|
get() {
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
+31
-89
@@ -1,4 +1,5 @@
|
|||||||
import { VNode, h } from "./vdom";
|
import { VNode, h } from "./vdom";
|
||||||
|
import { QWebVar, compileExpr } from "./qweb_expressions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owl QWeb Engine
|
* Owl QWeb Engine
|
||||||
@@ -56,18 +57,6 @@ export interface Directive {
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Const/global stuff/helpers
|
// Const/global stuff/helpers
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date".split(
|
|
||||||
","
|
|
||||||
);
|
|
||||||
|
|
||||||
const WORD_REPLACEMENT = {
|
|
||||||
and: "&&",
|
|
||||||
or: "||",
|
|
||||||
gt: ">",
|
|
||||||
gte: ">=",
|
|
||||||
lt: "<",
|
|
||||||
lte: "<="
|
|
||||||
};
|
|
||||||
|
|
||||||
const DISABLED_TAGS = [
|
const DISABLED_TAGS = [
|
||||||
"input",
|
"input",
|
||||||
@@ -151,11 +140,15 @@ export class QWeb {
|
|||||||
// able to map a qweb instance to a template name.
|
// able to map a qweb instance to a template name.
|
||||||
id = nextID++;
|
id = nextID++;
|
||||||
|
|
||||||
|
// slots contains sub templates defined with t-set inside t-widget nodes, and
|
||||||
|
// are meant to be used by the t-slot directive.
|
||||||
|
slots = {};
|
||||||
|
nextSlotId = 1;
|
||||||
|
|
||||||
constructor(data?: string) {
|
constructor(data?: string) {
|
||||||
if (data) {
|
if (data) {
|
||||||
this.addTemplates(data);
|
this.addTemplates(data);
|
||||||
}
|
}
|
||||||
this.addTemplate("default", "<div></div>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static addDirective(directive: Directive) {
|
static addDirective(directive: Directive) {
|
||||||
@@ -340,13 +333,15 @@ export class QWeb {
|
|||||||
}
|
}
|
||||||
if (ctx.parentNode) {
|
if (ctx.parentNode) {
|
||||||
ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`);
|
ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`);
|
||||||
|
} else if (ctx.parentTextNode) {
|
||||||
|
ctx.addLine(`vn${ctx.parentTextNode}.text += \`${text}\`;`);
|
||||||
} else {
|
} else {
|
||||||
// this is an unusual situation: this text node is the result of the
|
// this is an unusual situation: this text node is the result of the
|
||||||
// template rendering.
|
// template rendering.
|
||||||
let nodeID = ctx.generateID();
|
let nodeID = ctx.generateID();
|
||||||
ctx.addLine(`var vn${nodeID} = {text: \`${text}\`};`);
|
ctx.addLine(`var vn${nodeID} = {text: \`${text}\`};`);
|
||||||
ctx.rootContext.rootNode = nodeID;
|
ctx.rootContext.rootNode = nodeID;
|
||||||
ctx.rootContext.parentNode = nodeID;
|
ctx.rootContext.parentTextNode = nodeID;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -366,7 +361,7 @@ export class QWeb {
|
|||||||
for (let i = 0; i < attributes.length; i++) {
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
let attrName = attributes[i].name;
|
let attrName = attributes[i].name;
|
||||||
if (attrName.startsWith("t-")) {
|
if (attrName.startsWith("t-")) {
|
||||||
let dName = attrName.slice(2).split("-")[0];
|
let dName = attrName.slice(2).split(/-|\./)[0];
|
||||||
if (!(dName in DIRECTIVE_NAMES)) {
|
if (!(dName in DIRECTIVE_NAMES)) {
|
||||||
throw new Error(`Unknown QWeb directive: '${attrName}'`);
|
throw new Error(`Unknown QWeb directive: '${attrName}'`);
|
||||||
}
|
}
|
||||||
@@ -383,12 +378,13 @@ export class QWeb {
|
|||||||
const name = attributes[j].name;
|
const name = attributes[j].name;
|
||||||
if (
|
if (
|
||||||
name === "t-" + directive.name ||
|
name === "t-" + directive.name ||
|
||||||
name.startsWith("t-" + directive.name + "-")
|
name.startsWith("t-" + directive.name + "-") ||
|
||||||
|
name.startsWith("t-" + directive.name + ".")
|
||||||
) {
|
) {
|
||||||
fullName = name;
|
fullName = name;
|
||||||
value = attributes[j].textContent;
|
value = attributes[j].textContent;
|
||||||
validDirectives.push({ directive, value, fullName });
|
validDirectives.push({ directive, value, fullName });
|
||||||
if (directive.name === "on") {
|
if (directive.name === "on" || directive.name === 'model') {
|
||||||
withHandlers = true;
|
withHandlers = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,7 +514,8 @@ export class QWeb {
|
|||||||
// dynamic attributes
|
// dynamic attributes
|
||||||
if (name.startsWith("t-att-")) {
|
if (name.startsWith("t-att-")) {
|
||||||
let attName = name.slice(6);
|
let attName = name.slice(6);
|
||||||
let formattedValue = ctx.formatExpression(ctx.getValue(value!));
|
const v = ctx.getValue(value);
|
||||||
|
let formattedValue = v.id || ctx.formatExpression(v);
|
||||||
if (
|
if (
|
||||||
formattedValue[0] === "{" &&
|
formattedValue[0] === "{" &&
|
||||||
formattedValue[formattedValue.length - 1] === "}"
|
formattedValue[formattedValue.length - 1] === "}"
|
||||||
@@ -623,13 +620,14 @@ export class QWeb {
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Compilation Context
|
// Compilation Context
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
export class Context {
|
export class Context {
|
||||||
nextID: number = 1;
|
nextID: number = 1;
|
||||||
code: string[] = [];
|
code: string[] = [];
|
||||||
variables: { [key: string]: any } = {};
|
variables: { [key: string]: QWebVar } = {};
|
||||||
definedVariables: { [key: string]: string } = {};
|
|
||||||
escaping: boolean = false;
|
escaping: boolean = false;
|
||||||
parentNode: number | null = null;
|
parentNode: number | null = null;
|
||||||
|
parentTextNode: number | null = null;
|
||||||
rootNode: number | null = null;
|
rootNode: number | null = null;
|
||||||
indentLevel: number = 0;
|
indentLevel: number = 0;
|
||||||
rootContext: Context;
|
rootContext: Context;
|
||||||
@@ -654,7 +652,7 @@ export class Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withParent(node: number): Context {
|
withParent(node: number): Context {
|
||||||
if (this === this.rootContext && this.parentNode) {
|
if (this === this.rootContext && (this.parentNode || this.parentTextNode)) {
|
||||||
throw new Error("A template should not have more than one root node");
|
throw new Error("A template should not have more than one root node");
|
||||||
}
|
}
|
||||||
if (!this.rootContext.rootNode) {
|
if (!this.rootContext.rootNode) {
|
||||||
@@ -702,66 +700,14 @@ export class Context {
|
|||||||
return val in this.variables ? this.getValue(this.variables[val]) : val;
|
return val in this.variables ? this.getValue(this.variables[val]) : val;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatExpression(e: string): string {
|
/**
|
||||||
e = e.trim();
|
* Prepare an expression for being consumed at render time. Its main job
|
||||||
if (e[0] === "{" && e[e.length - 1] === "}") {
|
* is to
|
||||||
const innerExpr = e
|
* - replace unknown variables by a lookup in the context
|
||||||
.slice(1, -1)
|
* - replace already defined variables by their internal name
|
||||||
.split(",")
|
*/
|
||||||
.map(p => {
|
formatExpression(expr: string): string {
|
||||||
let [key, val] = p.trim().split(":");
|
return compileExpr(expr, this.variables);
|
||||||
if (key === "") {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (!val) {
|
|
||||||
val = key;
|
|
||||||
}
|
|
||||||
return `${key}: ${this.formatExpression(val)}`;
|
|
||||||
})
|
|
||||||
.join(",");
|
|
||||||
return "{" + innerExpr + "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thanks CHM for this code...
|
|
||||||
const chars = e.split("");
|
|
||||||
let instring = "";
|
|
||||||
let invar = "";
|
|
||||||
let invarPos = 0;
|
|
||||||
let r = "";
|
|
||||||
chars.push(" ");
|
|
||||||
for (let i = 0, ilen = chars.length; i < ilen; i++) {
|
|
||||||
let c = chars[i];
|
|
||||||
if (instring.length) {
|
|
||||||
if (c === instring && chars[i - 1] !== "\\") {
|
|
||||||
instring = "";
|
|
||||||
}
|
|
||||||
} else if (c === '"' || c === "'") {
|
|
||||||
instring = c;
|
|
||||||
} else if (c.match(/[a-zA-Z_\$]/) && !invar.length) {
|
|
||||||
invar = c;
|
|
||||||
invarPos = i;
|
|
||||||
continue;
|
|
||||||
} else if (c.match(/\W/) && invar.length) {
|
|
||||||
// TODO: Should check for possible spaces before dot
|
|
||||||
if (chars[invarPos - 1] !== "." && RESERVED_WORDS.indexOf(invar) < 0) {
|
|
||||||
if (!(invar in this.definedVariables)) {
|
|
||||||
invar =
|
|
||||||
WORD_REPLACEMENT[invar] ||
|
|
||||||
(invar in this.variables &&
|
|
||||||
this.formatExpression(this.variables[invar])) ||
|
|
||||||
"context['" + invar + "']";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r += invar;
|
|
||||||
invar = "";
|
|
||||||
} else if (invar.length) {
|
|
||||||
invar += c;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
r += c;
|
|
||||||
}
|
|
||||||
const result = r.slice(0, -1);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -777,15 +723,11 @@ export class Context {
|
|||||||
if (matches && matches[0].length === s.length) {
|
if (matches && matches[0].length === s.length) {
|
||||||
return `(${this.formatExpression(s.slice(2, -2))})`;
|
return `(${this.formatExpression(s.slice(2, -2))})`;
|
||||||
}
|
}
|
||||||
matches = s.match(/\#\{.*?\}/g);
|
|
||||||
if (matches && matches[0].length === s.length) {
|
|
||||||
return `(${this.formatExpression(s.slice(2, -1))})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let formatter = expr => "${" + this.formatExpression(expr) + "}";
|
let r = s.replace(
|
||||||
let r = s
|
/\{\{.*?\}\}/g,
|
||||||
.replace(/\{\{.*?\}\}/g, s => formatter(s.slice(2, -2)))
|
s => "${" + this.formatExpression(s.slice(2, -2)) + "}"
|
||||||
.replace(/\#\{.*?\}/g, s => formatter(s.slice(2, -1)));
|
);
|
||||||
return "`" + r + "`";
|
return "`" + r + "`";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-62
@@ -1,4 +1,5 @@
|
|||||||
import { Context, QWeb, UTILS } from "./qweb_core";
|
import { Context, QWeb, UTILS } from "./qweb_core";
|
||||||
|
import { QWebExprVar } from "./qweb_expressions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owl QWeb Directives
|
* Owl QWeb Directives
|
||||||
@@ -14,7 +15,6 @@ import { Context, QWeb, UTILS } from "./qweb_core";
|
|||||||
* - t-log
|
* - t-log
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// t-esc and t-raw
|
// t-esc and t-raw
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -30,42 +30,49 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Context) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "string") {
|
if (value.xml instanceof NodeList) {
|
||||||
let exprID = value;
|
for (let node of Array.from(value.xml)) {
|
||||||
if (!(value in ctx.definedVariables)) {
|
|
||||||
exprID = `_${ctx.generateID()}`;
|
|
||||||
ctx.addLine(`var ${exprID} = ${ctx.formatExpression(value)};`);
|
|
||||||
}
|
|
||||||
ctx.addIf(`${exprID} || ${exprID} === 0`);
|
|
||||||
if (!ctx.parentNode) {
|
|
||||||
throw new Error("Should not have a text node without a parent");
|
|
||||||
}
|
|
||||||
if (ctx.escaping) {
|
|
||||||
ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
|
|
||||||
} else {
|
|
||||||
let fragID = ctx.generateID();
|
|
||||||
ctx.addLine(`var frag${fragID} = this.utils.getFragment(${exprID})`);
|
|
||||||
let tempNodeID = ctx.generateID();
|
|
||||||
ctx.addLine(`var p${tempNodeID} = {hook: {`);
|
|
||||||
ctx.addLine(
|
|
||||||
` insert: n => n.elm.parentNode.replaceChild(frag${fragID}, n.elm),`
|
|
||||||
);
|
|
||||||
ctx.addLine(`}};`);
|
|
||||||
ctx.addLine(`var vn${tempNodeID} = h('div', p${tempNodeID})`);
|
|
||||||
ctx.addLine(`c${ctx.parentNode}.push(vn${tempNodeID});`);
|
|
||||||
}
|
|
||||||
if (node.childNodes.length) {
|
|
||||||
ctx.addElse();
|
|
||||||
qweb._compileChildren(node, ctx);
|
|
||||||
}
|
|
||||||
ctx.closeIf();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value instanceof NodeList) {
|
|
||||||
for (let node of Array.from(value)) {
|
|
||||||
qweb._compileNode(<ChildNode>node, ctx);
|
qweb._compileNode(<ChildNode>node, ctx);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
let exprID: string;
|
||||||
|
if (typeof value === "string") {
|
||||||
|
exprID = `_${ctx.generateID()}`;
|
||||||
|
ctx.addLine(`var ${exprID} = ${ctx.formatExpression(value)};`);
|
||||||
|
} else {
|
||||||
|
exprID = value.id;
|
||||||
|
}
|
||||||
|
ctx.addIf(`${exprID} || ${exprID} === 0`);
|
||||||
|
if (ctx.escaping) {
|
||||||
|
if (ctx.parentTextNode) {
|
||||||
|
ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
|
||||||
|
} else if (ctx.parentNode) {
|
||||||
|
ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
|
||||||
|
} else {
|
||||||
|
let nodeID = ctx.generateID();
|
||||||
|
ctx.rootContext.rootNode = nodeID;
|
||||||
|
ctx.rootContext.parentTextNode = nodeID;
|
||||||
|
ctx.addLine(`var vn${nodeID} = {text: ${exprID}};`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let fragID = ctx.generateID();
|
||||||
|
ctx.addLine(`var frag${fragID} = this.utils.getFragment(${exprID})`);
|
||||||
|
let tempNodeID = ctx.generateID();
|
||||||
|
ctx.addLine(`var p${tempNodeID} = {hook: {`);
|
||||||
|
ctx.addLine(
|
||||||
|
` insert: n => n.elm.parentNode.replaceChild(frag${fragID}, n.elm),`
|
||||||
|
);
|
||||||
|
ctx.addLine(`}};`);
|
||||||
|
ctx.addLine(`var vn${tempNodeID} = h('div', p${tempNodeID})`);
|
||||||
|
ctx.addLine(`c${ctx.parentNode}.push(vn${tempNodeID});`);
|
||||||
|
}
|
||||||
|
if (node.childNodes.length) {
|
||||||
|
ctx.addElse();
|
||||||
|
qweb._compileChildren(node, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.closeIf();
|
||||||
}
|
}
|
||||||
|
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
@@ -109,15 +116,21 @@ QWeb.addDirective({
|
|||||||
if (value) {
|
if (value) {
|
||||||
const formattedValue = ctx.formatExpression(value);
|
const formattedValue = ctx.formatExpression(value);
|
||||||
if (ctx.variables.hasOwnProperty(variable)) {
|
if (ctx.variables.hasOwnProperty(variable)) {
|
||||||
ctx.addLine(`${ctx.variables[variable]} = ${formattedValue}`);
|
ctx.addLine(
|
||||||
|
`${(<QWebExprVar>ctx.variables[variable]).id} = ${formattedValue}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const varName = `_${ctx.generateID()}`;
|
const varName = `_${ctx.generateID()}`;
|
||||||
ctx.addLine(`var ${varName} = ${formattedValue};`);
|
ctx.addLine(`var ${varName} = ${formattedValue};`);
|
||||||
ctx.definedVariables[varName] = formattedValue;
|
ctx.variables[variable] = {
|
||||||
ctx.variables[variable] = varName;
|
id: varName,
|
||||||
|
expr: formattedValue
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.variables[variable] = node.childNodes;
|
ctx.variables[variable] = {
|
||||||
|
xml: node.childNodes
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -189,30 +202,27 @@ QWeb.addDirective({
|
|||||||
tempCtx.nextID = ctx.rootContext.nextID;
|
tempCtx.nextID = ctx.rootContext.nextID;
|
||||||
qweb._compileNode(nodeCopy, tempCtx);
|
qweb._compileNode(nodeCopy, tempCtx);
|
||||||
const vars = Object.assign({}, ctx.variables, tempCtx.variables);
|
const vars = Object.assign({}, ctx.variables, tempCtx.variables);
|
||||||
var definedVariables = Object.assign(
|
|
||||||
{},
|
|
||||||
ctx.definedVariables,
|
|
||||||
tempCtx.definedVariables
|
|
||||||
);
|
|
||||||
ctx.rootContext.nextID = tempCtx.nextID;
|
ctx.rootContext.nextID = tempCtx.nextID;
|
||||||
|
|
||||||
// open new scope, if necessary
|
// open new scope, if necessary
|
||||||
const hasNewVariables = Object.keys(definedVariables).length > 0;
|
const hasNewVariables = Object.keys(tempCtx.variables).length > 0;
|
||||||
if (hasNewVariables) {
|
if (hasNewVariables) {
|
||||||
ctx.addLine("{");
|
ctx.addLine("{");
|
||||||
ctx.indent();
|
ctx.indent();
|
||||||
}
|
// add new variables, if any
|
||||||
|
for (let key in tempCtx.variables) {
|
||||||
// add new variables, if any
|
const v = tempCtx.variables[key];
|
||||||
for (let key in definedVariables) {
|
if ((<QWebExprVar>v).expr) {
|
||||||
ctx.addLine(`let ${key} = ${definedVariables[key]}`);
|
ctx.addLine(`let ${(<QWebExprVar>v).id} = ${(<QWebExprVar>v).expr};`);
|
||||||
|
}
|
||||||
|
// todo: handle XML variables...
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// compile sub template
|
// compile sub template
|
||||||
const subCtx = ctx
|
const subCtx = ctx
|
||||||
.subContext("caller", nodeCopy)
|
.subContext("caller", nodeCopy)
|
||||||
.subContext("variables", Object.create(vars))
|
.subContext("variables", Object.create(vars));
|
||||||
.subContext("definedVariables", Object.create(definedVariables));
|
|
||||||
|
|
||||||
qweb._compileNode(nodeTemplate.elem, subCtx);
|
qweb._compileNode(nodeTemplate.elem, subCtx);
|
||||||
|
|
||||||
@@ -243,23 +253,18 @@ QWeb.addDirective({
|
|||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`if (!_${arrayID}) { throw new Error('QWeb error: Invalid loop expression')}`
|
`if (!_${arrayID}) { throw new Error('QWeb error: Invalid loop expression')}`
|
||||||
);
|
);
|
||||||
ctx.addLine(
|
|
||||||
`if (typeof _${arrayID} === 'number') { _${arrayID} = Array.from(Array(_${arrayID}).keys())}`
|
|
||||||
);
|
|
||||||
let keysID = ctx.generateID();
|
let keysID = ctx.generateID();
|
||||||
ctx.addLine(
|
|
||||||
`var _${keysID} = _${arrayID} instanceof Array ? _${arrayID} : Object.keys(_${arrayID});`
|
|
||||||
);
|
|
||||||
ctx.addLine(`var _length${keysID} = _${keysID}.length;`);
|
|
||||||
let valuesID = ctx.generateID();
|
let valuesID = ctx.generateID();
|
||||||
ctx.addLine(
|
ctx.addLine(`var _${keysID} = _${valuesID} = _${arrayID};`);
|
||||||
`var _${valuesID} = _${arrayID} instanceof Array ? _${arrayID} : Object.values(_${arrayID});`
|
ctx.addIf(`!(_${arrayID} instanceof Array)`);
|
||||||
);
|
ctx.addLine(`_${keysID} = Object.keys(_${arrayID});`);
|
||||||
|
ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`);
|
||||||
|
ctx.closeIf();
|
||||||
|
ctx.addLine(`var _length${keysID} = _${keysID}.length;`);
|
||||||
ctx.addLine(`for (let i = 0; i < _length${keysID}; i++) {`);
|
ctx.addLine(`for (let i = 0; i < _length${keysID}; i++) {`);
|
||||||
ctx.indent();
|
ctx.indent();
|
||||||
ctx.addLine(`context.${name}_first = i === 0;`);
|
ctx.addLine(`context.${name}_first = i === 0;`);
|
||||||
ctx.addLine(`context.${name}_last = i === _length${keysID} - 1;`);
|
ctx.addLine(`context.${name}_last = i === _length${keysID} - 1;`);
|
||||||
ctx.addLine(`context.${name}_parity = i % 2 === 0 ? 'even' : 'odd';`);
|
|
||||||
ctx.addLine(`context.${name}_index = i;`);
|
ctx.addLine(`context.${name}_index = i;`);
|
||||||
ctx.addLine(`context.${name} = _${keysID}[i];`);
|
ctx.addLine(`context.${name} = _${keysID}[i];`);
|
||||||
ctx.addLine(`context.${name}_value = _${valuesID}[i];`);
|
ctx.addLine(`context.${name}_value = _${valuesID}[i];`);
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
/**
|
||||||
|
* Owl QWeb Expression Parser
|
||||||
|
*
|
||||||
|
* Owl needs in various contexts to be able to understand the structure of a
|
||||||
|
* string representing a javascript expression. The usual goal is to be able
|
||||||
|
* to rewrite some variables. For example, if a template has
|
||||||
|
*
|
||||||
|
* ```xml
|
||||||
|
* <t t-if="computeSomething({val: state.val})">...</t>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* this needs to be translated in something like this:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* if (context["computeSomething"]({val: context["state"].val})) { ... }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This file contains the implementation of an extremely naive tokenizer/parser
|
||||||
|
* and evaluator for javascript expressions. The supported grammar is basically
|
||||||
|
* only expressive enough to understand the shape of objects, of arrays, and
|
||||||
|
* various operators.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Misc types, constants and helpers
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date".split(
|
||||||
|
","
|
||||||
|
);
|
||||||
|
|
||||||
|
const WORD_REPLACEMENT = {
|
||||||
|
and: "&&",
|
||||||
|
or: "||",
|
||||||
|
gt: ">",
|
||||||
|
gte: ">=",
|
||||||
|
lt: "<",
|
||||||
|
lte: "<="
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface QWebExprVar {
|
||||||
|
id: string;
|
||||||
|
expr: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QWebXMLVar {
|
||||||
|
xml: NodeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QWebVar = QWebExprVar | QWebXMLVar;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Tokenizer
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
type TKind =
|
||||||
|
| "LEFT_BRACE"
|
||||||
|
| "RIGHT_BRACE"
|
||||||
|
| "LEFT_BRACKET"
|
||||||
|
| "RIGHT_BRACKET"
|
||||||
|
| "LEFT_PAREN"
|
||||||
|
| "RIGHT_PAREN"
|
||||||
|
| "COMMA"
|
||||||
|
| "VALUE"
|
||||||
|
| "SYMBOL"
|
||||||
|
| "OPERATOR"
|
||||||
|
| "COLON";
|
||||||
|
|
||||||
|
interface Token {
|
||||||
|
type: TKind;
|
||||||
|
value: string;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATIC_TOKEN_MAP: { [key: string]: TKind } = {
|
||||||
|
"{": "LEFT_BRACE",
|
||||||
|
"}": "RIGHT_BRACE",
|
||||||
|
"[": "LEFT_BRACKET",
|
||||||
|
"]": "RIGHT_BRACKET",
|
||||||
|
":": "COLON",
|
||||||
|
",": "COMMA",
|
||||||
|
"(": "LEFT_PAREN",
|
||||||
|
")": "RIGHT_PAREN"
|
||||||
|
};
|
||||||
|
|
||||||
|
const OPERATORS = ".,===,==,+,!,||,&&,>=,>,<=,<,?,-,*,/,%".split(',');
|
||||||
|
|
||||||
|
type Tokenizer = (expr: string) => Token | false;
|
||||||
|
|
||||||
|
let tokenizeString: Tokenizer = function(expr) {
|
||||||
|
let s = expr[0];
|
||||||
|
let start = s;
|
||||||
|
if (s !== "'" && s !== '"') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let i = 1;
|
||||||
|
let cur;
|
||||||
|
while (expr[i] && expr[i] !== start) {
|
||||||
|
cur = expr[i];
|
||||||
|
s += cur;
|
||||||
|
if (cur === "\\") {
|
||||||
|
i++;
|
||||||
|
cur = expr[i];
|
||||||
|
if (!cur) {
|
||||||
|
throw new Error("Invalid expression");
|
||||||
|
}
|
||||||
|
s += cur;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (expr[i] !== start) {
|
||||||
|
throw new Error("Invalid expression");
|
||||||
|
}
|
||||||
|
s += start;
|
||||||
|
return { type: "VALUE", value: s };
|
||||||
|
};
|
||||||
|
|
||||||
|
let tokenizeNumber: Tokenizer = function(expr) {
|
||||||
|
let s = expr[0];
|
||||||
|
if (s && s.match(/[0-9]/)) {
|
||||||
|
let i = 1;
|
||||||
|
while (expr[i] && expr[i].match(/[0-9]|\./)) {
|
||||||
|
s += expr[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return { type: "VALUE", value: s };
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tokenizeSymbol: Tokenizer = function(expr) {
|
||||||
|
let s = expr[0];
|
||||||
|
if (s && s.match(/[a-zA-Z_\$]/)) {
|
||||||
|
let i = 1;
|
||||||
|
while (expr[i] && expr[i].match(/\w/)) {
|
||||||
|
s += expr[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (s in WORD_REPLACEMENT) {
|
||||||
|
return { type: "OPERATOR", value: WORD_REPLACEMENT[s], size: s.length };
|
||||||
|
}
|
||||||
|
return { type: "SYMBOL", value: s };
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tokenizeStatic: Tokenizer = function(expr) {
|
||||||
|
const char = expr[0];
|
||||||
|
if (char && char in STATIC_TOKEN_MAP) {
|
||||||
|
return { type: STATIC_TOKEN_MAP[char], value: char };
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tokenizeOperator: Tokenizer = function(expr) {
|
||||||
|
for (let op of OPERATORS) {
|
||||||
|
if (expr.startsWith(op)) {
|
||||||
|
return { type: "OPERATOR", value: op };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TOKENIZERS = [
|
||||||
|
tokenizeString,
|
||||||
|
tokenizeNumber,
|
||||||
|
tokenizeSymbol,
|
||||||
|
tokenizeStatic,
|
||||||
|
tokenizeOperator
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a javascript expression (as a string) into a list of tokens. For
|
||||||
|
* example: `tokenize("1 + b")` will return:
|
||||||
|
* ```js
|
||||||
|
* [
|
||||||
|
* {type: "VALUE", value: "1"},
|
||||||
|
* {type: "OPERATOR", value: "+"},
|
||||||
|
* {type: "SYMBOL", value: "b"}
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function tokenize(expr: string): Token[] {
|
||||||
|
const result: Token[] = [];
|
||||||
|
let token: boolean | Token = true;
|
||||||
|
|
||||||
|
while (token) {
|
||||||
|
expr = expr.trim();
|
||||||
|
if (expr) {
|
||||||
|
for (let tokenizer of TOKENIZERS) {
|
||||||
|
token = tokenizer(expr);
|
||||||
|
if (token) {
|
||||||
|
result.push(token);
|
||||||
|
expr = expr.slice(token.size || token.value.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expr.length) {
|
||||||
|
throw new Error(`Tokenizer error: could not tokenize "${expr}"`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Expression "evaluator"
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the main function exported by this file. This is the code that will
|
||||||
|
* process an expression (given as a string) and returns another expression with
|
||||||
|
* proper lookups in the context.
|
||||||
|
*
|
||||||
|
* Usually, this kind of code would be very simple to do if we had an AST (so,
|
||||||
|
* if we had a javascript parser), since then, we would only need to find the
|
||||||
|
* variables and replace them. However, a parser is more complicated, and there
|
||||||
|
* are no standard builtin parser API.
|
||||||
|
*
|
||||||
|
* Since this method is applied to simple javasript expressions, and the work to
|
||||||
|
* be done is actually quite simple, we actually can get away with not using a
|
||||||
|
* parser, which helps with the code size.
|
||||||
|
*
|
||||||
|
* Here is the heuristic used by this method to determine if a token is a
|
||||||
|
* variable:
|
||||||
|
* - by default, all symbols are considered a variable
|
||||||
|
* - unless the previous token is a dot (in that case, this is a property: `a.b`)
|
||||||
|
* - or if the previous token is a left brace or a comma, and the next token is
|
||||||
|
* a colon (in that case, this is an object key: `{a: b}`)
|
||||||
|
*/
|
||||||
|
export function compileExpr(
|
||||||
|
expr: string,
|
||||||
|
vars: { [key: string]: QWebVar }
|
||||||
|
): string {
|
||||||
|
const tokens = tokenize(expr);
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < tokens.length; i++) {
|
||||||
|
let token = tokens[i];
|
||||||
|
if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) {
|
||||||
|
// we need to find if it is a variable
|
||||||
|
let isVar = true;
|
||||||
|
let prevToken = tokens[i - 1];
|
||||||
|
if (prevToken) {
|
||||||
|
if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
|
||||||
|
isVar = false;
|
||||||
|
} else if (
|
||||||
|
prevToken.type === "LEFT_BRACE" ||
|
||||||
|
prevToken.type === "COMMA"
|
||||||
|
) {
|
||||||
|
let nextToken = tokens[i + 1];
|
||||||
|
if (nextToken && nextToken.type === "COLON") {
|
||||||
|
isVar = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isVar) {
|
||||||
|
if (token.value in vars && "id" in vars[token.value]) {
|
||||||
|
token.value = (<QWebExprVar>vars[token.value]).id;
|
||||||
|
} else {
|
||||||
|
token.value = `context['${token.value}']`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += token.value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
+146
-28
@@ -1,4 +1,5 @@
|
|||||||
import { QWeb, UTILS } from "./qweb_core";
|
import { QWeb, UTILS } from "./qweb_core";
|
||||||
|
import { VNode } from "./vdom";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owl QWeb Extensions
|
* Owl QWeb Extensions
|
||||||
@@ -11,6 +12,8 @@ import { QWeb, UTILS } from "./qweb_core";
|
|||||||
* - t-transition
|
* - t-transition
|
||||||
* - t-widget/t-keepalive
|
* - t-widget/t-keepalive
|
||||||
* - t-mounted
|
* - t-mounted
|
||||||
|
* - t-slot
|
||||||
|
* - t-model
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -94,7 +97,17 @@ UTILS.nextFrame = function(cb: () => void) {
|
|||||||
requestAnimationFrame(() => requestAnimationFrame(cb));
|
requestAnimationFrame(() => requestAnimationFrame(cb));
|
||||||
};
|
};
|
||||||
|
|
||||||
UTILS.transitionInsert = function(elm: HTMLElement, name: string) {
|
UTILS.transitionInsert = function(vn: VNode, name: string) {
|
||||||
|
const elm = <HTMLElement>vn.elm;
|
||||||
|
// remove potential duplicated vnode that is currently being removed, to
|
||||||
|
// prevent from having twice the same node in the DOM during an animation
|
||||||
|
const dup =
|
||||||
|
elm.parentElement &&
|
||||||
|
elm.parentElement!.querySelector(`*[data-owl-key='${vn.key}']`);
|
||||||
|
if (dup) {
|
||||||
|
dup.remove();
|
||||||
|
}
|
||||||
|
|
||||||
elm.classList.add(name + "-enter");
|
elm.classList.add(name + "-enter");
|
||||||
elm.classList.add(name + "-enter-active");
|
elm.classList.add(name + "-enter-active");
|
||||||
const finalize = () => {
|
const finalize = () => {
|
||||||
@@ -108,11 +121,10 @@ UTILS.transitionInsert = function(elm: HTMLElement, name: string) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
UTILS.transitionRemove = function(
|
UTILS.transitionRemove = function(vn: VNode, name: string, rm: () => void) {
|
||||||
elm: HTMLElement,
|
const elm = <HTMLElement>vn.elm;
|
||||||
name: string,
|
elm.setAttribute("data-owl-key", vn.key!);
|
||||||
rm: () => void
|
|
||||||
) {
|
|
||||||
elm.classList.add(name + "-leave");
|
elm.classList.add(name + "-leave");
|
||||||
elm.classList.add(name + "-leave-active");
|
elm.classList.add(name + "-leave-active");
|
||||||
const finalize = () => {
|
const finalize = () => {
|
||||||
@@ -169,8 +181,8 @@ QWeb.addDirective({
|
|||||||
atNodeCreation({ value, addNodeHook }) {
|
atNodeCreation({ value, addNodeHook }) {
|
||||||
let name = value;
|
let name = value;
|
||||||
const hooks = {
|
const hooks = {
|
||||||
insert: `this.utils.transitionInsert(vn.elm, '${name}');`,
|
insert: `this.utils.transitionInsert(vn, '${name}');`,
|
||||||
remove: `this.utils.transitionRemove(vn.elm, '${name}', rm);`
|
remove: `this.utils.transitionRemove(vn, '${name}', rm);`
|
||||||
};
|
};
|
||||||
for (let hookName in hooks) {
|
for (let hookName in hooks) {
|
||||||
addNodeHook(hookName, hooks[hookName]);
|
addNodeHook(hookName, hooks[hookName]);
|
||||||
@@ -214,13 +226,6 @@ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, {
|
|||||||
* // t-widget directive: we start by evaluating the expression given by t-key:
|
* // t-widget directive: we start by evaluating the expression given by t-key:
|
||||||
* let key5 = "somestring";
|
* let key5 = "somestring";
|
||||||
*
|
*
|
||||||
* // We keep the index of the position of the widget in the closure. We push
|
|
||||||
* // null to reserve the slot, and will replace it later by the widget vnode,
|
|
||||||
* // when it will be ready (do not forget that preparing/rendering a widget is
|
|
||||||
* // asynchronous)
|
|
||||||
* let _2_index = c1.length;
|
|
||||||
* c1.push(null);
|
|
||||||
*
|
|
||||||
* // def3 is the promise that will contain later either the new widget
|
* // def3 is the promise that will contain later either the new widget
|
||||||
* // creation, or the props update...
|
* // creation, or the props update...
|
||||||
* let def3;
|
* let def3;
|
||||||
@@ -235,6 +240,13 @@ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, {
|
|||||||
* ? context.__owl__.children[context.__owl__.cmap[key5]]
|
* ? context.__owl__.children[context.__owl__.cmap[key5]]
|
||||||
* : false;
|
* : false;
|
||||||
*
|
*
|
||||||
|
* // We keep the index of the position of the widget in the closure. We push
|
||||||
|
* // null to reserve the slot, and will replace it later by the widget vnode,
|
||||||
|
* // when it will be ready (do not forget that preparing/rendering a widget is
|
||||||
|
* // asynchronous)
|
||||||
|
* let _2_index = c1.length;
|
||||||
|
* c1.push(null);
|
||||||
|
*
|
||||||
* // we evaluate here the props given to the component. It is done here to be
|
* // we evaluate here the props given to the component. It is done here to be
|
||||||
* // able to easily reference it later, and also, it might be an expensive
|
* // able to easily reference it later, and also, it might be an expensive
|
||||||
* // computation, so it is certainly better to do it only once
|
* // computation, so it is certainly better to do it only once
|
||||||
@@ -296,7 +308,7 @@ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, {
|
|||||||
* let nvn = w4._mount(vnode, vn.elm);
|
* let nvn = w4._mount(vnode, vn.elm);
|
||||||
* pvnode.elm = nvn.elm;
|
* pvnode.elm = nvn.elm;
|
||||||
* // what follows is only present if there are animations on the widget
|
* // what follows is only present if there are animations on the widget
|
||||||
* utils.transitionInsert(vn.elm, "fade");
|
* utils.transitionInsert(vn, "fade");
|
||||||
* },
|
* },
|
||||||
* remove() {
|
* remove() {
|
||||||
* // override with empty function to prevent from removing the node
|
* // override with empty function to prevent from removing the node
|
||||||
@@ -309,7 +321,7 @@ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, {
|
|||||||
* let finalize = () => {
|
* let finalize = () => {
|
||||||
* w4.destroy();
|
* w4.destroy();
|
||||||
* };
|
* };
|
||||||
* utils.transitionRemove(vn.elm, "fade", finalize);
|
* utils.transitionRemove(vn, "fade", finalize);
|
||||||
* }
|
* }
|
||||||
* };
|
* };
|
||||||
* // the pvnode is inserted at the correct position in the div's children
|
* // the pvnode is inserted at the correct position in the div's children
|
||||||
@@ -349,14 +361,15 @@ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, {
|
|||||||
|
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
name: "widget",
|
name: "widget",
|
||||||
extraNames: ["props", "keepalive"],
|
extraNames: ["props", "keepalive", "asyncroot"],
|
||||||
priority: 100,
|
priority: 100,
|
||||||
atNodeEncounter({ ctx, value, node }): boolean {
|
atNodeEncounter({ ctx, value, node, qweb }): boolean {
|
||||||
ctx.addLine("//WIDGET");
|
ctx.addLine("//WIDGET");
|
||||||
ctx.rootContext.shouldDefineOwner = true;
|
ctx.rootContext.shouldDefineOwner = true;
|
||||||
ctx.rootContext.shouldDefineQWeb = true;
|
ctx.rootContext.shouldDefineQWeb = true;
|
||||||
ctx.rootContext.shouldDefineUtils = true;
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
|
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
|
||||||
|
let async = node.getAttribute("t-asyncroot") ? true : false;
|
||||||
|
|
||||||
// t-on- events and t-transition
|
// t-on- events and t-transition
|
||||||
const events: [string, string[], string, string][] = [];
|
const events: [string, string[], string, string][] = [];
|
||||||
@@ -402,8 +415,6 @@ QWeb.addDirective({
|
|||||||
// want to evaluate it only once)
|
// want to evaluate it only once)
|
||||||
ctx.addLine(`let key${keyID} = ${key};`);
|
ctx.addLine(`let key${keyID} = ${key};`);
|
||||||
}
|
}
|
||||||
ctx.addLine(`let _${dummyID}_index = c${ctx.parentNode}.length;`);
|
|
||||||
ctx.addLine(`c${ctx.parentNode}.push(null);`);
|
|
||||||
ctx.addLine(`let def${defID};`);
|
ctx.addLine(`let def${defID};`);
|
||||||
let templateID = key
|
let templateID = key
|
||||||
? `key${keyID}`
|
? `key${keyID}`
|
||||||
@@ -421,19 +432,19 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
let transitionsInsertCode = "";
|
let transitionsInsertCode = "";
|
||||||
if (transition) {
|
if (transition) {
|
||||||
transitionsInsertCode = `utils.transitionInsert(vn.elm, '${transition}');`;
|
transitionsInsertCode = `utils.transitionInsert(vn, '${transition}');`;
|
||||||
}
|
}
|
||||||
let finalizeWidgetCode = `w${widgetID}.${
|
let finalizeWidgetCode = `w${widgetID}.${
|
||||||
keepAlive ? "unmount" : "destroy"
|
keepAlive ? "unmount" : "destroy"
|
||||||
}();`;
|
}();`;
|
||||||
if (ref) {
|
if (ref && !keepAlive) {
|
||||||
finalizeWidgetCode += `delete context.refs[${refKey}];`; // FIXME: shouldn't we keep ref if keepAlive is true?
|
finalizeWidgetCode += `delete context.refs[${refKey}];`;
|
||||||
}
|
}
|
||||||
if (transition) {
|
if (transition) {
|
||||||
finalizeWidgetCode = `let finalize = () => {
|
finalizeWidgetCode = `let finalize = () => {
|
||||||
${finalizeWidgetCode}
|
${finalizeWidgetCode}
|
||||||
};
|
};
|
||||||
utils.transitionRemove(vn.elm, '${transition}', finalize);`;
|
utils.transitionRemove(vn, '${transition}', finalize);`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let createHook = "";
|
let createHook = "";
|
||||||
@@ -494,6 +505,17 @@ QWeb.addDirective({
|
|||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`let w${widgetID} = ${templateID} in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[${templateID}]] : false;`
|
`let w${widgetID} = ${templateID} in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[${templateID}]] : false;`
|
||||||
);
|
);
|
||||||
|
ctx.addLine(`let _${dummyID}_index = c${ctx.parentNode}.length;`);
|
||||||
|
if (async) {
|
||||||
|
ctx.addLine(`const patchQueue${widgetID} = [];`);
|
||||||
|
ctx.addLine(
|
||||||
|
`c${
|
||||||
|
ctx.parentNode
|
||||||
|
}.push(w${widgetID} && w${widgetID}.__owl__.pvnode || null);`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.addLine(`c${ctx.parentNode}.push(null);`);
|
||||||
|
}
|
||||||
ctx.addLine(`let props${widgetID} = {${propStr}};`);
|
ctx.addLine(`let props${widgetID} = {${propStr}};`);
|
||||||
ctx.addIf(
|
ctx.addIf(
|
||||||
`w${widgetID} && w${widgetID}.__owl__.renderPromise && !w${widgetID}.__owl__.vnode`
|
`w${widgetID} && w${widgetID}.__owl__.renderPromise && !w${widgetID}.__owl__.vnode`
|
||||||
@@ -523,9 +545,32 @@ QWeb.addDirective({
|
|||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`
|
`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// SLOTS
|
||||||
|
if (node.childElementCount) {
|
||||||
|
const clone = <Element>node.cloneNode(true);
|
||||||
|
const slotNodes = clone.querySelectorAll("[t-set]");
|
||||||
|
const slotId = qweb.nextSlotId++;
|
||||||
|
ctx.addLine(`w${widgetID}.__owl__.slotId = ${slotId};`);
|
||||||
|
if (slotNodes.length) {
|
||||||
|
for (let i = 0, length = slotNodes.length; i < length; i++) {
|
||||||
|
const slotNode = slotNodes[i];
|
||||||
|
slotNode.parentElement!.removeChild(slotNode);
|
||||||
|
const key = slotNode.getAttribute("t-set")!;
|
||||||
|
slotNode.removeAttribute("t-set");
|
||||||
|
const slotFn = qweb._compile(`slot_${key}_template`, slotNode);
|
||||||
|
qweb.slots[`${slotId}_${key}`] = slotFn.bind(qweb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clone.childElementCount) {
|
||||||
|
const content = clone.children[0];
|
||||||
|
const slotFn = qweb._compile(`slot_default_template`, content);
|
||||||
|
qweb.slots[`${slotId}_default`] = slotFn.bind(qweb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
|
ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
|
||||||
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
||||||
// FIXME: click to re-add widget during remove transition -> leak
|
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${widgetID}._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeWidgetCode}}}});c${
|
`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}, hook: {insert(vn) {let nvn=w${widgetID}._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeWidgetCode}}}});c${
|
||||||
ctx.parentNode
|
ctx.parentNode
|
||||||
@@ -534,8 +579,9 @@ QWeb.addDirective({
|
|||||||
|
|
||||||
ctx.addElse();
|
ctx.addElse();
|
||||||
// need to update widget
|
// need to update widget
|
||||||
|
const patchQueueCode = async ? `patchQueue${widgetID}` : "extra.patchQueue";
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`def${defID} = def${defID} || w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`
|
`def${defID} = def${defID} || w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, ${patchQueueCode});`
|
||||||
);
|
);
|
||||||
let keepAliveCode = "";
|
let keepAliveCode = "";
|
||||||
if (keepAlive) {
|
if (keepAlive) {
|
||||||
@@ -550,7 +596,13 @@ QWeb.addDirective({
|
|||||||
);
|
);
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
|
|
||||||
ctx.addLine(`extra.promises.push(def${defID});`);
|
if (async) {
|
||||||
|
ctx.addLine(
|
||||||
|
`def${defID}.then(w${widgetID}._applyPatchQueue.bind(w${widgetID}, patchQueue${widgetID}));`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.addLine(`extra.promises.push(def${defID});`);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
node.hasAttribute("t-if") ||
|
node.hasAttribute("t-if") ||
|
||||||
@@ -602,3 +654,69 @@ QWeb.addDirective({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// t-slot
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
QWeb.addDirective({
|
||||||
|
name: "slot",
|
||||||
|
priority: 80,
|
||||||
|
atNodeEncounter({ ctx, value }): boolean {
|
||||||
|
const slotKey = ctx.generateID();
|
||||||
|
ctx.addLine(
|
||||||
|
`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`
|
||||||
|
);
|
||||||
|
ctx.addLine(
|
||||||
|
`c${ctx.parentNode}.push(slot${slotKey}(context.__owl__.parent, extra));`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// t-model
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
UTILS.toNumber = function(val: string): number | string {
|
||||||
|
const n = parseFloat(val);
|
||||||
|
return isNaN(n) ? val : n;
|
||||||
|
};
|
||||||
|
|
||||||
|
QWeb.addDirective({
|
||||||
|
name: "model",
|
||||||
|
priority: 42,
|
||||||
|
atNodeCreation({ ctx, nodeID, value, node, fullName }) {
|
||||||
|
const type = node.getAttribute("type");
|
||||||
|
let handler;
|
||||||
|
let event = fullName.includes(".lazy") ? "change" : "input";
|
||||||
|
if (node.tagName === "select") {
|
||||||
|
ctx.addLine(`p${nodeID}.props = {value: context.state['${value}']};`);
|
||||||
|
event = "change";
|
||||||
|
handler = `(ev) => {context.state['${value}'] = ev.target.value}`;
|
||||||
|
} else if (type === "checkbox") {
|
||||||
|
ctx.addLine(`p${nodeID}.props = {checked: context.state['${value}']};`);
|
||||||
|
handler = `(ev) => {context.state['${value}'] = ev.target.checked}`;
|
||||||
|
} else if (type === "radio") {
|
||||||
|
const nodeValue = node.getAttribute("value")!;
|
||||||
|
ctx.addLine(
|
||||||
|
`p${nodeID}.props = {checked:context.state['${value}'] === '${nodeValue}'};`
|
||||||
|
);
|
||||||
|
handler = `(ev) => {context.state['${value}'] = ev.target.value}`;
|
||||||
|
event = "click";
|
||||||
|
} else {
|
||||||
|
ctx.addLine(`p${nodeID}.props = {value: context.state['${value}']};`);
|
||||||
|
const trimCode = fullName.includes(".trim") ? ".trim()" : "";
|
||||||
|
let valueCode = `ev.target.value${trimCode}`;
|
||||||
|
if (fullName.includes(".number")) {
|
||||||
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
|
valueCode = `utils.toNumber(${valueCode})`;
|
||||||
|
}
|
||||||
|
handler = `(ev) => {context.state['${value}'] = ${valueCode}}`;
|
||||||
|
}
|
||||||
|
ctx.addLine(
|
||||||
|
`extra.handlers['${event}' + ${nodeID}] = extra.handlers['${event}' + ${nodeID}] || (${handler});`
|
||||||
|
);
|
||||||
|
ctx.addLine(
|
||||||
|
`p${nodeID}.on['${event}'] = extra.handlers['${event}' + ${nodeID}];`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
+112
-97
@@ -21,7 +21,7 @@ import { Observer } from "./observer";
|
|||||||
// Store Definition
|
// Store Definition
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
type Mutation = ({ state, commit, set, getters }, payload: any) => void;
|
type Mutation = ({ state, commit, getters }, payload: any) => void;
|
||||||
type Action = ({ commit, state, dispatch, env, getters }, payload: any) => void;
|
type Action = ({ commit, state, dispatch, env, getters }, payload: any) => void;
|
||||||
type Getter = ({ state, getters }, payload) => any;
|
type Getter = ({ state, getters }, payload) => any;
|
||||||
|
|
||||||
@@ -46,8 +46,8 @@ export class Store extends EventBus {
|
|||||||
debug: boolean;
|
debug: boolean;
|
||||||
env: any;
|
env: any;
|
||||||
observer: Observer;
|
observer: Observer;
|
||||||
set: any;
|
|
||||||
getters: { [name: string]: (payload?) => any };
|
getters: { [name: string]: (payload?) => any };
|
||||||
|
_gettersCache: { [name: string]: {} };
|
||||||
|
|
||||||
constructor(config: StoreConfig, options: StoreOption = {}) {
|
constructor(config: StoreConfig, options: StoreOption = {}) {
|
||||||
super();
|
super();
|
||||||
@@ -57,20 +57,31 @@ export class Store extends EventBus {
|
|||||||
this.mutations = config.mutations;
|
this.mutations = config.mutations;
|
||||||
this.env = config.env;
|
this.env = config.env;
|
||||||
this.observer = new Observer();
|
this.observer = new Observer();
|
||||||
this.observer.notifyCB = this.trigger.bind(this, "update");
|
this.observer.notifyCB = () => {
|
||||||
|
this._gettersCache = {};
|
||||||
|
this.trigger("update");
|
||||||
|
};
|
||||||
this.observer.allowMutations = false;
|
this.observer.allowMutations = false;
|
||||||
this.observer.observe(this.state);
|
this.observer.observe(this.state);
|
||||||
this.getters = {};
|
this.getters = {};
|
||||||
|
this._gettersCache = {};
|
||||||
|
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
this.history.push({ state: this.state });
|
this.history.push({ state: this.state });
|
||||||
}
|
}
|
||||||
this.set = this.observer.set.bind(this.observer);
|
|
||||||
|
|
||||||
|
const cTypes = ["undefined", "number", "string"];
|
||||||
for (let entry of Object.entries(config.getters || {})) {
|
for (let entry of Object.entries(config.getters || {})) {
|
||||||
const name: string = entry[0];
|
const name: string = entry[0];
|
||||||
const func: (...any) => any = entry[1];
|
const func: (...any) => any = entry[1];
|
||||||
this.getters[name] = payload => {
|
this.getters[name] = payload => {
|
||||||
|
if (this._commitLevel === 0 && cTypes.indexOf(typeof payload) >= 0) {
|
||||||
|
this._gettersCache[name] = this._gettersCache[name] || {};
|
||||||
|
this._gettersCache[name][payload] =
|
||||||
|
this._gettersCache[name][payload] ||
|
||||||
|
func({ state: this.state, getters: this.getters }, payload);
|
||||||
|
return this._gettersCache[name][payload];
|
||||||
|
}
|
||||||
return func({ state: this.state, getters: this.getters }, payload);
|
return func({ state: this.state, getters: this.getters }, payload);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -110,7 +121,6 @@ export class Store extends EventBus {
|
|||||||
{
|
{
|
||||||
commit: this.commit.bind(this),
|
commit: this.commit.bind(this),
|
||||||
state: this.state,
|
state: this.state,
|
||||||
set: this.set,
|
|
||||||
getters: this.getters
|
getters: this.getters
|
||||||
},
|
},
|
||||||
payload
|
payload
|
||||||
@@ -153,11 +163,22 @@ type Constructor<T> = new (...args: any[]) => T;
|
|||||||
interface EnvWithStore extends Env {
|
interface EnvWithStore extends Env {
|
||||||
store: Store;
|
store: Store;
|
||||||
}
|
}
|
||||||
|
type HashFunction = (a: any, b: any) => number;
|
||||||
|
interface StoreOptions {
|
||||||
|
getStore?(Env): Store;
|
||||||
|
hashFunction?: HashFunction;
|
||||||
|
deep?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
let nextID = 1;
|
let nextID = 1;
|
||||||
|
|
||||||
export function connect(mapStateToProps, options: any = {}) {
|
export function connect<E extends EnvWithStore, P, S>(
|
||||||
|
Comp: Constructor<Component<E, P, S>>,
|
||||||
|
mapStoreToProps,
|
||||||
|
options: StoreOptions = <StoreOptions>{}
|
||||||
|
) {
|
||||||
let hashFunction = options.hashFunction || null;
|
let hashFunction = options.hashFunction || null;
|
||||||
|
const getStore = options.getStore || (env => env.store);
|
||||||
|
|
||||||
if (!hashFunction) {
|
if (!hashFunction) {
|
||||||
let deep = "deep" in options ? options.deep : true;
|
let deep = "deep" in options ? options.deep : true;
|
||||||
@@ -183,101 +204,95 @@ export function connect(mapStateToProps, options: any = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return function<E extends EnvWithStore, P, S>(
|
const Result = class extends Comp {
|
||||||
Comp: Constructor<Component<E, P, S>>
|
constructor(parent, props?: any) {
|
||||||
) {
|
const env = parent instanceof Component ? parent.env : parent;
|
||||||
const Result = class extends Comp {
|
const store = getStore(env);
|
||||||
constructor(parent, props?: any) {
|
const ownProps = Object.assign({}, props || {});
|
||||||
const env = parent instanceof Component ? parent.env : parent;
|
const storeProps = mapStoreToProps(store.state, ownProps, store.getters);
|
||||||
const ownProps = Object.assign({}, props || {});
|
const mergedProps = Object.assign({}, props || {}, storeProps);
|
||||||
const storeProps = mapStateToProps(
|
super(parent, mergedProps);
|
||||||
env.store.state,
|
(<any>this.__owl__).ownProps = ownProps;
|
||||||
ownProps,
|
(<any>this.__owl__).currentStoreProps = storeProps;
|
||||||
env.store.getters
|
(<any>this.__owl__).store = store;
|
||||||
);
|
(<any>this.__owl__).storeHash = (<HashFunction>hashFunction)(
|
||||||
const mergedProps = Object.assign({}, props || {}, storeProps);
|
{
|
||||||
super(parent, mergedProps);
|
state: store.state,
|
||||||
(<any>this.__owl__).ownProps = ownProps;
|
storeProps: storeProps,
|
||||||
|
revNumber,
|
||||||
|
deepRevNumber
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentStoreProps: storeProps
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* We do not use the mounted hook here for a subtle reason: we want the
|
||||||
|
* updates to be called for the parents before the children. However,
|
||||||
|
* if we use the mounted hook, this will be done in the reverse order.
|
||||||
|
*/
|
||||||
|
_callMounted() {
|
||||||
|
(<any>this.__owl__).store.on("update", this, this._checkUpdate);
|
||||||
|
super._callMounted();
|
||||||
|
}
|
||||||
|
willUnmount() {
|
||||||
|
(<any>this.__owl__).store.off("update", this);
|
||||||
|
super.willUnmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkUpdate() {
|
||||||
|
const ownProps = (<any>this.__owl__).ownProps;
|
||||||
|
const storeProps = mapStoreToProps(
|
||||||
|
(<any>this.__owl__).store.state,
|
||||||
|
ownProps,
|
||||||
|
(<any>this.__owl__).store.getters
|
||||||
|
);
|
||||||
|
const options: any = {
|
||||||
|
currentStoreProps: (<any>this.__owl__).currentStoreProps
|
||||||
|
};
|
||||||
|
const storeHash = (<HashFunction>hashFunction)(
|
||||||
|
{
|
||||||
|
state: (<any>this.__owl__).store.state,
|
||||||
|
storeProps: storeProps,
|
||||||
|
revNumber,
|
||||||
|
deepRevNumber
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
let didChange = options.didChange;
|
||||||
|
if (storeHash !== (<any>this.__owl__).storeHash) {
|
||||||
|
didChange = true;
|
||||||
|
(<any>this.__owl__).storeHash = storeHash;
|
||||||
|
}
|
||||||
|
if (didChange) {
|
||||||
(<any>this.__owl__).currentStoreProps = storeProps;
|
(<any>this.__owl__).currentStoreProps = storeProps;
|
||||||
(<any>this.__owl__).storeHash = hashFunction(
|
this._updateProps(ownProps, false);
|
||||||
{
|
|
||||||
state: env.store.state,
|
|
||||||
storeProps: storeProps,
|
|
||||||
revNumber,
|
|
||||||
deepRevNumber
|
|
||||||
},
|
|
||||||
{
|
|
||||||
currentStoreProps: storeProps
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
/**
|
}
|
||||||
* We do not use the mounted hook here for a subtle reason: we want the
|
_updateProps(nextProps, forceUpdate, patchQueue?: any[]) {
|
||||||
* updates to be called for the parents before the children. However,
|
if ((<any>this.__owl__).ownProps !== nextProps) {
|
||||||
* if we use the mounted hook, this will be done in the reverse order.
|
(<any>this.__owl__).currentStoreProps = mapStoreToProps(
|
||||||
*/
|
(<any>this.__owl__).store.state,
|
||||||
_callMounted() {
|
|
||||||
this.env.store.on("update", this, this._checkUpdate);
|
|
||||||
super._callMounted();
|
|
||||||
}
|
|
||||||
willUnmount() {
|
|
||||||
this.env.store.off("update", this);
|
|
||||||
super.willUnmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkUpdate() {
|
|
||||||
const ownProps = (<any>this.__owl__).ownProps;
|
|
||||||
const storeProps = mapStateToProps(
|
|
||||||
this.env.store.state,
|
|
||||||
ownProps,
|
|
||||||
this.env.store.getters
|
|
||||||
);
|
|
||||||
const options: any = {
|
|
||||||
currentStoreProps: (<any>this.__owl__).currentStoreProps
|
|
||||||
};
|
|
||||||
const storeHash = hashFunction(
|
|
||||||
{
|
|
||||||
state: this.env.store.state,
|
|
||||||
storeProps: storeProps,
|
|
||||||
revNumber,
|
|
||||||
deepRevNumber
|
|
||||||
},
|
|
||||||
options
|
|
||||||
);
|
|
||||||
let didChange = options.didChange;
|
|
||||||
if (storeHash !== (<any>this.__owl__).storeHash) {
|
|
||||||
didChange = true;
|
|
||||||
(<any>this.__owl__).storeHash = storeHash;
|
|
||||||
}
|
|
||||||
if (didChange) {
|
|
||||||
(<any>this.__owl__).currentStoreProps = storeProps;
|
|
||||||
this._updateProps(ownProps, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_updateProps(nextProps, forceUpdate, patchQueue?: any[]) {
|
|
||||||
if ((<any>this.__owl__).ownProps !== nextProps) {
|
|
||||||
(<any>this.__owl__).currentStoreProps = mapStateToProps(
|
|
||||||
this.env.store.state,
|
|
||||||
nextProps,
|
|
||||||
this.env.store.getters
|
|
||||||
);
|
|
||||||
}
|
|
||||||
(<any>this.__owl__).ownProps = nextProps;
|
|
||||||
const mergedProps = Object.assign(
|
|
||||||
{},
|
|
||||||
nextProps,
|
nextProps,
|
||||||
(<any>this.__owl__).currentStoreProps
|
(<any>this.__owl__).store.getters
|
||||||
);
|
);
|
||||||
return super._updateProps(mergedProps, forceUpdate, patchQueue);
|
|
||||||
}
|
}
|
||||||
};
|
(<any>this.__owl__).ownProps = nextProps;
|
||||||
|
const mergedProps = Object.assign(
|
||||||
// we assign here a unique name to the resulting anonymous class.
|
{},
|
||||||
// this is necessary for Owl to be able to properly deduce templates.
|
nextProps,
|
||||||
// Otherwise, all connected components would have the same name, and then
|
(<any>this.__owl__).currentStoreProps
|
||||||
// each component after the first will necessarily have the same template.
|
);
|
||||||
let name = `ConnectedComponent${nextID++}`;
|
return super._updateProps(mergedProps, forceUpdate, patchQueue);
|
||||||
Object.defineProperty(Result, "name", { value: name });
|
}
|
||||||
return Result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// we assign here a unique name to the resulting anonymous class.
|
||||||
|
// this is necessary for Owl to be able to properly deduce templates.
|
||||||
|
// Otherwise, all connected components would have the same name, and then
|
||||||
|
// each component after the first will necessarily have the same template.
|
||||||
|
let name = `ConnectedComponent${nextID++}`;
|
||||||
|
Object.defineProperty(Result, "name", { value: name });
|
||||||
|
return Result;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-5
@@ -11,11 +11,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function whenReady(fn) {
|
export function whenReady(fn) {
|
||||||
if (document.readyState === "complete") {
|
return new Promise(function(resolve) {
|
||||||
fn();
|
if (document.readyState !== "loading") {
|
||||||
} else {
|
resolve();
|
||||||
document.addEventListener("DOMContentLoaded", fn, false);
|
} else {
|
||||||
}
|
document.addEventListener("DOMContentLoaded", resolve, false);
|
||||||
|
}
|
||||||
|
}).then(fn || function () {});
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedScripts: { [key: string]: Promise<void> } = {};
|
const loadedScripts: { [key: string]: Promise<void> } = {};
|
||||||
|
|||||||
+1
-8
@@ -59,14 +59,7 @@ function vnode(
|
|||||||
elm: Element | Text | undefined
|
elm: Element | Text | undefined
|
||||||
): VNode {
|
): VNode {
|
||||||
let key = data === undefined ? undefined : data.key;
|
let key = data === undefined ? undefined : data.key;
|
||||||
return {
|
return {sel, data, children, text, elm, key};
|
||||||
sel: sel,
|
|
||||||
data: data,
|
|
||||||
children: children,
|
|
||||||
text: text,
|
|
||||||
elm: elm,
|
|
||||||
key: key
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ exports[`animations t-transition combined with t-widget 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -30,10 +30,10 @@ exports[`animations t-transition combined with t-widget 1`] = `
|
|||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
def3 = w4._prepare();
|
def3 = w4._prepare();
|
||||||
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn.elm, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
|
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
|
||||||
w4.destroy();
|
w4.destroy();
|
||||||
};
|
};
|
||||||
utils.transitionRemove(vn.elm, 'chimay', finalize);}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
utils.transitionRemove(vn, 'chimay', finalize);}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
} else {
|
} else {
|
||||||
def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
@@ -54,10 +54,10 @@ exports[`animations t-transition combined with t-widget and t-if 1`] = `
|
|||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
if (context['state'].display) {
|
if (context['state'].display) {
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -74,10 +74,10 @@ exports[`animations t-transition combined with t-widget and t-if 1`] = `
|
|||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
def3 = w4._prepare();
|
def3 = w4._prepare();
|
||||||
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn.elm, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
|
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
|
||||||
w4.destroy();
|
w4.destroy();
|
||||||
};
|
};
|
||||||
utils.transitionRemove(vn.elm, 'chimay', finalize);}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
utils.transitionRemove(vn, 'chimay', finalize);}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
} else {
|
} else {
|
||||||
def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
@@ -96,10 +96,10 @@ exports[`animations t-transition with no delay/duration 1`] = `
|
|||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
p1.hook = {
|
p1.hook = {
|
||||||
insert: vn => {
|
insert: vn => {
|
||||||
this.utils.transitionInsert(vn.elm, 'jupiler');
|
this.utils.transitionInsert(vn, 'jupiler');
|
||||||
},
|
},
|
||||||
remove: (vn, rm) => {
|
remove: (vn, rm) => {
|
||||||
this.utils.transitionRemove(vn.elm, 'jupiler', rm);
|
this.utils.transitionRemove(vn, 'jupiler', rm);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
c1.push({text: \`blue\`});
|
c1.push({text: \`blue\`});
|
||||||
@@ -115,10 +115,10 @@ exports[`animations t-transition, on a simple node (insert) 1`] = `
|
|||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
p1.hook = {
|
p1.hook = {
|
||||||
insert: vn => {
|
insert: vn => {
|
||||||
this.utils.transitionInsert(vn.elm, 'chimay');
|
this.utils.transitionInsert(vn, 'chimay');
|
||||||
},
|
},
|
||||||
remove: (vn, rm) => {
|
remove: (vn, rm) => {
|
||||||
this.utils.transitionRemove(vn.elm, 'chimay', rm);
|
this.utils.transitionRemove(vn, 'chimay', rm);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
c1.push({text: \`blue\`});
|
c1.push({text: \`blue\`});
|
||||||
|
|||||||
@@ -1,5 +1,248 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`async rendering delayed t-widget with t-asyncroot directive 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
|
let owner = context;
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2,on:{}};
|
||||||
|
var vn2 = h('button', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
if (!context['updateApp']) {
|
||||||
|
throw new Error('Missing handler \\\\'' + 'updateApp' + \`\\\\' when evaluating template 'Parent'\`)
|
||||||
|
}
|
||||||
|
extra.handlers['click' + 2] = extra.handlers['click' + 2] || context['updateApp'].bind(owner);
|
||||||
|
p2.on['click'] = extra.handlers['click' + 2];
|
||||||
|
c2.push({text: \`Update App State\`});
|
||||||
|
var _3 = 'children';
|
||||||
|
let c4 = [], p4 = {key:4,attrs:{class: _3}};
|
||||||
|
var vn4 = h('div', p4, c4);
|
||||||
|
c1.push(vn4);
|
||||||
|
//WIDGET
|
||||||
|
let def6;
|
||||||
|
let w7 = 7 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[7]] : false;
|
||||||
|
let _5_index = c4.length;
|
||||||
|
c4.push(null);
|
||||||
|
let props7 = {val:context['state'].val};
|
||||||
|
if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) {
|
||||||
|
if (utils.shallowEqual(props7, w7.__owl__.renderProps)) {
|
||||||
|
def6 = w7.__owl__.renderPromise;
|
||||||
|
} else {
|
||||||
|
w7.destroy();
|
||||||
|
w7 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!w7) {
|
||||||
|
let widgetKey7 = \`Child\`;
|
||||||
|
let W7 = context.widgets && context.widgets[widgetKey7] || QWeb.widgets[widgetKey7];
|
||||||
|
if (!W7) {throw new Error('Cannot find the definition of widget \\"' + widgetKey7 + '\\"')}
|
||||||
|
w7 = new W7(owner, props7);
|
||||||
|
context.__owl__.cmap[7] = w7.__owl__.id;
|
||||||
|
def6 = w7._prepare();
|
||||||
|
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c4[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
|
||||||
|
} else {
|
||||||
|
def6 = def6 || w7._updateProps(props7, extra.forceUpdate, extra.patchQueue);
|
||||||
|
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c4[_5_index]=pvnode;});
|
||||||
|
}
|
||||||
|
extra.promises.push(def6);
|
||||||
|
//WIDGET
|
||||||
|
let def9;
|
||||||
|
let w10 = 10 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[10]] : false;
|
||||||
|
let _8_index = c4.length;
|
||||||
|
const patchQueue10 = [];
|
||||||
|
c4.push(w10 && w10.__owl__.pvnode || null);
|
||||||
|
let props10 = {val:context['state'].val};
|
||||||
|
if (w10 && w10.__owl__.renderPromise && !w10.__owl__.vnode) {
|
||||||
|
if (utils.shallowEqual(props10, w10.__owl__.renderProps)) {
|
||||||
|
def9 = w10.__owl__.renderPromise;
|
||||||
|
} else {
|
||||||
|
w10.destroy();
|
||||||
|
w10 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!w10) {
|
||||||
|
let widgetKey10 = \`AsyncChild\`;
|
||||||
|
let W10 = context.widgets && context.widgets[widgetKey10] || QWeb.widgets[widgetKey10];
|
||||||
|
if (!W10) {throw new Error('Cannot find the definition of widget \\"' + widgetKey10 + '\\"')}
|
||||||
|
w10 = new W10(owner, props10);
|
||||||
|
context.__owl__.cmap[10] = w10.__owl__.id;
|
||||||
|
def9 = w10._prepare();
|
||||||
|
def9 = def9.then(vnode=>{let pvnode=h(vnode.sel, {key: 10, hook: {insert(vn) {let nvn=w10._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});c4[_8_index]=pvnode;w10.__owl__.pvnode = pvnode;});
|
||||||
|
} else {
|
||||||
|
def9 = def9 || w10._updateProps(props10, extra.forceUpdate, patchQueue10);
|
||||||
|
def9 = def9.then(()=>{if (w10.__owl__.isDestroyed) {return};let pvnode=w10.__owl__.pvnode;c4[_8_index]=pvnode;});
|
||||||
|
}
|
||||||
|
def9.then(w10._applyPatchQueue.bind(w10, patchQueue10));
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`async rendering fast t-widget with t-asyncroot directive 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
|
let owner = context;
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2,on:{}};
|
||||||
|
var vn2 = h('button', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
if (!context['updateApp']) {
|
||||||
|
throw new Error('Missing handler \\\\'' + 'updateApp' + \`\\\\' when evaluating template 'Parent'\`)
|
||||||
|
}
|
||||||
|
extra.handlers['click' + 2] = extra.handlers['click' + 2] || context['updateApp'].bind(owner);
|
||||||
|
p2.on['click'] = extra.handlers['click' + 2];
|
||||||
|
c2.push({text: \`Update App State\`});
|
||||||
|
var _3 = 'children';
|
||||||
|
let c4 = [], p4 = {key:4,attrs:{class: _3}};
|
||||||
|
var vn4 = h('div', p4, c4);
|
||||||
|
c1.push(vn4);
|
||||||
|
//WIDGET
|
||||||
|
let def6;
|
||||||
|
let w7 = 7 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[7]] : false;
|
||||||
|
let _5_index = c4.length;
|
||||||
|
const patchQueue7 = [];
|
||||||
|
c4.push(w7 && w7.__owl__.pvnode || null);
|
||||||
|
let props7 = {val:context['state'].val};
|
||||||
|
if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) {
|
||||||
|
if (utils.shallowEqual(props7, w7.__owl__.renderProps)) {
|
||||||
|
def6 = w7.__owl__.renderPromise;
|
||||||
|
} else {
|
||||||
|
w7.destroy();
|
||||||
|
w7 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!w7) {
|
||||||
|
let widgetKey7 = \`Child\`;
|
||||||
|
let W7 = context.widgets && context.widgets[widgetKey7] || QWeb.widgets[widgetKey7];
|
||||||
|
if (!W7) {throw new Error('Cannot find the definition of widget \\"' + widgetKey7 + '\\"')}
|
||||||
|
w7 = new W7(owner, props7);
|
||||||
|
context.__owl__.cmap[7] = w7.__owl__.id;
|
||||||
|
def6 = w7._prepare();
|
||||||
|
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c4[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
|
||||||
|
} else {
|
||||||
|
def6 = def6 || w7._updateProps(props7, extra.forceUpdate, patchQueue7);
|
||||||
|
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c4[_5_index]=pvnode;});
|
||||||
|
}
|
||||||
|
def6.then(w7._applyPatchQueue.bind(w7, patchQueue7));
|
||||||
|
//WIDGET
|
||||||
|
let def9;
|
||||||
|
let w10 = 10 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[10]] : false;
|
||||||
|
let _8_index = c4.length;
|
||||||
|
c4.push(null);
|
||||||
|
let props10 = {val:context['state'].val};
|
||||||
|
if (w10 && w10.__owl__.renderPromise && !w10.__owl__.vnode) {
|
||||||
|
if (utils.shallowEqual(props10, w10.__owl__.renderProps)) {
|
||||||
|
def9 = w10.__owl__.renderPromise;
|
||||||
|
} else {
|
||||||
|
w10.destroy();
|
||||||
|
w10 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!w10) {
|
||||||
|
let widgetKey10 = \`AsyncChild\`;
|
||||||
|
let W10 = context.widgets && context.widgets[widgetKey10] || QWeb.widgets[widgetKey10];
|
||||||
|
if (!W10) {throw new Error('Cannot find the definition of widget \\"' + widgetKey10 + '\\"')}
|
||||||
|
w10 = new W10(owner, props10);
|
||||||
|
context.__owl__.cmap[10] = w10.__owl__.id;
|
||||||
|
def9 = w10._prepare();
|
||||||
|
def9 = def9.then(vnode=>{let pvnode=h(vnode.sel, {key: 10, hook: {insert(vn) {let nvn=w10._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});c4[_8_index]=pvnode;w10.__owl__.pvnode = pvnode;});
|
||||||
|
} else {
|
||||||
|
def9 = def9 || w10._updateProps(props10, extra.forceUpdate, extra.patchQueue);
|
||||||
|
def9 = def9.then(()=>{if (w10.__owl__.isDestroyed) {return};let pvnode=w10.__owl__.pvnode;c4[_8_index]=pvnode;});
|
||||||
|
}
|
||||||
|
extra.promises.push(def9);
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`async rendering t-widget with t-asyncroot directive: mixed re-renderings 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
|
let owner = context;
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2,on:{}};
|
||||||
|
var vn2 = h('button', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
if (!context['updateApp']) {
|
||||||
|
throw new Error('Missing handler \\\\'' + 'updateApp' + \`\\\\' when evaluating template 'Parent'\`)
|
||||||
|
}
|
||||||
|
extra.handlers['click' + 2] = extra.handlers['click' + 2] || context['updateApp'].bind(owner);
|
||||||
|
p2.on['click'] = extra.handlers['click' + 2];
|
||||||
|
c2.push({text: \`Update App State\`});
|
||||||
|
var _3 = 'children';
|
||||||
|
let c4 = [], p4 = {key:4,attrs:{class: _3}};
|
||||||
|
var vn4 = h('div', p4, c4);
|
||||||
|
c1.push(vn4);
|
||||||
|
//WIDGET
|
||||||
|
let def6;
|
||||||
|
let w7 = 7 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[7]] : false;
|
||||||
|
let _5_index = c4.length;
|
||||||
|
c4.push(null);
|
||||||
|
let props7 = {val:context['state'].val};
|
||||||
|
if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) {
|
||||||
|
if (utils.shallowEqual(props7, w7.__owl__.renderProps)) {
|
||||||
|
def6 = w7.__owl__.renderPromise;
|
||||||
|
} else {
|
||||||
|
w7.destroy();
|
||||||
|
w7 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!w7) {
|
||||||
|
let widgetKey7 = \`Child\`;
|
||||||
|
let W7 = context.widgets && context.widgets[widgetKey7] || QWeb.widgets[widgetKey7];
|
||||||
|
if (!W7) {throw new Error('Cannot find the definition of widget \\"' + widgetKey7 + '\\"')}
|
||||||
|
w7 = new W7(owner, props7);
|
||||||
|
context.__owl__.cmap[7] = w7.__owl__.id;
|
||||||
|
def6 = w7._prepare();
|
||||||
|
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c4[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
|
||||||
|
} else {
|
||||||
|
def6 = def6 || w7._updateProps(props7, extra.forceUpdate, extra.patchQueue);
|
||||||
|
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c4[_5_index]=pvnode;});
|
||||||
|
}
|
||||||
|
extra.promises.push(def6);
|
||||||
|
//WIDGET
|
||||||
|
let def9;
|
||||||
|
let w10 = 10 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[10]] : false;
|
||||||
|
let _8_index = c4.length;
|
||||||
|
const patchQueue10 = [];
|
||||||
|
c4.push(w10 && w10.__owl__.pvnode || null);
|
||||||
|
let props10 = {val:context['state'].val};
|
||||||
|
if (w10 && w10.__owl__.renderPromise && !w10.__owl__.vnode) {
|
||||||
|
if (utils.shallowEqual(props10, w10.__owl__.renderProps)) {
|
||||||
|
def9 = w10.__owl__.renderPromise;
|
||||||
|
} else {
|
||||||
|
w10.destroy();
|
||||||
|
w10 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!w10) {
|
||||||
|
let widgetKey10 = \`AsyncChild\`;
|
||||||
|
let W10 = context.widgets && context.widgets[widgetKey10] || QWeb.widgets[widgetKey10];
|
||||||
|
if (!W10) {throw new Error('Cannot find the definition of widget \\"' + widgetKey10 + '\\"')}
|
||||||
|
w10 = new W10(owner, props10);
|
||||||
|
context.__owl__.cmap[10] = w10.__owl__.id;
|
||||||
|
def9 = w10._prepare();
|
||||||
|
def9 = def9.then(vnode=>{let pvnode=h(vnode.sel, {key: 10, hook: {insert(vn) {let nvn=w10._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});c4[_8_index]=pvnode;w10.__owl__.pvnode = pvnode;});
|
||||||
|
} else {
|
||||||
|
def9 = def9 || w10._updateProps(props10, extra.forceUpdate, patchQueue10);
|
||||||
|
def9 = def9.then(()=>{if (w10.__owl__.isDestroyed) {return};let pvnode=w10.__owl__.pvnode;c4[_8_index]=pvnode;});
|
||||||
|
}
|
||||||
|
def9.then(w10._applyPatchQueue.bind(w10, patchQueue10));
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`class and style attributes with t-widget dynamic t-att-style is properly added and updated on widget root el 1`] = `
|
exports[`class and style attributes with t-widget dynamic t-att-style is properly added and updated on widget root el 1`] = `
|
||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
@@ -10,11 +253,11 @@ exports[`class and style attributes with t-widget dynamic t-att-style is properl
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
const _5 = context['state'].style;
|
const _5 = context['state'].style;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -51,11 +294,11 @@ exports[`class and style attributes with t-widget t-att-class is properly added/
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
|
let def3;
|
||||||
|
const _5 = {a:context['state'].a,b:context['state'].b};
|
||||||
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
let _2_index = c1.length;
|
let _2_index = c1.length;
|
||||||
c1.push(null);
|
c1.push(null);
|
||||||
let def3;
|
|
||||||
const _5 = {a: context['state'].a,b: context['state'].b};
|
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -98,23 +341,24 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = `
|
|||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = context['state'].numbers;
|
var _2 = context['state'].numbers;
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.number_first = i === 0;
|
context.number_first = i === 0;
|
||||||
context.number_last = i === _length3 - 1;
|
context.number_last = i === _length3 - 1;
|
||||||
context.number_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.number_index = i;
|
context.number_index = i;
|
||||||
context.number = _3[i];
|
context.number = _3[i];
|
||||||
context.number_value = _4[i];
|
context.number_value = _4[i];
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let key8 = context['number'];
|
let key8 = context['number'];
|
||||||
let _5_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def6;
|
let def6;
|
||||||
let w7 = key8 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[key8]] : false;
|
let w7 = key8 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[key8]] : false;
|
||||||
|
let _5_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props7 = {};
|
let props7 = {};
|
||||||
if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) {
|
if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props7, w7.__owl__.renderProps)) {
|
if (utils.shallowEqual(props7, w7.__owl__.renderProps)) {
|
||||||
@@ -152,10 +396,10 @@ exports[`composition t-widget with dynamic value 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -192,10 +436,10 @@ exports[`composition t-widget with dynamic value 2 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -232,10 +476,10 @@ exports[`other directives with t-widget t-on with handler bound to argument 1`]
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -272,10 +516,10 @@ exports[`other directives with t-widget t-on with handler bound to empty object
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -312,10 +556,10 @@ exports[`other directives with t-widget t-on with handler bound to empty object
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -352,10 +596,10 @@ exports[`other directives with t-widget t-on with handler bound to object 1`] =
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -372,7 +616,7 @@ exports[`other directives with t-widget t-on with handler bound to object 1`] =
|
|||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
def3 = w4._prepare();
|
def3 = w4._prepare();
|
||||||
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {val: 3}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {val:3}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
} else {
|
} else {
|
||||||
def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
@@ -392,10 +636,10 @@ exports[`other directives with t-widget t-on with prevent and self modifiers (or
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -432,10 +676,10 @@ exports[`other directives with t-widget t-on with self and prevent modifiers (or
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -472,10 +716,10 @@ exports[`other directives with t-widget t-on with self modifier 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -512,10 +756,10 @@ exports[`other directives with t-widget t-on with stop and/or prevent modifiers
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -553,10 +797,10 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
|
|||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let key5 = 'somestring';
|
let key5 = 'somestring';
|
||||||
let _2_index = c1.length;
|
|
||||||
c1.push(null);
|
|
||||||
let def3;
|
let def3;
|
||||||
let w4 = key5 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[key5]] : false;
|
let w4 = key5 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[key5]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
let props4 = {flag:context['state'].flag};
|
let props4 = {flag:context['state'].flag};
|
||||||
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
@@ -582,3 +826,211 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
|
|||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive .lazy modifier 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2,on:{}};
|
||||||
|
var vn2 = h('input', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
p2.props = {value: context.state['text']};
|
||||||
|
extra.handlers['change' + 2] = extra.handlers['change' + 2] || ((ev) => {context.state['text'] = ev.target.value});
|
||||||
|
p2.on['change'] = extra.handlers['change' + 2];
|
||||||
|
let c3 = [], p3 = {key:3};
|
||||||
|
var vn3 = h('span', p3, c3);
|
||||||
|
c1.push(vn3);
|
||||||
|
var _4 = context['state'].text;
|
||||||
|
if (_4 || _4 === 0) {
|
||||||
|
c3.push({text: _4});
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive basic use, on an input 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2,on:{}};
|
||||||
|
var vn2 = h('input', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
p2.props = {value: context.state['text']};
|
||||||
|
extra.handlers['input' + 2] = extra.handlers['input' + 2] || ((ev) => {context.state['text'] = ev.target.value});
|
||||||
|
p2.on['input'] = extra.handlers['input' + 2];
|
||||||
|
let c3 = [], p3 = {key:3};
|
||||||
|
var vn3 = h('span', p3, c3);
|
||||||
|
c1.push(vn3);
|
||||||
|
var _4 = context['state'].text;
|
||||||
|
if (_4 || _4 === 0) {
|
||||||
|
c3.push({text: _4});
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive on a select 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2,on:{}};
|
||||||
|
var vn2 = h('select', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
p2.props = {value: context.state['color']};
|
||||||
|
extra.handlers['change' + 2] = extra.handlers['change' + 2] || ((ev) => {context.state['color'] = ev.target.value});
|
||||||
|
p2.on['change'] = extra.handlers['change' + 2];
|
||||||
|
var _3 = '';
|
||||||
|
let c4 = [], p4 = {key:4,attrs:{value: _3}};
|
||||||
|
var vn4 = h('option', p4, c4);
|
||||||
|
c2.push(vn4);
|
||||||
|
c4.push({text: \`Please select one\`});
|
||||||
|
var _5 = 'red';
|
||||||
|
let c6 = [], p6 = {key:6,attrs:{value: _5}};
|
||||||
|
var vn6 = h('option', p6, c6);
|
||||||
|
c2.push(vn6);
|
||||||
|
c6.push({text: \`Red\`});
|
||||||
|
var _7 = 'blue';
|
||||||
|
let c8 = [], p8 = {key:8,attrs:{value: _7}};
|
||||||
|
var vn8 = h('option', p8, c8);
|
||||||
|
c2.push(vn8);
|
||||||
|
c8.push({text: \`Blue\`});
|
||||||
|
let c9 = [], p9 = {key:9};
|
||||||
|
var vn9 = h('span', p9, c9);
|
||||||
|
c1.push(vn9);
|
||||||
|
c9.push({text: \`Choice: \`});
|
||||||
|
var _10 = context['state'].color;
|
||||||
|
if (_10 || _10 === 0) {
|
||||||
|
c9.push({text: _10});
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive on an input type=radio 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
var _2 = 'radio';
|
||||||
|
var _3 = 'one';
|
||||||
|
var _4 = 'One';
|
||||||
|
let c5 = [], p5 = {key:5,attrs:{type: _2,id: _3,value: _4},on:{}};
|
||||||
|
var vn5 = h('input', p5, c5);
|
||||||
|
c1.push(vn5);
|
||||||
|
p5.props = {checked:context.state['choice'] === 'One'};
|
||||||
|
extra.handlers['click' + 5] = extra.handlers['click' + 5] || ((ev) => {context.state['choice'] = ev.target.value});
|
||||||
|
p5.on['click'] = extra.handlers['click' + 5];
|
||||||
|
var _6 = 'radio';
|
||||||
|
var _7 = 'two';
|
||||||
|
var _8 = 'Two';
|
||||||
|
let c9 = [], p9 = {key:9,attrs:{type: _6,id: _7,value: _8},on:{}};
|
||||||
|
var vn9 = h('input', p9, c9);
|
||||||
|
c1.push(vn9);
|
||||||
|
p9.props = {checked:context.state['choice'] === 'Two'};
|
||||||
|
extra.handlers['click' + 9] = extra.handlers['click' + 9] || ((ev) => {context.state['choice'] = ev.target.value});
|
||||||
|
p9.on['click'] = extra.handlers['click' + 9];
|
||||||
|
let c10 = [], p10 = {key:10};
|
||||||
|
var vn10 = h('span', p10, c10);
|
||||||
|
c1.push(vn10);
|
||||||
|
c10.push({text: \`Choice: \`});
|
||||||
|
var _11 = context['state'].choice;
|
||||||
|
if (_11 || _11 === 0) {
|
||||||
|
c10.push({text: _11});
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive on an input, type=checkbox 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
var _2 = 'checkbox';
|
||||||
|
let c3 = [], p3 = {key:3,attrs:{type: _2},on:{}};
|
||||||
|
var vn3 = h('input', p3, c3);
|
||||||
|
c1.push(vn3);
|
||||||
|
p3.props = {checked: context.state['flag']};
|
||||||
|
extra.handlers['input' + 3] = extra.handlers['input' + 3] || ((ev) => {context.state['flag'] = ev.target.checked});
|
||||||
|
p3.on['input'] = extra.handlers['input' + 3];
|
||||||
|
let c4 = [], p4 = {key:4};
|
||||||
|
var vn4 = h('span', p4, c4);
|
||||||
|
c1.push(vn4);
|
||||||
|
if (context['state'].flag) {
|
||||||
|
c4.push({text: \`yes\`});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
c4.push({text: \`no\`});
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive can define and call slots 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
|
let owner = context;
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
//WIDGET
|
||||||
|
let def3;
|
||||||
|
let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
|
let _2_index = c1.length;
|
||||||
|
c1.push(null);
|
||||||
|
let props4 = {};
|
||||||
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) {
|
||||||
|
if (utils.shallowEqual(props4, w4.__owl__.renderProps)) {
|
||||||
|
def3 = w4.__owl__.renderPromise;
|
||||||
|
} else {
|
||||||
|
w4.destroy();
|
||||||
|
w4 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!w4) {
|
||||||
|
let widgetKey4 = \`Dialog\`;
|
||||||
|
let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4];
|
||||||
|
if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')}
|
||||||
|
w4 = new W4(owner, props4);
|
||||||
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
|
w4.__owl__.slotId = 1;
|
||||||
|
def3 = w4._prepare();
|
||||||
|
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
|
} else {
|
||||||
|
def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
|
}
|
||||||
|
extra.promises.push(def3);
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive can define and call slots 2`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
var vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2};
|
||||||
|
var vn2 = h('div', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
const slot3 = this.slots[context.__owl__.slotId + '_' + 'header'];
|
||||||
|
c2.push(slot3(context.__owl__.parent, extra));
|
||||||
|
let c4 = [], p4 = {key:4};
|
||||||
|
var vn4 = h('div', p4, c4);
|
||||||
|
c1.push(vn4);
|
||||||
|
const slot5 = this.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||||
|
c4.push(slot5(context.__owl__.parent, extra));
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ exports[`attributes class and t-attf-class with ternary operation 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = 'hello ' + (context['value'] ? 'world' : '');
|
var _1 = 'hello ' + (context['value']?'world':'');
|
||||||
let c2 = [], p2 = {key:2,attrs:{class: _1}};
|
let c2 = [], p2 = {key:2,attrs:{class: _1}};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
@@ -70,7 +70,7 @@ exports[`attributes format expression 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = (context['value'] + 37);
|
var _1 = (context['value']+37);
|
||||||
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
@@ -81,7 +81,7 @@ exports[`attributes format expression, other format 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = (context['value'] + 37);
|
var _1 = (context['value']+37);
|
||||||
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
@@ -127,7 +127,7 @@ exports[`attributes from object variables set previously 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = {a: 'b'};
|
var _2 = {a:'b'};
|
||||||
var _3 = _2.a;
|
var _3 = _2.a;
|
||||||
let c4 = [], p4 = {key:4,attrs:{class: _3}};
|
let c4 = [], p4 = {key:4,attrs:{class: _3}};
|
||||||
var vn4 = h('span', p4, c4);
|
var vn4 = h('span', p4, c4);
|
||||||
@@ -223,7 +223,7 @@ exports[`attributes t-att-class with object 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = 'static';
|
var _1 = 'static';
|
||||||
var _3 = this.utils.objectToAttrString({a: context['b'],c: context['d'],e: context['f']});
|
var _3 = this.utils.objectToAttrString({a:context['b'],c:context['d'],e:context['f']});
|
||||||
var _2 = 'static' + (_3 ? ' ' + _3 : '');
|
var _2 = 'static' + (_3 ? ' ' + _3 : '');
|
||||||
let c4 = [], p4 = {key:4,attrs:{class: _2}};
|
let c4 = [], p4 = {key:4,attrs:{class: _2}};
|
||||||
var vn4 = h('div', p4, c4);
|
var vn4 = h('div', p4, c4);
|
||||||
@@ -246,7 +246,7 @@ exports[`attributes tuple literal 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = ['foo', 'bar'];
|
var _1 = ['foo','bar'];
|
||||||
let c2 = [], p2 = {key:2,attrs:{}};
|
let c2 = [], p2 = {key:2,attrs:{}};
|
||||||
if (_1 instanceof Array) {
|
if (_1 instanceof Array) {
|
||||||
p2.attrs[_1[0]] = _1[1];
|
p2.attrs[_1[0]] = _1[1];
|
||||||
@@ -303,7 +303,7 @@ exports[`debugging t-log 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = 42;
|
var _2 = 42;
|
||||||
console.log(_2 + 3)
|
console.log(_2+3)
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -317,14 +317,15 @@ exports[`foreach does not pollute the rendering context 1`] = `
|
|||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = [1];
|
var _2 = [1];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.item_first = i === 0;
|
context.item_first = i === 0;
|
||||||
context.item_last = i === _length3 - 1;
|
context.item_last = i === _length3 - 1;
|
||||||
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.item_index = i;
|
context.item_index = i;
|
||||||
context.item = _3[i];
|
context.item = _3[i];
|
||||||
context.item_value = _4[i];
|
context.item_value = _4[i];
|
||||||
@@ -344,16 +345,17 @@ exports[`foreach iterate on items (on a element node) 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = [1, 2];
|
var _2 = [1,2];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.item_first = i === 0;
|
context.item_first = i === 0;
|
||||||
context.item_last = i === _length3 - 1;
|
context.item_last = i === _length3 - 1;
|
||||||
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.item_index = i;
|
context.item_index = i;
|
||||||
context.item = _3[i];
|
context.item = _3[i];
|
||||||
context.item_value = _4[i];
|
context.item_value = _4[i];
|
||||||
@@ -376,16 +378,17 @@ exports[`foreach iterate on items 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = [3, 2, 1];
|
var _2 = [3,2,1];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.item_first = i === 0;
|
context.item_first = i === 0;
|
||||||
context.item_last = i === _length3 - 1;
|
context.item_last = i === _length3 - 1;
|
||||||
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.item_index = i;
|
context.item_index = i;
|
||||||
context.item = _3[i];
|
context.item = _3[i];
|
||||||
context.item_value = _4[i];
|
context.item_value = _4[i];
|
||||||
@@ -419,60 +422,15 @@ exports[`foreach iterate, dict param 1`] = `
|
|||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = context['value'];
|
var _2 = context['value'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
var _length3 = _3.length;
|
_3 = Object.keys(_2);
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
_4 = Object.values(_2);
|
||||||
for (let i = 0; i < _length3; i++) {
|
|
||||||
context.item_first = i === 0;
|
|
||||||
context.item_last = i === _length3 - 1;
|
|
||||||
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.item_index = i;
|
|
||||||
context.item = _3[i];
|
|
||||||
context.item_value = _4[i];
|
|
||||||
c1.push({text: \` [\`});
|
|
||||||
var _5 = context['item_index'];
|
|
||||||
if (_5 || _5 === 0) {
|
|
||||||
c1.push({text: _5});
|
|
||||||
}
|
|
||||||
c1.push({text: \`: \`});
|
|
||||||
var _6 = context['item'];
|
|
||||||
if (_6 || _6 === 0) {
|
|
||||||
c1.push({text: _6});
|
|
||||||
}
|
|
||||||
c1.push({text: \` \`});
|
|
||||||
var _7 = context['item_value'];
|
|
||||||
if (_7 || _7 === 0) {
|
|
||||||
c1.push({text: _7});
|
|
||||||
}
|
|
||||||
c1.push({text: \` - \`});
|
|
||||||
var _8 = context['item_parity'];
|
|
||||||
if (_8 || _8 === 0) {
|
|
||||||
c1.push({text: _8});
|
|
||||||
}
|
|
||||||
c1.push({text: \`] \`});
|
|
||||||
}
|
}
|
||||||
return vn1;
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`foreach iterate, integer param 1`] = `
|
|
||||||
"function anonymous(context,extra
|
|
||||||
) {
|
|
||||||
context = Object.create(context);
|
|
||||||
var h = this.utils.h;
|
|
||||||
let c1 = [], p1 = {key:1};
|
|
||||||
var vn1 = h('div', p1, c1);
|
|
||||||
var _2 = 3;
|
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.item_first = i === 0;
|
context.item_first = i === 0;
|
||||||
context.item_last = i === _length3 - 1;
|
context.item_last = i === _length3 - 1;
|
||||||
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.item_index = i;
|
context.item_index = i;
|
||||||
context.item = _3[i];
|
context.item = _3[i];
|
||||||
context.item_value = _4[i];
|
context.item_value = _4[i];
|
||||||
@@ -504,16 +462,17 @@ exports[`foreach iterate, position 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = 5;
|
var _2 = Array(5);
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.elem_first = i === 0;
|
context.elem_first = i === 0;
|
||||||
context.elem_last = i === _length3 - 1;
|
context.elem_last = i === _length3 - 1;
|
||||||
context.elem_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.elem_index = i;
|
context.elem_index = i;
|
||||||
context.elem = _3[i];
|
context.elem = _3[i];
|
||||||
context.elem_value = _4[i];
|
context.elem_value = _4[i];
|
||||||
@@ -525,7 +484,7 @@ exports[`foreach iterate, position 1`] = `
|
|||||||
c1.push({text: \` last\`});
|
c1.push({text: \` last\`});
|
||||||
}
|
}
|
||||||
c1.push({text: \` (\`});
|
c1.push({text: \` (\`});
|
||||||
var _5 = context['elem_parity'];
|
var _5 = context['elem_index'];
|
||||||
if (_5 || _5 === 0) {
|
if (_5 || _5 === 0) {
|
||||||
c1.push({text: _5});
|
c1.push({text: _5});
|
||||||
}
|
}
|
||||||
@@ -542,16 +501,17 @@ exports[`foreach warn if no key in some case 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = [1, 2];
|
var _2 = [1,2];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.item_first = i === 0;
|
context.item_first = i === 0;
|
||||||
context.item_last = i === _length3 - 1;
|
context.item_last = i === _length3 - 1;
|
||||||
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.item_index = i;
|
context.item_index = i;
|
||||||
context.item = _3[i];
|
context.item = _3[i];
|
||||||
context.item_value = _4[i];
|
context.item_value = _4[i];
|
||||||
@@ -605,14 +565,15 @@ exports[`misc global 1`] = `
|
|||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = [4,5,6];
|
var _2 = [4,5,6];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.value_first = i === 0;
|
context.value_first = i === 0;
|
||||||
context.value_last = i === _length3 - 1;
|
context.value_last = i === _length3 - 1;
|
||||||
context.value_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.value_index = i;
|
context.value_index = i;
|
||||||
context.value = _3[i];
|
context.value = _3[i];
|
||||||
context.value_value = _4[i];
|
context.value_value = _4[i];
|
||||||
@@ -624,13 +585,13 @@ exports[`misc global 1`] = `
|
|||||||
c5.push({text: _6});
|
c5.push({text: _6});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let _11 = 'bbb'
|
let _11 = 'bbb';
|
||||||
var _13 = 'agüero';
|
var _13 = 'agüero';
|
||||||
let c14 = [], p14 = {key:14,attrs:{\\"falló\\": _13},on:{}};
|
let c14 = [], p14 = {key:14,attrs:{\\"falló\\": _13},on:{}};
|
||||||
var vn14 = h('Año', p14, c14);
|
var vn14 = h('Año', p14, c14);
|
||||||
c1.push(vn14);
|
c1.push(vn14);
|
||||||
{
|
{
|
||||||
let _15 = 'aaa'
|
let _15 = 'aaa';
|
||||||
let c16 = [], p16 = {key:16,on:{}};
|
let c16 = [], p16 = {key:16,on:{}};
|
||||||
var vn16 = h('span', p16, c16);
|
var vn16 = h('span', p16, c16);
|
||||||
c14.push(vn16);
|
c14.push(vn16);
|
||||||
@@ -650,16 +611,13 @@ exports[`misc global 1`] = `
|
|||||||
c17.push({text: \`foo default\`});
|
c17.push({text: \`foo default\`});
|
||||||
}
|
}
|
||||||
var _19 = 'bbb';
|
var _19 = 'bbb';
|
||||||
{
|
let c20 = [], p20 = {key:20,on:{}};
|
||||||
let _19 = 'bbb'
|
var vn20 = h('span', p20, c20);
|
||||||
let c20 = [], p20 = {key:20,on:{}};
|
c14.push(vn20);
|
||||||
var vn20 = h('span', p20, c20);
|
if (_19 || _19 === 0) {
|
||||||
c14.push(vn20);
|
c20.push({text: _19});
|
||||||
if (_19 || _19 === 0) {
|
} else {
|
||||||
c20.push({text: _19});
|
c20.push({text: \`foo default\`});
|
||||||
} else {
|
|
||||||
c20.push({text: \`foo default\`});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -768,6 +726,18 @@ exports[`static templates empty div 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`static templates simple dynamic value 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
var _1 = context['text'];
|
||||||
|
if (_1 || _1 === 0) {
|
||||||
|
var vn2 = {text: _1};
|
||||||
|
}
|
||||||
|
return vn2;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`static templates simple string 1`] = `
|
exports[`static templates simple string 1`] = `
|
||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
@@ -777,6 +747,19 @@ exports[`static templates simple string 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`static templates simple string, with some dynamic value 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
var vn1 = {text: \`hello \`};
|
||||||
|
var _2 = context['text'];
|
||||||
|
if (_2 || _2 === 0) {
|
||||||
|
vn1.text += _2;
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-call (template calling basic caller 1`] = `
|
exports[`t-call (template calling basic caller 1`] = `
|
||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
@@ -795,11 +778,8 @@ exports[`t-call (template calling inherit context 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = 1;
|
var _2 = 1;
|
||||||
{
|
if (_2 || _2 === 0) {
|
||||||
let _2 = 1
|
c1.push({text: _2});
|
||||||
if (_2 || _2 === 0) {
|
|
||||||
c1.push({text: _2});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -812,7 +792,7 @@ exports[`t-call (template calling scoped parameters 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
{
|
{
|
||||||
let _2 = 42
|
let _2 = 42;
|
||||||
c1.push({text: \`ok\`});
|
c1.push({text: \`ok\`});
|
||||||
}
|
}
|
||||||
var _3 = context['foo'];
|
var _3 = context['foo'];
|
||||||
@@ -839,7 +819,7 @@ exports[`t-call (template calling with unused setbody 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
{
|
{
|
||||||
let _1 = 3
|
let _1 = 3;
|
||||||
let c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
c2.push({text: \`ok\`});
|
c2.push({text: \`ok\`});
|
||||||
@@ -866,7 +846,7 @@ exports[`t-call (template calling with used set body 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
{
|
{
|
||||||
let _2 = 'ok'
|
let _2 = 'ok';
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
c1.push({text: _2});
|
c1.push({text: _2});
|
||||||
}
|
}
|
||||||
@@ -955,13 +935,13 @@ exports[`t-if boolean value condition elif 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
if (context['color'] == 'black') {
|
if (context['color']=='black') {
|
||||||
c1.push({text: \`black pearl\`});
|
c1.push({text: \`black pearl\`});
|
||||||
}
|
}
|
||||||
else if (context['color'] == 'yellow') {
|
else if (context['color']=='yellow') {
|
||||||
c1.push({text: \`yellow submarine\`});
|
c1.push({text: \`yellow submarine\`});
|
||||||
}
|
}
|
||||||
else if (context['color'] == 'red') {
|
else if (context['color']=='red') {
|
||||||
c1.push({text: \`red is dead\`});
|
c1.push({text: \`red is dead\`});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1064,28 +1044,28 @@ exports[`t-if can use some boolean operators in expressions 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
if (context['cond1'] && context['cond2']) {
|
if (context['cond1']&&context['cond2']) {
|
||||||
c1.push({text: \`and\`});
|
c1.push({text: \`and\`});
|
||||||
}
|
}
|
||||||
if (context['cond1'] && context['cond3']) {
|
if (context['cond1']&&context['cond3']) {
|
||||||
c1.push({text: \`nope\`});
|
c1.push({text: \`nope\`});
|
||||||
}
|
}
|
||||||
if (context['cond1'] || context['cond3']) {
|
if (context['cond1']||context['cond3']) {
|
||||||
c1.push({text: \`or\`});
|
c1.push({text: \`or\`});
|
||||||
}
|
}
|
||||||
if (context['cond3'] || context['cond4']) {
|
if (context['cond3']||context['cond4']) {
|
||||||
c1.push({text: \`nope\`});
|
c1.push({text: \`nope\`});
|
||||||
}
|
}
|
||||||
if (context['m'] > 3) {
|
if (context['m']>3) {
|
||||||
c1.push({text: \`mgt\`});
|
c1.push({text: \`mgt\`});
|
||||||
}
|
}
|
||||||
if (context['n'] > 3) {
|
if (context['n']>3) {
|
||||||
c1.push({text: \`ngt\`});
|
c1.push({text: \`ngt\`});
|
||||||
}
|
}
|
||||||
if (context['m'] < 3) {
|
if (context['m']<3) {
|
||||||
c1.push({text: \`mlt\`});
|
c1.push({text: \`mlt\`});
|
||||||
}
|
}
|
||||||
if (context['n'] < 3) {
|
if (context['n']<3) {
|
||||||
c1.push({text: \`nlt\`});
|
c1.push({text: \`nlt\`});
|
||||||
}
|
}
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -1115,14 +1095,15 @@ exports[`t-key t-key directive in a list 1`] = `
|
|||||||
var vn1 = h('ul', p1, c1);
|
var vn1 = h('ul', p1, c1);
|
||||||
var _2 = context['beers'];
|
var _2 = context['beers'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.beer_first = i === 0;
|
context.beer_first = i === 0;
|
||||||
context.beer_last = i === _length3 - 1;
|
context.beer_last = i === _length3 - 1;
|
||||||
context.beer_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.beer_index = i;
|
context.beer_index = i;
|
||||||
context.beer = _3[i];
|
context.beer = _3[i];
|
||||||
context.beer_value = _4[i];
|
context.beer_value = _4[i];
|
||||||
@@ -1215,14 +1196,15 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
|
|||||||
var vn1 = h('ul', p1, c1);
|
var vn1 = h('ul', p1, c1);
|
||||||
var _2 = ['someval'];
|
var _2 = ['someval'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.action_first = i === 0;
|
context.action_first = i === 0;
|
||||||
context.action_last = i === _length3 - 1;
|
context.action_last = i === _length3 - 1;
|
||||||
context.action_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.action_index = i;
|
context.action_index = i;
|
||||||
context.action = _3[i];
|
context.action = _3[i];
|
||||||
context.action_value = _4[i];
|
context.action_value = _4[i];
|
||||||
@@ -1252,7 +1234,7 @@ exports[`t-on can bind handlers with object arguments 1`] = `
|
|||||||
if (!context['add']) {
|
if (!context['add']) {
|
||||||
throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)
|
throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)
|
||||||
}
|
}
|
||||||
p1.on['click'] = context['add'].bind(owner, {val: 5});
|
p1.on['click'] = context['add'].bind(owner, {val:5});
|
||||||
c1.push({text: \`Click\`});
|
c1.push({text: \`Click\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -1543,14 +1525,15 @@ exports[`t-ref refs in a loop 1`] = `
|
|||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = context['items'];
|
var _2 = context['items'];
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
|
var _3 = _4 = _2;
|
||||||
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
|
if (!(_2 instanceof Array)) {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
var _length3 = _3.length;
|
var _length3 = _3.length;
|
||||||
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
|
|
||||||
for (let i = 0; i < _length3; i++) {
|
for (let i = 0; i < _length3; i++) {
|
||||||
context.item_first = i === 0;
|
context.item_first = i === 0;
|
||||||
context.item_last = i === _length3 - 1;
|
context.item_last = i === _length3 - 1;
|
||||||
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.item_index = i;
|
context.item_index = i;
|
||||||
context.item = _3[i];
|
context.item = _3[i];
|
||||||
context.item_value = _4[i];
|
context.item_value = _4[i];
|
||||||
@@ -1578,7 +1561,7 @@ exports[`t-set evaluate value expression 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = 1 + 2;
|
var _2 = 1+2;
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
c1.push({text: _2});
|
c1.push({text: _2});
|
||||||
}
|
}
|
||||||
@@ -1592,7 +1575,7 @@ exports[`t-set evaluate value expression, part 2 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = context['somevariable'] + 2;
|
var _2 = context['somevariable']+2;
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
c1.push({text: _2});
|
c1.push({text: _2});
|
||||||
}
|
}
|
||||||
@@ -1668,7 +1651,7 @@ exports[`t-set t-set and t-if 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = context['value'];
|
var _2 = context['value'];
|
||||||
if (_2 === 'ok') {
|
if (_2==='ok') {
|
||||||
c1.push({text: \`grimbergen\`});
|
c1.push({text: \`grimbergen\`});
|
||||||
}
|
}
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -1681,7 +1664,7 @@ exports[`t-set t-set evaluates an expression only once 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = context['value'] + ' artois';
|
var _2 = context['value']+' artois';
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
c1.push({text: _2});
|
c1.push({text: _2});
|
||||||
}
|
}
|
||||||
@@ -1702,14 +1685,15 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
|
|||||||
var _2 = 1;
|
var _2 = 1;
|
||||||
var _3 = context['list'];
|
var _3 = context['list'];
|
||||||
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
if (typeof _3 === 'number') { _3 = Array.from(Array(_3).keys())}
|
var _4 = _5 = _3;
|
||||||
var _4 = _3 instanceof Array ? _3 : Object.keys(_3);
|
if (!(_3 instanceof Array)) {
|
||||||
|
_4 = Object.keys(_3);
|
||||||
|
_5 = Object.values(_3);
|
||||||
|
}
|
||||||
var _length4 = _4.length;
|
var _length4 = _4.length;
|
||||||
var _5 = _3 instanceof Array ? _3 : Object.values(_3);
|
|
||||||
for (let i = 0; i < _length4; i++) {
|
for (let i = 0; i < _length4; i++) {
|
||||||
context.elem_first = i === 0;
|
context.elem_first = i === 0;
|
||||||
context.elem_last = i === _length4 - 1;
|
context.elem_last = i === _length4 - 1;
|
||||||
context.elem_parity = i % 2 === 0 ? 'even' : 'odd';
|
|
||||||
context.elem_index = i;
|
context.elem_index = i;
|
||||||
context.elem = _4[i];
|
context.elem = _4[i];
|
||||||
context.elem_value = _5[i];
|
context.elem_value = _5[i];
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`connecting a component to store connecting a component to a local store 1`] = `"<div></div>"`;
|
||||||
|
|
||||||
|
exports[`connecting a component to store connecting a component to a local store 2`] = `"<div><span>hello</span></div>"`;
|
||||||
|
|
||||||
exports[`connecting a component to store connecting a component works 1`] = `"<div></div>"`;
|
exports[`connecting a component to store connecting a component works 1`] = `"<div></div>"`;
|
||||||
|
|
||||||
exports[`connecting a component to store connecting a component works 2`] = `"<div><span>hello</span></div>"`;
|
exports[`connecting a component to store connecting a component works 2`] = `"<div><span>hello</span></div>"`;
|
||||||
|
|||||||
+131
-2
@@ -253,11 +253,11 @@ describe("animations", () => {
|
|||||||
widget.state.display = false;
|
widget.state.display = false;
|
||||||
patchNextFrame(cb => {
|
patchNextFrame(cb => {
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-leave chimay-leave-active">blue</span></div>'
|
'<div><span class="chimay-leave chimay-leave-active" data-owl-key="4">blue</span></div>'
|
||||||
);
|
);
|
||||||
cb();
|
cb();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-leave-active chimay-leave-to">blue</span></div>'
|
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="4">blue</span></div>'
|
||||||
);
|
);
|
||||||
def.resolve();
|
def.resolve();
|
||||||
});
|
});
|
||||||
@@ -287,4 +287,133 @@ describe("animations", () => {
|
|||||||
expect(widget.f).toHaveBeenCalledTimes(1);
|
expect(widget.f).toHaveBeenCalledTimes(1);
|
||||||
unpatchNextFrame();
|
unpatchNextFrame();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-transition, remove and re-add before transitionend", async () => {
|
||||||
|
expect.assertions(11);
|
||||||
|
|
||||||
|
env.qweb.addTemplates(
|
||||||
|
`<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<button t-on-click="toggle">Toggle</button>
|
||||||
|
<span t-if="state.flag" t-transition="chimay">blue</span>
|
||||||
|
</div>
|
||||||
|
</templates>`
|
||||||
|
);
|
||||||
|
class Parent extends Widget {
|
||||||
|
constructor(parent) {
|
||||||
|
super(parent);
|
||||||
|
this.state = { flag: false };
|
||||||
|
}
|
||||||
|
toggle() {
|
||||||
|
this.state.flag = !this.state.flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = new Parent(env);
|
||||||
|
await widget.mount(fixture);
|
||||||
|
let button = widget.el!.querySelector("button");
|
||||||
|
|
||||||
|
let def = makeDeferred();
|
||||||
|
let phase = "enter";
|
||||||
|
patchNextFrame(cb => {
|
||||||
|
let spans = fixture.querySelectorAll("span");
|
||||||
|
expect(spans.length).toBe(1);
|
||||||
|
expect(spans[0].className).toBe(`chimay-${phase} chimay-${phase}-active`);
|
||||||
|
cb();
|
||||||
|
expect(spans[0].className).toBe(
|
||||||
|
`chimay-${phase}-active chimay-${phase}-to`
|
||||||
|
);
|
||||||
|
def.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
// click display the span
|
||||||
|
button!.click();
|
||||||
|
await def; // wait for the mocked repaint to be done
|
||||||
|
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><button>Toggle</button><span class="">blue</span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// click to remove the span, and click again to re-add it before transitionend
|
||||||
|
def = makeDeferred();
|
||||||
|
phase = "leave";
|
||||||
|
button!.click();
|
||||||
|
|
||||||
|
await def; // wait for the mocked repaint to be done
|
||||||
|
def = makeDeferred();
|
||||||
|
phase = "enter";
|
||||||
|
button!.click();
|
||||||
|
|
||||||
|
await def; // wait for the mocked repaint to be done
|
||||||
|
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><button>Toggle</button><span class="">blue</span></div>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-transition combined with t-widget, remove and re-add before transitionend", async () => {
|
||||||
|
expect.assertions(11);
|
||||||
|
|
||||||
|
env.qweb.addTemplates(
|
||||||
|
`<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<button t-on-click="toggle">Toggle</button>
|
||||||
|
<t t-if="state.flag" t-widget="Child" t-transition="chimay"/>
|
||||||
|
</div>
|
||||||
|
<span t-name="Child">blue</span>
|
||||||
|
</templates>`
|
||||||
|
);
|
||||||
|
class Child extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Child };
|
||||||
|
constructor(parent) {
|
||||||
|
super(parent);
|
||||||
|
this.state = { flag: false };
|
||||||
|
}
|
||||||
|
toggle() {
|
||||||
|
this.state.flag = !this.state.flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = new Parent(env);
|
||||||
|
await widget.mount(fixture);
|
||||||
|
let button = widget.el!.querySelector("button");
|
||||||
|
|
||||||
|
let def = makeDeferred();
|
||||||
|
let phase = "enter";
|
||||||
|
patchNextFrame(cb => {
|
||||||
|
let spans = fixture.querySelectorAll("span");
|
||||||
|
expect(spans.length).toBe(1);
|
||||||
|
expect(spans[0].className).toBe(`chimay-${phase} chimay-${phase}-active`);
|
||||||
|
cb();
|
||||||
|
expect(spans[0].className).toBe(
|
||||||
|
`chimay-${phase}-active chimay-${phase}-to`
|
||||||
|
);
|
||||||
|
def.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
// click display the span
|
||||||
|
button!.click();
|
||||||
|
await def; // wait for the mocked repaint to be done
|
||||||
|
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><button>Toggle</button><span class="">blue</span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// click to remove the span, and click again to re-add it before transitionend
|
||||||
|
def = makeDeferred();
|
||||||
|
phase = "leave";
|
||||||
|
button!.click();
|
||||||
|
|
||||||
|
await def; // wait for the mocked repaint to be done
|
||||||
|
def = makeDeferred();
|
||||||
|
phase = "enter";
|
||||||
|
button!.click();
|
||||||
|
|
||||||
|
await def; // wait for the mocked repaint to be done
|
||||||
|
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><button>Toggle</button><span class="">blue</span></div>'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+594
-20
@@ -6,7 +6,8 @@ import {
|
|||||||
makeTestEnv,
|
makeTestEnv,
|
||||||
nextMicroTick,
|
nextMicroTick,
|
||||||
nextTick,
|
nextTick,
|
||||||
normalize
|
normalize,
|
||||||
|
editInput
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -24,6 +25,7 @@ let env: Env;
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = makeTestFixture();
|
fixture = makeTestFixture();
|
||||||
env = makeTestEnv();
|
env = makeTestEnv();
|
||||||
|
env.qweb.addTemplate("Widget", "<div></div>");
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"Counter",
|
"Counter",
|
||||||
`<div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div>`
|
`<div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div>`
|
||||||
@@ -82,6 +84,19 @@ describe("basic widget properties", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div></div>");
|
expect(fixture.innerHTML).toBe("<div></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("crashes if it cannot find a template", async () => {
|
||||||
|
expect.assertions(1);
|
||||||
|
class SomeWidget extends Component<any, any, any> {}
|
||||||
|
const widget = new SomeWidget(env);
|
||||||
|
try {
|
||||||
|
await widget.mount(fixture);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe(
|
||||||
|
'Could not find template for component "SomeWidget"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("can be clicked on and updated", async () => {
|
test("can be clicked on and updated", async () => {
|
||||||
const counter = new Counter(env);
|
const counter = new Counter(env);
|
||||||
await counter.mount(fixture);
|
await counter.mount(fixture);
|
||||||
@@ -1144,6 +1159,39 @@ describe("composition", () => {
|
|||||||
expect(input2.value).toBe("test");
|
expect(input2.value).toBe("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("sub widget with t-ref and t-keepalive", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="ParentWidget">
|
||||||
|
<t t-if="state.ok"><t t-widget="ChildWidget" t-ref="child" t-keepalive="1"/></t>
|
||||||
|
</div>
|
||||||
|
<span t-name="ChildWidget">Hello</span>
|
||||||
|
</templates>`);
|
||||||
|
class ParentWidget extends Widget {
|
||||||
|
state = { ok: true };
|
||||||
|
widgets = { ChildWidget };
|
||||||
|
}
|
||||||
|
class ChildWidget extends Widget {}
|
||||||
|
const widget = new ParentWidget(env);
|
||||||
|
await widget.mount(fixture);
|
||||||
|
let child = children(widget)[0];
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span>Hello</span></div>");
|
||||||
|
expect(widget.refs.child).toEqual(child);
|
||||||
|
|
||||||
|
widget.state.ok = false;
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div></div>");
|
||||||
|
expect(widget.refs.child).toEqual(child);
|
||||||
|
|
||||||
|
widget.state.ok = true;
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span>Hello</span></div>");
|
||||||
|
expect(widget.refs.child).toEqual(child);
|
||||||
|
});
|
||||||
|
|
||||||
test("sub widgets rendered in a loop", async () => {
|
test("sub widgets rendered in a loop", async () => {
|
||||||
env.qweb.addTemplate("ChildWidget", `<span><t t-esc="props.n"/></span>`);
|
env.qweb.addTemplate("ChildWidget", `<span><t t-esc="props.n"/></span>`);
|
||||||
class ChildWidget extends Widget {}
|
class ChildWidget extends Widget {}
|
||||||
@@ -1815,7 +1863,7 @@ describe("random stuff/miscellaneous", () => {
|
|||||||
// directive as a key
|
// directive as a key
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"Test",
|
"Test",
|
||||||
`<div><t t-foreach="2">txt</t><t t-widget="widget"/></div>`
|
`<div><t t-foreach="Array(2)">txt</t><t t-widget="widget"/></div>`
|
||||||
);
|
);
|
||||||
class Test extends Widget {
|
class Test extends Widget {
|
||||||
widgets = { widget: Widget };
|
widgets = { widget: Widget };
|
||||||
@@ -2319,6 +2367,194 @@ describe("async rendering", () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
expect(destroyCount).toBe(0);
|
expect(destroyCount).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("delayed t-widget with t-asyncroot directive", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<button t-on-click="updateApp">Update App State</button>
|
||||||
|
<div class="children">
|
||||||
|
<t t-widget="Child" val="state.val"/>
|
||||||
|
<t t-widget="AsyncChild" t-asyncroot="1" val="state.val"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span t-name="Child"><t t-esc="props.val"/></span>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
|
||||||
|
let def;
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Child, AsyncChild };
|
||||||
|
state = { val: 0 };
|
||||||
|
|
||||||
|
updateApp() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Child extends Widget {}
|
||||||
|
class AsyncChild extends Child {
|
||||||
|
willUpdateProps() {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>0</span><span>0</span>"
|
||||||
|
);
|
||||||
|
|
||||||
|
// click on button to increment Parent counter
|
||||||
|
def = makeDeferred();
|
||||||
|
fixture.querySelector("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>1</span><span>0</span>"
|
||||||
|
);
|
||||||
|
|
||||||
|
def.resolve();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>1</span><span>1</span>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("fast t-widget with t-asyncroot directive", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<button t-on-click="updateApp">Update App State</button>
|
||||||
|
<div class="children">
|
||||||
|
<t t-widget="Child" t-asyncroot="1" val="state.val"/>
|
||||||
|
<t t-widget="AsyncChild" val="state.val"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span t-name="Child"><t t-esc="props.val"/></span>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
|
||||||
|
let def;
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Child, AsyncChild };
|
||||||
|
state = { val: 0 };
|
||||||
|
|
||||||
|
updateApp() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Child extends Widget {}
|
||||||
|
class AsyncChild extends Child {
|
||||||
|
willUpdateProps() {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>0</span><span>0</span>"
|
||||||
|
);
|
||||||
|
|
||||||
|
// click on button to increment Parent counter
|
||||||
|
def = makeDeferred();
|
||||||
|
fixture.querySelector("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>1</span><span>0</span>"
|
||||||
|
);
|
||||||
|
|
||||||
|
def.resolve();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>1</span><span>1</span>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-widget with t-asyncroot directive: mixed re-renderings", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<button t-on-click="updateApp">Update App State</button>
|
||||||
|
<div class="children">
|
||||||
|
<t t-widget="Child" val="state.val"/>
|
||||||
|
<t t-widget="AsyncChild" t-asyncroot="1" val="state.val"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span t-name="Child" t-on-click="increment">
|
||||||
|
<t t-esc="state.val"/>/<t t-esc="props.val"/>
|
||||||
|
</span>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
|
||||||
|
let def;
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Child, AsyncChild };
|
||||||
|
state = { val: 0 };
|
||||||
|
|
||||||
|
updateApp() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Child extends Widget {
|
||||||
|
state = { val: 0 };
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class AsyncChild extends Child {
|
||||||
|
willUpdateProps() {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>0/0</span><span>0/0</span>"
|
||||||
|
);
|
||||||
|
|
||||||
|
// click on button to increment Parent counter
|
||||||
|
def = makeDeferred();
|
||||||
|
fixture.querySelector("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>0/1</span><span>0/0</span>"
|
||||||
|
);
|
||||||
|
|
||||||
|
// click on each Child to increment their local counter
|
||||||
|
const children = parent.el!.querySelectorAll("span");
|
||||||
|
children[0]!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>1/1</span><span>0/0</span>"
|
||||||
|
);
|
||||||
|
|
||||||
|
children[1]!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>1/1</span><span>1/0</span>"
|
||||||
|
);
|
||||||
|
|
||||||
|
// finalize first re-rendering (coming from the props update)
|
||||||
|
def.resolve();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.querySelector(".children")!.innerHTML).toBe(
|
||||||
|
"<span>1/1</span><span>1/1</span>"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("updating environment", () => {
|
describe("updating environment", () => {
|
||||||
@@ -2465,24 +2701,6 @@ describe("widget and observable state", () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("widget can add observed keys to its state", async () => {
|
|
||||||
env.qweb.addTemplate(
|
|
||||||
"TestWidget",
|
|
||||||
`<div><t t-esc="state.a"/><t t-esc="state.b"/></div>`
|
|
||||||
);
|
|
||||||
class TestWidget extends Widget {
|
|
||||||
state: any = { a: 1 };
|
|
||||||
}
|
|
||||||
const widget = new TestWidget(env);
|
|
||||||
await widget.mount(fixture);
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div>1</div>");
|
|
||||||
|
|
||||||
widget.set(widget.state, "b", 2);
|
|
||||||
await nextTick();
|
|
||||||
expect(fixture.innerHTML).toBe("<div>12</div>");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("t-mounted directive", () => {
|
describe("t-mounted directive", () => {
|
||||||
@@ -2598,3 +2816,359 @@ describe("can deduce template from name", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<span>Rochefort 10</span>");
|
expect(fixture.innerHTML).toBe("<span>Rochefort 10</span>");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("t-slot directive", () => {
|
||||||
|
test("can define and call slots", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<t t-widget="Dialog">
|
||||||
|
<t t-set="header"><span>header</span></t>
|
||||||
|
<t t-set="footer"><span>footer</span></t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div t-name="Dialog">
|
||||||
|
<div><t t-slot="header"/></div>
|
||||||
|
<div><t t-slot="footer"/></div>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><div><span>header</span></div><div><span>footer</span></div></div></div>"
|
||||||
|
);
|
||||||
|
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||||
|
expect(env.qweb.templates.Dialog.fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("slots are rendered with proper context", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<span class="counter"><t t-esc="state.val"/></span>
|
||||||
|
<t t-widget="Dialog">
|
||||||
|
<t t-set="footer"><button t-on-click="doSomething">do something</button></t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<span t-name="Dialog"><t t-slot="footer"/></span>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Dialog };
|
||||||
|
state = { val: 0 };
|
||||||
|
doSomething() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
fixture.querySelector("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("refs are properly bound in slots", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<span class="counter"><t t-esc="state.val"/></span>
|
||||||
|
<t t-widget="Dialog">
|
||||||
|
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<span t-name="Dialog"><t t-slot="footer"/></span>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Dialog };
|
||||||
|
state = { val: 0 };
|
||||||
|
doSomething() {
|
||||||
|
this.state.val++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
(<any>parent.refs.myButton).click();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("t-model directive", () => {
|
||||||
|
test("basic use, on an input", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="SomeComponent">
|
||||||
|
<input t-model="text"/>
|
||||||
|
<span><t t-esc="state.text"/></span>
|
||||||
|
</div>
|
||||||
|
</templates>`);
|
||||||
|
class SomeComponent extends Widget {
|
||||||
|
state = { text: "" };
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent(env);
|
||||||
|
await comp.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
||||||
|
|
||||||
|
const input = fixture.querySelector("input")!;
|
||||||
|
await editInput(input, "test");
|
||||||
|
expect(comp.state.text).toBe("test");
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
||||||
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("on an input, type=checkbox", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="SomeComponent">
|
||||||
|
<input type="checkbox" t-model="flag"/>
|
||||||
|
<span>
|
||||||
|
<t t-if="state.flag">yes</t>
|
||||||
|
<t t-else="1">no</t>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</templates>`);
|
||||||
|
class SomeComponent extends Widget {
|
||||||
|
state = { flag: false };
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent(env);
|
||||||
|
await comp.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><input type="checkbox"><span>no</span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = fixture.querySelector("input")!;
|
||||||
|
input.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><input type="checkbox"><span>yes</span></div>'
|
||||||
|
);
|
||||||
|
expect(comp.state.flag).toBe(true);
|
||||||
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
||||||
|
|
||||||
|
input.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(comp.state.flag).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("on an textarea", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="SomeComponent">
|
||||||
|
<textarea t-model="text"/>
|
||||||
|
<span><t t-esc="state.text"/></span>
|
||||||
|
</div>
|
||||||
|
</templates>`);
|
||||||
|
class SomeComponent extends Widget {
|
||||||
|
state = { text: "" };
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent(env);
|
||||||
|
await comp.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><textarea></textarea><span></span></div>"
|
||||||
|
);
|
||||||
|
|
||||||
|
const textarea = fixture.querySelector("textarea")!;
|
||||||
|
await editInput(textarea, "test");
|
||||||
|
expect(comp.state.text).toBe("test");
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><textarea></textarea><span>test</span></div>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("on an input type=radio", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="SomeComponent">
|
||||||
|
<input type="radio" id="one" value="One" t-model="choice"/>
|
||||||
|
<input type="radio" id="two" value="Two" t-model="choice"/>
|
||||||
|
<span>Choice: <t t-esc="state.choice"/></span>
|
||||||
|
</div>
|
||||||
|
</templates>`);
|
||||||
|
class SomeComponent extends Widget {
|
||||||
|
state = { choice: "" };
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent(env);
|
||||||
|
await comp.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: </span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstInput = fixture.querySelector("input")!;
|
||||||
|
firstInput.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(comp.state.choice).toBe("One");
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: One</span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
const secondInput = fixture.querySelectorAll("input")[1];
|
||||||
|
secondInput.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(comp.state.choice).toBe("Two");
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><input type="radio" id="one" value="One"><input type="radio" id="two" value="Two"><span>Choice: Two</span></div>'
|
||||||
|
);
|
||||||
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("on a select", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="SomeComponent">
|
||||||
|
<select t-model="color">
|
||||||
|
<option value="">Please select one</option>
|
||||||
|
<option value="red">Red</option>
|
||||||
|
<option value="blue">Blue</option>
|
||||||
|
</select>
|
||||||
|
<span>Choice: <t t-esc="state.color"/></span>
|
||||||
|
</div>
|
||||||
|
</templates>`);
|
||||||
|
class SomeComponent extends Widget {
|
||||||
|
state = { color: "" };
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent(env);
|
||||||
|
await comp.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><select><option value="">Please select one</option><option value="red">Red</option><option value="blue">Blue</option></select><span>Choice: </span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
const select = fixture.querySelector("select")!;
|
||||||
|
select.value = "red";
|
||||||
|
select.dispatchEvent(new Event("change"));
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(comp.state.color).toBe("red");
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
'<div><select><option value="">Please select one</option><option value="red">Red</option><option value="blue">Blue</option></select><span>Choice: red</span></div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(".lazy modifier", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="SomeComponent">
|
||||||
|
<input t-model.lazy="text"/>
|
||||||
|
<span><t t-esc="state.text"/></span>
|
||||||
|
</div>
|
||||||
|
</templates>`);
|
||||||
|
class SomeComponent extends Widget {
|
||||||
|
state = { text: "" };
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent(env);
|
||||||
|
await comp.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
||||||
|
|
||||||
|
const input = fixture.querySelector("input")!;
|
||||||
|
input.value = "test";
|
||||||
|
input.dispatchEvent(new Event("input"));
|
||||||
|
await nextTick();
|
||||||
|
expect(comp.state.text).toBe("");
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
||||||
|
input.dispatchEvent(new Event("change"));
|
||||||
|
await nextTick();
|
||||||
|
expect(comp.state.text).toBe("test");
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
||||||
|
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(".trim modifier", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="SomeComponent">
|
||||||
|
<input t-model.trim="text"/>
|
||||||
|
<span><t t-esc="state.text"/></span>
|
||||||
|
</div>
|
||||||
|
</templates>`);
|
||||||
|
class SomeComponent extends Widget {
|
||||||
|
state = { text: "" };
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent(env);
|
||||||
|
await comp.mount(fixture);
|
||||||
|
|
||||||
|
const input = fixture.querySelector("input")!;
|
||||||
|
await editInput(input, " test ");
|
||||||
|
expect(comp.state.text).toBe("test");
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test(".number modifier", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="SomeComponent">
|
||||||
|
<input t-model.number="number"/>
|
||||||
|
<span><t t-esc="state.number"/></span>
|
||||||
|
</div>
|
||||||
|
</templates>`);
|
||||||
|
class SomeComponent extends Widget {
|
||||||
|
state = { number: 0 };
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent(env);
|
||||||
|
await comp.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span>0</span></div>");
|
||||||
|
|
||||||
|
const input = fixture.querySelector("input")!;
|
||||||
|
await editInput(input, "13");
|
||||||
|
expect(comp.state.number).toBe(13);
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span>13</span></div>");
|
||||||
|
|
||||||
|
await editInput(input, "invalid");
|
||||||
|
expect(comp.state.number).toBe("invalid");
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span>invalid</span></div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("content is the default slot", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="Parent">
|
||||||
|
<t t-widget="Dialog">
|
||||||
|
<span>sts rocks</span>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div t-name="Dialog"><t t-slot="default"/></div>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class Dialog extends Widget {}
|
||||||
|
class Parent extends Widget {
|
||||||
|
widgets = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent(env);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>sts rocks</span></div></div>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/**
|
||||||
|
* Doc Link Checker
|
||||||
|
*
|
||||||
|
* We define here a test to make sure that there are no dead link in the Owl
|
||||||
|
* documentation.
|
||||||
|
*/
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
const LINK_REGEXP = /\[([^\[]+)\]\(([^\)]+)\)/g;
|
||||||
|
const HEADING_REGEXP = /\n(#+\s*)(.*)/g;
|
||||||
|
|
||||||
|
// files to be checked
|
||||||
|
function getFiles(): string[] {
|
||||||
|
const DOCFILES = fs.readdirSync("doc").map(f => `doc/${f}`);
|
||||||
|
const MAINREADME = "README.md";
|
||||||
|
return DOCFILES.concat(MAINREADME);
|
||||||
|
}
|
||||||
|
|
||||||
|
test("All markdown links work", () => {
|
||||||
|
let linkNumber = 0;
|
||||||
|
let invalidLinkNumber = 0;
|
||||||
|
const files = getFiles();
|
||||||
|
const data = readDocData(files);
|
||||||
|
for (let file of data) {
|
||||||
|
for (let link of file.links) {
|
||||||
|
// DEBUG: uncomment next line
|
||||||
|
// console.warn(`Checking "${link.name}" in "${file.name}"`);
|
||||||
|
linkNumber++;
|
||||||
|
if (!isLinkValid(link, file, data)) {
|
||||||
|
console.warn(`Invalid Link: "${link.name}" in "${file.name}"`);
|
||||||
|
invalidLinkNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(invalidLinkNumber).toBe(0);
|
||||||
|
expect(linkNumber).toBeGreaterThan(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
interface MarkDownLink {
|
||||||
|
name: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MarkDownSection {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileData {
|
||||||
|
name: string;
|
||||||
|
links: MarkDownLink[];
|
||||||
|
sections: MarkDownSection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLinkValid(
|
||||||
|
link: MarkDownLink,
|
||||||
|
current: FileData,
|
||||||
|
files: FileData[]
|
||||||
|
): boolean {
|
||||||
|
const parts = link.link.split("#");
|
||||||
|
const currentParts = current.name.split("/");
|
||||||
|
const path = currentParts.length > 1 ? currentParts[0] + '/' : "";
|
||||||
|
const fullName = path + parts[0];
|
||||||
|
if (parts.length === 1) {
|
||||||
|
// no # in url
|
||||||
|
if (parts[0].endsWith(".md")) {
|
||||||
|
// it is a local md file
|
||||||
|
if (!files.find(f => f.name === fullName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const file =
|
||||||
|
parts[0] === "" ? current : files.find(f => f.name === fullName);
|
||||||
|
if (!file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!file.sections.find(s => s.slug === parts[1])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
|
||||||
|
function slugify(str) {
|
||||||
|
const a = "àáäâãåăæçèéëêǵḧìíïîḿńǹñòóöôœøṕŕßśșțùúüûǘẃẍÿź·_,:;";
|
||||||
|
const b = "aaaaaaaaceeeeghiiiimnnnooooooprssstuuuuuwxyz-----";
|
||||||
|
const p = new RegExp(a.split("").join("|"), "g");
|
||||||
|
return str
|
||||||
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\//g, "") // remove /
|
||||||
|
.replace(/\s+/g, "-") // Replace spaces with -
|
||||||
|
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
||||||
|
.replace(/&/g, "-and-") // Replace & with ‘and’
|
||||||
|
.replace(/[^\w\-]+/g, "") // Remove all non-word characters
|
||||||
|
.replace(/\-\-+/g, "-") // Replace multiple - with single -
|
||||||
|
.replace(/^-+/, "") // Trim - from start of text
|
||||||
|
.replace(/-+$/, ""); // Trim - from end of text
|
||||||
|
}
|
||||||
|
|
||||||
|
function readDocData(files: string[]): FileData[] {
|
||||||
|
const result: FileData[] = [];
|
||||||
|
|
||||||
|
for (let file of files) {
|
||||||
|
const fileData: FileData = {
|
||||||
|
name: file,
|
||||||
|
links: [],
|
||||||
|
sections: []
|
||||||
|
};
|
||||||
|
const content = fs.readFileSync(file, { encoding: "utf8" });
|
||||||
|
let m;
|
||||||
|
// get links info
|
||||||
|
do {
|
||||||
|
m = LINK_REGEXP.exec(content);
|
||||||
|
if (m) {
|
||||||
|
fileData.links.push({ name: m[0], link: m[2] });
|
||||||
|
}
|
||||||
|
} while (m);
|
||||||
|
// get sections info
|
||||||
|
do {
|
||||||
|
m = HEADING_REGEXP.exec(content);
|
||||||
|
if (m) {
|
||||||
|
fileData.sections.push({ name: m[0], slug: slugify(m[2]) });
|
||||||
|
}
|
||||||
|
} while (m);
|
||||||
|
|
||||||
|
result.push(fileData);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -91,3 +91,13 @@ export function patchNextFrame(f: Function) {
|
|||||||
export function unpatchNextFrame() {
|
export function unpatchNextFrame() {
|
||||||
UTILS.nextFrame = nextFrame;
|
UTILS.nextFrame = nextFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function editInput(
|
||||||
|
input: HTMLInputElement | HTMLTextAreaElement,
|
||||||
|
value: string
|
||||||
|
) {
|
||||||
|
input.value = value;
|
||||||
|
input.dispatchEvent(new Event("input"));
|
||||||
|
input.dispatchEvent(new Event("change"));
|
||||||
|
return nextTick();
|
||||||
|
}
|
||||||
|
|||||||
@@ -134,6 +134,57 @@ describe("observer", () => {
|
|||||||
expect(arr[0].__owl__.rev).toBe(3);
|
expect(arr[0].__owl__.rev).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("set new property on observed object", async () => {
|
||||||
|
const observer = new Observer();
|
||||||
|
observer.notifyCB = jest.fn();
|
||||||
|
const state: any = { a: 1 };
|
||||||
|
observer.observe(state);
|
||||||
|
expect(state.__owl__.rev).toBe(1);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
expect(observer.notifyCB).toBeCalledTimes(0);
|
||||||
|
|
||||||
|
Observer.set(state, "b", 8);
|
||||||
|
await nextMicroTick();
|
||||||
|
expect(state.__owl__.rev).toBe(2);
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(observer.notifyCB).toBeCalledTimes(1);
|
||||||
|
expect(state.b).toBe(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("delete property from observed object", async () => {
|
||||||
|
const observer = new Observer();
|
||||||
|
observer.notifyCB = jest.fn();
|
||||||
|
const state: any = { a: 1, b: 8 };
|
||||||
|
observer.observe(state);
|
||||||
|
expect(state.__owl__.rev).toBe(1);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
expect(observer.notifyCB).toBeCalledTimes(0);
|
||||||
|
|
||||||
|
Observer.delete(state, "b");
|
||||||
|
await nextMicroTick();
|
||||||
|
expect(state.__owl__.rev).toBe(2);
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(observer.notifyCB).toBeCalledTimes(1);
|
||||||
|
expect(state).toEqual({ a: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("set element in observed array", async () => {
|
||||||
|
const observer = new Observer();
|
||||||
|
observer.notifyCB = jest.fn();
|
||||||
|
const state: any = ["a"];
|
||||||
|
observer.observe(state);
|
||||||
|
expect(state.__owl__.rev).toBe(1);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
expect(observer.notifyCB).toBeCalledTimes(0);
|
||||||
|
|
||||||
|
Observer.set(state, 1, "b");
|
||||||
|
await nextMicroTick();
|
||||||
|
expect(state.__owl__.rev).toBe(2);
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(observer.notifyCB).toBeCalledTimes(1);
|
||||||
|
expect(state).toEqual(["a", "b"]);
|
||||||
|
});
|
||||||
|
|
||||||
test("properly observe arrays in object", () => {
|
test("properly observe arrays in object", () => {
|
||||||
const observer = new Observer();
|
const observer = new Observer();
|
||||||
const state: any = { arr: [] };
|
const state: any = { arr: [] };
|
||||||
|
|||||||
+22
-38
@@ -24,6 +24,16 @@ describe("static templates", () => {
|
|||||||
expect(renderToString(qweb, "test")).toBe("hello vdom");
|
expect(renderToString(qweb, "test")).toBe("hello vdom");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("simple dynamic value", () => {
|
||||||
|
qweb.addTemplate("test", "<t><t t-esc=\"text\"/></t>");
|
||||||
|
expect(renderToString(qweb, "test", {text: "hello vdom"})).toBe("hello vdom");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("simple string, with some dynamic value", () => {
|
||||||
|
qweb.addTemplate("test", "<t>hello <t t-esc=\"text\"/></t>");
|
||||||
|
expect(renderToString(qweb, "test", {text: "vdom"})).toBe("hello vdom");
|
||||||
|
});
|
||||||
|
|
||||||
test("empty div", () => {
|
test("empty div", () => {
|
||||||
qweb.addTemplate("test", "<div></div>");
|
qweb.addTemplate("test", "<div></div>");
|
||||||
expect(renderToString(qweb, "test")).toBe("<div></div>");
|
expect(renderToString(qweb, "test")).toBe("<div></div>");
|
||||||
@@ -84,23 +94,6 @@ describe("error handling", () => {
|
|||||||
).toThrow("Missing event name with t-on directive");
|
).toThrow("Missing event name with t-on directive");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("error when compiled code is invalid", () => {
|
|
||||||
const consoleWarn = console.warn;
|
|
||||||
const consoleGroupCollapsed = console.groupCollapsed;
|
|
||||||
console.warn = jest.fn();
|
|
||||||
console.groupCollapsed = jest.fn();
|
|
||||||
qweb.addTemplate(
|
|
||||||
"templatename",
|
|
||||||
`<div t-att-hey="}/^function invalid{{>'"></div>`
|
|
||||||
);
|
|
||||||
expect(() => qweb.render("templatename")).toThrow(
|
|
||||||
"Invalid generated code while compiling template 'templatename': Unexpected token }"
|
|
||||||
);
|
|
||||||
expect(console.warn).toBeCalledTimes(1);
|
|
||||||
console.warn = consoleWarn;
|
|
||||||
console.groupCollapsed = consoleGroupCollapsed;
|
|
||||||
});
|
|
||||||
|
|
||||||
test("error when unknown directive", () => {
|
test("error when unknown directive", () => {
|
||||||
qweb.addTemplate(
|
qweb.addTemplate(
|
||||||
"templatename",
|
"templatename",
|
||||||
@@ -184,7 +177,7 @@ describe("t-set", () => {
|
|||||||
test("t-set and t-if", () => {
|
test("t-set and t-if", () => {
|
||||||
qweb.addTemplate(
|
qweb.addTemplate(
|
||||||
"test",
|
"test",
|
||||||
`<div >
|
`<div>
|
||||||
<t t-set="v" t-value="value"/>
|
<t t-set="v" t-value="value"/>
|
||||||
<t t-if="v === 'ok'">grimbergen</t>
|
<t t-if="v === 'ok'">grimbergen</t>
|
||||||
</div>`
|
</div>`
|
||||||
@@ -485,7 +478,7 @@ describe("attributes", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("format expression, other format", () => {
|
test("format expression, other format", () => {
|
||||||
qweb.addTemplate("test", `<div t-attf-foo="#{value + 37}"/>`);
|
qweb.addTemplate("test", `<div t-attf-foo="{{value + 37}}"/>`);
|
||||||
const result = renderToString(qweb, "test", { value: 5 });
|
const result = renderToString(qweb, "test", { value: 5 });
|
||||||
expect(result).toBe(`<div foo="42"></div>`);
|
expect(result).toBe(`<div foo="42"></div>`);
|
||||||
});
|
});
|
||||||
@@ -530,7 +523,10 @@ describe("attributes", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("class and t-attf-class with ternary operation", () => {
|
test("class and t-attf-class with ternary operation", () => {
|
||||||
qweb.addTemplate("test", `<div class="hello" t-attf-class="#{value ? 'world' : ''}"/>`);
|
qweb.addTemplate(
|
||||||
|
"test",
|
||||||
|
`<div class="hello" t-attf-class="{{value ? 'world' : ''}}"/>`
|
||||||
|
);
|
||||||
const result = renderToString(qweb, "test", { value: true });
|
const result = renderToString(qweb, "test", { value: true });
|
||||||
expect(result).toBe(`<div class="hello world"></div>`);
|
expect(result).toBe(`<div class="hello world"></div>`);
|
||||||
});
|
});
|
||||||
@@ -657,25 +653,13 @@ describe("foreach", () => {
|
|||||||
"test",
|
"test",
|
||||||
`
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-foreach="5" t-as="elem">
|
<t t-foreach="Array(5)" t-as="elem">
|
||||||
-<t t-if="elem_first"> first</t><t t-if="elem_last"> last</t> (<t t-esc="elem_parity"/>)
|
-<t t-if="elem_first"> first</t><t t-if="elem_last"> last</t> (<t t-esc="elem_index"/>)
|
||||||
</t>
|
</t>
|
||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
const result = trim(renderToString(qweb, "test"));
|
const result = trim(renderToString(qweb, "test"));
|
||||||
const expected = `<div>-first(even)-(odd)-(even)-(odd)-last(even)</div>`;
|
const expected = `<div>-first(0)-(1)-(2)-(3)-last(4)</div>`;
|
||||||
expect(result).toBe(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("iterate, integer param", () => {
|
|
||||||
qweb.addTemplate(
|
|
||||||
"test",
|
|
||||||
`<div><t t-foreach="3" t-as="item">
|
|
||||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
|
||||||
</t></div>`
|
|
||||||
);
|
|
||||||
const result = trim(renderToString(qweb, "test"));
|
|
||||||
const expected = `<div>[0:00][1:11][2:22]</div>`;
|
|
||||||
expect(result).toBe(expected);
|
expect(result).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -685,14 +669,14 @@ describe("foreach", () => {
|
|||||||
`
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-foreach="value" t-as="item">
|
<t t-foreach="value" t-as="item">
|
||||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/> - <t t-esc="item_parity"/>]
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||||
</t>
|
</t>
|
||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
const result = trim(
|
const result = trim(
|
||||||
renderToString(qweb, "test", { value: { a: 1, b: 2, c: 3 } })
|
renderToString(qweb, "test", { value: { a: 1, b: 2, c: 3 } })
|
||||||
);
|
);
|
||||||
const expected = `<div>[0:a1-even][1:b2-odd][2:c3-even]</div>`;
|
const expected = `<div>[0:a1][1:b2][2:c3]</div>`;
|
||||||
expect(result).toBe(expected);
|
expect(result).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1121,7 +1105,7 @@ describe("loading templates", () => {
|
|||||||
test("does not crash if string does not have templates", () => {
|
test("does not crash if string does not have templates", () => {
|
||||||
const data = "";
|
const data = "";
|
||||||
qweb.addTemplates(data);
|
qweb.addTemplates(data);
|
||||||
expect(Object.keys(qweb.templates)).toEqual(["default"]);
|
expect(Object.keys(qweb.templates)).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import { compileExpr, tokenize } from "../src/qweb_expressions";
|
||||||
|
|
||||||
|
describe("tokenizer", () => {
|
||||||
|
test("simple tokens", () => {
|
||||||
|
expect(tokenize("1.3")).toEqual([
|
||||||
|
{ type: "VALUE", value: "1.3" }
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(tokenize("{}")).toEqual([
|
||||||
|
{ type: "LEFT_BRACE", value: "{" },
|
||||||
|
{ type: "RIGHT_BRACE", value: "}" }
|
||||||
|
]);
|
||||||
|
expect(tokenize("{ }}")).toEqual([
|
||||||
|
{ type: "LEFT_BRACE", value: "{" },
|
||||||
|
{ type: "RIGHT_BRACE", value: "}" },
|
||||||
|
{ type: "RIGHT_BRACE", value: "}" }
|
||||||
|
]);
|
||||||
|
expect(tokenize("a")).toEqual([{ type: "SYMBOL", value: "a" }]);
|
||||||
|
expect(tokenize("true")).toEqual([{ type: "SYMBOL", value: "true" }]);
|
||||||
|
expect(tokenize("abcde")).toEqual([{ type: "SYMBOL", value: "abcde" }]);
|
||||||
|
expect(tokenize("_ab2")).toEqual([{ type: "SYMBOL", value: "_ab2" }]);
|
||||||
|
expect(tokenize("$ab2")).toEqual([{ type: "SYMBOL", value: "$ab2" }]);
|
||||||
|
expect(tokenize("ABC")).toEqual([{ type: "SYMBOL", value: "ABC" }]);
|
||||||
|
|
||||||
|
expect(tokenize("{a: 2}")).toEqual([
|
||||||
|
{ type: "LEFT_BRACE", value: "{" },
|
||||||
|
{ type: "SYMBOL", value: "a" },
|
||||||
|
{ type: "COLON", value: ":" },
|
||||||
|
{ type: "VALUE", value: "2" },
|
||||||
|
{ type: "RIGHT_BRACE", value: "}" }
|
||||||
|
]);
|
||||||
|
expect(tokenize("a,")).toEqual([
|
||||||
|
{ type: "SYMBOL", value: "a" },
|
||||||
|
{ type: "COMMA", value: "," }
|
||||||
|
]);
|
||||||
|
expect(tokenize("][")).toEqual([
|
||||||
|
{ type: "RIGHT_BRACKET", value: "]" },
|
||||||
|
{ type: "LEFT_BRACKET", value: "[" }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("various operators", () => {
|
||||||
|
expect(tokenize(">= <= < >")).toEqual([
|
||||||
|
{ type: "OPERATOR", value: ">=" },
|
||||||
|
{ type: "OPERATOR", value: "<=" },
|
||||||
|
{ type: "OPERATOR", value: "<" },
|
||||||
|
{ type: "OPERATOR", value: ">" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("strings", () => {
|
||||||
|
expect(() => tokenize("'")).toThrow("Invalid expression");
|
||||||
|
expect(() => tokenize("'\\")).toThrow("Invalid expression");
|
||||||
|
expect(() => tokenize("'\\'")).toThrow("Invalid expression");
|
||||||
|
expect(tokenize("'hello ged'")).toEqual([
|
||||||
|
{ type: "VALUE", value: "'hello ged'" }
|
||||||
|
]);
|
||||||
|
expect(tokenize("'hello \\'ged\\''")).toEqual([
|
||||||
|
{ type: "VALUE", value: "'hello \\'ged\\''" }
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(() => tokenize('"')).toThrow("Invalid expression");
|
||||||
|
expect(() => tokenize('"\\"')).toThrow("Invalid expression");
|
||||||
|
expect(tokenize('"hello ged"')).toEqual([
|
||||||
|
{ type: "VALUE", value: '"hello ged"' }
|
||||||
|
]);
|
||||||
|
expect(tokenize('"hello ged"}')).toEqual([
|
||||||
|
{ type: "VALUE", value: '"hello ged"' },
|
||||||
|
{ type: "RIGHT_BRACE", value: "}" }
|
||||||
|
]);
|
||||||
|
expect(tokenize('"hello \\"ged\\""')).toEqual([
|
||||||
|
{ type: "VALUE", value: '"hello \\"ged\\""' }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("expression evaluation", () => {
|
||||||
|
test("simple static values", () => {
|
||||||
|
expect(compileExpr("1", {})).toBe("1");
|
||||||
|
expect(compileExpr("1 ", {})).toBe("1");
|
||||||
|
expect(compileExpr("'some string#/, {' ", {})).toBe("'some string#/, {'");
|
||||||
|
expect(compileExpr("{ } ", {})).toBe("{}");
|
||||||
|
expect(compileExpr("{a: 1} ", {})).toBe("{a:1}");
|
||||||
|
expect(compileExpr("{a: 1, b: 2 } ", {})).toBe("{a:1,b:2}");
|
||||||
|
expect(compileExpr("[] ", {})).toBe("[]");
|
||||||
|
expect(compileExpr("[1] ", {})).toBe("[1]");
|
||||||
|
expect(compileExpr("['1', '2'] ", {})).toBe("['1','2']");
|
||||||
|
expect(compileExpr("['1', \"2\"] ", {})).toBe("['1',\"2\"]");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("various types of 'words'", () => {
|
||||||
|
expect(compileExpr("true", {})).toBe("true");
|
||||||
|
expect(compileExpr("false", {})).toBe("false");
|
||||||
|
expect(compileExpr("debugger", {})).toBe("debugger");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("parenthesis", () => {
|
||||||
|
expect(compileExpr("(1)", {})).toBe("(1)");
|
||||||
|
expect(compileExpr("a*(1 +3)", {})).toBe("context['a']*(1+3)");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("objects and sub objects", () => {
|
||||||
|
expect(compileExpr("{a:{b:1}} ", {})).toBe("{a:{b:1}}");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("replacing variables", () => {
|
||||||
|
expect(compileExpr("a", {})).toBe("context['a']");
|
||||||
|
expect(compileExpr("a", { a: { id: "_3", expr: "" } })).toBe("_3");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("arrays and objects", () => {
|
||||||
|
expect(compileExpr("[{b:1}] ", {})).toBe("[{b:1}]");
|
||||||
|
expect(compileExpr("{a: []} ", {})).toBe("{a:[]}");
|
||||||
|
expect(compileExpr("[{b:1, c: [1, {d: {e: 3}} ]}] ", {})).toBe(
|
||||||
|
"[{b:1,c:[1,{d:{e:3}}]}]"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("dot operator", () => {
|
||||||
|
expect(compileExpr("a.b", {})).toBe("context['a'].b");
|
||||||
|
expect(compileExpr("a.b.c", {})).toBe("context['a'].b.c");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("various unary operators", () => {
|
||||||
|
expect(compileExpr("!flag", {})).toBe("!context['flag']");
|
||||||
|
expect(compileExpr("-3", {})).toBe("-3");
|
||||||
|
expect(compileExpr("-a", {})).toBe("-context['a']");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("various binary operators", () => {
|
||||||
|
expect(compileExpr("color == 'black'", {})).toBe(
|
||||||
|
"context['color']=='black'"
|
||||||
|
);
|
||||||
|
expect(compileExpr("a || b", {})).toBe("context['a']||context['b']");
|
||||||
|
expect(compileExpr("color === 'black'", {})).toBe(
|
||||||
|
"context['color']==='black'"
|
||||||
|
);
|
||||||
|
expect(compileExpr("'li_'+item", {})).toBe("'li_'+context['item']");
|
||||||
|
expect(compileExpr("state.val > 1", {})).toBe("context['state'].val>1");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("boolean operations", () => {
|
||||||
|
expect(compileExpr("a && b", {})).toBe("context['a']&&context['b']");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ternary operators", () => {
|
||||||
|
expect(compileExpr("a ? b: '2'", {})).toBe("context['a']?context['b']:'2'");
|
||||||
|
expect(compileExpr("a ? b: (c or '2') ", {})).toBe(
|
||||||
|
"context['a']?context['b']:(context['c']||'2')"
|
||||||
|
);
|
||||||
|
expect(compileExpr("a ? {test:c}: [1,u]", {})).toBe(
|
||||||
|
"context['a']?{test:context['c']}:[1,context['u']]"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("word replacement", () => {
|
||||||
|
expect(compileExpr("a or b", {})).toBe("context['a']||context['b']");
|
||||||
|
expect(compileExpr("a and b", {})).toBe("context['a']&&context['b']");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("function calls", () => {
|
||||||
|
expect(compileExpr("a()", {})).toBe("context['a']()");
|
||||||
|
expect(compileExpr("a(1)", {})).toBe("context['a'](1)");
|
||||||
|
expect(compileExpr("a(1,2)", {})).toBe("context['a'](1,2)");
|
||||||
|
expect(compileExpr("a(1,2,{a:[a]})", {})).toBe(
|
||||||
|
"context['a'](1,2,{a:[context['a']]})"
|
||||||
|
);
|
||||||
|
expect(compileExpr("'x'.toUpperCase()", {})).toBe("'x'.toUpperCase()");
|
||||||
|
expect(compileExpr("'x'.toUpperCase({a: 3})", {})).toBe(
|
||||||
|
"'x'.toUpperCase({a:3})"
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
compileExpr("'x'.toUpperCase(a)", { a: { id: "_v5", expr: "" } })
|
||||||
|
).toBe("'x'.toUpperCase(_v5)");
|
||||||
|
expect(
|
||||||
|
compileExpr("'x'.toUpperCase({b: a})", { a: { id: "_v5", expr: "" } })
|
||||||
|
).toBe("'x'.toUpperCase({b:_v5})");
|
||||||
|
});
|
||||||
|
});
|
||||||
+278
-71
@@ -152,24 +152,6 @@ describe("basic use", () => {
|
|||||||
store.dispatch("someaction");
|
store.dispatch("someaction");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("set function is given to mutations", async () => {
|
|
||||||
let updateCounter = 0;
|
|
||||||
const state = { bertinchamps: "brune" };
|
|
||||||
const mutations = {
|
|
||||||
addInfo({ state, set }) {
|
|
||||||
set(state, "chouffe", "blonde");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const store = new Store({ state, mutations, actions: {} });
|
|
||||||
store.on("update", null, () => updateCounter++);
|
|
||||||
|
|
||||||
expect(updateCounter).toBe(0);
|
|
||||||
store.commit("addInfo");
|
|
||||||
await nextMicroTick();
|
|
||||||
expect(updateCounter).toBe(1);
|
|
||||||
expect(store.state).toEqual({ bertinchamps: "brune", chouffe: "blonde" });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("can have getters from store", async () => {
|
test("can have getters from store", async () => {
|
||||||
const state = {
|
const state = {
|
||||||
beers: {
|
beers: {
|
||||||
@@ -200,6 +182,145 @@ describe("basic use", () => {
|
|||||||
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getters are memoized", async () => {
|
||||||
|
const state = {
|
||||||
|
beers: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: "bertinchamps",
|
||||||
|
tasterID: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tasters: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: "aaron"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let n = 0;
|
||||||
|
const getters = {
|
||||||
|
beerTasterName({ state }, beerID) {
|
||||||
|
n++;
|
||||||
|
return state.tasters[state.beers[beerID].tasterID].name;
|
||||||
|
},
|
||||||
|
bestBeerName({ state }) {
|
||||||
|
n++;
|
||||||
|
return state.beers[1].name;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = new Store({ state, mutations: {}, actions: {}, getters });
|
||||||
|
expect((<any>store.getters).bestBeerName()).toBe("bertinchamps");
|
||||||
|
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
||||||
|
expect(n).toBe(2);
|
||||||
|
expect((<any>store.getters).bestBeerName()).toBe("bertinchamps");
|
||||||
|
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
||||||
|
expect(n).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getters taking Array as argument aren't memoized", async () => {
|
||||||
|
const state = {
|
||||||
|
beers: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: "bertinchamps",
|
||||||
|
tasterID: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let n = 0;
|
||||||
|
const getters = {
|
||||||
|
getBeerNames({ state }, beerIDs) {
|
||||||
|
n++;
|
||||||
|
return beerIDs.map(beerID => {
|
||||||
|
return state.beers[beerID].name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const store = new Store({ state, mutations: {}, actions: {}, getters });
|
||||||
|
expect((<any>store.getters).getBeerNames([1])).toEqual(["bertinchamps"]);
|
||||||
|
expect(n).toBe(1);
|
||||||
|
expect((<any>store.getters).getBeerNames([1])).toEqual(["bertinchamps"]);
|
||||||
|
expect(n).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getters cache is nuked on store changes", async () => {
|
||||||
|
const state = {
|
||||||
|
beers: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: "bertinchamps",
|
||||||
|
tasterID: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tasters: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: "aaron"
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
id: 2,
|
||||||
|
name: "gery"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mutations = {
|
||||||
|
changeTaster({ state }, {beerID, tasterID}) {
|
||||||
|
state.beers[beerID].tasterID = tasterID;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let n = 0;
|
||||||
|
const getters = {
|
||||||
|
beerTasterName({ state }, beerID) {
|
||||||
|
n++;
|
||||||
|
return state.tasters[state.beers[beerID].tasterID].name;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = new Store({ state, mutations: mutations, actions: {}, getters });
|
||||||
|
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
||||||
|
expect(n).toBe(1);
|
||||||
|
expect((<any>store.getters).beerTasterName(1)).toBe("aaron");
|
||||||
|
expect(n).toBe(1);
|
||||||
|
|
||||||
|
store.commit('changeTaster', {beerID: 1, tasterID: 2});
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect((<any>store.getters).beerTasterName(1)).toBe("gery");
|
||||||
|
expect(n).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getters cache is disabled during a mutation", async () => {
|
||||||
|
const state = {
|
||||||
|
beers: {
|
||||||
|
1: {
|
||||||
|
id: 1,
|
||||||
|
name: "bertinchamps"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mutations = {
|
||||||
|
renameBeer({ state, getters }, beerID) {
|
||||||
|
expect(getters.beerName(beerID)).toBe('bertinchamps');
|
||||||
|
state.beers[1].name = 'chouffe';
|
||||||
|
expect(getters.beerName(beerID)).toBe('chouffe');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let n = 0;
|
||||||
|
const getters = {
|
||||||
|
beerName({ state }, beerID) {
|
||||||
|
n++;
|
||||||
|
return state.beers[beerID].name;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = new Store({ state, mutations: mutations, actions: {}, getters });
|
||||||
|
|
||||||
|
store.commit('renameBeer', 1);
|
||||||
|
expect((<any>store.getters).beerName(1)).toBe('chouffe');
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(n).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
test("getters given to actions", async () => {
|
test("getters given to actions", async () => {
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
const state = {
|
const state = {
|
||||||
@@ -429,10 +550,13 @@ describe("connecting a component to store", () => {
|
|||||||
state.todos.push({ msg });
|
state.todos.push({ msg });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function mapStateToProps(s) {
|
function mapStoreToProps(s) {
|
||||||
return { todos: s.todos };
|
return { todos: s.todos };
|
||||||
}
|
}
|
||||||
const TodoApp = connect(mapStateToProps)(App);
|
const TodoApp = connect(
|
||||||
|
App,
|
||||||
|
mapStoreToProps
|
||||||
|
);
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, mutations });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const app = new TodoApp(env);
|
const app = new TodoApp(env);
|
||||||
@@ -452,7 +576,7 @@ describe("connecting a component to store", () => {
|
|||||||
state.todos[0].title = title;
|
state.todos[0].title = title;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function mapStateToProps(s) {
|
function mapStoreToProps(s) {
|
||||||
return { todos: s.todos };
|
return { todos: s.todos };
|
||||||
}
|
}
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, mutations });
|
||||||
@@ -469,13 +593,15 @@ describe("connecting a component to store", () => {
|
|||||||
class App extends Component<any, any, any> {}
|
class App extends Component<any, any, any> {}
|
||||||
|
|
||||||
const DeepTodoApp = connect(
|
const DeepTodoApp = connect(
|
||||||
mapStateToProps,
|
App,
|
||||||
|
mapStoreToProps,
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)(App);
|
);
|
||||||
const ShallowTodoApp = connect(
|
const ShallowTodoApp = connect(
|
||||||
mapStateToProps,
|
App,
|
||||||
|
mapStoreToProps,
|
||||||
{ deep: false }
|
{ deep: false }
|
||||||
)(App);
|
);
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const deepTodoApp = new DeepTodoApp(env);
|
const deepTodoApp = new DeepTodoApp(env);
|
||||||
const shallowTodoApp = new ShallowTodoApp(env);
|
const shallowTodoApp = new ShallowTodoApp(env);
|
||||||
@@ -494,6 +620,51 @@ describe("connecting a component to store", () => {
|
|||||||
expect(shallowFix.innerHTML).toMatchSnapshot();
|
expect(shallowFix.innerHTML).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("connecting a component to a local store", async () => {
|
||||||
|
env.qweb.addTemplates(`
|
||||||
|
<templates>
|
||||||
|
<div t-name="App">
|
||||||
|
<t t-foreach="props.todos" t-as="todo" t-key="todo">
|
||||||
|
<t t-widget="Todo" msg="todo.msg"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<span t-name="Todo"><t t-esc="props.msg"/></span>
|
||||||
|
</templates>
|
||||||
|
`);
|
||||||
|
class App extends Component<any, any, any> {
|
||||||
|
widgets = { Todo };
|
||||||
|
}
|
||||||
|
class Todo extends Component<any, any, any> {}
|
||||||
|
|
||||||
|
(<any>env).store = new Store({});
|
||||||
|
const store = new Store({
|
||||||
|
state: { todos: [] },
|
||||||
|
mutations: {
|
||||||
|
addTodo({ state }, msg) {
|
||||||
|
state.todos.push({ msg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function mapStoreToProps(s) {
|
||||||
|
return { todos: s.todos };
|
||||||
|
}
|
||||||
|
const TodoApp = connect(
|
||||||
|
App,
|
||||||
|
mapStoreToProps,
|
||||||
|
{
|
||||||
|
getStore: () => store
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const app = new TodoApp(env);
|
||||||
|
|
||||||
|
await app.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toMatchSnapshot();
|
||||||
|
|
||||||
|
(<any>app.__owl__).store.commit("addTodo", "hello");
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
test("connected child components with custom hooks", async () => {
|
test("connected child components with custom hooks", async () => {
|
||||||
let steps: any = [];
|
let steps: any = [];
|
||||||
env.qweb.addTemplate("Child", `<div/>`);
|
env.qweb.addTemplate("Child", `<div/>`);
|
||||||
@@ -506,7 +677,10 @@ describe("connecting a component to store", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectedChild = connect(s => s)(Child);
|
const ConnectedChild = connect(
|
||||||
|
Child,
|
||||||
|
s => s
|
||||||
|
);
|
||||||
|
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"Parent",
|
"Parent",
|
||||||
@@ -548,10 +722,13 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
env.qweb.addTemplate("TodoItem", `<span><t t-esc="props.text"/></span>`);
|
env.qweb.addTemplate("TodoItem", `<span><t t-esc="props.text"/></span>`);
|
||||||
class TodoItem extends Component<any, any, any> {}
|
class TodoItem extends Component<any, any, any> {}
|
||||||
const ConnectedTodo = connect((state, props) => {
|
const ConnectedTodo = connect(
|
||||||
const todo = state.todos.find(t => t.id === props.id);
|
TodoItem,
|
||||||
return todo;
|
(state, props) => {
|
||||||
})(TodoItem);
|
const todo = state.todos.find(t => t.id === props.id);
|
||||||
|
return todo;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"TodoList",
|
"TodoList",
|
||||||
@@ -565,10 +742,13 @@ describe("connecting a component to store", () => {
|
|||||||
widgets = { ConnectedTodo };
|
widgets = { ConnectedTodo };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStoreToProps(state) {
|
||||||
return { todos: state.todos };
|
return { todos: state.todos };
|
||||||
}
|
}
|
||||||
const ConnectedTodoList = connect(mapStateToProps)(TodoList);
|
const ConnectedTodoList = connect(
|
||||||
|
TodoList,
|
||||||
|
mapStoreToProps
|
||||||
|
);
|
||||||
|
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const app = new ConnectedTodoList(env);
|
const app = new ConnectedTodoList(env);
|
||||||
@@ -606,13 +786,16 @@ describe("connecting a component to store", () => {
|
|||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
class TodoItem extends Component<any, any, any> {}
|
class TodoItem extends Component<any, any, any> {}
|
||||||
const ConnectedTodo = connect((state, props, getters) => {
|
const ConnectedTodo = connect(
|
||||||
const todo = state.todos.find(t => t.id === props.id);
|
TodoItem,
|
||||||
return {
|
(state, props, getters) => {
|
||||||
activeTodoText: getters.text(todo.id),
|
const todo = state.todos.find(t => t.id === props.id);
|
||||||
importantTodoText: getters.importantTodoText()
|
return {
|
||||||
};
|
activeTodoText: getters.text(todo.id),
|
||||||
})(TodoItem);
|
importantTodoText: getters.importantTodoText()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"TodoList",
|
"TodoList",
|
||||||
@@ -626,10 +809,13 @@ describe("connecting a component to store", () => {
|
|||||||
widgets = { ConnectedTodo };
|
widgets = { ConnectedTodo };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStoreToProps(state) {
|
||||||
return { todos: state.todos };
|
return { todos: state.todos };
|
||||||
}
|
}
|
||||||
const ConnectedTodoList = connect(mapStateToProps)(TodoList);
|
const ConnectedTodoList = connect(
|
||||||
|
TodoList,
|
||||||
|
mapStoreToProps
|
||||||
|
);
|
||||||
|
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
const app = new ConnectedTodoList(env);
|
const app = new ConnectedTodoList(env);
|
||||||
@@ -643,9 +829,12 @@ describe("connecting a component to store", () => {
|
|||||||
test("connected component is updated when props are updated", async () => {
|
test("connected component is updated when props are updated", async () => {
|
||||||
env.qweb.addTemplate("Beer", `<span><t t-esc="props.name"/></span>`);
|
env.qweb.addTemplate("Beer", `<span><t t-esc="props.name"/></span>`);
|
||||||
class Beer extends Component<any, any, any> {}
|
class Beer extends Component<any, any, any> {}
|
||||||
const ConnectedBeer = connect((state, props) => {
|
const ConnectedBeer = connect(
|
||||||
return state.beers[props.id];
|
Beer,
|
||||||
})(Beer);
|
(state, props) => {
|
||||||
|
return state.beers[props.id];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"App",
|
"App",
|
||||||
@@ -691,10 +880,13 @@ describe("connecting a component to store", () => {
|
|||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, mutations });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStoreToProps(state) {
|
||||||
return { beers: state.beers, otherKey: 1 };
|
return { beers: state.beers, otherKey: 1 };
|
||||||
}
|
}
|
||||||
const ConnectedApp = connect(mapStateToProps)(App);
|
const ConnectedApp = connect(
|
||||||
|
App,
|
||||||
|
mapStoreToProps
|
||||||
|
);
|
||||||
const app = new ConnectedApp(env);
|
const app = new ConnectedApp(env);
|
||||||
|
|
||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
@@ -717,13 +909,16 @@ describe("connecting a component to store", () => {
|
|||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
class Beer extends Component<any, any, any> {}
|
class Beer extends Component<any, any, any> {}
|
||||||
const ConnectedBeer = connect((state, props) => {
|
const ConnectedBeer = connect(
|
||||||
return {
|
Beer,
|
||||||
selected: state.beers[props.id],
|
(state, props) => {
|
||||||
consumed: state.beers[state.consumedID] || null,
|
return {
|
||||||
taster: state.taster
|
selected: state.beers[props.id],
|
||||||
};
|
consumed: state.beers[state.consumedID] || null,
|
||||||
})(Beer);
|
taster: state.taster
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"App",
|
"App",
|
||||||
@@ -786,13 +981,16 @@ describe("connecting a component to store", () => {
|
|||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
class Beer extends Component<any, any, any> {}
|
class Beer extends Component<any, any, any> {}
|
||||||
const ConnectedBeer = connect((state, props) => {
|
const ConnectedBeer = connect(
|
||||||
return {
|
Beer,
|
||||||
selected: state.beers[props.id],
|
(state, props) => {
|
||||||
consumed: state.beers[state.consumedID] || null,
|
return {
|
||||||
taster: state.taster
|
selected: state.beers[props.id],
|
||||||
};
|
consumed: state.beers[state.consumedID] || null,
|
||||||
})(Beer);
|
taster: state.taster
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
"App",
|
"App",
|
||||||
@@ -883,18 +1081,24 @@ describe("connecting a component to store", () => {
|
|||||||
class Parent extends Component<any, any, any> {
|
class Parent extends Component<any, any, any> {
|
||||||
widgets = { Child: ConnectedChild };
|
widgets = { Child: ConnectedChild };
|
||||||
}
|
}
|
||||||
const ConnectedParent = connect(function(s) {
|
const ConnectedParent = connect(
|
||||||
steps.push("parent");
|
Parent,
|
||||||
return { current: s.current, isvisible: s.isvisible };
|
function(s) {
|
||||||
})(Parent);
|
steps.push("parent");
|
||||||
|
return { current: s.current, isvisible: s.isvisible };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
env.qweb.addTemplate("Child", `<span><t t-esc="props.msg"/></span>`);
|
env.qweb.addTemplate("Child", `<span><t t-esc="props.msg"/></span>`);
|
||||||
class Child extends Component<any, any, any> {}
|
class Child extends Component<any, any, any> {}
|
||||||
|
|
||||||
const ConnectedChild = connect(function(s, props) {
|
const ConnectedChild = connect(
|
||||||
steps.push("child");
|
Child,
|
||||||
return { msg: s.msg[props.key] };
|
function(s, props) {
|
||||||
})(Child);
|
steps.push("child");
|
||||||
|
return { msg: s.msg[props.key] };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const state = { current: "a", msg: { a: "a", b: "b" } };
|
const state = { current: "a", msg: { a: "a", b: "b" } };
|
||||||
const mutations = {
|
const mutations = {
|
||||||
@@ -927,9 +1131,12 @@ describe("connecting a component to store", () => {
|
|||||||
steps.push("patched");
|
steps.push("patched");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const ConnectedApp = connect(function(s) {
|
const ConnectedApp = connect(
|
||||||
return { msg: s.msg };
|
App,
|
||||||
})(App);
|
function(s) {
|
||||||
|
return { msg: s.msg };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const state = { msg: "a" };
|
const state = { msg: "a" };
|
||||||
const mutations = {
|
const mutations = {
|
||||||
|
|||||||
+118
-35
@@ -1,4 +1,9 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
odoo.define("app", function(require) {
|
odoo.define("app", function(require) {
|
||||||
const Widget = require("web.Widget");
|
const Widget = require("web.Widget");
|
||||||
@@ -97,14 +102,23 @@ odoo.define("app", function(require) {
|
|||||||
"click .o_btn_msg.10000": function() {
|
"click .o_btn_msg.10000": function() {
|
||||||
this.addMessages(10000);
|
this.addMessages(10000);
|
||||||
},
|
},
|
||||||
"click .o_btn_msg.50000": function() {
|
"click .o_btn_msg.30000": function() {
|
||||||
this.addMessages(50000);
|
this.addMessages(30000);
|
||||||
},
|
},
|
||||||
"click .updateSomeMessages": function() {
|
"click .updateSomeMessages": function() {
|
||||||
this.updateSomeMessages();
|
this.updateSomeMessages();
|
||||||
},
|
},
|
||||||
"click .clear": function() {
|
"click .clear": function() {
|
||||||
this.clear();
|
this.clear();
|
||||||
|
},
|
||||||
|
"click .o_multiple": function() {
|
||||||
|
this.multipleFlag = !this.multipleFlag;
|
||||||
|
},
|
||||||
|
"click .o_clear": function() {
|
||||||
|
this.clearFlag = !this.clearFlag;
|
||||||
|
},
|
||||||
|
"click .clear-log": function() {
|
||||||
|
this.$log[0].innerHTML = "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
custom_events: {
|
custom_events: {
|
||||||
@@ -116,11 +130,15 @@ odoo.define("app", function(require) {
|
|||||||
this.widgets = {};
|
this.widgets = {};
|
||||||
this.isInDom = false;
|
this.isInDom = false;
|
||||||
this.messageCount = 0;
|
this.messageCount = 0;
|
||||||
|
this.multipleFlag = false;
|
||||||
|
this.clearFlag = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
start: function() {
|
start: function() {
|
||||||
this.$content = this.$(".content");
|
this.$content = this.$(".content");
|
||||||
this.$msgCount = this.$(".message_count");
|
this.$msgCount = this.$(".message_count");
|
||||||
|
this.$log = this.$(".log-content");
|
||||||
|
this.log("Benchmarking odoo widgets, 12.0");
|
||||||
},
|
},
|
||||||
on_attach_callback: function() {
|
on_attach_callback: function() {
|
||||||
this.isInDom = true;
|
this.isInDom = true;
|
||||||
@@ -144,34 +162,41 @@ odoo.define("app", function(require) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
addMessages: function(n) {
|
addMessages: function(n) {
|
||||||
var self = this;
|
const self = this;
|
||||||
startMeasure("add " + n);
|
this.benchmark("add " + n, () => {
|
||||||
const defs = [];
|
const defs = [];
|
||||||
const messages = buildData(n);
|
const messages = buildData(n);
|
||||||
for (let message of messages) {
|
for (let message of messages) {
|
||||||
const widget = new Message(this, message);
|
const widget = new Message(this, message);
|
||||||
this.widgets[message.id] = widget;
|
this.widgets[message.id] = widget;
|
||||||
defs.push(widget.appendTo("<div>"));
|
defs.push(widget.appendTo("<div>"));
|
||||||
}
|
}
|
||||||
$.when
|
return $.when
|
||||||
.apply($, defs)
|
.apply($, defs)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
for (let message of messages) {
|
for (let message of messages) {
|
||||||
let widget = self.widgets[message.id];
|
let widget = self.widgets[message.id];
|
||||||
dom.append(self.$content, widget.$el, {
|
dom.append(self.$content, widget.$el, {
|
||||||
in_DOM: this.isInDom,
|
in_DOM: this.isInDom,
|
||||||
callbacks: [{ widget: widget }]
|
callbacks: [{ widget: widget }]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
self.messageCount += n;
|
self.messageCount += n;
|
||||||
self.updateMessageCount();
|
self.updateMessageCount();
|
||||||
stopMeasure();
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clear: function() {
|
clear: function() {
|
||||||
startMeasure("clear");
|
startMeasure("clear");
|
||||||
|
this._clear();
|
||||||
|
stopMeasure(info => {
|
||||||
|
this.log(info.msg);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_clear: function() {
|
||||||
this.$content.empty();
|
this.$content.empty();
|
||||||
for (let key in this.widgets) {
|
for (let key in this.widgets) {
|
||||||
this.widgets[key].destroy();
|
this.widgets[key].destroy();
|
||||||
@@ -179,15 +204,14 @@ odoo.define("app", function(require) {
|
|||||||
}
|
}
|
||||||
this.messageCount = 0;
|
this.messageCount = 0;
|
||||||
this.updateMessageCount();
|
this.updateMessageCount();
|
||||||
stopMeasure();
|
|
||||||
},
|
},
|
||||||
updateSomeMessages: function() {
|
updateSomeMessages: function() {
|
||||||
startMeasure("update every 10th");
|
this.benchmark("update every 10th", () => {
|
||||||
const widgets = Object.values(this.widgets);
|
const widgets = Object.values(this.widgets);
|
||||||
for (let i = 0; i < widgets.length; i += 10) {
|
for (let i = 0; i < widgets.length; i += 10) {
|
||||||
widgets[i].update();
|
widgets[i].update();
|
||||||
}
|
}
|
||||||
stopMeasure();
|
});
|
||||||
},
|
},
|
||||||
_onRemoveMessage: function(ev) {
|
_onRemoveMessage: function(ev) {
|
||||||
startMeasure("remove message");
|
startMeasure("remove message");
|
||||||
@@ -196,6 +220,65 @@ odoo.define("app", function(require) {
|
|||||||
this.messageCount--;
|
this.messageCount--;
|
||||||
this.updateMessageCount();
|
this.updateMessageCount();
|
||||||
stopMeasure();
|
stopMeasure();
|
||||||
|
},
|
||||||
|
|
||||||
|
benchmark: function(message, fn, callback) {
|
||||||
|
if (this.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.clearFlag) {
|
||||||
|
this._benchmark("clear", this._clear.bind(this), finalize, false);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_benchmark: function(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
const benchmark = fn();
|
||||||
|
(benchmark && benchmark.then ? benchmark : $.when()).then(() => {
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
log: function(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.$log[0].appendChild(div);
|
||||||
|
this.$log[0].scrollTop = this.$log[0].scrollHeight;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<templates>
|
||||||
|
<div class="main" t-name="root">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button class="o_btn_msg 100">Add 100 messages</button>
|
||||||
|
<button class="o_btn_msg 1000">Add 1k messages</button>
|
||||||
|
<button class="o_btn_msg 10000">Add 10k messages</button>
|
||||||
|
<button class="o_btn_msg 30000">Add 30k messages</button>
|
||||||
|
<button class="updateSomeMessages">Update every 10th message</button>
|
||||||
|
<button class="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" class="o_multiple" id="multipleflag" />
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" class="o_clear" id="clearFlag" />
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info message_count">
|
||||||
|
Number of messages: 0
|
||||||
|
</div>
|
||||||
|
<div class="title">Log <span class="clear-log">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="message" class="message">
|
||||||
|
<span class="author"><t t-esc="widget.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="widget.msg"/></span>
|
||||||
|
<button class="remove">Remove</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="counter">
|
||||||
|
<button class="o_increment"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
+118
-35
@@ -1,4 +1,9 @@
|
|||||||
import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
odoo.define("app", function(require) {
|
odoo.define("app", function(require) {
|
||||||
const Widget = require("web.Widget");
|
const Widget = require("web.Widget");
|
||||||
@@ -97,14 +102,23 @@ odoo.define("app", function(require) {
|
|||||||
"click .o_btn_msg.10000": function() {
|
"click .o_btn_msg.10000": function() {
|
||||||
this.addMessages(10000);
|
this.addMessages(10000);
|
||||||
},
|
},
|
||||||
"click .o_btn_msg.50000": function() {
|
"click .o_btn_msg.30000": function() {
|
||||||
this.addMessages(50000);
|
this.addMessages(30000);
|
||||||
},
|
},
|
||||||
"click .updateSomeMessages": function() {
|
"click .updateSomeMessages": function() {
|
||||||
this.updateSomeMessages();
|
this.updateSomeMessages();
|
||||||
},
|
},
|
||||||
"click .clear": function() {
|
"click .clear": function() {
|
||||||
this.clear();
|
this.clear();
|
||||||
|
},
|
||||||
|
"click .o_multiple": function() {
|
||||||
|
this.multipleFlag = !this.multipleFlag;
|
||||||
|
},
|
||||||
|
"click .o_clear": function() {
|
||||||
|
this.clearFlag = !this.clearFlag;
|
||||||
|
},
|
||||||
|
"click .clear-log": function() {
|
||||||
|
this.$log[0].innerHTML = "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
custom_events: {
|
custom_events: {
|
||||||
@@ -116,11 +130,15 @@ odoo.define("app", function(require) {
|
|||||||
this.widgets = {};
|
this.widgets = {};
|
||||||
this.isInDom = false;
|
this.isInDom = false;
|
||||||
this.messageCount = 0;
|
this.messageCount = 0;
|
||||||
|
this.multipleFlag = false;
|
||||||
|
this.clearFlag = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
start: function() {
|
start: function() {
|
||||||
this.$content = this.$(".content");
|
this.$content = this.$(".content");
|
||||||
this.$msgCount = this.$(".message_count");
|
this.$msgCount = this.$(".message_count");
|
||||||
|
this.$log = this.$(".log-content");
|
||||||
|
this.log("Benchmarking odoo widgets, 12.3");
|
||||||
},
|
},
|
||||||
on_attach_callback: function() {
|
on_attach_callback: function() {
|
||||||
this.isInDom = true;
|
this.isInDom = true;
|
||||||
@@ -144,34 +162,41 @@ odoo.define("app", function(require) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
addMessages: function(n) {
|
addMessages: function(n) {
|
||||||
var self = this;
|
const self = this;
|
||||||
startMeasure("add " + n);
|
this.benchmark("add " + n, () => {
|
||||||
const defs = [];
|
const defs = [];
|
||||||
const messages = buildData(n);
|
const messages = buildData(n);
|
||||||
for (let message of messages) {
|
for (let message of messages) {
|
||||||
const widget = new Message(this, message);
|
const widget = new Message(this, message);
|
||||||
this.widgets[message.id] = widget;
|
this.widgets[message.id] = widget;
|
||||||
defs.push(widget.appendTo("<div>"));
|
defs.push(widget.appendTo("<div>"));
|
||||||
}
|
}
|
||||||
$.when
|
return $.when
|
||||||
.apply($, defs)
|
.apply($, defs)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
for (let message of messages) {
|
for (let message of messages) {
|
||||||
let widget = self.widgets[message.id];
|
let widget = self.widgets[message.id];
|
||||||
dom.append(self.$content, widget.$el, {
|
dom.append(self.$content, widget.$el, {
|
||||||
in_DOM: this.isInDom,
|
in_DOM: this.isInDom,
|
||||||
callbacks: [{ widget: widget }]
|
callbacks: [{ widget: widget }]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
self.messageCount += n;
|
self.messageCount += n;
|
||||||
self.updateMessageCount();
|
self.updateMessageCount();
|
||||||
stopMeasure();
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clear: function() {
|
clear: function() {
|
||||||
startMeasure("clear");
|
startMeasure("clear");
|
||||||
|
this._clear();
|
||||||
|
stopMeasure(info => {
|
||||||
|
this.log(info.msg);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_clear: function() {
|
||||||
this.$content.empty();
|
this.$content.empty();
|
||||||
for (let key in this.widgets) {
|
for (let key in this.widgets) {
|
||||||
this.widgets[key].destroy();
|
this.widgets[key].destroy();
|
||||||
@@ -179,15 +204,14 @@ odoo.define("app", function(require) {
|
|||||||
}
|
}
|
||||||
this.messageCount = 0;
|
this.messageCount = 0;
|
||||||
this.updateMessageCount();
|
this.updateMessageCount();
|
||||||
stopMeasure();
|
|
||||||
},
|
},
|
||||||
updateSomeMessages: function() {
|
updateSomeMessages: function() {
|
||||||
startMeasure("update every 10th");
|
this.benchmark("update every 10th", () => {
|
||||||
const widgets = Object.values(this.widgets);
|
const widgets = Object.values(this.widgets);
|
||||||
for (let i = 0; i < widgets.length; i += 10) {
|
for (let i = 0; i < widgets.length; i += 10) {
|
||||||
widgets[i].update();
|
widgets[i].update();
|
||||||
}
|
}
|
||||||
stopMeasure();
|
});
|
||||||
},
|
},
|
||||||
_onRemoveMessage: function(ev) {
|
_onRemoveMessage: function(ev) {
|
||||||
startMeasure("remove message");
|
startMeasure("remove message");
|
||||||
@@ -196,6 +220,65 @@ odoo.define("app", function(require) {
|
|||||||
this.messageCount--;
|
this.messageCount--;
|
||||||
this.updateMessageCount();
|
this.updateMessageCount();
|
||||||
stopMeasure();
|
stopMeasure();
|
||||||
|
},
|
||||||
|
|
||||||
|
benchmark: function(message, fn, callback) {
|
||||||
|
if (this.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.clearFlag) {
|
||||||
|
this._benchmark("clear", this._clear.bind(this), finalize, false);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_benchmark: function(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
const benchmark = fn();
|
||||||
|
(benchmark && benchmark.then ? benchmark : $.when()).then(() => {
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
log: function(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.$log[0].appendChild(div);
|
||||||
|
this.$log[0].scrollTop = this.$log[0].scrollHeight;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<templates>
|
||||||
|
<div class="main" t-name="root">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button class="o_btn_msg 100">Add 100 messages</button>
|
||||||
|
<button class="o_btn_msg 1000">Add 1k messages</button>
|
||||||
|
<button class="o_btn_msg 10000">Add 10k messages</button>
|
||||||
|
<button class="o_btn_msg 30000">Add 30k messages</button>
|
||||||
|
<button class="updateSomeMessages">Update every 10th message</button>
|
||||||
|
<button class="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" class="o_multiple" id="multipleflag" />
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" class="o_clear" id="clearFlag" />
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info message_count">
|
||||||
|
Number of messages: 0
|
||||||
|
</div>
|
||||||
|
<div class="title">Log <span class="clear-log">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="message" class="message">
|
||||||
|
<span class="author"><t t-esc="widget.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="widget.msg"/></span>
|
||||||
|
<button class="remove">Remove</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="counter">
|
||||||
|
<button class="o_increment"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Likes Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { counter: 0 };
|
||||||
|
template = "Counter";
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends owl.Component {
|
||||||
|
widgets = { Counter };
|
||||||
|
template = "Message";
|
||||||
|
|
||||||
|
shouldUpdate(nextProps) {
|
||||||
|
return nextProps !== this.props;
|
||||||
|
}
|
||||||
|
removeMessage() {
|
||||||
|
this.trigger("remove_message", {
|
||||||
|
id: this.props.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = { Message };
|
||||||
|
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||||
|
template = "App";
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.log(
|
||||||
|
`Benchmarking Owl v${owl._version} (build date: ${
|
||||||
|
owl._date
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.state.messages = [];
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = buildData(n);
|
||||||
|
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._benchmark("clear", () => {
|
||||||
|
this.state.messages = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||||
|
const msg = Object.assign({}, messages[i]);
|
||||||
|
msg.author += "!!!";
|
||||||
|
this.set(messages, i, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(data) {
|
||||||
|
this.benchmark("remove message", () => {
|
||||||
|
const index = this.state.messages.findIndex(m => m.id === data.id);
|
||||||
|
this.state.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.log.appendChild(div);
|
||||||
|
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.refs.log.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultiple() {
|
||||||
|
this.state.multipleFlag = !this.state.multipleFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClear() {
|
||||||
|
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Application initialization
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
async function start() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(templates)
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||||
|
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||||
|
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||||
|
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||||
|
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||||
|
<button t-on-click="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||||
|
<hr/>
|
||||||
|
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content" t-ref="'log'"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content" t-on-remove-message="removeMessage">
|
||||||
|
<t t-foreach="state.messages" t-as="message">
|
||||||
|
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Message" class="message">
|
||||||
|
<span class="author"><t t-esc="props.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="props.msg"/></span>
|
||||||
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Likes Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { counter: 0 };
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends owl.Component {
|
||||||
|
widgets = { Counter };
|
||||||
|
|
||||||
|
shouldUpdate(nextProps) {
|
||||||
|
return nextProps !== this.props;
|
||||||
|
}
|
||||||
|
removeMessage() {
|
||||||
|
this.trigger("remove_message", {
|
||||||
|
id: this.props.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = { Message };
|
||||||
|
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.log(
|
||||||
|
`Benchmarking Owl v${owl._version} (build date: ${
|
||||||
|
owl._date
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.state.messages = [];
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = buildData(n);
|
||||||
|
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._benchmark("clear", () => {
|
||||||
|
this.state.messages = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||||
|
const msg = Object.assign({}, messages[i]);
|
||||||
|
msg.author += "!!!";
|
||||||
|
this.set(messages, i, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(data) {
|
||||||
|
this.benchmark("remove message", () => {
|
||||||
|
const index = this.state.messages.findIndex(m => m.id === data.id);
|
||||||
|
this.state.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.log.appendChild(div);
|
||||||
|
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.refs.log.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultiple() {
|
||||||
|
this.state.multipleFlag = !this.state.multipleFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClear() {
|
||||||
|
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Application initialization
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
async function start() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(templates)
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||||
|
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||||
|
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||||
|
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||||
|
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||||
|
<button t-on-click="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||||
|
<hr/>
|
||||||
|
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content" t-ref="'log'"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content" t-on-remove-message="removeMessage">
|
||||||
|
<t t-foreach="state.messages" t-as="message">
|
||||||
|
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Message" class="message">
|
||||||
|
<span class="author"><t t-esc="props.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="props.msg"/></span>
|
||||||
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Likes Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { counter: 0 };
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends owl.Component {
|
||||||
|
widgets = { Counter };
|
||||||
|
|
||||||
|
shouldUpdate(nextProps) {
|
||||||
|
return nextProps !== this.props;
|
||||||
|
}
|
||||||
|
removeMessage() {
|
||||||
|
this.trigger("remove_message", {
|
||||||
|
id: this.props.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = { Message };
|
||||||
|
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.log(
|
||||||
|
`Benchmarking Owl v${owl.__info__.version} (build date: ${
|
||||||
|
owl.__info__.date
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.state.messages = [];
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = buildData(n);
|
||||||
|
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.benchmark("clear", () => {
|
||||||
|
this.state.messages = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||||
|
const msg = Object.assign({}, messages[i]);
|
||||||
|
msg.author += "!!!";
|
||||||
|
this.set(messages, i, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(data) {
|
||||||
|
this.benchmark("remove message", () => {
|
||||||
|
const index = this.state.messages.findIndex(m => m.id === data.id);
|
||||||
|
this.state.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.log.appendChild(div);
|
||||||
|
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.refs.log.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultiple() {
|
||||||
|
this.state.multipleFlag = !this.state.multipleFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClear() {
|
||||||
|
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Application initialization
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
async function start() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(templates)
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||||
|
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||||
|
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||||
|
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||||
|
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||||
|
<button t-on-click="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||||
|
<hr/>
|
||||||
|
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content" t-ref="log"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content" t-on-remove-message="removeMessage">
|
||||||
|
<t t-foreach="state.messages" t-as="message">
|
||||||
|
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Message" class="message">
|
||||||
|
<span class="author"><t t-esc="props.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="props.msg"/></span>
|
||||||
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Likes Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { counter: 0 };
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends owl.Component {
|
||||||
|
widgets = { Counter };
|
||||||
|
|
||||||
|
shouldUpdate(nextProps) {
|
||||||
|
return nextProps.message !== this.props.message;
|
||||||
|
}
|
||||||
|
removeMessage() {
|
||||||
|
this.trigger("remove-message", {
|
||||||
|
id: this.props.message.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = { Message };
|
||||||
|
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.log(
|
||||||
|
`Benchmarking Owl v${owl.__info__.version} (build date: ${
|
||||||
|
owl.__info__.date
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.state.messages = [];
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = buildData(n);
|
||||||
|
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._benchmark("clear", () => {
|
||||||
|
this.state.messages = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||||
|
const msg = Object.assign({}, messages[i]);
|
||||||
|
msg.author += "!!!";
|
||||||
|
this.set(messages, i, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(event) {
|
||||||
|
this.benchmark("remove message", () => {
|
||||||
|
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
|
||||||
|
this.state.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.log.appendChild(div);
|
||||||
|
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.refs.log.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultiple() {
|
||||||
|
this.state.multipleFlag = !this.state.multipleFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClear() {
|
||||||
|
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Application initialization
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
async function start() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(templates)
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>OWL 0.13.0 Benchmark</title>
|
||||||
|
<link href="../shared/main.css" rel="stylesheet"/>
|
||||||
|
<script src='owl.js'></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='main'></div>
|
||||||
|
<script src='app.js' type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
|||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||||
|
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||||
|
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||||
|
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||||
|
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||||
|
<button t-on-click="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||||
|
<hr/>
|
||||||
|
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content" t-ref="log"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content" t-on-remove-message="removeMessage">
|
||||||
|
<t t-foreach="state.messages" t-as="message">
|
||||||
|
<t t-widget="Message" t-key="message.id" message="message"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Message" class="message">
|
||||||
|
<span class="author"><t t-esc="props.message.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="props.message.msg"/></span>
|
||||||
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Likes Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { counter: 0 };
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends owl.Component {
|
||||||
|
widgets = { Counter };
|
||||||
|
|
||||||
|
shouldUpdate(nextProps) {
|
||||||
|
return nextProps.message !== this.props.message;
|
||||||
|
}
|
||||||
|
removeMessage() {
|
||||||
|
this.trigger("remove-message", {
|
||||||
|
id: this.props.message.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = { Message };
|
||||||
|
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.log(
|
||||||
|
`Benchmarking Owl v${owl.__info__.version} (build date: ${
|
||||||
|
owl.__info__.date
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.state.messages = [];
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = buildData(n);
|
||||||
|
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._benchmark("clear", () => {
|
||||||
|
this.state.messages = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||||
|
const msg = Object.assign({}, messages[i]);
|
||||||
|
msg.author += "!!!";
|
||||||
|
this.set(messages, i, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(event) {
|
||||||
|
this.benchmark("remove message", () => {
|
||||||
|
const index = this.state.messages.findIndex(m => m.id === event.detail.id);
|
||||||
|
this.state.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.log.appendChild(div);
|
||||||
|
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.refs.log.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultiple() {
|
||||||
|
this.state.multipleFlag = !this.state.multipleFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClear() {
|
||||||
|
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Application initialization
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
async function start() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(templates)
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>OWL 0.14.0 Benchmark</title>
|
||||||
|
<link href="../shared/main.css" rel="stylesheet"/>
|
||||||
|
<script src='owl.js'></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='main'></div>
|
||||||
|
<script src='app.js' type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
|||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||||
|
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||||
|
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||||
|
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||||
|
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||||
|
<button t-on-click="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||||
|
<hr/>
|
||||||
|
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content" t-ref="log"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content" t-on-remove-message="removeMessage">
|
||||||
|
<t t-foreach="state.messages" t-as="message">
|
||||||
|
<t t-widget="Message" t-key="message.id" message="message"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Message" class="message">
|
||||||
|
<span class="author"><t t-esc="props.message.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="props.message.msg"/></span>
|
||||||
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Likes Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { counter: 0 };
|
||||||
|
template = "Counter";
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends owl.Component {
|
||||||
|
widgets = { Counter };
|
||||||
|
template = "Message";
|
||||||
|
|
||||||
|
shouldUpdate(nextProps) {
|
||||||
|
return nextProps !== this.props;
|
||||||
|
}
|
||||||
|
removeMessage() {
|
||||||
|
this.trigger("remove_message", {
|
||||||
|
id: this.props.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = { Message };
|
||||||
|
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||||
|
template = "App";
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.log(
|
||||||
|
`Benchmarking Owl v${owl._version} (build date: ${
|
||||||
|
owl._date
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.state.messages = [];
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = buildData(n);
|
||||||
|
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._benchmark("clear", () => {
|
||||||
|
this.state.messages = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||||
|
const msg = Object.assign({}, messages[i]);
|
||||||
|
msg.author += "!!!";
|
||||||
|
this.set(messages, i, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(data) {
|
||||||
|
this.benchmark("remove message", () => {
|
||||||
|
const index = this.state.messages.findIndex(m => m.id === data.id);
|
||||||
|
this.state.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.log.appendChild(div);
|
||||||
|
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.refs.log.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultiple() {
|
||||||
|
this.state.multipleFlag = !this.state.multipleFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClear() {
|
||||||
|
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Application initialization
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
async function start() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(templates)
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||||
|
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||||
|
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||||
|
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||||
|
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||||
|
<button t-on-click="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||||
|
<hr/>
|
||||||
|
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content" t-ref="'log'"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content" t-on-remove-message="removeMessage">
|
||||||
|
<t t-foreach="state.messages" t-as="message">
|
||||||
|
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Message" class="message">
|
||||||
|
<span class="author"><t t-esc="props.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="props.msg"/></span>
|
||||||
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Likes Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { counter: 0 };
|
||||||
|
template = "Counter";
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends owl.Component {
|
||||||
|
widgets = { Counter };
|
||||||
|
template = "Message";
|
||||||
|
|
||||||
|
shouldUpdate(nextProps) {
|
||||||
|
return nextProps !== this.props;
|
||||||
|
}
|
||||||
|
removeMessage() {
|
||||||
|
this.trigger("remove_message", {
|
||||||
|
id: this.props.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = { Message };
|
||||||
|
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||||
|
template = "App";
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.log(
|
||||||
|
`Benchmarking Owl v${owl._version} (build date: ${
|
||||||
|
owl._date
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.state.messages = [];
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = buildData(n);
|
||||||
|
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._benchmark("clear", () => {
|
||||||
|
this.state.messages = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||||
|
const msg = Object.assign({}, messages[i]);
|
||||||
|
msg.author += "!!!";
|
||||||
|
this.set(messages, i, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(data) {
|
||||||
|
this.benchmark("remove message", () => {
|
||||||
|
const index = this.state.messages.findIndex(m => m.id === data.id);
|
||||||
|
this.state.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.log.appendChild(div);
|
||||||
|
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.refs.log.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultiple() {
|
||||||
|
this.state.multipleFlag = !this.state.multipleFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClear() {
|
||||||
|
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Application initialization
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
async function start() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(templates)
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||||
|
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||||
|
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||||
|
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||||
|
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||||
|
<button t-on-click="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||||
|
<hr/>
|
||||||
|
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content" t-ref="'log'"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content" t-on-remove-message="removeMessage">
|
||||||
|
<t t-foreach="state.messages" t-as="message">
|
||||||
|
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Message" class="message">
|
||||||
|
<span class="author"><t t-esc="props.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="props.msg"/></span>
|
||||||
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Likes Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { counter: 0 };
|
||||||
|
template = "Counter";
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends owl.Component {
|
||||||
|
widgets = { Counter };
|
||||||
|
template = "Message";
|
||||||
|
|
||||||
|
shouldUpdate(nextProps) {
|
||||||
|
return nextProps !== this.props;
|
||||||
|
}
|
||||||
|
removeMessage() {
|
||||||
|
this.trigger("remove_message", {
|
||||||
|
id: this.props.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = { Message };
|
||||||
|
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||||
|
template = "App";
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.log(
|
||||||
|
`Benchmarking Owl v${owl._version} (build date: ${
|
||||||
|
owl._date
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.state.messages = [];
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = buildData(n);
|
||||||
|
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._benchmark("clear", () => {
|
||||||
|
this.state.messages = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||||
|
const msg = Object.assign({}, messages[i]);
|
||||||
|
msg.author += "!!!";
|
||||||
|
this.set(messages, i, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(data) {
|
||||||
|
this.benchmark("remove message", () => {
|
||||||
|
const index = this.state.messages.findIndex(m => m.id === data.id);
|
||||||
|
this.state.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.log.appendChild(div);
|
||||||
|
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.refs.log.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultiple() {
|
||||||
|
this.state.multipleFlag = !this.state.multipleFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClear() {
|
||||||
|
this.state.clearAfterFlag = !this.state.clearAfterFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Application initialization
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
async function start() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(templates)
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||||
|
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||||
|
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||||
|
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||||
|
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||||
|
<button t-on-click="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="multipleflag" t-on-change="toggleMultiple" t-att-checked="state.multipleFlag"/>
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="clearFlag" t-on-change="toggleClear" t-att-checked="state.clearAfterFlag"/>
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||||
|
<hr/>
|
||||||
|
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content" t-ref="'log'"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content" t-on-remove-message="removeMessage">
|
||||||
|
<t t-foreach="state.messages" t-as="message">
|
||||||
|
<t t-widget="Message" t-key="message.id" t-props="message" t-on-remove_message="removeMessage"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Message" class="message">
|
||||||
|
<span class="author"><t t-esc="props.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="props.msg"/></span>
|
||||||
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Likes Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends owl.Component {
|
||||||
|
state = { counter: 0 };
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends owl.Component {
|
||||||
|
widgets = { Counter };
|
||||||
|
|
||||||
|
shouldUpdate(nextProps) {
|
||||||
|
return nextProps.message !== this.props.message;
|
||||||
|
}
|
||||||
|
removeMessage() {
|
||||||
|
this.trigger("remove-message", {
|
||||||
|
id: this.props.message.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class App extends owl.Component {
|
||||||
|
widgets = { Message };
|
||||||
|
state = { messages: [], multipleFlag: false, clearAfterFlag: false };
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.log(
|
||||||
|
`Benchmarking Owl v${owl.__info__.version} (build date: ${
|
||||||
|
owl.__info__.date
|
||||||
|
})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.state.messages = [];
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = buildData(n);
|
||||||
|
this.state.messages.push.apply(this.state.messages, newMessages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._benchmark("clear", () => {
|
||||||
|
this.state.messages = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const setState = owl.Observer.set;
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < this.state.messages.length; i += 10) {
|
||||||
|
const msg = Object.assign({}, messages[i]);
|
||||||
|
msg.author += "!!!";
|
||||||
|
setState(messages, i, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(event) {
|
||||||
|
this.benchmark("remove message", () => {
|
||||||
|
const index = this.state.messages.findIndex(
|
||||||
|
m => m.id === event.detail.id
|
||||||
|
);
|
||||||
|
this.state.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.log.appendChild(div);
|
||||||
|
this.refs.log.scrollTop = this.refs.log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLog() {
|
||||||
|
this.refs.log.innerHTML = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Application initialization
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
async function start() {
|
||||||
|
const templates = await owl.utils.loadTemplates("templates.xml");
|
||||||
|
const env = {
|
||||||
|
qweb: new owl.QWeb(templates)
|
||||||
|
};
|
||||||
|
const app = new App(env);
|
||||||
|
app.mount(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
<script src='../../owl.js'></script>
|
<script src='../../owl.js'></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id='main'></div>
|
|
||||||
<script src='app.js' type="module"></script>
|
<script src='app.js' type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">
|
||||||
|
<div class="left-thing">
|
||||||
|
<div class="title">Actions</div>
|
||||||
|
<div class="panel">
|
||||||
|
<button t-on-click="addMessages(100)">Add 100 messages</button>
|
||||||
|
<button t-on-click="addMessages(1000)">Add 1k messages</button>
|
||||||
|
<button t-on-click="addMessages(10000)">Add 10k messages</button>
|
||||||
|
<button t-on-click="addMessages(30000)">Add 30k messages</button>
|
||||||
|
<button t-on-click="updateSomeMessages">Update every 10th messages</button>
|
||||||
|
<button t-on-click="clear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="flags">
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="multipleflag" t-model="multipleFlag"/>
|
||||||
|
<label for="multipleflag">Do it 20x</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="clearFlag" t-model="clearAfterFlag" />
|
||||||
|
<label for="clearFlag">Clear after</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">Number of messages: <t t-esc="state.messages.length"/></div>
|
||||||
|
<hr/>
|
||||||
|
<div class="title">Log <span class="clear-log" t-on-click="clearLog">(clear)</span></div>
|
||||||
|
<div class="log">
|
||||||
|
<div class="log-content" t-ref="log"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-thing">
|
||||||
|
<div class="content" t-on-remove-message="removeMessage">
|
||||||
|
<t t-foreach="state.messages" t-as="message">
|
||||||
|
<t t-widget="Message" t-key="message.id" message="message"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Message" class="message">
|
||||||
|
<span class="author"><t t-esc="props.message.author"/></span>
|
||||||
|
<span class="msg"><t t-esc="props.message.msg"/></span>
|
||||||
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
|
<t t-widget="Counter"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="Counter">
|
||||||
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
import {
|
||||||
|
buildData,
|
||||||
|
startMeasure,
|
||||||
|
stopMeasure,
|
||||||
|
formatNumber
|
||||||
|
} from "../shared/utils.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Counter Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Counter extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
counter: 0
|
||||||
|
};
|
||||||
|
this.increment = this.increment.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: this.increment
|
||||||
|
},
|
||||||
|
"Value: ",
|
||||||
|
this.state.counter
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.setState({
|
||||||
|
counter: this.state.counter + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Message Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Message extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.removeMessage = this.removeMessage.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: "message"
|
||||||
|
},
|
||||||
|
React.createElement(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className: "author"
|
||||||
|
},
|
||||||
|
this.props.message.author
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className: "msg"
|
||||||
|
},
|
||||||
|
this.props.message.msg
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
className: "remove",
|
||||||
|
onClick: this.removeMessage
|
||||||
|
},
|
||||||
|
"Remove"
|
||||||
|
),
|
||||||
|
React.createElement(Counter, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage() {
|
||||||
|
this.props.removeCB(this.props.message.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Root Widget
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class Main extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
messages: [],
|
||||||
|
multipleFlag: false,
|
||||||
|
clearAfterFlag: false
|
||||||
|
};
|
||||||
|
this.removeMessage = this.removeMessage.bind(this);
|
||||||
|
this.toggleMultipleFlag = this.toggleMultipleFlag.bind(this);
|
||||||
|
this.toggleClearAfterFlag = this.toggleClearAfterFlag.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.log(`Benchmarking React v${React.version}`);
|
||||||
|
}
|
||||||
|
log(str, isBold) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
if (isBold) {
|
||||||
|
div.classList.add("bold");
|
||||||
|
}
|
||||||
|
div.textContent = `> ${str}`;
|
||||||
|
this.refs.logref.appendChild(div);
|
||||||
|
this.refs.logref.scrollTop = this.refs.logref.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMultipleFlag() {
|
||||||
|
this.setState({ multipleFlag: !this.state.multipleFlag });
|
||||||
|
}
|
||||||
|
toggleClearAfterFlag() {
|
||||||
|
this.setState({ clearAfterFlag: !this.state.clearAfterFlag });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const msgList = this.state.messages.map(m =>
|
||||||
|
React.createElement(Message, {
|
||||||
|
key: m.id,
|
||||||
|
message: m,
|
||||||
|
removeCB: this.removeMessage
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: "main"
|
||||||
|
},
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: "left-thing"
|
||||||
|
},
|
||||||
|
React.createElement("div", { className: "title" }, "Actions"),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "panel" },
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: _ => this.addMessages(100)
|
||||||
|
},
|
||||||
|
"Add 100 messages"
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: _ => this.addMessages(1000)
|
||||||
|
},
|
||||||
|
"Add 1k messages"
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: _ => this.addMessages(10000)
|
||||||
|
},
|
||||||
|
"Add 10k messages"
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: _ => this.addMessages(30000)
|
||||||
|
},
|
||||||
|
"Add 30k messages"
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: _ => this.updateSomeMessages()
|
||||||
|
},
|
||||||
|
"Update every 10th message"
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
onClick: _ => this.clear()
|
||||||
|
},
|
||||||
|
"Clear"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "flags" },
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
React.createElement("input", {
|
||||||
|
type: "checkbox",
|
||||||
|
id: "multipleFlag",
|
||||||
|
value: this.state.multipleFlag,
|
||||||
|
onChange: this.toggleMultipleFlag
|
||||||
|
}),
|
||||||
|
React.createElement("label", { for: "multipleFlag" }, " Do it 20x")
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
React.createElement("input", {
|
||||||
|
type: "checkbox",
|
||||||
|
id: "clearFlag",
|
||||||
|
value: this.state.clearAfterFlag,
|
||||||
|
onChange: this.toggleClearAfterFlag
|
||||||
|
}),
|
||||||
|
React.createElement("label", { for: "clearFlag" }, " Clear after")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "info" },
|
||||||
|
"Number of messages: ",
|
||||||
|
this.state.messages.length
|
||||||
|
),
|
||||||
|
React.createElement("hr"),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "title" },
|
||||||
|
"Log ",
|
||||||
|
React.createElement("span", { className: "clear-log" }, "(clear)")
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "log" },
|
||||||
|
React.createElement("div", {
|
||||||
|
className: "log-content",
|
||||||
|
ref: "logref"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: "right-thing"
|
||||||
|
},
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: "content"
|
||||||
|
},
|
||||||
|
msgList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessages(n) {
|
||||||
|
this.benchmark("add " + n, () => {
|
||||||
|
const newMessages = this.state.messages.concat(buildData(n));
|
||||||
|
this.setState({
|
||||||
|
messages: newMessages
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._benchmark("clear", () => {
|
||||||
|
this.setState({
|
||||||
|
messages: []
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSomeMessages() {
|
||||||
|
this.benchmark("update every 10th", () => {
|
||||||
|
const messages = this.state.messages;
|
||||||
|
for (let i = 0; i < messages.length; i += 10) {
|
||||||
|
messages[i].author += "!!!";
|
||||||
|
}
|
||||||
|
this.forceUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(id) {
|
||||||
|
startMeasure("remove message");
|
||||||
|
const index = this.state.messages.findIndex(m => m.id === id);
|
||||||
|
const messages = this.state.messages.slice();
|
||||||
|
messages.splice(index, 1);
|
||||||
|
this.setState({
|
||||||
|
messages
|
||||||
|
});
|
||||||
|
stopMeasure();
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmark(message, fn, callback) {
|
||||||
|
if (this.state.multipleFlag) {
|
||||||
|
const N = 20;
|
||||||
|
let n = N;
|
||||||
|
let total = 0;
|
||||||
|
let cb = info => {
|
||||||
|
let finalize = () => {
|
||||||
|
n--;
|
||||||
|
total += info.delta;
|
||||||
|
if (n === 0) {
|
||||||
|
const avg = total / N;
|
||||||
|
this.log(`Average: ${formatNumber(avg)}ms`, true);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (this.state.clearAfterFlag) {
|
||||||
|
this._benchmark(
|
||||||
|
"clear",
|
||||||
|
() => {
|
||||||
|
this.setState({ messages: [] });
|
||||||
|
},
|
||||||
|
finalize,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._benchmark(message, fn, cb);
|
||||||
|
} else {
|
||||||
|
this._benchmark(message, fn, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_benchmark(message, fn, cb, log = true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startMeasure(message);
|
||||||
|
fn();
|
||||||
|
stopMeasure(info => {
|
||||||
|
if (log) {
|
||||||
|
this.log(info.msg);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
cb(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
} //-----------------------------
|
||||||
|
// INIT
|
||||||
|
//-----------------------------
|
||||||
|
|
||||||
|
ReactDOM.render(React.createElement(Main, null), document.body);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user