mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 671965026d | |||
| bc7fe9023d | |||
| c2c7997df3 | |||
| a1d7e6c987 | |||
| a1e982aba5 | |||
| 2ce027a0a6 | |||
| 61f21511a5 | |||
| 5682577870 | |||
| 25b633cc63 | |||
| f16b267931 | |||
| 30ec952588 | |||
| 46ba77d0cc | |||
| 042e376c2b | |||
| b9f1af2ad5 | |||
| 7df0fdcc0a | |||
| 73d743a1c3 | |||
| 8758dfcedf | |||
| 0a10bbc023 | |||
| 9c1d369e94 | |||
| 9556696fa8 | |||
| 04eb720619 | |||
| 8e098e8ed6 | |||
| c836cead58 | |||
| 361ecbe90b | |||
| ae8d7d3be2 | |||
| ed4f92211c | |||
| f8804d6501 | |||
| de7c680387 | |||
| 6dd7391cdc | |||
| dfa7d5932d | |||
| 53911de00a | |||
| 1760d0d336 | |||
| 0aba9748c9 | |||
| 8ee0973dab |
@@ -36,11 +36,6 @@ Here is a short example to illustrate interactive widgets:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class ClickCounter extends owl.Component {
|
class ClickCounter extends owl.Component {
|
||||||
inlineTemplate = `
|
|
||||||
<button t-on-click="increment">
|
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
|
||||||
</button>`;
|
|
||||||
|
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
@@ -48,7 +43,12 @@ class ClickCounter extends owl.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const qweb = new owl.QWeb();
|
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 counter = new ClickCounter({ qweb });
|
const counter = new ClickCounter({ qweb });
|
||||||
counter.mount(document.body);
|
counter.mount(document.body);
|
||||||
```
|
```
|
||||||
@@ -59,19 +59,20 @@ More interesting examples can be found on the [playground](https://odoo.github.i
|
|||||||
|
|
||||||
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.10.0.js](https://github.com/odoo/owl/releases/download/v0.10.0/owl.js)
|
- [owl-0.11.0.js](https://github.com/odoo/owl/releases/download/v0.11.0/owl.js)
|
||||||
- [owl-0.10.0.min.js](https://github.com/odoo/owl/releases/download/v0.10.0/owl.min.js)
|
- [owl-0.11.0.min.js](https://github.com/odoo/owl/releases/download/v0.11.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 extras` | build extras applications, start a static server (see [here](extras/readme.md)) |
|
||||||
|
| `npm run extras:watch` | same as `extras`, but with a watcher to rebuild owl |
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
+40
-15
@@ -10,6 +10,7 @@
|
|||||||
- [Methods](#methods)
|
- [Methods](#methods)
|
||||||
- [Lifecycle](#lifecycle)
|
- [Lifecycle](#lifecycle)
|
||||||
- [Semantics](#semantics)
|
- [Semantics](#semantics)
|
||||||
|
- [Asynchronous rendering](#asynchronous-rendering)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -29,9 +30,9 @@ OWL components are the building blocks for user interface. They are designed to
|
|||||||
and follow the QWeb specification. This is a requirement for Odoo.
|
and follow the QWeb specification. This is a requirement for Odoo.
|
||||||
|
|
||||||
OWL components are defined as a subclass of Component. The rendering is
|
OWL components are defined as a subclass of Component. The rendering is
|
||||||
exclusively done by a [QWeb](qweb.md) template (either defined inline or preloaded in QWeb).
|
exclusively done by a [QWeb](qweb.md) template (which needs to be preloaded in QWeb).
|
||||||
The rendering is done by QWeb, which will generate a virtual dom representation
|
Rendering a component generates a virtual dom representation
|
||||||
of the widget, then patch the DOM to apply the changes in an efficient way.
|
of the widget, which is then patched to the DOM, in order to apply the changes in an efficient way.
|
||||||
|
|
||||||
OWL components observe their states, and rerender themselves whenever it is
|
OWL components observe their states, and rerender themselves whenever it is
|
||||||
changed. This is done by an [observer](observer.md).
|
changed. This is done by an [observer](observer.md).
|
||||||
@@ -42,7 +43,6 @@ Let us have a look at a simple component:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class ClickCounter extends owl.Component {
|
class ClickCounter extends owl.Component {
|
||||||
template = "click-counter";
|
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
@@ -52,7 +52,7 @@ class ClickCounter extends owl.Component {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<button t-name="click-counter" t-on-click="increment">
|
<button t-name="ClickCounter" t-on-click="increment">
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
</button>
|
</button>
|
||||||
```
|
```
|
||||||
@@ -61,7 +61,8 @@ Note that this code is written in ESNext style, so it will only run on the
|
|||||||
latest browsers without a transpilation step.
|
latest browsers without a transpilation step.
|
||||||
|
|
||||||
This example show how a component should be defined: it simply subclasses the
|
This example show how a component should be defined: it simply subclasses the
|
||||||
Component class. It needs to define a template (`click-counter` here). Also,
|
Component class. If no `template` key is defined, then
|
||||||
|
Owl will use the component's name as template name. Here,
|
||||||
a state object is defined. It is not mandatory to use the state object, but it
|
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.
|
||||||
@@ -74,7 +75,7 @@ templates are standard [QWeb](qweb.md) templates, but with an extra directive:
|
|||||||
widgets:
|
widgets:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="parent">
|
<div t-name="ParentWidget">
|
||||||
<span>some text</span>
|
<span>some text</span>
|
||||||
<t t-widget="MyWidget" t-props="{info: 13}"/>
|
<t t-widget="MyWidget" t-props="{info: 13}"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,7 +83,6 @@ widgets:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
class ParentWidget extends owl.Component {
|
class ParentWidget extends owl.Component {
|
||||||
template = 'parent';
|
|
||||||
widgets = { MyWidget: MyWidget};
|
widgets = { MyWidget: MyWidget};
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ additional css classes or style for the sub widget: css declared in `class`, `st
|
|||||||
root widget element.
|
root widget element.
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="parent">
|
<div t-name="ParentWidget">
|
||||||
<t t-widget="MyWidget" class="someClass" style="font-weight:bold;" t-props="{info: 13}"/>
|
<t t-widget="MyWidget" class="someClass" style="font-weight:bold;" t-props="{info: 13}"/>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
@@ -112,7 +112,7 @@ between a valid css class added by the component, or some custom code, and a
|
|||||||
class that need to be removed. This is why we only support the explicit syntax
|
class that need to be removed. This is why we only support the explicit syntax
|
||||||
with a class object:
|
with a class object:
|
||||||
|
|
||||||
```js
|
```xml
|
||||||
<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}" />
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -123,6 +123,10 @@ It exists in the context of an environment (`env`), which is propagated from a
|
|||||||
parent to its children. The environment needs to have a QWeb instance, which
|
parent to its children. The environment needs to have a QWeb instance, which
|
||||||
will be used to render the component template.
|
will be used to render the component template.
|
||||||
|
|
||||||
|
Be aware that the name of the component may be significant: if a component does
|
||||||
|
not define a `template` key, then Owl will lookup in QWeb to
|
||||||
|
find a template with the component name (or one of its ancestor).
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
- **`el`** (HTMLElement | null): reference to the DOM root node of the element. It is `null` when the
|
- **`el`** (HTMLElement | null): reference to the DOM root node of the element. It is `null` when the
|
||||||
@@ -130,12 +134,9 @@ will be used to render the component template.
|
|||||||
|
|
||||||
- **`env`** (Object): the component environment, which contains a QWeb instance.
|
- **`env`** (Object): the component environment, which contains a QWeb instance.
|
||||||
|
|
||||||
- **`template`** (string): a string, which is the name of the QWeb template that will render
|
- **`template`** (string, optional): if given, this is the name of the QWeb template that will render
|
||||||
the component.
|
the component.
|
||||||
|
|
||||||
- **`inlineTemplate`** (string, optional): a string that represents a xml template. If set,
|
|
||||||
this will be loaded into QWeb and used instead of the `template` property.
|
|
||||||
|
|
||||||
- **`state`** (Object): this is the location of the component's state, if there is
|
- **`state`** (Object): this is the location of the component's state, if there is
|
||||||
any. After the willStart method, the `state` property is observed, and each
|
any. After the willStart method, the `state` property is observed, and each
|
||||||
change will cause the widget to rerender itself.
|
change will cause the widget to rerender itself.
|
||||||
@@ -235,7 +236,6 @@ implemented in most cases:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
class ClickCounter extends owl.Component {
|
class ClickCounter extends owl.Component {
|
||||||
template = "click-counter";
|
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
|
||||||
...
|
...
|
||||||
@@ -431,3 +431,28 @@ Here is what Owl will do:
|
|||||||
5. patching of `D`
|
5. patching of `D`
|
||||||
|
|
||||||
6. `patched` hooks are called on `D`, `C`
|
6. `patched` hooks are called on `D`, `C`
|
||||||
|
|
||||||
|
## Asynchronous rendering
|
||||||
|
|
||||||
|
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
|
||||||
|
carefully about all possible interactions. Clearly, this is also true for Owl
|
||||||
|
components.
|
||||||
|
|
||||||
|
There are two different common problems with Owl asynchronous rendering model:
|
||||||
|
|
||||||
|
- any widget can delay the rendering (initial and subsequent) of the whole
|
||||||
|
application
|
||||||
|
- for a given widget, there are two independant situations that will trigger an
|
||||||
|
asynchronous rerendering: a change in the state, or a change in the props.
|
||||||
|
These changes may be done at different times, and Owl has no way of knowing
|
||||||
|
how to reconcile the resulting renderings.
|
||||||
|
|
||||||
|
Here are a few tips on how to work with asynchronous widgets:
|
||||||
|
|
||||||
|
1. minimize the use of asynchronous widgets!
|
||||||
|
2. Maybe move the asynchronous logic in a store, which then triggers (mostly)
|
||||||
|
synchronous renderings
|
||||||
|
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
|
||||||
|
second, and only once (see `owl.utils.loadJS`)
|
||||||
|
|||||||
+221
-38
@@ -3,25 +3,30 @@
|
|||||||
## Content
|
## Content
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
|
- [QWeb Engine](#qweb-engine)
|
||||||
- [QWeb Specification](#qweb-specification)
|
- [QWeb Specification](#qweb-specification)
|
||||||
- [Static html nodes](#static-html-nodes)
|
|
||||||
- [`t-esc` directive](#t-esc-directive)
|
- [Static html nodes](#static-html-nodes)
|
||||||
- [`t-raw` directive](#t-raw-directive)
|
- [`t-esc` directive](#t-esc-directive)
|
||||||
- [`t-set` directive](#t-set-directive)
|
- [`t-raw` directive](#t-raw-directive)
|
||||||
- [`t-if` directive](#t-if-directive)
|
- [`t-set` directive](#t-set-directive)
|
||||||
- [Expression evaluation](#expression-evaluation)
|
- [`t-if` directive](#t-if-directive)
|
||||||
- [`t-att` directive (dynamic attributes)](#t-att-directive-dynamic-attributes)
|
- [Expression evaluation](#expression-evaluation)
|
||||||
- [`t-call` directive (sub templates)](#t-call-directive-sub-templates)
|
- [Dynamic attributes (`t-att` and `t-attf` directives)](#dynamic-attributes-t-att-and-t-attf-directives)
|
||||||
|
- [`t-attf` directive (dynamic attributes)](#t-att-directive-dynamic-attributes)
|
||||||
|
- [`t-call` directive (sub templates)](#t-call-directive-sub-templates)
|
||||||
|
|
||||||
- [JS/OWL Specific Extensions](#jsowl-specific-extensions)
|
- [JS/OWL Specific Extensions](#jsowl-specific-extensions)
|
||||||
- [`t-on` directive](#t-on-directive)
|
|
||||||
- [Component: `t-widget`, `t-props`](#component-t-widget-t-props)
|
- [`t-on` directive](#t-on-directive)
|
||||||
- [`t-ref` directive](#t-ref-directive)
|
- [Component: `t-widget`, `t-props`](#component-t-widget-t-props)
|
||||||
- [`t-key` directive](#t-key-directive)
|
- [`t-ref` directive](#t-ref-directive)
|
||||||
- [`t-transition` directive](#t-transition-directive)
|
- [`t-key` directive](#t-key-directive)
|
||||||
- [Debugging (`t-debug` and `t-log`)](#debugging-t-debug-and-t-log)
|
- [`t-transition` directive](#t-transition-directive)
|
||||||
- [White spaces](#white-spaces)
|
- [`t-mounted` directive](#t-mounted-directive)
|
||||||
- [Root nodes](#root-nodes)
|
- [Debugging (`t-debug` and `t-log`)](#debugging-t-debug-and-t-log)
|
||||||
|
- [White spaces](#white-spaces)
|
||||||
|
- [Root nodes](#root-nodes)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -38,9 +43,54 @@ templates into functions that output a virtual DOM instead of a string. This is
|
|||||||
necessary for the component system. In addition, it has a few extra directives
|
necessary for the component system. In addition, it has a few extra directives
|
||||||
(see [OWL Specific Extensions](#owlspecificextensions))
|
(see [OWL Specific Extensions](#owlspecificextensions))
|
||||||
|
|
||||||
|
## QWeb Engine
|
||||||
|
|
||||||
|
This section is about the javascript code that implements the `QWeb` specification.
|
||||||
|
Owl exports a `QWeb` class in `owl.QWeb`. To use it, it just needs to be
|
||||||
|
instantiated:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const qweb = new owl.QWeb();
|
||||||
|
```
|
||||||
|
|
||||||
|
It's API is quite simple:
|
||||||
|
|
||||||
|
- **`constructor(data)`**: constructor. Takes an optional string to add initial
|
||||||
|
templates (see `addTemplates` for more information on format of the string).
|
||||||
|
|
||||||
|
```js
|
||||||
|
const qweb = new owl.QWeb(TEMPLATES);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`addTemplate(name, xmlStr)`**: add a specific template.
|
||||||
|
|
||||||
|
```js
|
||||||
|
qweb.addTemplate("mytemplate", "<div>hello</div>");
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`addTemplates(xmlStr)`**: add a list of templates (identified by `t-name`
|
||||||
|
attribute).
|
||||||
|
|
||||||
|
```js
|
||||||
|
const TEMPLATES = `
|
||||||
|
<templates>
|
||||||
|
<div t-name="App" class="main">main</div>
|
||||||
|
<div t-name="OtherWidget">other widget</div>
|
||||||
|
</templates>`;
|
||||||
|
qweb.addTemplates(TEMPLATES);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`render(name, context, extra)`**: renders a template. This returns a `vnode`,
|
||||||
|
which is a virtual representation of the DOM (see [vdom doc](vdom.md)).
|
||||||
|
|
||||||
|
```js
|
||||||
|
const vnode = qweb.render("App", widget);
|
||||||
|
```
|
||||||
|
|
||||||
## QWeb Specification
|
## QWeb Specification
|
||||||
|
|
||||||
|
We define in this section the specification of how `QWeb` templates should be
|
||||||
|
rendered.
|
||||||
|
|
||||||
### Static html nodes
|
### Static html nodes
|
||||||
|
|
||||||
@@ -196,7 +246,7 @@ compile time.
|
|||||||
| Word | will be replaced by |
|
| Word | will be replaced by |
|
||||||
| ----- | ------------------- |
|
| ----- | ------------------- |
|
||||||
| `and` | `&&` |
|
| `and` | `&&` |
|
||||||
| `or` | `\|\|` |
|
| `or` | `\|\|` |
|
||||||
| `gt` | `>` |
|
| `gt` | `>` |
|
||||||
| `gte` | `>=` |
|
| `gte` | `>=` |
|
||||||
| `lt` | `<` |
|
| `lt` | `<` |
|
||||||
@@ -208,7 +258,7 @@ compile time.
|
|||||||
<div><p t-if="10 + 2 gt 5">ok</p></div>
|
<div><p t-if="10 + 2 gt 5">ok</p></div>
|
||||||
```
|
```
|
||||||
|
|
||||||
### `t-att` directive (dynamic attributes)
|
### Dynamic attributes (`t-att` and `t-attf` directives)
|
||||||
|
|
||||||
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:
|
||||||
@@ -216,7 +266,7 @@ evaluate an expression (at rendering time) and bind an attribute to its result:
|
|||||||
For example, if we have `id` set to 32 in the rendering context,
|
For example, if we have `id` set to 32 in the rendering context,
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-att-data-action-id="id"/> <!-- result: <div data-action-id="32"></div> -->
|
<div t-att-data-action-id="id"/> <!-- result: <div data-action-id="32"></div> -->
|
||||||
```
|
```
|
||||||
|
|
||||||
If an expression evaluates to a falsy value, it will not be set at all:
|
If an expression evaluates to a falsy value, it will not be set at all:
|
||||||
@@ -225,6 +275,21 @@ If an expression evaluates to a falsy value, it will not be set at all:
|
|||||||
<div t-att-foo="false"/> <!-- result: <div></div> -->
|
<div t-att-foo="false"/> <!-- result: <div></div> -->
|
||||||
```
|
```
|
||||||
|
|
||||||
|
There is another way to format a string attribute: the `t-attf-` directive. With
|
||||||
|
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
|
||||||
|
<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> -->
|
||||||
|
```
|
||||||
|
|
||||||
### `t-call` directive (sub templates)
|
### `t-call` directive (sub templates)
|
||||||
|
|
||||||
QWeb templates can be used for top level rendering, but they can also be used
|
QWeb templates can be used for top level rendering, but they can also be used
|
||||||
@@ -242,12 +307,12 @@ templates), using the `t-call` directive:
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
will be rendered as `<div><p>owl</p></div>`. This example shows that the sub
|
will be rendered as `<div><p>owl</p></div>`. This example shows that the sub
|
||||||
template is rendered with the execution context of the parent. The sub template
|
template is rendered with the execution context of the parent. The sub template
|
||||||
is actually inlined in the main template, but in a sub scope: variables defined
|
is actually inlined in the main template, but in a sub scope: variables defined
|
||||||
in the sub template do not escape.
|
in the sub template do not escape.
|
||||||
|
|
||||||
Sometimes, one might want to pass information to the sub template. In that case,
|
Sometimes, one might want to pass information to the sub template. In that case,
|
||||||
the content of the body of the `t-call` directive is available as a special
|
the content of the body of the `t-call` directive is available as a special
|
||||||
magic variable `0`:
|
magic variable `0`:
|
||||||
|
|
||||||
@@ -273,43 +338,141 @@ will result in :
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## JS/OWL Specific Extensions
|
## JS/OWL Specific Extensions
|
||||||
|
|
||||||
### `t-on` directive
|
### `t-on` directive
|
||||||
|
|
||||||
|
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 are two different use cases.
|
||||||
|
|
||||||
|
1. Register an event handler on a DOM node
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<button t-on-click="someMethod">Do something</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
This will be roughly translated in javascript like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
button.addEventListener("click", widget.someMethod.bind(widget));
|
||||||
|
```
|
||||||
|
|
||||||
|
The suffix (`click` in this example) is simply the name of the actual DOM
|
||||||
|
event.
|
||||||
|
|
||||||
|
2. Register an event handler on a component. This will not capture a DOM event,
|
||||||
|
but rather a _business_ event:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<t t-widget="MyWidget" t-on-menuLoaded="someMethod"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
class MyWidget {
|
||||||
|
someWhere() {
|
||||||
|
const payload = ...;
|
||||||
|
this.trigger('menuLoaded', payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, the parent widget will receive the payload in its `someMethod` handler,
|
||||||
|
whenever the event is triggered.
|
||||||
|
|
||||||
|
The `t-on` directive also allows to prebind some arguments. For example,
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<button t-on-click="someMethod(expr)">Do something</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, `expr` is a valid Owl expression, so it could be `true` or some variable
|
||||||
|
from the rendering context.
|
||||||
|
|
||||||
### Component: `t-widget`, `t-props`
|
### Component: `t-widget`, `t-props`
|
||||||
|
|
||||||
|
The `t-widget` and the `t-props` directives are the key to a declarative component
|
||||||
|
system. They allow a template to define where and how a sub widget is created
|
||||||
|
and/or updated. For example:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-name="ParentWidget">
|
||||||
|
<t t-widget="ChildWidget" t-props="{count: state.val}"/>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
class ParentWidget {
|
||||||
|
widgets = { ChildWidget };
|
||||||
|
state = { val: 4 };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Whenever the template is rendered, it will automatically create the subwidget
|
||||||
|
`ChildWidget` at the correct place. It needs to find the reference to the
|
||||||
|
actual component class in the special `widgets` key.
|
||||||
|
|
||||||
|
In this example, the child widget will receive the object `{count: 4}` in its
|
||||||
|
constructor. This will be assigned to the `props` variable, which can be accessed
|
||||||
|
on the widget (and also, in the template). Whenever the state is updated, then
|
||||||
|
the subwidget will also be updated automatically.
|
||||||
|
|
||||||
### `t-ref` directive
|
### `t-ref` directive
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div>
|
||||||
|
<div t-ref="someDiv"/>
|
||||||
|
<t t-widget="SubWidget" t-ref="someWidget"/>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, the widget will be able to access the `div` and the component
|
||||||
|
inside the special `refs` variable:
|
||||||
|
|
||||||
|
```js
|
||||||
|
this.refs.someDiv;
|
||||||
|
this.refs.someWidget;
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful for various usecases: for example, integrating with an external
|
||||||
|
library that needs to render itself inside an actual DOM node. Or for calling
|
||||||
|
some method on a sub widget.
|
||||||
|
|
||||||
|
Note: if used on a component, the reference will be set in the `refs`
|
||||||
|
variable between `willPatch` and `patched`.
|
||||||
|
|
||||||
### `t-key` directive
|
### `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,
|
||||||
the focused element or the state of an input. This is why we use a virtual dom
|
the focused element or the state of an input. This is why we use a virtual dom
|
||||||
algorithm to keep the actual DOM node as much as possible. However, this is
|
algorithm to keep the actual DOM node as much as possible. However, this is
|
||||||
sometimes not enough, and we need to help Owl decide if an element is actually
|
sometimes not enough, and we need to help Owl decide if an element is actually
|
||||||
the same, or is different. The `t-key` directive is used to give an identity to an element.
|
the same, or is different. The `t-key` directive is used to give an identity to an element.
|
||||||
|
|
||||||
There are three main use cases:
|
There are three main use cases:
|
||||||
|
|
||||||
- *elements in a list*:
|
- _elements in a list_:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<span t-foreach="todos" t-as="todo" t-key="todo.id">
|
<span t-foreach="todos" t-as="todo" t-key="todo.id">
|
||||||
<t t-esc="todo.text"/>
|
<t t-esc="todo.text"/>
|
||||||
</span>
|
</span>
|
||||||
```
|
```
|
||||||
|
|
||||||
- *`t-if`/`t-else`*
|
- _`t-if`/`t-else`_
|
||||||
|
|
||||||
- *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-transition` directive
|
### `t-transition` directive
|
||||||
|
|
||||||
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
|
||||||
the lifetime of a node. Since this is not easy to do by hand, Owl `t-transition`
|
the lifetime of a node. Since this is not easy to do by hand, Owl `t-transition`
|
||||||
directive is there to help.
|
directive is there to help.
|
||||||
|
|
||||||
Whenever a node has a `t-transition` directive, with a `name` value, the following
|
Whenever a node has a `t-transition` directive, with a `name` value, the following
|
||||||
@@ -326,6 +489,7 @@ At node creation:
|
|||||||
ends.
|
ends.
|
||||||
|
|
||||||
At node destruction:
|
At node destruction:
|
||||||
|
|
||||||
- the css classes `name-leave` and `name-leave-active` will be added before the
|
- the css classes `name-leave` and `name-leave-active` will be added before the
|
||||||
node is removed to the DOM,
|
node is removed to the DOM,
|
||||||
- the css class `name-leave` will be removed on the next animation frame (so it
|
- the css class `name-leave` will be removed on the next animation frame (so it
|
||||||
@@ -342,16 +506,36 @@ For example, a simple fade in/out effect can be done with this:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.fade-enter-active, .fade-leave-active {
|
.fade-enter-active,
|
||||||
transition: opacity .5s;
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.5s;
|
||||||
}
|
}
|
||||||
.fade-enter, .fade-leave-to {
|
.fade-enter,
|
||||||
opacity: 0;
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: more information on animations are available [here](doc/animations.md).
|
Note: more information on animations are available [here](doc/animations.md).
|
||||||
|
|
||||||
|
### `t-mounted` directive
|
||||||
|
|
||||||
|
The `t-mounted` directive allows to register a callback to execute whenever the node
|
||||||
|
is inserted into the DOM.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div><input t-ref="someInput" t-mounted="focusMe"/></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
class MyWidget extends owl.Component {
|
||||||
|
...
|
||||||
|
focusMe() {
|
||||||
|
this.refs.someInput.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Debugging (`t-debug` and `t-log`)
|
### Debugging (`t-debug` and `t-log`)
|
||||||
|
|
||||||
The javascript QWeb implementation provides two useful debugging directives:
|
The javascript QWeb implementation provides two useful debugging directives:
|
||||||
@@ -362,7 +546,7 @@ The javascript QWeb implementation provides two useful debugging directives:
|
|||||||
<t t-if="a_test">
|
<t t-if="a_test">
|
||||||
<t t-debug="">
|
<t t-debug="">
|
||||||
</t>
|
</t>
|
||||||
````
|
```
|
||||||
|
|
||||||
will stop execution if the browser dev tools are open.
|
will stop execution if the browser dev tools are open.
|
||||||
|
|
||||||
@@ -375,7 +559,6 @@ will stop execution if the browser dev tools are open.
|
|||||||
|
|
||||||
will print 42 to the console
|
will print 42 to the console
|
||||||
|
|
||||||
|
|
||||||
### 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:
|
||||||
@@ -386,7 +569,7 @@ White spaces in a templates are handled in a special way:
|
|||||||
|
|
||||||
### 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:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
@@ -406,6 +589,6 @@ precisely, the result of a template rendering should have a single root node:
|
|||||||
Extra root nodes will actually be ignored (even though they will be rendered
|
Extra root nodes will actually be ignored (even though they will be rendered
|
||||||
in memory).
|
in memory).
|
||||||
|
|
||||||
Note: this does not apply to subtemplates (see the `t-call` directive). In that
|
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.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
- [Store](store.md)
|
- [Store](store.md)
|
||||||
- [Observer](observer.md)
|
- [Observer](observer.md)
|
||||||
- [Virtual DOM](vdom.md)
|
- [Virtual DOM](vdom.md)
|
||||||
|
- [Utils](utils.md)
|
||||||
|
|
||||||
## Miscellaneous
|
## Miscellaneous
|
||||||
- [Quick Start](quick_start.md)
|
- [Quick Start](quick_start.md)
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# 🦉 Utils 🦉
|
||||||
|
|
||||||
|
Owl export a few useful utility functions, to help with common issues. Those
|
||||||
|
functions are all available in the `owl.utils` namespace.
|
||||||
|
|
||||||
|
## Content
|
||||||
|
|
||||||
|
- [`whenReady`](#whenready)
|
||||||
|
- [`loadJS`](#loadjs)
|
||||||
|
- [`loadTemplates`](#loadtemplates)
|
||||||
|
- [`escape`](#escape)
|
||||||
|
- [`debounce`](#debounce)
|
||||||
|
- [`patch` and `unpatch`](#patchandunpatch)
|
||||||
|
|
||||||
|
## `whenReady`
|
||||||
|
|
||||||
|
The function `whenReady` is useful to register some code that need to be executed
|
||||||
|
as soon as the document (page) is ready:
|
||||||
|
|
||||||
|
```js
|
||||||
|
owl.utils.whenReady(function () {
|
||||||
|
const qweb = new owl.QWeb();
|
||||||
|
const app = new App({ qweb });
|
||||||
|
app.mount(document.body);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## `loadJS`
|
||||||
|
|
||||||
|
## `loadTemplates`
|
||||||
|
|
||||||
|
## `escape`
|
||||||
|
|
||||||
|
## `debounce`
|
||||||
|
|
||||||
|
## `patch` and `unpatch`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
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();
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>OWL 0.10.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,32 @@
|
|||||||
|
<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>
|
||||||
@@ -4,7 +4,6 @@ import { buildData, startMeasure, stopMeasure } from "../shared/utils.js";
|
|||||||
// Likes Counter Widget
|
// Likes Counter Widget
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class Counter extends owl.Component {
|
class Counter extends owl.Component {
|
||||||
template = "counter";
|
|
||||||
state = { counter: 0 };
|
state = { counter: 0 };
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
@@ -16,7 +15,6 @@ class Counter extends owl.Component {
|
|||||||
// Message Widget
|
// Message Widget
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class Message extends owl.Component {
|
class Message extends owl.Component {
|
||||||
template = "message";
|
|
||||||
widgets = { Counter };
|
widgets = { Counter };
|
||||||
|
|
||||||
shouldUpdate(nextProps) {
|
shouldUpdate(nextProps) {
|
||||||
@@ -33,7 +31,6 @@ class Message extends owl.Component {
|
|||||||
// Root Widget
|
// Root Widget
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
template = "root";
|
|
||||||
widgets = { Message };
|
widgets = { Message };
|
||||||
state = { messages: [] };
|
state = { messages: [] };
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<templates>
|
<templates>
|
||||||
<div t-name="root" class="main">
|
<div t-name="App" class="main">
|
||||||
<div class="left-thing">
|
<div class="left-thing">
|
||||||
<div>Number of msg: <t t-esc="state.messages.length"/></div>
|
<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(100)">Add 100 messages</button>
|
||||||
@@ -18,14 +18,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
<div t-name="Message" class="message">
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
<span class="author"><t t-esc="props.author"/></span>
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
<span class="msg"><t t-esc="props.msg"/></span>
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
<t t-widget="Counter"/>
|
<t t-widget="Counter"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="counter">
|
<div t-name="Counter">
|
||||||
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
<button t-on-click="increment">Value: <t t-esc="state.counter"/></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<li><a href="benchmarks/owl-0.7.0">OWL 0.7.0</a></li>
|
<li><a href="benchmarks/owl-0.7.0">OWL 0.7.0</a></li>
|
||||||
<li><a href="benchmarks/owl-0.8.0">OWL 0.8.0</a></li>
|
<li><a href="benchmarks/owl-0.8.0">OWL 0.8.0</a></li>
|
||||||
<li><a href="benchmarks/owl-0.9.0">OWL 0.9.0</a></li>
|
<li><a href="benchmarks/owl-0.9.0">OWL 0.9.0</a></li>
|
||||||
|
<li><a href="benchmarks/owl-0.10.0">OWL 0.10.0</a></li>
|
||||||
<li><a href="benchmarks/owl-master">OWL Master</a></li>
|
<li><a href="benchmarks/owl-master">OWL Master</a></li>
|
||||||
<li><a href="benchmarks/vue">Vue</a></li>
|
<li><a href="benchmarks/vue">Vue</a></li>
|
||||||
<li><a href="benchmarks/react">React</a></li>
|
<li><a href="benchmarks/react">React</a></li>
|
||||||
|
|||||||
+170
-138
@@ -1,5 +1,8 @@
|
|||||||
import { SAMPLES } from "./samples.js";
|
import { SAMPLES } from "./samples.js";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Constants, helpers, utils
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
let owlJS;
|
let owlJS;
|
||||||
|
|
||||||
async function owlSourceCode() {
|
async function owlSourceCode() {
|
||||||
@@ -58,82 +61,90 @@ while True:
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
/**
|
||||||
// Tabbed editor
|
* Make an iframe, with all the js, css and xml properly injected.
|
||||||
//------------------------------------------------------------------------------
|
*/
|
||||||
class TabbedEditor extends owl.Component {
|
function makeCodeIframe(js, css, xml, errorHandler) {
|
||||||
constructor() {
|
// check templates
|
||||||
super(...arguments);
|
var qweb = new owl.QWeb();
|
||||||
this.template = "tabbed-editor";
|
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "");
|
||||||
this.state = {
|
|
||||||
currentTab: this.props.display.split("|")[0]
|
|
||||||
};
|
|
||||||
this.tabs = {
|
|
||||||
js: this.props.display.includes("js"),
|
|
||||||
xml: this.props.display.includes("xml"),
|
|
||||||
css: this.props.display.includes("css")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mounted() {
|
// will throw error if there is something wrong with xml
|
||||||
this.editor = ace.edit(this.refs.editor);
|
qweb.addTemplates(sanitizedXML);
|
||||||
|
|
||||||
// remove this for xml/css (?)
|
// create iframe
|
||||||
this.editor.session.setOption("useWorker", false);
|
const iframe = document.createElement("iframe");
|
||||||
this.editor.setValue(this.props[this.state.currentTab], -1);
|
|
||||||
this.editor.setFontSize("12px");
|
iframe.onload = () => {
|
||||||
this.editor.setTheme("ace/theme/monokai");
|
const doc = iframe.contentDocument;
|
||||||
this.editor.session.setMode(MODES[this.state.currentTab]);
|
// inject js
|
||||||
const tabSize = this.state.currentTab === "xml" ? 2 : 4;
|
const owlScript = doc.createElement("script");
|
||||||
this.editor.session.setOption("tabSize", tabSize);
|
owlScript.type = "text/javascript";
|
||||||
this.editor.on("blur", () => {
|
owlScript.src = "../owl.js";
|
||||||
const editorValue = this.editor.getValue();
|
owlScript.addEventListener("load", () => {
|
||||||
const propsValue = this.props[this.state.currentTab];
|
const script = doc.createElement("script");
|
||||||
if (editorValue !== propsValue) {
|
script.type = "text/javascript";
|
||||||
this.trigger("updateCode", {
|
const content = `window.TEMPLATES = \`${sanitizedXML}\`\n${js}`;
|
||||||
type: this.state.currentTab,
|
script.innerHTML = content;
|
||||||
value: editorValue
|
iframe.contentWindow.addEventListener("error", errorHandler);
|
||||||
});
|
iframe.contentWindow.addEventListener("unhandledrejection", errorHandler);
|
||||||
}
|
setTimeout(function() {
|
||||||
|
if (iframe.contentWindow) {
|
||||||
|
iframe.contentWindow.removeEventListener("error", errorHandler);
|
||||||
|
iframe.contentWindow.removeEventListener(
|
||||||
|
"unhandledrejection",
|
||||||
|
errorHandler
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
doc.body.appendChild(script);
|
||||||
});
|
});
|
||||||
|
doc.head.appendChild(owlScript);
|
||||||
|
|
||||||
|
// inject css
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.innerHTML = css;
|
||||||
|
doc.head.appendChild(style);
|
||||||
|
};
|
||||||
|
return iframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a zip file containing a functioning application
|
||||||
|
*/
|
||||||
|
async function makeApp(js, css, xml) {
|
||||||
|
await owl.utils.loadJS("libs/jszip.min.js");
|
||||||
|
|
||||||
|
const zip = new JSZip();
|
||||||
|
const processedJS = js
|
||||||
|
.split("\n")
|
||||||
|
.map(l => (l === "" ? "" : " " + l))
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
const JS = `async function startApp() {
|
||||||
|
// Loading templates
|
||||||
|
let TEMPLATES;
|
||||||
|
try {
|
||||||
|
TEMPLATES = await owl.utils.loadTemplates('app.xml');
|
||||||
|
} catch(e) {
|
||||||
|
document.write(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
patched() {
|
// Application code
|
||||||
if (this.editor) {
|
${processedJS}
|
||||||
window.dispatchEvent(new Event("resize"));
|
}
|
||||||
this.editor.setValue(this.props[this.state.currentTab], -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
willUnmount() {
|
// wait for DOM ready before starting
|
||||||
this.editor.destroy();
|
owl.utils.whenReady(startApp);`;
|
||||||
delete this.editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTab(tab) {
|
zip.file("app.js", JS);
|
||||||
this.editor.setValue(this.props[tab], -1);
|
zip.file("app.css", css);
|
||||||
|
zip.file("app.py", APP_PY);
|
||||||
const mode = MODES[tab];
|
zip.file("app.xml", xml);
|
||||||
this.editor.session.setMode(mode);
|
zip.file("index.html", DEFAULT_HTML);
|
||||||
const tabSize = tab === "xml" ? 2 : 4;
|
zip.file("owl.js", owlSourceCode());
|
||||||
this.editor.session.setOption("tabSize", tabSize);
|
return zip.generateAsync({ type: "blob" });
|
||||||
this.state.currentTab = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseDown(ev) {
|
|
||||||
if (ev.target.tagName === "DIV") {
|
|
||||||
let y = ev.clientY;
|
|
||||||
const resizer = ev => {
|
|
||||||
const delta = ev.clientY - y;
|
|
||||||
y = ev.clientY;
|
|
||||||
this.trigger("updatePanelHeight", { delta });
|
|
||||||
};
|
|
||||||
document.body.addEventListener("mousemove", resizer);
|
|
||||||
document.body.addEventListener("mouseup", () => {
|
|
||||||
document.body.removeEventListener("mousemove", resizer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -142,7 +153,6 @@ class TabbedEditor extends owl.Component {
|
|||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
this.template = "playground";
|
|
||||||
this.version = owl._version;
|
this.version = owl._version;
|
||||||
this.SAMPLES = SAMPLES;
|
this.SAMPLES = SAMPLES;
|
||||||
this.widgets = { TabbedEditor };
|
this.widgets = { TabbedEditor };
|
||||||
@@ -157,6 +167,10 @@ class App extends owl.Component {
|
|||||||
leftPaneWidth: Math.ceil(window.innerWidth / 2),
|
leftPaneWidth: Math.ceil(window.innerWidth / 2),
|
||||||
topPanelHeight: null
|
topPanelHeight: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.toggleLayout = owl.utils.debounce(this.toggleLayout, 250, true);
|
||||||
|
this.runCode = owl.utils.debounce(this.runCode, 250, true);
|
||||||
|
this.downloadCode = owl.utils.debounce(this.downloadCode, 250, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayError(error) {
|
displayError(error) {
|
||||||
@@ -169,61 +183,26 @@ class App extends owl.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async runCode() {
|
runCode() {
|
||||||
this.state.displayWelcome = false;
|
this.state.displayWelcome = false;
|
||||||
// check templates
|
let subiframe;
|
||||||
var qweb = new owl.QWeb();
|
let error = false;
|
||||||
var error = false;
|
const errorHandler = e => this.displayError(e.message || e.reason.message);
|
||||||
const sanitizedXML = this.state.xml.replace(/<!--[\s\S]*?-->/g, "");
|
|
||||||
try {
|
try {
|
||||||
qweb.loadTemplates(sanitizedXML);
|
const { js, css, xml } = this.state;
|
||||||
|
subiframe = makeCodeIframe(js, css, xml, errorHandler);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
//probably problem with the templates
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
this.displayError(error.message);
|
this.displayError(error.message);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this.state.error = false;
|
this.state.error = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create iframe
|
|
||||||
const iframe = document.createElement("iframe");
|
|
||||||
|
|
||||||
iframe.onload = () => {
|
|
||||||
const doc = iframe.contentDocument;
|
|
||||||
// inject js
|
|
||||||
const owlScript = doc.createElement("script");
|
|
||||||
owlScript.type = "text/javascript";
|
|
||||||
owlScript.src = "../owl.js";
|
|
||||||
owlScript.addEventListener("load", () => {
|
|
||||||
const script = doc.createElement("script");
|
|
||||||
script.type = "text/javascript";
|
|
||||||
const content = `window.TEMPLATES = \`${sanitizedXML}\`\n${
|
|
||||||
this.state.js
|
|
||||||
}`;
|
|
||||||
script.innerHTML = content;
|
|
||||||
const errorHandler = e => this.displayError(e.message || e.reason.message);
|
|
||||||
iframe.contentWindow.addEventListener("error", errorHandler);
|
|
||||||
iframe.contentWindow.addEventListener("unhandledrejection", errorHandler);
|
|
||||||
setTimeout(function() {
|
|
||||||
if (iframe.contentWindow) {
|
|
||||||
iframe.contentWindow.removeEventListener("error", errorHandler);
|
|
||||||
iframe.contentWindow.removeEventListener("unhandledrejection", errorHandler);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
doc.body.appendChild(script);
|
|
||||||
});
|
|
||||||
doc.head.appendChild(owlScript);
|
|
||||||
|
|
||||||
// inject css
|
|
||||||
const style = document.createElement("style");
|
|
||||||
style.innerHTML = this.state.css;
|
|
||||||
doc.head.appendChild(style);
|
|
||||||
};
|
|
||||||
this.refs.content.innerHTML = "";
|
this.refs.content.innerHTML = "";
|
||||||
this.refs.content.appendChild(iframe);
|
this.refs.content.appendChild(subiframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSample(ev) {
|
setSample(ev) {
|
||||||
@@ -280,38 +259,91 @@ class App extends owl.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async downloadCode() {
|
async downloadCode() {
|
||||||
|
const { js, css, xml } = this.state;
|
||||||
|
const content = await makeApp(js, css, xml);
|
||||||
await owl.utils.loadJS("libs/FileSaver.min.js");
|
await owl.utils.loadJS("libs/FileSaver.min.js");
|
||||||
await owl.utils.loadJS("libs/jszip.min.js");
|
saveAs(content, "app.zip");
|
||||||
|
|
||||||
const zip = new JSZip();
|
|
||||||
|
|
||||||
const JS = `async function startApp() {
|
|
||||||
// Loading templates
|
|
||||||
let TEMPLATES;
|
|
||||||
try {
|
|
||||||
TEMPLATES = await owl.utils.loadTemplates('app.xml');
|
|
||||||
} catch(e) {
|
|
||||||
document.write(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Application code
|
|
||||||
${this.state.js.split('\n').map(l => l === '' ? '' : ' ' + l).join('\n')}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for DOM ready before starting
|
//------------------------------------------------------------------------------
|
||||||
owl.utils.whenReady(startApp);`;
|
// Tabbed editor
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
class TabbedEditor extends owl.Component {
|
||||||
|
constructor(parent, props) {
|
||||||
|
super(parent, props);
|
||||||
|
this.state = {
|
||||||
|
currentTab: props.js ? "js" : props.xml ? "xml" : "css"
|
||||||
|
};
|
||||||
|
this.setTab = owl.utils.debounce(this.setTab, 250, true);
|
||||||
|
|
||||||
zip.file("app.js", JS);
|
this.sessions = {};
|
||||||
zip.file("app.css", this.state.css);
|
for (let tab of ["js", "xml", "css"]) {
|
||||||
zip.file("app.py", APP_PY);
|
if (props[tab]) {
|
||||||
zip.file("app.xml", this.state.xml);
|
this.sessions[tab] = new ace.EditSession(props[tab], MODES[tab]);
|
||||||
zip.file("index.html", DEFAULT_HTML);
|
this.sessions[tab].setOption("useWorker", false);
|
||||||
zip.file("owl.js", owlSourceCode());
|
const tabSize = tab === "xml" ? 2 : 4;
|
||||||
zip.generateAsync({ type: "blob" }).then(function(content) {
|
this.sessions[tab].setOption("tabSize", tabSize);
|
||||||
saveAs(content, "app.zip");
|
this.sessions[tab].setUndoManager(new ace.UndoManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = ace.edit(this.refs.editor);
|
||||||
|
|
||||||
|
this.editor.setValue(this.props[this.state.currentTab], -1);
|
||||||
|
this.editor.setFontSize("12px");
|
||||||
|
this.editor.setTheme("ace/theme/monokai");
|
||||||
|
this.editor.setSession(this.sessions[this.state.currentTab]);
|
||||||
|
const tabSize = this.state.currentTab === "xml" ? 2 : 4;
|
||||||
|
this.editor.session.setOption("tabSize", tabSize);
|
||||||
|
this.editor.on("blur", () => {
|
||||||
|
const editorValue = this.editor.getValue();
|
||||||
|
const propsValue = this.props[this.state.currentTab];
|
||||||
|
if (editorValue !== propsValue) {
|
||||||
|
this.trigger("updateCode", {
|
||||||
|
type: this.state.currentTab,
|
||||||
|
value: editorValue
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patched() {
|
||||||
|
const session = this.sessions[this.state.currentTab];
|
||||||
|
session.setValue(this.props[this.state.currentTab], -1);
|
||||||
|
this.editor.setSession(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
willUnmount() {
|
||||||
|
this.editor.destroy();
|
||||||
|
delete this.editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTab(tab) {
|
||||||
|
if (this.state.currentTab !== tab) {
|
||||||
|
this.state.currentTab = tab;
|
||||||
|
const session = this.sessions[this.state.currentTab];
|
||||||
|
session.doc.setValue(this.props[tab], -1);
|
||||||
|
this.editor.setSession(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseDown(ev) {
|
||||||
|
if (ev.target.tagName === "DIV") {
|
||||||
|
let y = ev.clientY;
|
||||||
|
const resizer = ev => {
|
||||||
|
const delta = ev.clientY - y;
|
||||||
|
y = ev.clientY;
|
||||||
|
this.trigger("updatePanelHeight", { delta });
|
||||||
|
};
|
||||||
|
document.body.addEventListener("mousemove", resizer);
|
||||||
|
document.body.addEventListener("mouseup", () => {
|
||||||
|
document.body.removeEventListener("mousemove", resizer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ body {
|
|||||||
display: grid;
|
display: grid;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
grid-template-columns: auto 6px 100%;
|
grid-template-columns: auto 8px 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LEFT BAR ****************************************/
|
/* LEFT BAR ****************************************/
|
||||||
@@ -115,6 +115,7 @@ body {
|
|||||||
width: 34px;
|
width: 34px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid transparent;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab.active {
|
.tab.active {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
const CLICK_COUNTER = `class ClickCounter extends owl.Component {
|
const CLICK_COUNTER = `class ClickCounter extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "clickcounter";
|
|
||||||
this.state = { value: 0 };
|
this.state = { value: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ counter.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const CLICK_COUNTER_XML = `<templates>
|
const CLICK_COUNTER_XML = `<templates>
|
||||||
<button t-name="clickcounter" t-on-click="increment">
|
<button t-name="ClickCounter" t-on-click="increment">
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
</button>
|
</button>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
@@ -29,7 +28,6 @@ const CLICK_COUNTER_CSS = `button {
|
|||||||
|
|
||||||
const CLICK_COUNTER_ESNEXT = `// This example will not work if your browser does not support ESNext class fields
|
const CLICK_COUNTER_ESNEXT = `// This example will not work if your browser does not support ESNext class fields
|
||||||
class ClickCounter extends owl.Component {
|
class ClickCounter extends owl.Component {
|
||||||
template = "clickcounter";
|
|
||||||
state = { value: 0 };
|
state = { value: 0 };
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
@@ -45,7 +43,6 @@ counter.mount(document.body);
|
|||||||
const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component {
|
const WIDGET_COMPOSITION = `class ClickCounter extends owl.Component {
|
||||||
constructor(parent, props) {
|
constructor(parent, props) {
|
||||||
super(parent, props);
|
super(parent, props);
|
||||||
this.template = "clickcounter";
|
|
||||||
this.state = { value: props.initialState || 0 };
|
this.state = { value: props.initialState || 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +56,6 @@ let nextId = 1;
|
|||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "app";
|
|
||||||
this.state = { counters: [] }
|
this.state = { counters: [] }
|
||||||
this.widgets = { ClickCounter };
|
this.widgets = { ClickCounter };
|
||||||
}
|
}
|
||||||
@@ -75,11 +71,11 @@ app.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const WIDGET_COMPOSITION_XML = `<templates>
|
const WIDGET_COMPOSITION_XML = `<templates>
|
||||||
<button t-name="clickcounter" t-on-click="increment">
|
<button t-name="ClickCounter" t-on-click="increment">
|
||||||
Click Me! [<t t-esc="state.value"/>]
|
Click Me! [<t t-esc="state.value"/>]
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div t-name="app">
|
<div t-name="App">
|
||||||
<div><button t-on-click="addCounter">Add a counter</button></div>
|
<div><button t-on-click="addCounter">Add a counter</button></div>
|
||||||
<div>
|
<div>
|
||||||
<t t-foreach="state.counters" t-as="counter">
|
<t t-foreach="state.counters" t-as="counter">
|
||||||
@@ -97,7 +93,6 @@ const WIDGET_COMPOSITION_CSS = `button {
|
|||||||
|
|
||||||
const ANIMATION = `// This example will not work if your browser does not support ESNext class fields
|
const ANIMATION = `// This example will not work if your browser does not support ESNext class fields
|
||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
template = "app";
|
|
||||||
state = {flag: 0};
|
state = {flag: 0};
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
@@ -111,7 +106,7 @@ app.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const ANIMATION_XML = `<templates>
|
const ANIMATION_XML = `<templates>
|
||||||
<div t-name="app">
|
<div t-name="App">
|
||||||
<button t-on-click="toggle">
|
<button t-on-click="toggle">
|
||||||
Click Me!
|
Click Me!
|
||||||
</button>
|
</button>
|
||||||
@@ -152,7 +147,6 @@ const ANIMATION_CSS = `button {
|
|||||||
const LIFECYCLE_DEMO = `class HookWidget extends owl.Component {
|
const LIFECYCLE_DEMO = `class HookWidget extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "demo.hookwidget";
|
|
||||||
this.state = { n: 0 };
|
this.state = { n: 0 };
|
||||||
console.log("constructor");
|
console.log("constructor");
|
||||||
}
|
}
|
||||||
@@ -183,7 +177,6 @@ class ParentWidget extends owl.Component {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.widgets = { HookWidget };
|
this.widgets = { HookWidget };
|
||||||
this.template = "demo.parentwidget";
|
|
||||||
this.state = { n: 0, flag: true };
|
this.state = { n: 0, flag: true };
|
||||||
}
|
}
|
||||||
increment() {
|
increment() {
|
||||||
@@ -200,14 +193,14 @@ widget.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const LIFECYCLE_DEMO_XML = `<templates>
|
const LIFECYCLE_DEMO_XML = `<templates>
|
||||||
<div t-name="demo.parentwidget">
|
<div t-name="ParentWidget">
|
||||||
<button t-on-click="increment">Increment</button>
|
<button t-on-click="increment">Increment</button>
|
||||||
<button t-on-click="toggleSubWidget">ToggleSubWidget</button>
|
<button t-on-click="toggleSubWidget">ToggleSubWidget</button>
|
||||||
<div t-if="state.flag">
|
<div t-if="state.flag">
|
||||||
<t t-widget="HookWidget" t-props="{n:state.n}"/>
|
<t t-widget="HookWidget" t-props="{n:state.n}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div t-name="demo.hookwidget" t-on-click="increment">Demo Sub Widget. Props: <t t-esc="props.n"/>. State: <t t-esc="state.n"/>. (click on me to update me)</div>
|
<div t-name="HookWidget" t-on-click="increment">Demo Sub Widget. Props: <t t-esc="props.n"/>. State: <t t-esc="state.n"/>. (click on me to update me)</div>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
|
|
||||||
const BENCHMARK_APP = `//------------------------------------------------------------------------------
|
const BENCHMARK_APP = `//------------------------------------------------------------------------------
|
||||||
@@ -241,7 +234,6 @@ for (let i = 1; i < 16000; i++) {
|
|||||||
class Counter extends owl.Component {
|
class Counter extends owl.Component {
|
||||||
constructor(parent, props) {
|
constructor(parent, props) {
|
||||||
super(parent, props);
|
super(parent, props);
|
||||||
this.template = "counter";
|
|
||||||
this.state = { counter: props.initialState || 0 };
|
this.state = { counter: props.initialState || 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +248,6 @@ class Counter extends owl.Component {
|
|||||||
class Message extends owl.Component {
|
class Message extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "message";
|
|
||||||
this.widgets = { Counter };
|
this.widgets = { Counter };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +264,6 @@ class Message extends owl.Component {
|
|||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.template = "root";
|
|
||||||
this.widgets = { Message };
|
this.widgets = { Message };
|
||||||
this.state = { messages: messages.slice(0, 10) };
|
this.state = { messages: messages.slice(0, 10) };
|
||||||
}
|
}
|
||||||
@@ -351,7 +341,7 @@ const BENCHMARK_APP_CSS = `.main {
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
const BENCHMARK_APP_XML = `<templates>
|
const BENCHMARK_APP_XML = `<templates>
|
||||||
<div t-name="root" class="main">
|
<div t-name="App" class="main">
|
||||||
<div class="left-thing">
|
<div class="left-thing">
|
||||||
<div class="counter">
|
<div class="counter">
|
||||||
<button t-on-click="increment(-1)">-</button>
|
<button t-on-click="increment(-1)">-</button>
|
||||||
@@ -374,14 +364,14 @@ const BENCHMARK_APP_XML = `<templates>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="message" class="message">
|
<div t-name="Message" class="message">
|
||||||
<span class="author"><t t-esc="props.author"/></span>
|
<span class="author"><t t-esc="props.author"/></span>
|
||||||
<span class="msg"><t t-esc="props.msg"/></span>
|
<span class="msg"><t t-esc="props.msg"/></span>
|
||||||
<button class="remove" t-on-click="removeMessage">Remove</button>
|
<button class="remove" t-on-click="removeMessage">Remove</button>
|
||||||
<t t-widget="Counter" t-props="{initialState: props.id}"/>
|
<t t-widget="Counter" t-props="{initialState: props.id}"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="counter">
|
<div t-name="Counter">
|
||||||
<button t-on-click="increment(-1)">-</button>
|
<button t-on-click="increment(-1)">-</button>
|
||||||
<span style="font-weight:bold">Value: <t t-esc="state.counter"/></span>
|
<span style="font-weight:bold">Value: <t t-esc="state.counter"/></span>
|
||||||
<button t-on-click="increment(1)">+</button>
|
<button t-on-click="increment(1)">+</button>
|
||||||
@@ -477,7 +467,6 @@ function makeStore() {
|
|||||||
// TodoItem
|
// TodoItem
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
class TodoItem extends owl.Component {
|
class TodoItem extends owl.Component {
|
||||||
template = "todoitem";
|
|
||||||
state = { isEditing: false };
|
state = { isEditing: false };
|
||||||
|
|
||||||
removeTodo() {
|
removeTodo() {
|
||||||
@@ -490,11 +479,12 @@ class TodoItem extends owl.Component {
|
|||||||
|
|
||||||
async editTodo() {
|
async editTodo() {
|
||||||
this.state.isEditing = true;
|
this.state.isEditing = true;
|
||||||
setTimeout(() => {
|
}
|
||||||
this.refs.input.value = "";
|
|
||||||
this.refs.input.focus();
|
focusInput() {
|
||||||
this.refs.input.value = this.props.title;
|
this.refs.input.value = "";
|
||||||
});
|
this.refs.input.focus();
|
||||||
|
this.refs.input.value = this.props.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyup(ev) {
|
handleKeyup(ev) {
|
||||||
@@ -535,7 +525,6 @@ function mapStateToProps(state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TodoApp extends owl.Component {
|
class TodoApp extends owl.Component {
|
||||||
template = "todoapp";
|
|
||||||
widgets = { TodoItem };
|
widgets = { TodoItem };
|
||||||
state = { filter: "all" };
|
state = { filter: "all" };
|
||||||
|
|
||||||
@@ -602,7 +591,7 @@ app.mount(document.body);
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const TODO_APP_STORE_XML = `<templates>
|
const TODO_APP_STORE_XML = `<templates>
|
||||||
<section t-name="todoapp" class="todoapp">
|
<section t-name="TodoApp" class="todoapp">
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<h1>todos</h1>
|
<h1>todos</h1>
|
||||||
<input class="new-todo" autofocus="true" autocomplete="off" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
|
<input class="new-todo" autofocus="true" autocomplete="off" placeholder="What needs to be done?" t-on-keyup="addTodo"/>
|
||||||
@@ -640,7 +629,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<li t-name="todoitem" class="todo" t-att-class="{completed: props.completed, editing: state.isEditing}">
|
<li t-name="TodoItem" class="todo" t-att-class="{completed: props.completed, editing: state.isEditing}">
|
||||||
<div class="view">
|
<div class="view">
|
||||||
<input class="toggle" type="checkbox" t-on-change="toggleTodo" t-att-checked="props.completed"/>
|
<input class="toggle" type="checkbox" t-on-change="toggleTodo" t-att-checked="props.completed"/>
|
||||||
<label t-on-dblclick="editTodo">
|
<label t-on-dblclick="editTodo">
|
||||||
@@ -648,7 +637,7 @@ const TODO_APP_STORE_XML = `<templates>
|
|||||||
</label>
|
</label>
|
||||||
<button class="destroy" t-on-click="removeTodo"></button>
|
<button class="destroy" t-on-click="removeTodo"></button>
|
||||||
</div>
|
</div>
|
||||||
<input class="edit" t-ref="'input'" t-if="state.isEditing" t-att-value="props.title" t-on-keyup="handleKeyup" t-on-blur="handleBlur"/>
|
<input class="edit" t-ref="'input'" t-if="state.isEditing" t-att-value="props.title" t-on-keyup="handleKeyup" t-mounted="focusInput" t-on-blur="handleBlur"/>
|
||||||
</li>
|
</li>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
|
|
||||||
@@ -1034,39 +1023,25 @@ html .clear-completed:active {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const RESPONSIVE = `class Navbar extends owl.Component {
|
const RESPONSIVE = `class Navbar extends owl.Component {}
|
||||||
template="navbar";
|
|
||||||
}
|
|
||||||
|
|
||||||
class ControlPanel extends owl.Component {
|
class ControlPanel extends owl.Component {
|
||||||
template="controlpanel";
|
|
||||||
widgets = { MobileSearchView };
|
widgets = { MobileSearchView };
|
||||||
}
|
}
|
||||||
|
|
||||||
class FormView extends owl.Component {
|
class FormView extends owl.Component {
|
||||||
template="formview";
|
|
||||||
widgets = { AdvancedWidget };
|
widgets = { AdvancedWidget };
|
||||||
}
|
}
|
||||||
|
|
||||||
class AdvancedWidget extends owl.Component {
|
class AdvancedWidget extends owl.Component {}
|
||||||
template="advancedwidget";
|
|
||||||
}
|
|
||||||
|
|
||||||
class Chatter extends owl.Component {
|
class Chatter extends owl.Component {}
|
||||||
template="chatter";
|
|
||||||
}
|
|
||||||
|
|
||||||
class MobileSearchView extends owl.Component {
|
class MobileSearchView extends owl.Component {}
|
||||||
template="mobilesearchview";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class App extends owl.Component {
|
class App extends owl.Component {
|
||||||
constructor() {
|
widgets = { Navbar, ControlPanel, FormView, Chatter };
|
||||||
super(...arguments);
|
|
||||||
this.template = "app";
|
|
||||||
this.widgets = { Navbar, ControlPanel, FormView, Chatter };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMobile() {
|
function isMobile() {
|
||||||
@@ -1081,39 +1056,39 @@ const env = {
|
|||||||
const app = new App(env);
|
const app = new App(env);
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
|
|
||||||
window.addEventListener(
|
function updateEnv() {
|
||||||
"resize",
|
const _isMobile = isMobile();
|
||||||
owl.utils.debounce(function() {
|
if (_isMobile !== env.isMobile) {
|
||||||
const _isMobile = isMobile();
|
app.updateEnv({
|
||||||
if (_isMobile !== env.isMobile) {
|
isMobile: _isMobile
|
||||||
app.updateEnv({
|
});
|
||||||
isMobile: _isMobile
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}, 20)
|
window.addEventListener("resize", owl.utils.debounce(updateEnv, 20));
|
||||||
);`;
|
`;
|
||||||
|
|
||||||
const RESPONSIVE_XML = `<templates>
|
const RESPONSIVE_XML = `<templates>
|
||||||
<div t-name="navbar" class="navbar">Navbar</div>
|
<div t-name="Navbar" class="navbar">Navbar</div>
|
||||||
|
|
||||||
<div t-name="controlpanel" class="controlpanel">
|
<div t-name="ControlPanel" class="controlpanel">
|
||||||
<h2>controlpanel</h2>
|
<h2>controlpanel</h2>
|
||||||
<t t-if="env.isMobile" t-widget="MobileSearchView"/>
|
<t t-if="env.isMobile" t-widget="MobileSearchView"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="formview" class="formview">
|
<div t-name="FormView" class="formview">
|
||||||
<h2>formview</h2>
|
<h2>formview</h2>
|
||||||
<t t-if="!env.isMobile" t-widget="AdvancedWidget"/>
|
<t t-if="!env.isMobile" t-widget="AdvancedWidget"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="chatter" class="chatter">
|
<div t-name="Chatter" class="chatter">
|
||||||
<h2>Chatter</h2>
|
<h2>Chatter</h2>
|
||||||
<t t-foreach="100" t-as="item"><div>Message <t t-esc="item"/></div></t>
|
<t t-foreach="100" t-as="item"><div>Message <t t-esc="item"/></div></t>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="mobilesearchview">MOBILE searchview</div>
|
<div t-name="MobileSearchView">MOBILE searchview</div>
|
||||||
|
|
||||||
<div t-name="app" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}">
|
<div t-name="App" class="app" t-att-class="{mobile: env.isMobile, desktop: !env.isMobile}">
|
||||||
<t t-widget="Navbar"/>
|
<t t-widget="Navbar"/>
|
||||||
<t t-widget="ControlPanel"/>
|
<t t-widget="ControlPanel"/>
|
||||||
<div class="content-wrapper" t-if="!env.isMobile">
|
<div class="content-wrapper" t-if="!env.isMobile">
|
||||||
@@ -1128,7 +1103,7 @@ const RESPONSIVE_XML = `<templates>
|
|||||||
</t>
|
</t>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div t-name="advancedwidget">
|
<div t-name="AdvancedWidget">
|
||||||
This widget is only created in desktop mode.
|
This widget is only created in desktop mode.
|
||||||
<button>Button!</button>
|
<button>Button!</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<templates>
|
<templates>
|
||||||
<div t-name="tabbed-editor" class="tabbed-editor" t-att-style="props.style">
|
<div t-name="TabbedEditor" class="tabbed-editor">
|
||||||
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
|
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
|
||||||
<a t-if="tabs.js" class="tab flash" t-att-class="{active: state.currentTab==='js'}" t-on-click="setTab('js')">JS</a>
|
<t t-foreach="['js', 'xml', 'css']" t-as="tab">
|
||||||
<a t-if="tabs.xml" class="tab flash" t-att-class="{active: state.currentTab==='xml'}" t-on-click="setTab('xml')">XML</a>
|
<a t-ref="tab" t-if="props[tab]" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="setTab(tab)">
|
||||||
<a t-if="tabs.css" class="tab flash" t-att-class="{active: state.currentTab==='css'}" t-on-click="setTab('css')">CSS</a>
|
<t t-esc="tab"/>
|
||||||
|
</a>
|
||||||
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div class="code-editor" t-ref="'editor'"></div>
|
<div class="code-editor" t-ref="'editor'"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div t-name="playground" class="playground">
|
<div t-name="App" class="playground">
|
||||||
<div class="left-bar" t-att-style="leftPaneStyle" t-att-class="{split: state.splitLayout}">
|
<div class="left-bar" t-att-style="leftPaneStyle" t-att-class="{split: state.splitLayout}">
|
||||||
<div class="menubar">
|
<div class="menubar">
|
||||||
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
|
<a class="btn run-code flash" t-on-click="runCode" title="Execute this Code">▶ Run</a>
|
||||||
@@ -20,13 +22,19 @@
|
|||||||
<a class="btn flash" t-on-click="downloadCode" title="Download a Zip with this Code"><i class="fas fa-download"></i></a>
|
<a class="btn flash" t-on-click="downloadCode" title="Download a Zip with this Code"><i class="fas fa-download"></i></a>
|
||||||
<a class="layout-selector flash" t-on-click="toggleLayout" title="Toggle Layout"><i class="fas" t-att-class="state.splitLayout ? 'fa-toggle-on' : 'fa-toggle-off'"></i></a>
|
<a class="layout-selector flash" t-on-click="toggleLayout" title="Toggle Layout"><i class="fas" t-att-class="state.splitLayout ? 'fa-toggle-on' : 'fa-toggle-off'"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<t t-if="!state.splitLayout">
|
<t t-if="state.splitLayout">
|
||||||
<t t-widget="TabbedEditor" t-props="{js:state.js, css:state.css, xml: state.xml, display: 'js|xml|css'}" t-on-updateCode="updateCode"/>
|
<t t-widget="TabbedEditor"
|
||||||
|
t-props="{js:state.js, css:false, xml: false}"
|
||||||
|
t-on-updateCode="updateCode"
|
||||||
|
t-att-style="topEditorStyle"/>
|
||||||
|
<div class="separator horizontal"/>
|
||||||
|
<t t-widget="TabbedEditor" t-keepalive="1"
|
||||||
|
t-props="{js:false, css:state.css, xml: state.xml, resizeable: true}"
|
||||||
|
t-on-updateCode="updateCode"
|
||||||
|
t-on-updatePanelHeight="updatePanelHeight"/>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="1">
|
<t t-else="1">
|
||||||
<t t-widget="TabbedEditor" t-props="{js:state.js, css:state.css, xml: state.xml, display: 'js', style:topEditorStyle}" t-on-updateCode="updateCode"/>
|
<t t-widget="TabbedEditor" t-props="{js:state.js, css:state.css, xml: state.xml, display: 'js|xml|css'}" t-on-updateCode="updateCode"/>
|
||||||
<div class="separator horizontal"/>
|
|
||||||
<t t-widget="TabbedEditor" t-keepalive="1" t-props="{js:state.js, css:state.css, xml: state.xml, display: 'xml|css', resizeable: true}" t-on-updateCode="updateCode" t-on-updatePanelHeight="updatePanelHeight"/>
|
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
<div class="separator vertical" t-on-mousedown="onMouseDown"/>
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ To help work/improve/learn with OWL, we have here:
|
|||||||
|
|
||||||
Both of them can be accessed by using a static http server. A simple python
|
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:
|
server is available in `server.py`. There is also a npm script to start it:
|
||||||
`npm run extras`.
|
`npm run extras` (and its version with a watcher: `npm run extras:watch`).
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
|
|||||||
+4
-6
@@ -1,20 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "owl",
|
"name": "owl",
|
||||||
"version": "0.10.0",
|
"version": "0.11.0",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:js": "tsc --target esnext --module es6 --outDir dist/owl src/*",
|
"build:js": "tsc --target esnext --module es6 --outDir dist/owl src/*",
|
||||||
"build:js:es5": "tsc --target es5 --module es6 --outDir dist/owl src/*",
|
|
||||||
"build:bundle": "rollup -c",
|
"build:bundle": "rollup -c",
|
||||||
"build": "npm run build:js && npm run build:bundle",
|
"build": "npm run build:js && npm run build:bundle",
|
||||||
"build:es5": "npm run build:js:es5 && npm run build:bundle",
|
|
||||||
"minify": "uglifyjs dist/owl.js -o dist/owl.min.js --compress --mangle",
|
"minify": "uglifyjs dist/owl.js -o dist/owl.min.js --compress --mangle",
|
||||||
"dev": "npm-run-all --parallel \"build:* -- --watch\"",
|
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"preextras": "npm run build",
|
"extras:serve": "python extras/server.py",
|
||||||
"extras": "python extras/server.py"
|
"extras": "npm run build && npm run extras:serve",
|
||||||
|
"extras:watch": "npm-run-all --parallel extras:serve \"build:* -- --watch\""
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
+43
-14
@@ -25,7 +25,6 @@ export interface Env {
|
|||||||
export interface Meta<T extends Env, Props> {
|
export interface Meta<T extends Env, Props> {
|
||||||
readonly id: number;
|
readonly id: number;
|
||||||
vnode: VNode | null;
|
vnode: VNode | null;
|
||||||
isStarted: boolean;
|
|
||||||
isMounted: boolean;
|
isMounted: boolean;
|
||||||
isDestroyed: boolean;
|
isDestroyed: boolean;
|
||||||
parent: Component<T, any, any> | null;
|
parent: Component<T, any, any> | null;
|
||||||
@@ -40,8 +39,15 @@ export interface Meta<T extends Env, Props> {
|
|||||||
boundHandlers: { [key: number]: any };
|
boundHandlers: { [key: number]: any };
|
||||||
observer?: Observer;
|
observer?: Observer;
|
||||||
render?: CompiledTemplate;
|
render?: CompiledTemplate;
|
||||||
|
mountedHandlers: { [key: number]: Function };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a component does not define explicitely a template
|
||||||
|
// key, it needs to find a template with its name (or a parent's). This is
|
||||||
|
// qweb dependant, so we need a place to store this information indexed by
|
||||||
|
// qweb instances.
|
||||||
|
const TEMPLATE_MAP: { [key: number]: { [name: string]: string } } = {};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Widget
|
// Widget
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -53,8 +59,7 @@ export class Component<
|
|||||||
State extends {}
|
State extends {}
|
||||||
> extends EventBus {
|
> extends EventBus {
|
||||||
readonly __owl__: Meta<Env, Props>;
|
readonly __owl__: Meta<Env, Props>;
|
||||||
template: string = "default";
|
template?: string;
|
||||||
inlineTemplate: string | null = null;
|
|
||||||
|
|
||||||
get el(): HTMLElement | null {
|
get el(): HTMLElement | null {
|
||||||
return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null;
|
return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null;
|
||||||
@@ -110,7 +115,6 @@ export class Component<
|
|||||||
this.__owl__ = {
|
this.__owl__ = {
|
||||||
id: id,
|
id: id,
|
||||||
vnode: null,
|
vnode: null,
|
||||||
isStarted: false,
|
|
||||||
isMounted: false,
|
isMounted: false,
|
||||||
isDestroyed: false,
|
isDestroyed: false,
|
||||||
parent: p,
|
parent: p,
|
||||||
@@ -119,7 +123,8 @@ export class Component<
|
|||||||
renderId: 1,
|
renderId: 1,
|
||||||
renderPromise: null,
|
renderPromise: null,
|
||||||
renderProps: props || null,
|
renderProps: props || null,
|
||||||
boundHandlers: {}
|
boundHandlers: {},
|
||||||
|
mountedHandlers: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +231,9 @@ export class Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.__owl__.isMounted = true;
|
this.__owl__.isMounted = true;
|
||||||
|
for (let key in this.__owl__.mountedHandlers) {
|
||||||
|
this.__owl__.mountedHandlers[key]();
|
||||||
|
}
|
||||||
this.mounted();
|
this.mounted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,14 +381,35 @@ export class Component<
|
|||||||
if (this.__owl__.isDestroyed) {
|
if (this.__owl__.isDestroyed) {
|
||||||
return Promise.resolve(h("div"));
|
return Promise.resolve(h("div"));
|
||||||
}
|
}
|
||||||
this.__owl__.isStarted = true;
|
const qweb = this.env.qweb;
|
||||||
if (this.inlineTemplate) {
|
if (!this.template) {
|
||||||
this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true);
|
let tmap = TEMPLATE_MAP[qweb.id];
|
||||||
|
if (!tmap) {
|
||||||
|
tmap = {};
|
||||||
|
TEMPLATE_MAP[qweb.id] = tmap;
|
||||||
|
}
|
||||||
|
let p = (<any>this).constructor;
|
||||||
|
let name: string = p.name;
|
||||||
|
let template = tmap[name];
|
||||||
|
if (template) {
|
||||||
|
this.template = template;
|
||||||
|
} else {
|
||||||
|
while (
|
||||||
|
(template = p.name) &&
|
||||||
|
!(template in qweb.templates) &&
|
||||||
|
p !== Component
|
||||||
|
) {
|
||||||
|
p = p.__proto__;
|
||||||
|
}
|
||||||
|
if (p === Component) {
|
||||||
|
this.template = "default";
|
||||||
|
} else {
|
||||||
|
tmap[name] = template;
|
||||||
|
this.template = template;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.__owl__.render = this.env.qweb.render.bind(
|
this.__owl__.render = qweb.render.bind(qweb, this.template);
|
||||||
this.env.qweb,
|
|
||||||
this.inlineTemplate || this.template
|
|
||||||
);
|
|
||||||
this._observeState();
|
this._observeState();
|
||||||
return this._render();
|
return this._render();
|
||||||
}
|
}
|
||||||
@@ -400,6 +429,7 @@ export class Component<
|
|||||||
let vnode = this.__owl__.render!(this, {
|
let vnode = this.__owl__.render!(this, {
|
||||||
promises,
|
promises,
|
||||||
handlers: this.__owl__.boundHandlers,
|
handlers: this.__owl__.boundHandlers,
|
||||||
|
mountedHandlers: this.__owl__.mountedHandlers,
|
||||||
forceUpdate: force,
|
forceUpdate: force,
|
||||||
patchQueue
|
patchQueue
|
||||||
});
|
});
|
||||||
@@ -425,8 +455,7 @@ export class Component<
|
|||||||
_mount(vnode: VNode, elm: HTMLElement): VNode {
|
_mount(vnode: VNode, elm: HTMLElement): VNode {
|
||||||
this.__owl__.vnode = patch(elm, vnode);
|
this.__owl__.vnode = patch(elm, vnode);
|
||||||
if (
|
if (
|
||||||
this.__owl__.parent &&
|
this.__owl__.parent!.__owl__.isMounted &&
|
||||||
this.__owl__.parent.__owl__.isMounted &&
|
|
||||||
!this.__owl__.isMounted
|
!this.__owl__.isMounted
|
||||||
) {
|
) {
|
||||||
this._callMounted();
|
this._callMounted();
|
||||||
|
|||||||
+5
-3
@@ -39,6 +39,7 @@ for (let method of methodsToPatch) {
|
|||||||
if (!this.__observer__.allowMutations) {
|
if (!this.__observer__.allowMutations) {
|
||||||
throw new Error(`Array cannot be changed here")`);
|
throw new Error(`Array cannot be changed here")`);
|
||||||
}
|
}
|
||||||
|
this.__observer__.rev++;
|
||||||
this.__observer__.notifyChange();
|
this.__observer__.notifyChange();
|
||||||
this.__owl__.rev++;
|
this.__owl__.rev++;
|
||||||
let parent = this;
|
let parent = this;
|
||||||
@@ -74,7 +75,6 @@ export class Observer {
|
|||||||
|
|
||||||
notifyCB() {}
|
notifyCB() {}
|
||||||
notifyChange() {
|
notifyChange() {
|
||||||
this.rev++;
|
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
if (this.dirty) {
|
if (this.dirty) {
|
||||||
@@ -105,6 +105,7 @@ export class Observer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set(target: any, key: number | string, value: any) {
|
set(target: any, key: number | string, value: any) {
|
||||||
|
this.rev++;
|
||||||
this._addProp(target, key, value);
|
this._addProp(target, key, value);
|
||||||
target.__owl__.rev++;
|
target.__owl__.rev++;
|
||||||
this.notifyChange();
|
this.notifyChange();
|
||||||
@@ -112,7 +113,7 @@ export class Observer {
|
|||||||
|
|
||||||
_observeObj<T extends { __owl__?: any }>(obj: T, parent?: any) {
|
_observeObj<T extends { __owl__?: any }>(obj: T, parent?: any) {
|
||||||
const keys = Object.keys(obj);
|
const keys = Object.keys(obj);
|
||||||
obj.__owl__ = { rev: 1, deepRev: 1, parent };
|
obj.__owl__ = { rev: this.rev, deepRev: this.rev, parent };
|
||||||
Object.defineProperty(obj, "__owl__", { enumerable: false });
|
Object.defineProperty(obj, "__owl__", { enumerable: false });
|
||||||
for (let key of keys) {
|
for (let key of keys) {
|
||||||
this._addProp(obj, key, obj[key]);
|
this._addProp(obj, key, obj[key]);
|
||||||
@@ -120,7 +121,7 @@ export class Observer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_observeArr(arr: Array<any>, parent?: any) {
|
_observeArr(arr: Array<any>, parent?: any) {
|
||||||
(<any>arr).__owl__ = { rev: 1, deepRev: 1, parent };
|
(<any>arr).__owl__ = { rev: this.rev, deepRev: this.rev, parent };
|
||||||
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;
|
||||||
@@ -142,6 +143,7 @@ export class Observer {
|
|||||||
},
|
},
|
||||||
set(newVal) {
|
set(newVal) {
|
||||||
if (newVal !== value) {
|
if (newVal !== value) {
|
||||||
|
self.rev++;
|
||||||
if (!self.allowMutations) {
|
if (!self.allowMutations) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Observed state cannot be changed here! (key: "${key}", val: "${newVal}")`
|
`Observed state cannot be changed here! (key: "${key}", val: "${newVal}")`
|
||||||
|
|||||||
+492
-452
@@ -1,5 +1,4 @@
|
|||||||
import { VNode, h } from "./vdom";
|
import { VNode, h } from "./vdom";
|
||||||
import { parseXML } from "./utils";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owl QWeb Engine
|
* Owl QWeb Engine
|
||||||
@@ -31,7 +30,6 @@ interface Template {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface CompilationInfo {
|
interface CompilationInfo {
|
||||||
nodeID?: number;
|
|
||||||
node: Element;
|
node: Element;
|
||||||
qweb: QWeb;
|
qweb: QWeb;
|
||||||
ctx: Context;
|
ctx: Context;
|
||||||
@@ -39,6 +37,11 @@ interface CompilationInfo {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NodeCreationCompilationInfo extends CompilationInfo {
|
||||||
|
nodeID: number;
|
||||||
|
addNodeHook: Function;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Directive {
|
export interface Directive {
|
||||||
name: string;
|
name: string;
|
||||||
extraNames?: string[];
|
extraNames?: string[];
|
||||||
@@ -46,7 +49,7 @@ export interface Directive {
|
|||||||
// if return true, then directive is fully applied and there is no need to
|
// if return true, then directive is fully applied and there is no need to
|
||||||
// keep processing node. Otherwise, we keep going.
|
// keep processing node. Otherwise, we keep going.
|
||||||
atNodeEncounter?(info: CompilationInfo): boolean | void;
|
atNodeEncounter?(info: CompilationInfo): boolean | void;
|
||||||
atNodeCreation?(info: CompilationInfo): void;
|
atNodeCreation?(info: NodeCreationCompilationInfo): void;
|
||||||
finalize?(info: CompilationInfo): void;
|
finalize?(info: CompilationInfo): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +90,12 @@ const DIRECTIVE_NAMES = {
|
|||||||
|
|
||||||
const DIRECTIVES: Directive[] = [];
|
const DIRECTIVES: Directive[] = [];
|
||||||
|
|
||||||
|
const NODE_HOOKS_PARAMS = {
|
||||||
|
create: "(_, n)",
|
||||||
|
insert: "vn",
|
||||||
|
remove: "(vn, rm)"
|
||||||
|
};
|
||||||
|
|
||||||
export const UTILS = {
|
export const UTILS = {
|
||||||
h: h,
|
h: h,
|
||||||
objectToAttrString(obj: Object): string {
|
objectToAttrString(obj: Object): string {
|
||||||
@@ -100,6 +109,486 @@ export const UTILS = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function parseXML(xml: string): Document {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(xml, "text/xml");
|
||||||
|
if (doc.getElementsByTagName("parsererror").length) {
|
||||||
|
throw new Error("Invalid XML in template");
|
||||||
|
}
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextID = 1;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// QWeb rendering engine
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
export class QWeb {
|
||||||
|
templates: { [name: string]: Template } = {};
|
||||||
|
utils = UTILS;
|
||||||
|
|
||||||
|
// the id field is useful to be able to hash qweb instances. The current
|
||||||
|
// use case is that component's templates are qweb dependant, and need to be
|
||||||
|
// able to map a qweb instance to a template name.
|
||||||
|
id = nextID++;
|
||||||
|
|
||||||
|
constructor(data?: string) {
|
||||||
|
if (data) {
|
||||||
|
this.addTemplates(data);
|
||||||
|
}
|
||||||
|
this.addTemplate("default", "<div></div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
static addDirective(directive: Directive) {
|
||||||
|
DIRECTIVES.push(directive);
|
||||||
|
DIRECTIVE_NAMES[directive.name] = 1;
|
||||||
|
DIRECTIVES.sort((d1, d2) => d1.priority - d2.priority);
|
||||||
|
if (directive.extraNames) {
|
||||||
|
directive.extraNames.forEach(n => (DIRECTIVE_NAMES[n] = 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a template to the internal template map. Note that it is not
|
||||||
|
* immediately compiled.
|
||||||
|
*/
|
||||||
|
addTemplate(name: string, xmlString: string) {
|
||||||
|
const doc = parseXML(xmlString);
|
||||||
|
if (!doc.firstChild) {
|
||||||
|
throw new Error("Invalid template (should not be empty)");
|
||||||
|
}
|
||||||
|
this._addTemplate(name, <Element>doc.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load templates from a xml (as a string). This will look up for the first
|
||||||
|
* <templates> tag, and will consider each child of this as a template, with
|
||||||
|
* the name given by the t-name attribute.
|
||||||
|
*/
|
||||||
|
addTemplates(xmlstr: string) {
|
||||||
|
const doc = parseXML(xmlstr);
|
||||||
|
const templates = doc.getElementsByTagName("templates")[0];
|
||||||
|
if (!templates) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let elem of <any>templates.children) {
|
||||||
|
const name = elem.getAttribute("t-name");
|
||||||
|
this._addTemplate(name, elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_addTemplate(name: string, elem: Element) {
|
||||||
|
if (name in this.templates) {
|
||||||
|
throw new Error(`Template ${name} already defined`);
|
||||||
|
}
|
||||||
|
this._processTemplate(elem);
|
||||||
|
const template = {
|
||||||
|
elem,
|
||||||
|
fn: (context, extra) => {
|
||||||
|
const compiledFunction = this._compile(name, elem);
|
||||||
|
template.fn = compiledFunction;
|
||||||
|
return compiledFunction.call(this, context, extra);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.templates[name] = template;
|
||||||
|
}
|
||||||
|
|
||||||
|
_processTemplate(elem: Element) {
|
||||||
|
let tbranch = elem.querySelectorAll("[t-elif], [t-else]");
|
||||||
|
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
||||||
|
let node = tbranch[i];
|
||||||
|
let prevElem = node.previousElementSibling!;
|
||||||
|
let pattr = function(name) {
|
||||||
|
return prevElem.getAttribute(name);
|
||||||
|
};
|
||||||
|
let nattr = function(name) {
|
||||||
|
return +!!node.getAttribute(name);
|
||||||
|
};
|
||||||
|
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
||||||
|
if (pattr("t-foreach")) {
|
||||||
|
throw new Error(
|
||||||
|
"t-if cannot stay at the same level as t-foreach when using t-elif or t-else"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
["t-if", "t-elif", "t-else"].map(nattr).reduce(function(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}) > 1
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Only one conditional branching directive is allowed per node"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// All text nodes between branch nodes are removed
|
||||||
|
let textNode;
|
||||||
|
while ((textNode = node.previousSibling) !== prevElem) {
|
||||||
|
if (textNode.nodeValue.trim().length) {
|
||||||
|
throw new Error("text is not allowed between branching directives");
|
||||||
|
}
|
||||||
|
textNode.remove();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"t-elif and t-else directives must be preceded by a t-if or t-elif directive"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Render a template
|
||||||
|
*
|
||||||
|
* @param {string} name the template should already have been added
|
||||||
|
*/
|
||||||
|
render(name: string, context: EvalContext = {}, extra: any = null): VNode {
|
||||||
|
const template = this.templates[name];
|
||||||
|
if (!template) {
|
||||||
|
throw new Error(`Template ${name} does not exist`);
|
||||||
|
}
|
||||||
|
return template.fn.call(this, context, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
_compile(name: string, elem: Element): CompiledTemplate {
|
||||||
|
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
||||||
|
const ctx = new Context(name);
|
||||||
|
this._compileNode(elem, ctx);
|
||||||
|
|
||||||
|
if (ctx.shouldProtectContext) {
|
||||||
|
ctx.code.unshift(" context = Object.create(context);");
|
||||||
|
}
|
||||||
|
if (ctx.shouldDefineOwner) {
|
||||||
|
// this is necessary to prevent some directives (t-forach for ex) to
|
||||||
|
// pollute the rendering context by adding some keys in it.
|
||||||
|
ctx.code.unshift(" let owner = context;");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.rootNode) {
|
||||||
|
throw new Error("A template should have one root node");
|
||||||
|
}
|
||||||
|
ctx.addLine(`return vn${ctx.rootNode};`);
|
||||||
|
let template;
|
||||||
|
try {
|
||||||
|
template = new Function(
|
||||||
|
"context",
|
||||||
|
"extra",
|
||||||
|
ctx.code.join("\n")
|
||||||
|
) as CompiledTemplate;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid generated code while compiling template '${ctx.templateName.replace(
|
||||||
|
/`/g,
|
||||||
|
"'"
|
||||||
|
)}': ${e.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isDebug) {
|
||||||
|
console.log(
|
||||||
|
`Template: ${this.templates[name].elem.outerHTML}\nCompiled code:\n` +
|
||||||
|
template.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate code from an xml node
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_compileNode(node: ChildNode, ctx: Context) {
|
||||||
|
if (!(node instanceof Element)) {
|
||||||
|
// this is a text node, there are no directive to apply
|
||||||
|
let text = node.textContent!;
|
||||||
|
if (!ctx.inPreTag) {
|
||||||
|
if (lineBreakRE.test(text) && !text.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
text = text.replace(whitespaceRE, " ");
|
||||||
|
}
|
||||||
|
if (ctx.parentNode) {
|
||||||
|
ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`);
|
||||||
|
} else {
|
||||||
|
// this is an unusual situation: this text node is the result of the
|
||||||
|
// template rendering.
|
||||||
|
let nodeID = ctx.generateID();
|
||||||
|
ctx.addLine(`var vn${nodeID} = {text: \`${text}\`};`);
|
||||||
|
ctx.rootContext.rootNode = nodeID;
|
||||||
|
ctx.rootContext.parentNode = nodeID;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = (<Element>node).attributes;
|
||||||
|
|
||||||
|
const validDirectives: {
|
||||||
|
directive: Directive;
|
||||||
|
value: string;
|
||||||
|
fullName: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
let withHandlers = false;
|
||||||
|
|
||||||
|
// maybe this is not optimal: we iterate on all attributes here, and again
|
||||||
|
// just after for each directive.
|
||||||
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
|
let attrName = attributes[i].name;
|
||||||
|
if (attrName.startsWith("t-")) {
|
||||||
|
let dName = attrName.slice(2).split("-")[0];
|
||||||
|
if (!(dName in DIRECTIVE_NAMES)) {
|
||||||
|
throw new Error(`Unknown QWeb directive: '${attrName}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let directive of DIRECTIVES) {
|
||||||
|
let fullName;
|
||||||
|
let value;
|
||||||
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
|
const name = attributes[i].name;
|
||||||
|
if (
|
||||||
|
name === "t-" + directive.name ||
|
||||||
|
name.startsWith("t-" + directive.name + "-")
|
||||||
|
) {
|
||||||
|
fullName = name;
|
||||||
|
value = attributes[i].textContent;
|
||||||
|
validDirectives.push({ directive, value, fullName });
|
||||||
|
if (directive.name === "on") {
|
||||||
|
withHandlers = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let { directive, value, fullName } of validDirectives) {
|
||||||
|
if (directive.atNodeEncounter) {
|
||||||
|
const isDone = directive.atNodeEncounter({
|
||||||
|
node,
|
||||||
|
qweb: this,
|
||||||
|
ctx,
|
||||||
|
fullName,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
if (isDone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.nodeName !== "t") {
|
||||||
|
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
|
||||||
|
ctx = ctx.withParent(nodeID);
|
||||||
|
let nodeHooks = {};
|
||||||
|
let addNodeHook = function(hook, handler) {
|
||||||
|
nodeHooks[hook] = nodeHooks[hook] || [];
|
||||||
|
nodeHooks[hook].push(handler);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let { directive, value, fullName } of validDirectives) {
|
||||||
|
if (directive.atNodeCreation) {
|
||||||
|
directive.atNodeCreation({
|
||||||
|
node,
|
||||||
|
qweb: this,
|
||||||
|
ctx,
|
||||||
|
fullName,
|
||||||
|
value,
|
||||||
|
nodeID,
|
||||||
|
addNodeHook
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(nodeHooks).length) {
|
||||||
|
ctx.addLine(`p${nodeID}.hook = {`);
|
||||||
|
for (let hook in nodeHooks) {
|
||||||
|
ctx.addLine(` ${hook}: ${NODE_HOOKS_PARAMS[hook]} => {`);
|
||||||
|
for (let handler of nodeHooks[hook]) {
|
||||||
|
ctx.addLine(` ${handler}`);
|
||||||
|
}
|
||||||
|
ctx.addLine(` },`);
|
||||||
|
}
|
||||||
|
ctx.addLine(`};`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.nodeName === "pre") {
|
||||||
|
ctx = ctx.subContext("inPreTag", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._compileChildren(node, ctx);
|
||||||
|
|
||||||
|
for (let { directive, value, fullName } of validDirectives) {
|
||||||
|
if (directive.finalize) {
|
||||||
|
directive.finalize({ node, qweb: this, ctx, fullName, value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_compileGenericNode(
|
||||||
|
node: ChildNode,
|
||||||
|
ctx: Context,
|
||||||
|
withHandlers: boolean = true
|
||||||
|
): number {
|
||||||
|
// nodeType 1 is generic tag
|
||||||
|
if (node.nodeType !== 1) {
|
||||||
|
throw new Error("unsupported node type");
|
||||||
|
}
|
||||||
|
const attributes = (<Element>node).attributes;
|
||||||
|
const attrs: string[] = [];
|
||||||
|
const props: string[] = [];
|
||||||
|
const tattrs: number[] = [];
|
||||||
|
|
||||||
|
function handleBooleanProps(key, val) {
|
||||||
|
let isProp = false;
|
||||||
|
if (node.nodeName === "input" && key === "checked") {
|
||||||
|
let type = (<Element>node).getAttribute("type");
|
||||||
|
if (type === "checkbox" || type === "radio") {
|
||||||
|
isProp = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.nodeName === "option" && key === "selected") {
|
||||||
|
isProp = true;
|
||||||
|
}
|
||||||
|
if (key === "disabled" && DISABLED_TAGS.indexOf(node.nodeName) > -1) {
|
||||||
|
isProp = true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(key === "readonly" && node.nodeName === "input") ||
|
||||||
|
node.nodeName === "textarea"
|
||||||
|
) {
|
||||||
|
isProp = true;
|
||||||
|
}
|
||||||
|
if (isProp) {
|
||||||
|
props.push(`${key}: _${val}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function formatter(expr) {
|
||||||
|
return "${" + ctx.formatExpression(expr) + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
|
let name = attributes[i].name;
|
||||||
|
const value = attributes[i].textContent!;
|
||||||
|
|
||||||
|
// regular attributes
|
||||||
|
if (
|
||||||
|
!name.startsWith("t-") &&
|
||||||
|
!(<Element>node).getAttribute("t-attf-" + name)
|
||||||
|
) {
|
||||||
|
const attID = ctx.generateID();
|
||||||
|
ctx.addLine(`var _${attID} = '${value}';`);
|
||||||
|
if (!name.match(/^[a-zA-Z]+$/)) {
|
||||||
|
// attribute contains 'non letters' => we want to quote it
|
||||||
|
name = '"' + name + '"';
|
||||||
|
}
|
||||||
|
attrs.push(`${name}: _${attID}`);
|
||||||
|
handleBooleanProps(name, attID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamic attributes
|
||||||
|
if (name.startsWith("t-att-")) {
|
||||||
|
let attName = name.slice(6);
|
||||||
|
let formattedValue = ctx.formatExpression(ctx.getValue(value!));
|
||||||
|
if (
|
||||||
|
formattedValue[0] === "{" &&
|
||||||
|
formattedValue[formattedValue.length - 1] === "}"
|
||||||
|
) {
|
||||||
|
formattedValue = `this.utils.objectToAttrString(${formattedValue})`;
|
||||||
|
}
|
||||||
|
const attID = ctx.generateID();
|
||||||
|
if (!attName.match(/^[a-zA-Z]+$/)) {
|
||||||
|
// attribute contains 'non letters' => we want to quote it
|
||||||
|
attName = '"' + attName + '"';
|
||||||
|
}
|
||||||
|
// we need to combine dynamic with non dynamic attributes:
|
||||||
|
// class="a" t-att-class="'yop'" should be rendered as class="a yop"
|
||||||
|
const attValue = (<Element>node).getAttribute(attName);
|
||||||
|
if (attValue) {
|
||||||
|
const attValueID = ctx.generateID();
|
||||||
|
ctx.addLine(`var _${attValueID} = ${formattedValue};`);
|
||||||
|
formattedValue = `'${attValue}' + (_${attValueID} ? ' ' + _${attValueID} : '')`;
|
||||||
|
const attrIndex = attrs.findIndex(att =>
|
||||||
|
att.startsWith(attName + ":")
|
||||||
|
);
|
||||||
|
attrs.splice(attrIndex, 1);
|
||||||
|
}
|
||||||
|
ctx.addLine(`var _${attID} = ${formattedValue};`);
|
||||||
|
attrs.push(`${attName}: _${attID}`);
|
||||||
|
handleBooleanProps(attName, attID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.startsWith("t-attf-")) {
|
||||||
|
let attName = name.slice(7);
|
||||||
|
if (!attName.match(/^[a-zA-Z]+$/)) {
|
||||||
|
// attribute contains 'non letters' => we want to quote it
|
||||||
|
attName = '"' + attName + '"';
|
||||||
|
}
|
||||||
|
const formattedExpr = value!
|
||||||
|
.replace(/\{\{.*?\}\}/g, s => formatter(s.slice(2, -2)))
|
||||||
|
.replace(/\#\{.*?\}/g, s => formatter(s.slice(2, -1)));
|
||||||
|
const attID = ctx.generateID();
|
||||||
|
let staticVal = (<Element>node).getAttribute(attName);
|
||||||
|
if (staticVal) {
|
||||||
|
ctx.addLine(
|
||||||
|
`var _${attID} = '${staticVal} ' + \`${formattedExpr}\`;`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.addLine(`var _${attID} = \`${formattedExpr}\`;`);
|
||||||
|
}
|
||||||
|
attrs.push(`${attName}: _${attID}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// t-att= attributes
|
||||||
|
if (name === "t-att") {
|
||||||
|
let id = ctx.generateID();
|
||||||
|
ctx.addLine(`var _${id} = ${ctx.formatExpression(value!)};`);
|
||||||
|
tattrs.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let nodeID = ctx.generateID();
|
||||||
|
let nodeKey: any = (<Element>node).getAttribute("t-key");
|
||||||
|
if (nodeKey) {
|
||||||
|
nodeKey = ctx.formatExpression(nodeKey);
|
||||||
|
} else {
|
||||||
|
nodeKey = nodeID;
|
||||||
|
}
|
||||||
|
const parts = [`key:${nodeKey}`];
|
||||||
|
if (attrs.length + tattrs.length > 0) {
|
||||||
|
parts.push(`attrs:{${attrs.join(",")}}`);
|
||||||
|
}
|
||||||
|
if (props.length > 0) {
|
||||||
|
parts.push(`props:{${props.join(",")}}`);
|
||||||
|
}
|
||||||
|
if (withHandlers) {
|
||||||
|
parts.push(`on:{}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.addLine(`let c${nodeID} = [], p${nodeID} = {${parts.join(",")}};`);
|
||||||
|
for (let id of tattrs) {
|
||||||
|
ctx.addIf(`_${id} instanceof Array`);
|
||||||
|
ctx.addLine(`p${nodeID}.attrs[_${id}[0]] = _${id}[1];`);
|
||||||
|
ctx.addElse();
|
||||||
|
ctx.addLine(`for (let key in _${id}) {`);
|
||||||
|
ctx.indent();
|
||||||
|
ctx.addLine(`p${nodeID}.attrs[key] = _${id}[key];`);
|
||||||
|
ctx.dedent();
|
||||||
|
ctx.addLine(`}`);
|
||||||
|
ctx.closeIf();
|
||||||
|
}
|
||||||
|
ctx.addLine(
|
||||||
|
`var vn${nodeID} = h('${node.nodeName}', p${nodeID}, c${nodeID});`
|
||||||
|
);
|
||||||
|
if (ctx.parentNode) {
|
||||||
|
ctx.addLine(`c${ctx.parentNode}.push(vn${nodeID});`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeID;
|
||||||
|
}
|
||||||
|
|
||||||
|
_compileChildren(node: ChildNode, ctx: Context) {
|
||||||
|
if (node.childNodes.length > 0) {
|
||||||
|
for (let child of Array.from(node.childNodes)) {
|
||||||
|
this._compileNode(child, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Compilation Context
|
// Compilation Context
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -242,452 +731,3 @@ export class Context {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// QWeb rendering engine
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
export class QWeb {
|
|
||||||
templates: { [name: string]: Template } = {};
|
|
||||||
utils = UTILS;
|
|
||||||
|
|
||||||
constructor(data?: string) {
|
|
||||||
if (data) {
|
|
||||||
this.loadTemplates(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static addDirective(directive: Directive) {
|
|
||||||
DIRECTIVES.push(directive);
|
|
||||||
DIRECTIVE_NAMES[directive.name] = 1;
|
|
||||||
DIRECTIVES.sort((d1, d2) => d1.priority - d2.priority);
|
|
||||||
if (directive.extraNames) {
|
|
||||||
directive.extraNames.forEach(n => (DIRECTIVE_NAMES[n] = 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a template to the internal template map. Note that it is not
|
|
||||||
* immediately compiled.
|
|
||||||
*/
|
|
||||||
addTemplate(
|
|
||||||
name: string,
|
|
||||||
xmlString: string,
|
|
||||||
allowDuplicates: boolean = false
|
|
||||||
) {
|
|
||||||
if (name in this.templates && allowDuplicates) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const doc = parseXML(xmlString);
|
|
||||||
if (!doc.firstChild) {
|
|
||||||
throw new Error("Invalid template (should not be empty)");
|
|
||||||
}
|
|
||||||
this._addTemplate(name, <Element>doc.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
_addTemplate(name: string, elem: Element) {
|
|
||||||
if (name in this.templates) {
|
|
||||||
throw new Error(`Template ${name} already defined`);
|
|
||||||
}
|
|
||||||
this._processTemplate(elem);
|
|
||||||
const template = {
|
|
||||||
elem,
|
|
||||||
fn: (context, extra) => {
|
|
||||||
const compiledFunction = this._compile(name, elem);
|
|
||||||
template.fn = compiledFunction;
|
|
||||||
return compiledFunction.call(this, context, extra);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.templates[name] = template;
|
|
||||||
}
|
|
||||||
|
|
||||||
_processTemplate(elem: Element) {
|
|
||||||
let tbranch = elem.querySelectorAll("[t-elif], [t-else]");
|
|
||||||
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
|
||||||
let node = tbranch[i];
|
|
||||||
let prevElem = node.previousElementSibling!;
|
|
||||||
let pattr = function(name) {
|
|
||||||
return prevElem.getAttribute(name);
|
|
||||||
};
|
|
||||||
let nattr = function(name) {
|
|
||||||
return +!!node.getAttribute(name);
|
|
||||||
};
|
|
||||||
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
|
||||||
if (pattr("t-foreach")) {
|
|
||||||
throw new Error(
|
|
||||||
"t-if cannot stay at the same level as t-foreach when using t-elif or t-else"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
["t-if", "t-elif", "t-else"].map(nattr).reduce(function(a, b) {
|
|
||||||
return a + b;
|
|
||||||
}) > 1
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
"Only one conditional branching directive is allowed per node"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// All text nodes between branch nodes are removed
|
|
||||||
let textNode;
|
|
||||||
while ((textNode = node.previousSibling) !== prevElem) {
|
|
||||||
if (textNode.nodeValue.trim().length) {
|
|
||||||
throw new Error("text is not allowed between branching directives");
|
|
||||||
}
|
|
||||||
textNode.remove();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
"t-elif and t-else directives must be preceded by a t-if or t-elif directive"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Load templates from a xml (as a string). This will look up for the first
|
|
||||||
* <templates> tag, and will consider each child of this as a template, with
|
|
||||||
* the name given by the t-name attribute.
|
|
||||||
*/
|
|
||||||
loadTemplates(xmlstr: string) {
|
|
||||||
const doc = parseXML(xmlstr);
|
|
||||||
const templates = doc.getElementsByTagName("templates")[0];
|
|
||||||
if (!templates) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (let elem of <any>templates.children) {
|
|
||||||
const name = elem.getAttribute("t-name");
|
|
||||||
this._addTemplate(name, elem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Render a template
|
|
||||||
*
|
|
||||||
* @param {string} name the template should already have been added
|
|
||||||
*/
|
|
||||||
render(name: string, context: EvalContext = {}, extra: any = null): VNode {
|
|
||||||
const template = this.templates[name];
|
|
||||||
if (!template) {
|
|
||||||
throw new Error(`Template ${name} does not exist`);
|
|
||||||
}
|
|
||||||
return template.fn.call(this, context, extra);
|
|
||||||
}
|
|
||||||
|
|
||||||
_compile(name: string, elem: Element): CompiledTemplate {
|
|
||||||
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
|
||||||
const ctx = new Context(name);
|
|
||||||
this._compileNode(elem, ctx);
|
|
||||||
|
|
||||||
if (ctx.shouldProtectContext) {
|
|
||||||
ctx.code.unshift(" context = Object.create(context);");
|
|
||||||
}
|
|
||||||
if (ctx.shouldDefineOwner) {
|
|
||||||
// this is necessary to prevent some directives (t-forach for ex) to
|
|
||||||
// pollute the rendering context by adding some keys in it.
|
|
||||||
ctx.code.unshift(" let owner = context;");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ctx.rootNode) {
|
|
||||||
throw new Error("A template should have one root node");
|
|
||||||
}
|
|
||||||
ctx.addLine(`return vn${ctx.rootNode};`);
|
|
||||||
let template;
|
|
||||||
try {
|
|
||||||
template = new Function(
|
|
||||||
"context",
|
|
||||||
"extra",
|
|
||||||
ctx.code.join("\n")
|
|
||||||
) as CompiledTemplate;
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid generated code while compiling template '${ctx.templateName.replace(
|
|
||||||
/`/g,
|
|
||||||
"'"
|
|
||||||
)}': ${e.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (isDebug) {
|
|
||||||
console.log(
|
|
||||||
`Template: ${this.templates[name].elem.outerHTML}\nCompiled code:\n` +
|
|
||||||
template.toString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate code from an xml node
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_compileNode(node: ChildNode, ctx: Context) {
|
|
||||||
if (!(node instanceof Element)) {
|
|
||||||
// this is a text node, there are no directive to apply
|
|
||||||
let text = node.textContent!;
|
|
||||||
if (!ctx.inPreTag) {
|
|
||||||
if (lineBreakRE.test(text) && !text.trim()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
text = text.replace(whitespaceRE, " ");
|
|
||||||
}
|
|
||||||
if (ctx.parentNode) {
|
|
||||||
ctx.addLine(`c${ctx.parentNode}.push({text: \`${text}\`});`);
|
|
||||||
} else {
|
|
||||||
// this is an unusual situation: this text node is the result of the
|
|
||||||
// template rendering.
|
|
||||||
let nodeID = ctx.generateID();
|
|
||||||
ctx.addLine(`var vn${nodeID} = {text: \`${text}\`};`);
|
|
||||||
ctx.rootContext.rootNode = nodeID;
|
|
||||||
ctx.rootContext.parentNode = nodeID;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attributes = (<Element>node).attributes;
|
|
||||||
|
|
||||||
const validDirectives: {
|
|
||||||
directive: Directive;
|
|
||||||
value: string;
|
|
||||||
fullName: string;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
let withHandlers = false;
|
|
||||||
|
|
||||||
// maybe this is not optimal: we iterate on all attributes here, and again
|
|
||||||
// just after for each directive.
|
|
||||||
for (let i = 0; i < attributes.length; i++) {
|
|
||||||
let attrName = attributes[i].name;
|
|
||||||
if (attrName.startsWith("t-")) {
|
|
||||||
let dName = attrName.slice(2).split("-")[0];
|
|
||||||
if (!(dName in DIRECTIVE_NAMES)) {
|
|
||||||
throw new Error(`Unknown QWeb directive: '${attrName}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let directive of DIRECTIVES) {
|
|
||||||
let fullName;
|
|
||||||
let value;
|
|
||||||
for (let i = 0; i < attributes.length; i++) {
|
|
||||||
const name = attributes[i].name;
|
|
||||||
if (
|
|
||||||
name === "t-" + directive.name ||
|
|
||||||
name.startsWith("t-" + directive.name + "-")
|
|
||||||
) {
|
|
||||||
fullName = name;
|
|
||||||
value = attributes[i].textContent;
|
|
||||||
validDirectives.push({ directive, value, fullName });
|
|
||||||
if (directive.name === "on") {
|
|
||||||
withHandlers = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let { directive, value, fullName } of validDirectives) {
|
|
||||||
if (directive.atNodeEncounter) {
|
|
||||||
const isDone = directive.atNodeEncounter({
|
|
||||||
node,
|
|
||||||
qweb: this,
|
|
||||||
ctx,
|
|
||||||
fullName,
|
|
||||||
value
|
|
||||||
});
|
|
||||||
if (isDone) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.nodeName !== "t") {
|
|
||||||
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
|
|
||||||
ctx = ctx.withParent(nodeID);
|
|
||||||
|
|
||||||
for (let { directive, value, fullName } of validDirectives) {
|
|
||||||
if (directive.atNodeCreation) {
|
|
||||||
directive.atNodeCreation({
|
|
||||||
node,
|
|
||||||
qweb: this,
|
|
||||||
ctx,
|
|
||||||
fullName,
|
|
||||||
value,
|
|
||||||
nodeID
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node.nodeName === "pre") {
|
|
||||||
ctx = ctx.subContext("inPreTag", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._compileChildren(node, ctx);
|
|
||||||
|
|
||||||
for (let { directive, value, fullName } of validDirectives) {
|
|
||||||
if (directive.finalize) {
|
|
||||||
directive.finalize({ node, qweb: this, ctx, fullName, value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_compileGenericNode(
|
|
||||||
node: ChildNode,
|
|
||||||
ctx: Context,
|
|
||||||
withHandlers: boolean = true
|
|
||||||
): number {
|
|
||||||
// nodeType 1 is generic tag
|
|
||||||
if (node.nodeType !== 1) {
|
|
||||||
throw new Error("unsupported node type");
|
|
||||||
}
|
|
||||||
const attributes = (<Element>node).attributes;
|
|
||||||
const attrs: string[] = [];
|
|
||||||
const props: string[] = [];
|
|
||||||
const tattrs: number[] = [];
|
|
||||||
|
|
||||||
function handleBooleanProps(key, val) {
|
|
||||||
let isProp = false;
|
|
||||||
if (node.nodeName === "input" && key === "checked") {
|
|
||||||
let type = (<Element>node).getAttribute("type");
|
|
||||||
if (type === "checkbox" || type === "radio") {
|
|
||||||
isProp = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node.nodeName === "option" && key === "selected") {
|
|
||||||
isProp = true;
|
|
||||||
}
|
|
||||||
if (key === "disabled" && DISABLED_TAGS.indexOf(node.nodeName) > -1) {
|
|
||||||
isProp = true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(key === "readonly" && node.nodeName === "input") ||
|
|
||||||
node.nodeName === "textarea"
|
|
||||||
) {
|
|
||||||
isProp = true;
|
|
||||||
}
|
|
||||||
if (isProp) {
|
|
||||||
props.push(`${key}: _${val}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < attributes.length; i++) {
|
|
||||||
let name = attributes[i].name;
|
|
||||||
const value = attributes[i].textContent!;
|
|
||||||
|
|
||||||
// regular attributes
|
|
||||||
if (
|
|
||||||
!name.startsWith("t-") &&
|
|
||||||
!(<Element>node).getAttribute("t-attf-" + name)
|
|
||||||
) {
|
|
||||||
const attID = ctx.generateID();
|
|
||||||
ctx.addLine(`var _${attID} = '${value}';`);
|
|
||||||
if (!name.match(/^[a-zA-Z]+$/)) {
|
|
||||||
// attribute contains 'non letters' => we want to quote it
|
|
||||||
name = '"' + name + '"';
|
|
||||||
}
|
|
||||||
attrs.push(`${name}: _${attID}`);
|
|
||||||
handleBooleanProps(name, attID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// dynamic attributes
|
|
||||||
if (name.startsWith("t-att-")) {
|
|
||||||
let attName = name.slice(6);
|
|
||||||
let formattedValue = ctx.formatExpression(ctx.getValue(value!));
|
|
||||||
if (
|
|
||||||
formattedValue[0] === "{" &&
|
|
||||||
formattedValue[formattedValue.length - 1] === "}"
|
|
||||||
) {
|
|
||||||
formattedValue = `this.utils.objectToAttrString(${formattedValue})`;
|
|
||||||
}
|
|
||||||
const attID = ctx.generateID();
|
|
||||||
if (!attName.match(/^[a-zA-Z]+$/)) {
|
|
||||||
// attribute contains 'non letters' => we want to quote it
|
|
||||||
attName = '"' + attName + '"';
|
|
||||||
}
|
|
||||||
// we need to combine dynamic with non dynamic attributes:
|
|
||||||
// class="a" t-att-class="'yop'" should be rendered as class="a yop"
|
|
||||||
const attValue = (<Element>node).getAttribute(attName);
|
|
||||||
if (attValue) {
|
|
||||||
const attValueID = ctx.generateID();
|
|
||||||
ctx.addLine(`var _${attValueID} = ${formattedValue};`);
|
|
||||||
formattedValue = `'${attValue}' + (_${attValueID} ? ' ' + _${attValueID} : '')`;
|
|
||||||
const attrIndex = attrs.findIndex(att =>
|
|
||||||
att.startsWith(attName + ":")
|
|
||||||
);
|
|
||||||
attrs.splice(attrIndex, 1);
|
|
||||||
}
|
|
||||||
ctx.addLine(`var _${attID} = ${formattedValue};`);
|
|
||||||
attrs.push(`${attName}: _${attID}`);
|
|
||||||
handleBooleanProps(attName, attID);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.startsWith("t-attf-")) {
|
|
||||||
let attName = name.slice(7);
|
|
||||||
if (!attName.match(/^[a-zA-Z]+$/)) {
|
|
||||||
// attribute contains 'non letters' => we want to quote it
|
|
||||||
attName = '"' + attName + '"';
|
|
||||||
}
|
|
||||||
const formattedExpr = value!.replace(
|
|
||||||
/\{\{.*?\}\}/g,
|
|
||||||
s => "${" + ctx.formatExpression(s.slice(2, -2)) + "}"
|
|
||||||
);
|
|
||||||
const attID = ctx.generateID();
|
|
||||||
let staticVal = (<Element>node).getAttribute(attName);
|
|
||||||
if (staticVal) {
|
|
||||||
ctx.addLine(
|
|
||||||
`var _${attID} = '${staticVal} ' + \`${formattedExpr}\`;`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ctx.addLine(`var _${attID} = \`${formattedExpr}\`;`);
|
|
||||||
}
|
|
||||||
attrs.push(`${attName}: _${attID}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// t-att= attributes
|
|
||||||
if (name === "t-att") {
|
|
||||||
let id = ctx.generateID();
|
|
||||||
ctx.addLine(`var _${id} = ${ctx.formatExpression(value!)};`);
|
|
||||||
tattrs.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let nodeID = ctx.generateID();
|
|
||||||
let nodeKey: any = (<Element>node).getAttribute("t-key");
|
|
||||||
if (nodeKey) {
|
|
||||||
nodeKey = ctx.formatExpression(nodeKey);
|
|
||||||
} else {
|
|
||||||
nodeKey = nodeID;
|
|
||||||
}
|
|
||||||
const parts = [`key:${nodeKey}`];
|
|
||||||
if (attrs.length + tattrs.length > 0) {
|
|
||||||
parts.push(`attrs:{${attrs.join(",")}}`);
|
|
||||||
}
|
|
||||||
if (props.length > 0) {
|
|
||||||
parts.push(`props:{${props.join(",")}}`);
|
|
||||||
}
|
|
||||||
if (withHandlers) {
|
|
||||||
parts.push(`on:{}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.addLine(`var c${nodeID} = [], p${nodeID} = {${parts.join(",")}};`);
|
|
||||||
for (let id of tattrs) {
|
|
||||||
ctx.addIf(`_${id} instanceof Array`);
|
|
||||||
ctx.addLine(`p${nodeID}.attrs[_${id}[0]] = _${id}[1];`);
|
|
||||||
ctx.addElse();
|
|
||||||
ctx.addLine(`for (let key in _${id}) {`);
|
|
||||||
ctx.indent();
|
|
||||||
ctx.addLine(`p${nodeID}.attrs[key] = _${id}[key];`);
|
|
||||||
ctx.dedent();
|
|
||||||
ctx.addLine(`}`);
|
|
||||||
ctx.closeIf();
|
|
||||||
}
|
|
||||||
ctx.addLine(
|
|
||||||
`var vn${nodeID} = h('${node.nodeName}', p${nodeID}, c${nodeID});`
|
|
||||||
);
|
|
||||||
if (ctx.parentNode) {
|
|
||||||
ctx.addLine(`c${ctx.parentNode}.push(vn${nodeID});`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeID;
|
|
||||||
}
|
|
||||||
|
|
||||||
_compileChildren(node: ChildNode, ctx: Context) {
|
|
||||||
if (node.childNodes.length > 0) {
|
|
||||||
for (let child of Array.from(node.childNodes)) {
|
|
||||||
this._compileNode(child, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+242
-63
@@ -10,6 +10,7 @@ import { QWeb, UTILS } from "./qweb_core";
|
|||||||
* - t-ref
|
* - t-ref
|
||||||
* - t-transition
|
* - t-transition
|
||||||
* - t-widget/t-props/t-keepalive
|
* - t-widget/t-props/t-keepalive
|
||||||
|
* - t-mounted
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -56,12 +57,10 @@ QWeb.addDirective({
|
|||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
name: "ref",
|
name: "ref",
|
||||||
priority: 95,
|
priority: 95,
|
||||||
atNodeCreation({ ctx, nodeID, value }) {
|
atNodeCreation({ ctx, nodeID, value, addNodeHook }) {
|
||||||
const refKey = `ref${ctx.generateID()}`;
|
const refKey = `ref${ctx.generateID()}`;
|
||||||
ctx.addLine(`const ${refKey} = ${ctx.formatExpression(value)}`);
|
ctx.addLine(`const ${refKey} = ${ctx.formatExpression(value)}`);
|
||||||
ctx.addLine(`p${nodeID}.hook = {
|
addNodeHook("create", `context.refs[${refKey}] = n.elm;`);
|
||||||
create: (_, n) => context.refs[${refKey}] = n.elm,
|
|
||||||
};`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,25 +146,166 @@ function whenTransitionEnd(elm: HTMLElement, cb) {
|
|||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
name: "transition",
|
name: "transition",
|
||||||
priority: 96,
|
priority: 96,
|
||||||
atNodeCreation({ ctx, value, nodeID }) {
|
atNodeCreation({ ctx, value, addNodeHook }) {
|
||||||
let name = value;
|
let name = value;
|
||||||
ctx.addLine(`p${nodeID}.hook = {
|
const hooks = {
|
||||||
create: (_, n) => {
|
create: `this.utils.transitionCreate(n.elm, '${name}');`,
|
||||||
this.utils.transitionCreate(n.elm, '${name}');
|
insert: `this.utils.transitionInsert(vn.elm, '${name}');`,
|
||||||
},
|
remove: `this.utils.transitionRemove(vn.elm, '${name}', rm);`
|
||||||
insert: vn => {
|
};
|
||||||
this.utils.transitionInsert(vn.elm, '${name}');
|
for (let hookName in hooks) {
|
||||||
},
|
addNodeHook(hookName, hooks[hookName]);
|
||||||
remove: (vn, rm) => {
|
}
|
||||||
this.utils.transitionRemove(vn.elm, '${name}', rm);
|
|
||||||
}
|
|
||||||
};`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// t-widget
|
// t-widget
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The t-widget directive is certainly a complicated and hard to maintain piece
|
||||||
|
* of code. To help you, fellow developer, if you have to maintain it, I offer
|
||||||
|
* you this advice: Good luck...
|
||||||
|
*
|
||||||
|
* Since it is not 'direct' code, but rather code that generates other code, it
|
||||||
|
* is not easy to understand. To help you, here is a detailed and commented
|
||||||
|
* explanation of the code generated by the t-widget directive for the following
|
||||||
|
* situation:
|
||||||
|
* ```xml
|
||||||
|
* <t t-widget="child" t-key="'somestring'" t-props="{flag:state.flag}"/>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* // this is the virtual node representing the parent div
|
||||||
|
* let c1 = [], p1 = { key: 1 };
|
||||||
|
* var vn1 = h("div", p1, c1);
|
||||||
|
*
|
||||||
|
* // t-widget directive: we start by evaluating the expression given by t-key:
|
||||||
|
* 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 deferred that will contain later either the new widget
|
||||||
|
* // creation, or the props update...
|
||||||
|
* let def3;
|
||||||
|
*
|
||||||
|
* // this is kind of tricky: we need here to find if the widget was already
|
||||||
|
* // created by a previous rendering. This is done by checking the internal
|
||||||
|
* // `cmap` (children map) of the parent widget: it maps keys to widget ids,
|
||||||
|
* // and, then, if there is an id, we look into the children list to get the
|
||||||
|
* // instance
|
||||||
|
* let w4 =
|
||||||
|
* key5 in context.__owl__.cmap
|
||||||
|
* ? context.__owl__.children[context.__owl__.cmap[key5]]
|
||||||
|
* : false;
|
||||||
|
*
|
||||||
|
* // 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
|
||||||
|
* // computation, so it is certainly better to do it only once
|
||||||
|
* let props4 = { flag: context["state"].flag };
|
||||||
|
*
|
||||||
|
* // If we have a widget, currently rendering, but not ready yet, and which was
|
||||||
|
* // rendered with different props, we do not want to wait for it to be ready,
|
||||||
|
* // then update it. We simply destroy it, and start anew.
|
||||||
|
* if (
|
||||||
|
* w4 &&
|
||||||
|
* w4.__owl__.renderPromise &&
|
||||||
|
* !w4.__owl__.isStarted &&
|
||||||
|
* props4 !== w4.__owl__.renderProps
|
||||||
|
* ) {
|
||||||
|
* w4.destroy();
|
||||||
|
* w4 = false;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* if (!w4) {
|
||||||
|
* // in this situation, we need to create a new widget. First step is
|
||||||
|
* // to get a reference to the class, then create an instance with
|
||||||
|
* // current context as parent, and the props.
|
||||||
|
* let W4 = context.widgets["child"];
|
||||||
|
* if (!W4) {
|
||||||
|
* throw new Error("Cannot find the definition of widget 'child'");
|
||||||
|
* }
|
||||||
|
* w4 = new W4(owner, props4);
|
||||||
|
*
|
||||||
|
* // Whenever we rerender the parent widget, we need to be sure that we
|
||||||
|
* // are able to find the widget instance. To do that, we register it to
|
||||||
|
* // the parent cmap (children map). Note that the 'template' key is
|
||||||
|
* // used here, since this is what identify the widget from the template
|
||||||
|
* // perspective.
|
||||||
|
* context.__owl__.cmap[key5] = w4.__owl__.id;
|
||||||
|
*
|
||||||
|
* // _prepare is called, to basically call willStart, then render the
|
||||||
|
* // widget
|
||||||
|
* def3 = w4._prepare();
|
||||||
|
*
|
||||||
|
* def3 = def3.then(vnode => {
|
||||||
|
* // we create here a virtual node for the parent (NOT the widget). This
|
||||||
|
* // means that the vdom of the parent will be stopped here, and from
|
||||||
|
* // the parent's perspective, it simply is a vnode with no children.
|
||||||
|
* // However, it shares the same dom element with the component root
|
||||||
|
* // vnode.
|
||||||
|
* let pvnode = h(vnode.sel, { key: key5 });
|
||||||
|
*
|
||||||
|
* // we add hooks to the parent vnode so we can interact with the new
|
||||||
|
* // widget at the proper time
|
||||||
|
* pvnode.data.hook = {
|
||||||
|
* insert(vn) {
|
||||||
|
* // the _mount method will patch the widget vdom into the elm vn.elm,
|
||||||
|
* // then call the mounted hooks. However, suprisingly, the snabbdom
|
||||||
|
* // patch method actually replace the elm by a new elm, so we need
|
||||||
|
* // to synchronise the pvnode elm with the resulting elm
|
||||||
|
* let nvn = w4._mount(vnode, vn.elm);
|
||||||
|
* pvnode.elm = nvn.elm;
|
||||||
|
* },
|
||||||
|
* remove() {
|
||||||
|
* // apparently, in some cases, it is necessary to call the destroy
|
||||||
|
* // method here
|
||||||
|
* w4.destroy();
|
||||||
|
* },
|
||||||
|
* destroy() {
|
||||||
|
* // and here...
|
||||||
|
* w4.destroy();
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* // the pvnode is inserted at the correct position in the div's children
|
||||||
|
* c1[_2_index] = pvnode;
|
||||||
|
*
|
||||||
|
* // we keep here a reference to the parent vnode (representing the
|
||||||
|
* // widget, so we can reuse it later whenever we update the widget
|
||||||
|
* w4.__owl__.pvnode = pvnode;
|
||||||
|
* });
|
||||||
|
* } else {
|
||||||
|
* // this is the 'update' path of the directive.
|
||||||
|
* // the call to _updateProps is the actual widget update
|
||||||
|
* def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
|
* def3 = def3.then(() => {
|
||||||
|
* // if widget was destroyed in the meantime, we do nothing (so, this
|
||||||
|
* // means that the parent's element children list will have a null in
|
||||||
|
* // the widget's position, which will cause the pvnode to be removed
|
||||||
|
* // when it is patched.
|
||||||
|
* if (w4.__owl__.isDestroyed) {
|
||||||
|
* return;
|
||||||
|
* }
|
||||||
|
* // like above, we register the pvnode to the children list, so it
|
||||||
|
* // will not be patched out of the dom.
|
||||||
|
* let pvnode = w4.__owl__.pvnode;
|
||||||
|
* c1[_2_index] = pvnode;
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // we register the deferred here so the parent can coordinate its patch operation
|
||||||
|
* // with all the children.
|
||||||
|
* extra.promises.push(def3);
|
||||||
|
* return vn1;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
name: "widget",
|
name: "widget",
|
||||||
extraNames: ["props", "keepalive"],
|
extraNames: ["props", "keepalive"],
|
||||||
@@ -210,47 +350,7 @@ QWeb.addDirective({
|
|||||||
: ctx.inLoop
|
: ctx.inLoop
|
||||||
? `String(-${widgetID} - i)`
|
? `String(-${widgetID} - i)`
|
||||||
: String(widgetID);
|
: String(widgetID);
|
||||||
ctx.addLine(
|
|
||||||
`let w${widgetID} = ${templateID} in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[${templateID}]] : false;`
|
|
||||||
);
|
|
||||||
ctx.addLine(`let props${widgetID} = ${props || "{}"};`);
|
|
||||||
ctx.addLine(`let isNew${widgetID} = !w${widgetID};`);
|
|
||||||
|
|
||||||
// check if we can reuse current rendering promise
|
|
||||||
ctx.addIf(`w${widgetID} && w${widgetID}.__owl__.renderPromise`);
|
|
||||||
ctx.addIf(`w${widgetID}.__owl__.isStarted`);
|
|
||||||
ctx.addLine(
|
|
||||||
`def${defID} = w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`
|
|
||||||
);
|
|
||||||
ctx.addElse();
|
|
||||||
ctx.addLine(`isNew${widgetID} = true`);
|
|
||||||
ctx.addIf(`props${widgetID} === w${widgetID}.__owl__.renderProps`);
|
|
||||||
ctx.addLine(`def${defID} = w${widgetID}.__owl__.renderPromise;`);
|
|
||||||
ctx.addElse();
|
|
||||||
ctx.addLine(`w${widgetID}.destroy();`);
|
|
||||||
ctx.addLine(`w${widgetID} = false`);
|
|
||||||
ctx.closeIf();
|
|
||||||
ctx.closeIf();
|
|
||||||
ctx.closeIf();
|
|
||||||
|
|
||||||
ctx.addIf(`!def${defID}`);
|
|
||||||
ctx.addIf(`w${widgetID}`);
|
|
||||||
ctx.addLine(
|
|
||||||
`def${defID} = w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`
|
|
||||||
);
|
|
||||||
ctx.addElse();
|
|
||||||
ctx.addLine(
|
|
||||||
`w${widgetID} = new context.widgets['${value}'](owner, props${widgetID});`
|
|
||||||
);
|
|
||||||
ctx.addLine(
|
|
||||||
`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`
|
|
||||||
);
|
|
||||||
for (let [event, method] of events) {
|
|
||||||
ctx.addLine(`w${widgetID}.on('${event}', owner, owner['${method}'])`);
|
|
||||||
}
|
|
||||||
ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
|
|
||||||
ctx.closeIf();
|
|
||||||
ctx.closeIf();
|
|
||||||
let ref = node.getAttribute("t-ref");
|
let ref = node.getAttribute("t-ref");
|
||||||
let refExpr = "";
|
let refExpr = "";
|
||||||
let refKey: string = "";
|
let refKey: string = "";
|
||||||
@@ -300,28 +400,107 @@ QWeb.addDirective({
|
|||||||
const styleCode = styleExpr ? `vn.elm.style = ${styleExpr}` : "";
|
const styleCode = styleExpr ? `vn.elm.style = ${styleExpr}` : "";
|
||||||
createHook = `vnode.data.hook = {create(_, vn){${classCode}${styleCode}}};`;
|
createHook = `vnode.data.hook = {create(_, vn){${classCode}${styleCode}}};`;
|
||||||
}
|
}
|
||||||
ctx.addIf(`isNew${widgetID}`);
|
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`def${defID} = def${defID}.then(vnode=>{${createHook}let pvnode=h(vnode.sel, {key: ${templateID}});c${
|
`let w${widgetID} = ${templateID} in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[${templateID}]] : false;`
|
||||||
ctx.parentNode
|
|
||||||
}[_${dummyID}_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=w${widgetID}._mount(vnode, vn.elm);pvnode.elm=nvn.elm;${refExpr}},remove(){${finalizeWidgetCode}},destroy(){${finalizeWidgetCode}}}; w${widgetID}.__owl__.pvnode = pvnode;});`
|
|
||||||
);
|
);
|
||||||
|
ctx.addLine(`let props${widgetID} = ${props || "{}"};`);
|
||||||
|
ctx.addIf(
|
||||||
|
`w${widgetID} && w${widgetID}.__owl__.renderPromise && !w${widgetID}.__owl__.vnode && props${widgetID} !== w${widgetID}.__owl__.renderProps`
|
||||||
|
);
|
||||||
|
ctx.addLine(`w${widgetID}.destroy();`);
|
||||||
|
ctx.addLine(`w${widgetID} = false`);
|
||||||
|
ctx.closeIf();
|
||||||
|
|
||||||
|
ctx.addIf(`!w${widgetID}`);
|
||||||
|
// new widget
|
||||||
|
ctx.addLine(`let W${widgetID} = context.widgets['${value}'];`);
|
||||||
|
|
||||||
|
// maybe only do this in dev mode...
|
||||||
|
ctx.addLine(
|
||||||
|
`if (!W${widgetID}) {throw new Error(\`Cannot find the definition of widget "${value}"\`)}`
|
||||||
|
);
|
||||||
|
ctx.addLine(`w${widgetID} = new W${widgetID}(owner, props${widgetID});`);
|
||||||
|
ctx.addLine(
|
||||||
|
`context.__owl__.cmap[${templateID}] = w${widgetID}.__owl__.id;`
|
||||||
|
);
|
||||||
|
for (let [event, method] of events) {
|
||||||
|
ctx.addLine(`w${widgetID}.on('${event}', owner, owner['${method}'])`);
|
||||||
|
}
|
||||||
|
ctx.addLine(`def${defID} = w${widgetID}._prepare();`);
|
||||||
|
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}},remove(){${finalizeWidgetCode}},destroy(){${finalizeWidgetCode}}}});c${
|
||||||
|
ctx.parentNode
|
||||||
|
}[_${dummyID}_index]=pvnode;w${widgetID}.__owl__.pvnode = pvnode;});`
|
||||||
|
);
|
||||||
|
|
||||||
ctx.addElse();
|
ctx.addElse();
|
||||||
|
// need to update widget
|
||||||
|
ctx.addLine(
|
||||||
|
`def${defID} = w${widgetID}._updateProps(props${widgetID}, extra.forceUpdate, extra.patchQueue);`
|
||||||
|
);
|
||||||
|
let keepAliveCode = "";
|
||||||
|
if (keepAlive) {
|
||||||
|
keepAliveCode = `pvnode.data.hook.insert = vn => {vn.elm.parentNode.replaceChild(w${widgetID}.el,vn.elm);vn.elm=w${widgetID}.el;w${widgetID}.__mount();};`;
|
||||||
|
}
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`def${defID} = def${defID}.then(()=>{if (w${widgetID}.__owl__.isDestroyed) {return};${
|
`def${defID} = def${defID}.then(()=>{if (w${widgetID}.__owl__.isDestroyed) {return};${
|
||||||
tattStyle ? `w${widgetID}.el.style=${tattStyle};` : ""
|
tattStyle ? `w${widgetID}.el.style=${tattStyle};` : ""
|
||||||
}${updateClassCode}let vnode;if (!w${widgetID}.__owl__.vnode){vnode=w${widgetID}.__owl__.pvnode} else { vnode=h(w${widgetID}.__owl__.vnode.sel, {key: ${templateID}});vnode.elm=w${widgetID}.el;vnode.data.hook = {insert(a){a.elm.parentNode.replaceChild(w${widgetID}.el,a.elm);a.elm=w${widgetID}.el;w${widgetID}.__mount();},remove(){${finalizeWidgetCode}}, destroy() {${finalizeWidgetCode}}}}c${
|
}${updateClassCode}let pvnode=w${widgetID}.__owl__.pvnode;${keepAliveCode}c${
|
||||||
ctx.parentNode
|
ctx.parentNode
|
||||||
}[_${dummyID}_index]=vnode;});`
|
}[_${dummyID}_index]=pvnode;});`
|
||||||
);
|
);
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
|
|
||||||
ctx.addLine(`extra.promises.push(def${defID});`);
|
ctx.addLine(`extra.promises.push(def${defID});`);
|
||||||
|
|
||||||
if (node.hasAttribute("t-if") || node.hasAttribute("t-else")) {
|
if (
|
||||||
|
node.hasAttribute("t-if") ||
|
||||||
|
node.hasAttribute("t-else") ||
|
||||||
|
node.hasAttribute("t-elif")
|
||||||
|
) {
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// t-mounted
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
QWeb.addDirective({
|
||||||
|
name: "mounted",
|
||||||
|
priority: 97,
|
||||||
|
atNodeCreation({ ctx, fullName, value, nodeID, addNodeHook }) {
|
||||||
|
ctx.rootContext.shouldDefineOwner = true;
|
||||||
|
const eventName = fullName.slice(5);
|
||||||
|
if (!eventName) {
|
||||||
|
throw new Error("Missing event name with t-on directive");
|
||||||
|
}
|
||||||
|
let extraArgs;
|
||||||
|
let handler = value.replace(/\(.*\)/, function(args) {
|
||||||
|
extraArgs = args.slice(1, -1);
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
let error = `(function () {throw new Error('Missing handler \\'' + '${handler}' + \`\\' when evaluating template '${ctx.templateName.replace(
|
||||||
|
/`/g,
|
||||||
|
"'"
|
||||||
|
)}'\`)})()`;
|
||||||
|
if (extraArgs) {
|
||||||
|
ctx.addLine(
|
||||||
|
`extra.mountedHandlers[${nodeID}] = (context['${handler}'] || ${error}).bind(owner, ${ctx.formatExpression(
|
||||||
|
extraArgs
|
||||||
|
)});`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.addLine(
|
||||||
|
`extra.mountedHandlers[${nodeID}] = extra.mountedHandlers[${nodeID}] || (context['${handler}'] || ${error}).bind(owner);`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
addNodeHook(
|
||||||
|
"insert",
|
||||||
|
`if (context.__owl__.isMounted) { extra.mountedHandlers[${nodeID}](); }`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
+12
-2
@@ -72,7 +72,7 @@ export class Store extends EventBus {
|
|||||||
const func: (...any) => any = entry[1];
|
const func: (...any) => any = entry[1];
|
||||||
this.getters[name] = payload => {
|
this.getters[name] = payload => {
|
||||||
return func({ state: this.state, getters: this.getters }, payload);
|
return func({ state: this.state, getters: this.getters }, payload);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +154,8 @@ interface EnvWithStore extends Env {
|
|||||||
store: Store;
|
store: Store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nextID = 1;
|
||||||
|
|
||||||
export function connect(mapStateToProps, options: any = {}) {
|
export function connect(mapStateToProps, options: any = {}) {
|
||||||
let hashFunction = options.hashFunction || null;
|
let hashFunction = options.hashFunction || null;
|
||||||
|
|
||||||
@@ -184,7 +186,7 @@ export function connect(mapStateToProps, options: any = {}) {
|
|||||||
return function<E extends EnvWithStore, P, S>(
|
return function<E extends EnvWithStore, P, S>(
|
||||||
Comp: Constructor<Component<E, P, S>>
|
Comp: Constructor<Component<E, P, S>>
|
||||||
) {
|
) {
|
||||||
return class extends Comp {
|
const Result = class extends Comp {
|
||||||
constructor(parent, props?: any) {
|
constructor(parent, props?: any) {
|
||||||
const env = parent instanceof Component ? parent.env : parent;
|
const env = parent instanceof Component ? parent.env : parent;
|
||||||
const ownProps = Object.assign({}, props || {});
|
const ownProps = Object.assign({}, props || {});
|
||||||
@@ -269,5 +271,13 @@ export function connect(mapStateToProps, options: any = {}) {
|
|||||||
return super._updateProps(mergedProps, forceUpdate, patchQueue);
|
return super._updateProps(mergedProps, forceUpdate, patchQueue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-129
@@ -3,17 +3,54 @@
|
|||||||
*
|
*
|
||||||
* We have here a small collection of utility functions:
|
* We have here a small collection of utility functions:
|
||||||
*
|
*
|
||||||
* - escape
|
|
||||||
* - memoize
|
|
||||||
* - debounce
|
|
||||||
* - patch
|
|
||||||
* - unpatch
|
|
||||||
* - loadTemplates
|
|
||||||
* - loadJS
|
|
||||||
* - whenReady
|
* - whenReady
|
||||||
* - parseXML
|
* - loadJS
|
||||||
|
* - loadTemplates
|
||||||
|
* - escape
|
||||||
|
* - debounce
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export function whenReady(fn) {
|
||||||
|
if (document.readyState === "complete") {
|
||||||
|
fn();
|
||||||
|
} else {
|
||||||
|
document.addEventListener("DOMContentLoaded", fn, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadedScripts: { [key: string]: Promise<void> } = {};
|
||||||
|
|
||||||
|
export function loadJS(url: string): Promise<void> {
|
||||||
|
if (url in loadedScripts) {
|
||||||
|
return loadedScripts[url];
|
||||||
|
}
|
||||||
|
const promise: Promise<void> = new Promise(function(resolve, reject) {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.type = "text/javascript";
|
||||||
|
script.src = url;
|
||||||
|
script.onload = function() {
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
script.onerror = function() {
|
||||||
|
reject(`Error loading file '${url}'`);
|
||||||
|
};
|
||||||
|
const head = document.head || document.getElementsByTagName("head")[0];
|
||||||
|
head.appendChild(script);
|
||||||
|
});
|
||||||
|
loadedScripts[url] = promise;
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadTemplates(url: string): Promise<string> {
|
||||||
|
const result = await fetch(url);
|
||||||
|
if (!result.ok) {
|
||||||
|
throw new Error("Error while fetching xml templates");
|
||||||
|
}
|
||||||
|
let templates = await result.text();
|
||||||
|
templates = templates.replace(/<!--[\s\S]*?-->/g, "");
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
|
||||||
export function escape(str: string | number | undefined): string {
|
export function escape(str: string | number | undefined): string {
|
||||||
if (str === undefined) {
|
if (str === undefined) {
|
||||||
return "";
|
return "";
|
||||||
@@ -29,26 +66,6 @@ export function escape(str: string | number | undefined): string {
|
|||||||
.replace(/`/g, "`");
|
.replace(/`/g, "`");
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HashFn = (args: any[]) => string;
|
|
||||||
|
|
||||||
export function memoize<R, T extends (...args: any[]) => R>(
|
|
||||||
f: T,
|
|
||||||
hash?: HashFn
|
|
||||||
): T {
|
|
||||||
if (!hash) {
|
|
||||||
hash = args => args.map(a => String(a)).join(",");
|
|
||||||
}
|
|
||||||
let cache: { [key: string]: R } = {};
|
|
||||||
function memoizedFunction(...args: any[]) {
|
|
||||||
let hashValue = hash!(args);
|
|
||||||
if (!(hashValue in cache)) {
|
|
||||||
cache[hashValue] = f(...args);
|
|
||||||
}
|
|
||||||
return cache[hashValue];
|
|
||||||
}
|
|
||||||
return memoizedFunction as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a function, that, as long as it continues to be invoked, will not
|
* Returns a function, that, as long as it continues to be invoked, will not
|
||||||
* be triggered. The function will be called after it stops being called for
|
* be triggered. The function will be called after it stops being called for
|
||||||
@@ -80,104 +97,3 @@ export function debounce(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patch(C: any, patchName: string, patch: any) {
|
|
||||||
const proto = C.prototype;
|
|
||||||
if (!proto.__patches) {
|
|
||||||
proto.__patches = {
|
|
||||||
origMethods: {},
|
|
||||||
patches: {},
|
|
||||||
current: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (proto.__patches.patches[patchName]) {
|
|
||||||
throw new Error(`Patch [${patchName}] already exists`);
|
|
||||||
}
|
|
||||||
proto.__patches.patches[patchName] = patch;
|
|
||||||
applyPatch(proto, patch);
|
|
||||||
proto.__patches.current.push(patchName);
|
|
||||||
|
|
||||||
function applyPatch(proto, patch) {
|
|
||||||
Object.keys(patch).forEach(function(methodName) {
|
|
||||||
const method = patch[methodName];
|
|
||||||
if (typeof method === "function") {
|
|
||||||
const original = proto[methodName];
|
|
||||||
if (!(methodName in proto.__patches.origMethods)) {
|
|
||||||
proto.__patches.origMethods[methodName] = original;
|
|
||||||
}
|
|
||||||
proto[methodName] = function(...args) {
|
|
||||||
this._super = original;
|
|
||||||
return method.call(this, ...args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unpatch(C: any, patchName: string) {
|
|
||||||
const proto = C.prototype;
|
|
||||||
const patchInfo = proto.__patches;
|
|
||||||
delete proto.__patches;
|
|
||||||
|
|
||||||
// reset to original
|
|
||||||
for (let k in patchInfo.origMethods) {
|
|
||||||
proto[k] = patchInfo.origMethods[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply other patches
|
|
||||||
for (let name of patchInfo.current) {
|
|
||||||
if (name !== patchName) {
|
|
||||||
patch(C, name, patchInfo.patches[name]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadTemplates(url: string): Promise<string> {
|
|
||||||
const result = await fetch(url);
|
|
||||||
if (!result.ok) {
|
|
||||||
throw new Error("Error while fetching xml templates");
|
|
||||||
}
|
|
||||||
let templates = await result.text();
|
|
||||||
templates = templates.replace(/<!--[\s\S]*?-->/g, "");
|
|
||||||
return templates;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadedScripts: { [key: string]: Promise<void> } = {};
|
|
||||||
|
|
||||||
export function loadJS(url: string): Promise<void> {
|
|
||||||
if (url in loadedScripts) {
|
|
||||||
return loadedScripts[url];
|
|
||||||
}
|
|
||||||
const promise: Promise<void> = new Promise(function(resolve, reject) {
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.type = "text/javascript";
|
|
||||||
script.src = url;
|
|
||||||
script.onload = function() {
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
script.onerror = function() {
|
|
||||||
reject(`Error loading file '${url}'`);
|
|
||||||
};
|
|
||||||
const head = document.head || document.getElementsByTagName("head")[0];
|
|
||||||
head.appendChild(script);
|
|
||||||
});
|
|
||||||
loadedScripts[url] = promise;
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function whenReady(fn) {
|
|
||||||
if (document.readyState === "complete") {
|
|
||||||
fn();
|
|
||||||
} else {
|
|
||||||
document.addEventListener("DOMContentLoaded", fn, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseXML(xml: string): Document {
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const doc = parser.parseFromString(xml, "text/xml");
|
|
||||||
if (doc.getElementsByTagName("parsererror").length) {
|
|
||||||
throw new Error("Invalid XML in template");
|
|
||||||
}
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,42 +5,29 @@ exports[`class and style attributes with t-widget dynamic t-att-style is properl
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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;
|
let _2_index = c1.length;
|
||||||
c1.push(null);
|
c1.push(null);
|
||||||
let def3;
|
let def3;
|
||||||
|
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 props4 = {};
|
let props4 = {};
|
||||||
let isNew4 = !w4;
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode && props4 !== w4.__owl__.renderProps) {
|
||||||
if (w4 && w4.__owl__.renderPromise) {
|
w4.destroy();
|
||||||
if (w4.__owl__.isStarted) {
|
w4 = false
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
|
||||||
} else {
|
|
||||||
isNew4 = true
|
|
||||||
if (props4 === w4.__owl__.renderProps) {
|
|
||||||
def3 = w4.__owl__.renderPromise;
|
|
||||||
} else {
|
|
||||||
w4.destroy();
|
|
||||||
w4 = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!def3) {
|
if (!w4) {
|
||||||
if (w4) {
|
let W4 = context.widgets['child'];
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
||||||
} else {
|
w4 = new W4(owner, props4);
|
||||||
w4 = new context.widgets['child'](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.style = _5}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn){let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
}
|
|
||||||
}
|
|
||||||
const _5 = context['state'].style;
|
|
||||||
if (isNew4) {
|
|
||||||
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.style = _5}};let pvnode=h(vnode.sel, {key: 4});c1[_2_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=w4._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}; w4.__owl__.pvnode = pvnode;});
|
|
||||||
} else {
|
} else {
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};w4.el.style=_5;let vnode;if (!w4.__owl__.vnode){vnode=w4.__owl__.pvnode} else { vnode=h(w4.__owl__.vnode.sel, {key: 4});vnode.elm=w4.el;vnode.data.hook = {insert(a){a.elm.parentNode.replaceChild(w4.el,a.elm);a.elm=w4.el;w4.__mount();},remove(){w4.destroy()}, destroy() {w4.destroy()}}}c1[_2_index]=vnode;});
|
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};w4.el.style=_5;let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
}
|
}
|
||||||
extra.promises.push(def3);
|
extra.promises.push(def3);
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -52,46 +39,33 @@ exports[`class and style attributes with t-widget t-att-class is properly added/
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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;
|
let _2_index = c1.length;
|
||||||
c1.push(null);
|
c1.push(null);
|
||||||
let def3;
|
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 w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false;
|
||||||
let props4 = {};
|
let props4 = {};
|
||||||
let isNew4 = !w4;
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode && props4 !== w4.__owl__.renderProps) {
|
||||||
if (w4 && w4.__owl__.renderPromise) {
|
w4.destroy();
|
||||||
if (w4.__owl__.isStarted) {
|
w4 = false
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
|
||||||
} else {
|
|
||||||
isNew4 = true
|
|
||||||
if (props4 === w4.__owl__.renderProps) {
|
|
||||||
def3 = w4.__owl__.renderPromise;
|
|
||||||
} else {
|
|
||||||
w4.destroy();
|
|
||||||
w4 = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!def3) {
|
if (!w4) {
|
||||||
if (w4) {
|
let W4 = context.widgets['child'];
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
||||||
} else {
|
w4 = new W4(owner, props4);
|
||||||
w4 = new context.widgets['child'](owner, props4);
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
def3 = w4._prepare();
|
||||||
def3 = w4._prepare();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const _5 = {a: context['state'].a,b: context['state'].b};
|
|
||||||
if (isNew4) {
|
|
||||||
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){for (let k in _5) {
|
def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){for (let k in _5) {
|
||||||
if (_5[k]) {
|
if (_5[k]) {
|
||||||
vn.elm.classList.add(k);
|
vn.elm.classList.add(k);
|
||||||
}
|
}
|
||||||
}}};let pvnode=h(vnode.sel, {key: 4});c1[_2_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=w4._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}; w4.__owl__.pvnode = pvnode;});
|
}}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn){let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
} else {
|
} else {
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let cl=w4.el.classList;for (let k in _5) {if (_5[k]) {cl.add(k)} else {cl.remove(k)}}let vnode;if (!w4.__owl__.vnode){vnode=w4.__owl__.pvnode} else { vnode=h(w4.__owl__.vnode.sel, {key: 4});vnode.elm=w4.el;vnode.data.hook = {insert(a){a.elm.parentNode.replaceChild(w4.el,a.elm);a.elm=w4.el;w4.__mount();},remove(){w4.destroy()}, destroy() {w4.destroy()}}}c1[_2_index]=vnode;});
|
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
||||||
|
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let cl=w4.el.classList;for (let k in _5) {if (_5[k]) {cl.add(k)} else {cl.remove(k)}}let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;});
|
||||||
}
|
}
|
||||||
extra.promises.push(def3);
|
extra.promises.push(def3);
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -104,7 +78,7 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = `
|
|||||||
let owner = context;
|
let owner = context;
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key: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')}
|
||||||
@@ -125,33 +99,20 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = `
|
|||||||
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 props7 = {};
|
let props7 = {};
|
||||||
let isNew7 = !w7;
|
if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode && props7 !== w7.__owl__.renderProps) {
|
||||||
if (w7 && w7.__owl__.renderPromise) {
|
w7.destroy();
|
||||||
if (w7.__owl__.isStarted) {
|
w7 = false
|
||||||
def6 = w7._updateProps(props7, extra.forceUpdate, extra.patchQueue);
|
|
||||||
} else {
|
|
||||||
isNew7 = true
|
|
||||||
if (props7 === w7.__owl__.renderProps) {
|
|
||||||
def6 = w7.__owl__.renderPromise;
|
|
||||||
} else {
|
|
||||||
w7.destroy();
|
|
||||||
w7 = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!def6) {
|
if (!w7) {
|
||||||
if (w7) {
|
let W7 = context.widgets['ChildWidget'];
|
||||||
def6 = w7._updateProps(props7, extra.forceUpdate, extra.patchQueue);
|
if (!W7) {throw new Error(\`Cannot find the definition of widget \\"ChildWidget\\"\`)}
|
||||||
} else {
|
w7 = new W7(owner, props7);
|
||||||
w7 = new context.widgets['ChildWidget'](owner, props7);
|
context.__owl__.cmap[key8] = w7.__owl__.id;
|
||||||
context.__owl__.cmap[key8] = w7.__owl__.id;
|
def6 = w7._prepare();
|
||||||
def6 = w7._prepare();
|
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: key8, hook: {insert(vn){let nvn=w7._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove(){w7.destroy()},destroy(){w7.destroy()}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;});
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isNew7) {
|
|
||||||
def6 = def6.then(vnode=>{let pvnode=h(vnode.sel, {key: key8});c1[_5_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=w7._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w7.destroy()},destroy(){w7.destroy()}}; w7.__owl__.pvnode = pvnode;});
|
|
||||||
} else {
|
} else {
|
||||||
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let vnode;if (!w7.__owl__.vnode){vnode=w7.__owl__.pvnode} else { vnode=h(w7.__owl__.vnode.sel, {key: key8});vnode.elm=w7.el;vnode.data.hook = {insert(a){a.elm.parentNode.replaceChild(w7.el,a.elm);a.elm=w7.el;w7.__mount();},remove(){w7.destroy()}, destroy() {w7.destroy()}}}c1[_5_index]=vnode;});
|
def6 = w7._updateProps(props7, extra.forceUpdate, extra.patchQueue);
|
||||||
|
def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c1[_5_index]=pvnode;});
|
||||||
}
|
}
|
||||||
extra.promises.push(def6);
|
extra.promises.push(def6);
|
||||||
}
|
}
|
||||||
@@ -164,7 +125,7 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//WIDGET
|
//WIDGET
|
||||||
let key5 = 'somestring';
|
let key5 = 'somestring';
|
||||||
@@ -173,33 +134,20 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
|
|||||||
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 props4 = {flag: context['state'].flag};
|
let props4 = {flag: context['state'].flag};
|
||||||
let isNew4 = !w4;
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode && props4 !== w4.__owl__.renderProps) {
|
||||||
if (w4 && w4.__owl__.renderPromise) {
|
w4.destroy();
|
||||||
if (w4.__owl__.isStarted) {
|
w4 = false
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
|
||||||
} else {
|
|
||||||
isNew4 = true
|
|
||||||
if (props4 === w4.__owl__.renderProps) {
|
|
||||||
def3 = w4.__owl__.renderPromise;
|
|
||||||
} else {
|
|
||||||
w4.destroy();
|
|
||||||
w4 = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!def3) {
|
if (!w4) {
|
||||||
if (w4) {
|
let W4 = context.widgets['child'];
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
||||||
} else {
|
w4 = new W4(owner, props4);
|
||||||
w4 = new context.widgets['child'](owner, props4);
|
context.__owl__.cmap[key5] = w4.__owl__.id;
|
||||||
context.__owl__.cmap[key5] = w4.__owl__.id;
|
def3 = w4._prepare();
|
||||||
def3 = w4._prepare();
|
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: key5, hook: {insert(vn){let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isNew4) {
|
|
||||||
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: key5});c1[_2_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=w4._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}; w4.__owl__.pvnode = pvnode;});
|
|
||||||
} else {
|
} else {
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let vnode;if (!w4.__owl__.vnode){vnode=w4.__owl__.pvnode} else { vnode=h(w4.__owl__.vnode.sel, {key: key5});vnode.elm=w4.el;vnode.data.hook = {insert(a){a.elm.parentNode.replaceChild(w4.el,a.elm);a.elm=w4.el;w4.__mount();},remove(){w4.destroy()}, destroy() {w4.destroy()}}}c1[_2_index]=vnode;});
|
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);
|
extra.promises.push(def3);
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -211,7 +159,7 @@ exports[`random stuff/miscellaneous t-props should not be undefined (snapshottin
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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;
|
let _2_index = c1.length;
|
||||||
@@ -219,33 +167,20 @@ exports[`random stuff/miscellaneous t-props should not be undefined (snapshottin
|
|||||||
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 props4 = {};
|
let props4 = {};
|
||||||
let isNew4 = !w4;
|
if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode && props4 !== w4.__owl__.renderProps) {
|
||||||
if (w4 && w4.__owl__.renderPromise) {
|
w4.destroy();
|
||||||
if (w4.__owl__.isStarted) {
|
w4 = false
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
|
||||||
} else {
|
|
||||||
isNew4 = true
|
|
||||||
if (props4 === w4.__owl__.renderProps) {
|
|
||||||
def3 = w4.__owl__.renderPromise;
|
|
||||||
} else {
|
|
||||||
w4.destroy();
|
|
||||||
w4 = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!def3) {
|
if (!w4) {
|
||||||
if (w4) {
|
let W4 = context.widgets['child'];
|
||||||
def3 = w4._updateProps(props4, extra.forceUpdate, extra.patchQueue);
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
||||||
} else {
|
w4 = new W4(owner, props4);
|
||||||
w4 = new context.widgets['child'](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;},remove(){w4.destroy()},destroy(){w4.destroy()}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;});
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isNew4) {
|
|
||||||
def3 = def3.then(vnode=>{let pvnode=h(vnode.sel, {key: 4});c1[_2_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=w4._mount(vnode, vn.elm);pvnode.elm=nvn.elm;},remove(){w4.destroy()},destroy(){w4.destroy()}}; w4.__owl__.pvnode = pvnode;});
|
|
||||||
} else {
|
} else {
|
||||||
def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let vnode;if (!w4.__owl__.vnode){vnode=w4.__owl__.pvnode} else { vnode=h(w4.__owl__.vnode.sel, {key: 4});vnode.elm=w4.el;vnode.data.hook = {insert(a){a.elm.parentNode.replaceChild(w4.el,a.elm);a.elm=w4.el;w4.__mount();},remove(){w4.destroy()}, destroy() {w4.destroy()}}}c1[_2_index]=vnode;});
|
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);
|
extra.promises.push(def3);
|
||||||
return vn1;
|
return vn1;
|
||||||
|
|||||||
@@ -4,22 +4,22 @@ exports[`animations t-transition, on a simple node 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
p2.hook = {
|
p2.hook = {
|
||||||
create: (_, n) => {
|
create: (_, n) => {
|
||||||
this.utils.transitionCreate(n.elm, 'chimay');
|
this.utils.transitionCreate(n.elm, 'chimay');
|
||||||
},
|
},
|
||||||
insert: vn => {
|
insert: vn => {
|
||||||
this.utils.transitionInsert(vn.elm, 'chimay');
|
this.utils.transitionInsert(vn.elm, 'chimay');
|
||||||
},
|
},
|
||||||
remove: (vn, rm) => {
|
remove: (vn, rm) => {
|
||||||
this.utils.transitionRemove(vn.elm, 'chimay', rm);
|
this.utils.transitionRemove(vn.elm, 'chimay', rm);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
c2.push({text: \`blue\`});
|
c2.push({text: \`blue\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -32,7 +32,7 @@ exports[`attributes dynamic attribute falsy variable 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = context['value'];
|
var _1 = context['value'];
|
||||||
var 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;
|
||||||
}"
|
}"
|
||||||
@@ -43,7 +43,7 @@ exports[`attributes dynamic attribute with a dash 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = context['id'];
|
var _1 = context['id'];
|
||||||
var c2 = [], p2 = {key:2,attrs:{\\"data-action-id\\": _1}};
|
let c2 = [], p2 = {key:2,attrs:{\\"data-action-id\\": _1}};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
}"
|
}"
|
||||||
@@ -54,7 +54,7 @@ exports[`attributes dynamic attributes 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = 'bar';
|
var _1 = 'bar';
|
||||||
var 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;
|
||||||
}"
|
}"
|
||||||
@@ -65,7 +65,7 @@ exports[`attributes dynamic formatted attributes with a dash 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = \`Some text \${context['id']}\`;
|
var _1 = \`Some text \${context['id']}\`;
|
||||||
var c2 = [], p2 = {key:2,attrs:{\\"aria-label\\": _1}};
|
let c2 = [], p2 = {key:2,attrs:{\\"aria-label\\": _1}};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
}"
|
}"
|
||||||
@@ -76,7 +76,7 @@ exports[`attributes fixed variable 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = context['value'];
|
var _1 = context['value'];
|
||||||
var 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;
|
||||||
}"
|
}"
|
||||||
@@ -87,7 +87,18 @@ exports[`attributes format expression 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = \`\${context['value'] + 37}\`;
|
var _1 = \`\${context['value'] + 37}\`;
|
||||||
var c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
||||||
|
var vn2 = h('div', p2, c2);
|
||||||
|
return vn2;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`attributes format expression, other format 1`] = `
|
||||||
|
"function anonymous(context,extra
|
||||||
|
) {
|
||||||
|
var h = this.utils.h;
|
||||||
|
var _1 = \`\${context['value'] + 37}\`;
|
||||||
|
let c2 = [], p2 = {key:2,attrs:{foo: _1}};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
}"
|
}"
|
||||||
@@ -98,7 +109,7 @@ exports[`attributes format literal 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = \`bar\`;
|
var _1 = \`bar\`;
|
||||||
var 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;
|
||||||
}"
|
}"
|
||||||
@@ -109,7 +120,7 @@ exports[`attributes format multiple 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = \`a \${context['value1']} is \${context['value2']} of \${context['value3']} ]\`;
|
var _1 = \`a \${context['value1']} is \${context['value2']} of \${context['value3']} ]\`;
|
||||||
var 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;
|
||||||
}"
|
}"
|
||||||
@@ -120,7 +131,7 @@ exports[`attributes format value 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = \`b\${context['value']}r\`;
|
var _1 = \`b\${context['value']}r\`;
|
||||||
var 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;
|
||||||
}"
|
}"
|
||||||
@@ -130,11 +141,11 @@ exports[`attributes from object variables set previously 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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;
|
||||||
var 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);
|
||||||
c1.push(vn4);
|
c1.push(vn4);
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -145,11 +156,11 @@ exports[`attributes from variables set previously 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = 'def';
|
var _2 = 'def';
|
||||||
var _3 = _2;
|
var _3 = _2;
|
||||||
var 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);
|
||||||
c1.push(vn4);
|
c1.push(vn4);
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -161,7 +172,7 @@ exports[`attributes object 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = context['value'];
|
var _1 = context['value'];
|
||||||
var 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];
|
||||||
} else {
|
} else {
|
||||||
@@ -181,7 +192,7 @@ exports[`attributes static attributes 1`] = `
|
|||||||
var _1 = 'a';
|
var _1 = 'a';
|
||||||
var _2 = 'b';
|
var _2 = 'b';
|
||||||
var _3 = 'c';
|
var _3 = 'c';
|
||||||
var c4 = [], p4 = {key:4,attrs:{foo: _1,bar: _2,baz: _3}};
|
let c4 = [], p4 = {key:4,attrs:{foo: _1,bar: _2,baz: _3}};
|
||||||
var vn4 = h('div', p4, c4);
|
var vn4 = h('div', p4, c4);
|
||||||
return vn4;
|
return vn4;
|
||||||
}"
|
}"
|
||||||
@@ -193,7 +204,7 @@ exports[`attributes static attributes on void elements 1`] = `
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = '/test.jpg';
|
var _1 = '/test.jpg';
|
||||||
var _2 = 'Test';
|
var _2 = 'Test';
|
||||||
var c3 = [], p3 = {key:3,attrs:{src: _1,alt: _2}};
|
let c3 = [], p3 = {key:3,attrs:{src: _1,alt: _2}};
|
||||||
var vn3 = h('img', p3, c3);
|
var vn3 = h('img', p3, c3);
|
||||||
return vn3;
|
return vn3;
|
||||||
}"
|
}"
|
||||||
@@ -204,7 +215,7 @@ exports[`attributes static attributes with dashes 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = 'Close';
|
var _1 = 'Close';
|
||||||
var c2 = [], p2 = {key:2,attrs:{\\"aria-label\\": _1}};
|
let c2 = [], p2 = {key:2,attrs:{\\"aria-label\\": _1}};
|
||||||
var vn2 = h('div', p2, c2);
|
var vn2 = h('div', p2, c2);
|
||||||
return vn2;
|
return vn2;
|
||||||
}"
|
}"
|
||||||
@@ -217,7 +228,7 @@ exports[`attributes t-att-class and class should combine together 1`] = `
|
|||||||
var _1 = 'hello';
|
var _1 = 'hello';
|
||||||
var _3 = context['value'];
|
var _3 = context['value'];
|
||||||
var _2 = 'hello' + (_3 ? ' ' + _3 : '');
|
var _2 = 'hello' + (_3 ? ' ' + _3 : '');
|
||||||
var 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);
|
||||||
return vn4;
|
return vn4;
|
||||||
}"
|
}"
|
||||||
@@ -230,7 +241,7 @@ exports[`attributes t-att-class with object 1`] = `
|
|||||||
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 : '');
|
||||||
var 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);
|
||||||
return vn4;
|
return vn4;
|
||||||
}"
|
}"
|
||||||
@@ -241,7 +252,7 @@ exports[`attributes t-attf-class should combine with class 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = 'hello ' + \`world\`;
|
var _1 = 'hello ' + \`world\`;
|
||||||
var 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;
|
||||||
}"
|
}"
|
||||||
@@ -252,7 +263,7 @@ exports[`attributes tuple literal 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = ['foo', 'bar'];
|
var _1 = ['foo', 'bar'];
|
||||||
var 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];
|
||||||
} else {
|
} else {
|
||||||
@@ -270,7 +281,7 @@ exports[`attributes tuple variable 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = context['value'];
|
var _1 = context['value'];
|
||||||
var 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];
|
||||||
} else {
|
} else {
|
||||||
@@ -288,11 +299,11 @@ exports[`debugging t-debug 1`] = `
|
|||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
debugger;
|
debugger;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
if (true) {
|
if (true) {
|
||||||
debugger;
|
debugger;
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
c2.push({text: \`hey\`});
|
c2.push({text: \`hey\`});
|
||||||
@@ -305,7 +316,7 @@ exports[`debugging t-log 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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)
|
||||||
@@ -318,7 +329,7 @@ exports[`foreach does not pollute the rendering context 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
@@ -346,7 +357,7 @@ exports[`foreach iterate on items (on a element node) 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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')}
|
||||||
@@ -360,7 +371,7 @@ exports[`foreach iterate on items (on a element node) 1`] = `
|
|||||||
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];
|
||||||
var c5 = [], p5 = {key:context['item']};
|
let c5 = [], p5 = {key:context['item']};
|
||||||
var vn5 = h('span', p5, c5);
|
var vn5 = h('span', p5, c5);
|
||||||
c1.push(vn5);
|
c1.push(vn5);
|
||||||
var _6 = context['item'];
|
var _6 = context['item'];
|
||||||
@@ -377,7 +388,7 @@ exports[`foreach iterate on items 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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')}
|
||||||
@@ -417,7 +428,7 @@ exports[`foreach iterate, dict param 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
@@ -462,7 +473,7 @@ exports[`foreach iterate, integer param 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = 3;
|
var _2 = 3;
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
@@ -502,7 +513,7 @@ exports[`foreach iterate, position 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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 = 5;
|
||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
@@ -539,7 +550,7 @@ exports[`foreach warn if no key in some case 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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')}
|
||||||
@@ -553,7 +564,7 @@ exports[`foreach warn if no key in some case 1`] = `
|
|||||||
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];
|
||||||
var c5 = [], p5 = {key:5};
|
let c5 = [], p5 = {key:5};
|
||||||
var vn5 = h('span', p5, c5);
|
var vn5 = h('span', p5, c5);
|
||||||
c1.push(vn5);
|
c1.push(vn5);
|
||||||
var _6 = context['item'];
|
var _6 = context['item'];
|
||||||
@@ -569,7 +580,7 @@ exports[`loading templates can initialize qweb with a string 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
c1.push({text: \`jupiler\`});
|
c1.push({text: \`jupiler\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -580,13 +591,13 @@ exports[`loading templates can load a few templates from a xml string 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('ul', p1, c1);
|
var vn1 = h('ul', p1, c1);
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('li', p2, c2);
|
var vn2 = h('li', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
c2.push({text: \`ok\`});
|
c2.push({text: \`ok\`});
|
||||||
var c3 = [], p3 = {key:3};
|
let c3 = [], p3 = {key:3};
|
||||||
var vn3 = h('li', p3, c3);
|
var vn3 = h('li', p3, c3);
|
||||||
c1.push(vn3);
|
c1.push(vn3);
|
||||||
c3.push({text: \`foo\`});
|
c3.push({text: \`foo\`});
|
||||||
@@ -599,7 +610,7 @@ exports[`misc global 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key: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')}
|
||||||
@@ -613,7 +624,7 @@ exports[`misc global 1`] = `
|
|||||||
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];
|
||||||
var c5 = [], p5 = {key:5,on:{}};
|
let c5 = [], p5 = {key:5,on:{}};
|
||||||
var vn5 = h('span', p5, c5);
|
var vn5 = h('span', p5, c5);
|
||||||
c1.push(vn5);
|
c1.push(vn5);
|
||||||
var _6 = context['value'];
|
var _6 = context['value'];
|
||||||
@@ -623,12 +634,12 @@ exports[`misc global 1`] = `
|
|||||||
{
|
{
|
||||||
let _11 = 'bbb'
|
let _11 = 'bbb'
|
||||||
var _13 = 'agüero';
|
var _13 = 'agüero';
|
||||||
var 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'
|
||||||
var 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);
|
||||||
if (_15 || _15 === 0) {
|
if (_15 || _15 === 0) {
|
||||||
@@ -637,7 +648,7 @@ exports[`misc global 1`] = `
|
|||||||
c16.push({text: \`foo default\`});
|
c16.push({text: \`foo default\`});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var c17 = [], p17 = {key:17,on:{}};
|
let c17 = [], p17 = {key:17,on:{}};
|
||||||
var vn17 = h('span', p17, c17);
|
var vn17 = h('span', p17, c17);
|
||||||
c14.push(vn17);
|
c14.push(vn17);
|
||||||
var _18 = context['foo'];
|
var _18 = context['foo'];
|
||||||
@@ -649,7 +660,7 @@ exports[`misc global 1`] = `
|
|||||||
var _19 = 'bbb';
|
var _19 = 'bbb';
|
||||||
{
|
{
|
||||||
let _19 = 'bbb'
|
let _19 = 'bbb'
|
||||||
var c20 = [], p20 = {key:20,on:{}};
|
let c20 = [], p20 = {key:20,on:{}};
|
||||||
var vn20 = h('span', p20, c20);
|
var vn20 = h('span', p20, c20);
|
||||||
c14.push(vn20);
|
c14.push(vn20);
|
||||||
if (_19 || _19 === 0) {
|
if (_19 || _19 === 0) {
|
||||||
@@ -660,7 +671,7 @@ exports[`misc global 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var c21 = [], p21 = {key:21,on:{}};
|
let c21 = [], p21 = {key:21,on:{}};
|
||||||
var vn21 = h('div', p21, c21);
|
var vn21 = h('div', p21, c21);
|
||||||
c1.push(vn21);
|
c1.push(vn21);
|
||||||
var _22 = context['toto'];
|
var _22 = context['toto'];
|
||||||
@@ -684,7 +695,7 @@ exports[`special cases for some boolean html attributes/properties input type= c
|
|||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var _1 = 'checkbox';
|
var _1 = 'checkbox';
|
||||||
var _2 = context['flag'];
|
var _2 = context['flag'];
|
||||||
var c3 = [], p3 = {key:3,attrs:{type: _1,checked: _2},props:{checked: _2}};
|
let c3 = [], p3 = {key:3,attrs:{type: _1,checked: _2},props:{checked: _2}};
|
||||||
var vn3 = h('input', p3, c3);
|
var vn3 = h('input', p3, c3);
|
||||||
return vn3;
|
return vn3;
|
||||||
}"
|
}"
|
||||||
@@ -694,36 +705,36 @@ exports[`special cases for some boolean html attributes/properties various boole
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = 'checkbox';
|
var _2 = 'checkbox';
|
||||||
var _3 = 'checked';
|
var _3 = 'checked';
|
||||||
var c4 = [], p4 = {key:4,attrs:{type: _2,checked: _3},props:{checked: _3}};
|
let c4 = [], p4 = {key:4,attrs:{type: _2,checked: _3},props:{checked: _3}};
|
||||||
var vn4 = h('input', p4, c4);
|
var vn4 = h('input', p4, c4);
|
||||||
c1.push(vn4);
|
c1.push(vn4);
|
||||||
var _5 = 'checked';
|
var _5 = 'checked';
|
||||||
var c6 = [], p6 = {key:6,attrs:{checked: _5}};
|
let c6 = [], p6 = {key:6,attrs:{checked: _5}};
|
||||||
var vn6 = h('input', p6, c6);
|
var vn6 = h('input', p6, c6);
|
||||||
c1.push(vn6);
|
c1.push(vn6);
|
||||||
var _7 = 'checked';
|
var _7 = 'checked';
|
||||||
var c8 = [], p8 = {key:8,attrs:{checked: _7}};
|
let c8 = [], p8 = {key:8,attrs:{checked: _7}};
|
||||||
var vn8 = h('div', p8, c8);
|
var vn8 = h('div', p8, c8);
|
||||||
c1.push(vn8);
|
c1.push(vn8);
|
||||||
var _9 = 'selected';
|
var _9 = 'selected';
|
||||||
var c10 = [], p10 = {key:10,attrs:{selected: _9}};
|
let c10 = [], p10 = {key:10,attrs:{selected: _9}};
|
||||||
var vn10 = h('div', p10, c10);
|
var vn10 = h('div', p10, c10);
|
||||||
c1.push(vn10);
|
c1.push(vn10);
|
||||||
var _11 = 'selected';
|
var _11 = 'selected';
|
||||||
var _12 = '1';
|
var _12 = '1';
|
||||||
var c13 = [], p13 = {key:13,attrs:{selected: _11,other: _12},props:{selected: _11}};
|
let c13 = [], p13 = {key:13,attrs:{selected: _11,other: _12},props:{selected: _11}};
|
||||||
var vn13 = h('option', p13, c13);
|
var vn13 = h('option', p13, c13);
|
||||||
c1.push(vn13);
|
c1.push(vn13);
|
||||||
var _14 = 'readonly';
|
var _14 = 'readonly';
|
||||||
var c15 = [], p15 = {key:15,attrs:{readonly: _14},props:{readonly: _14}};
|
let c15 = [], p15 = {key:15,attrs:{readonly: _14},props:{readonly: _14}};
|
||||||
var vn15 = h('input', p15, c15);
|
var vn15 = h('input', p15, c15);
|
||||||
c1.push(vn15);
|
c1.push(vn15);
|
||||||
var _16 = 'disabled';
|
var _16 = 'disabled';
|
||||||
var c17 = [], p17 = {key:17,attrs:{disabled: _16},props:{disabled: _16}};
|
let c17 = [], p17 = {key:17,attrs:{disabled: _16},props:{disabled: _16}};
|
||||||
var vn17 = h('button', p17, c17);
|
var vn17 = h('button', p17, c17);
|
||||||
c1.push(vn17);
|
c1.push(vn17);
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -734,9 +745,9 @@ exports[`static templates div with a span child node 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
c2.push({text: \`word\`});
|
c2.push({text: \`word\`});
|
||||||
@@ -748,7 +759,7 @@ exports[`static templates div with a text node 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
c1.push({text: \`word\`});
|
c1.push({text: \`word\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -759,7 +770,7 @@ exports[`static templates empty div 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -778,7 +789,7 @@ exports[`t-call (template calling basic caller 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
c1.push({text: \`ok\`});
|
c1.push({text: \`ok\`});
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -789,7 +800,7 @@ exports[`t-call (template calling inherit context 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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;
|
||||||
{
|
{
|
||||||
@@ -806,7 +817,7 @@ exports[`t-call (template calling scoped parameters 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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
|
||||||
@@ -824,7 +835,7 @@ exports[`t-call (template calling with unused body 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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\`});
|
||||||
return vn2;
|
return vn2;
|
||||||
@@ -837,7 +848,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
|
||||||
var 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\`});
|
||||||
}
|
}
|
||||||
@@ -849,7 +860,7 @@ exports[`t-call (template calling with used body 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('h1', p2, c2);
|
var vn2 = h('h1', p2, c2);
|
||||||
c2.push({text: \`ok\`});
|
c2.push({text: \`ok\`});
|
||||||
return vn2;
|
return vn2;
|
||||||
@@ -860,7 +871,7 @@ exports[`t-call (template calling with used set body 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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'
|
||||||
@@ -876,7 +887,7 @@ exports[`t-esc escaping on a node 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
var _2 = 'ok';
|
var _2 = 'ok';
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -890,7 +901,7 @@ exports[`t-esc escaping on a node with a body 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
var _2 = 'ok';
|
var _2 = 'ok';
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -906,7 +917,7 @@ exports[`t-esc escaping on a node with a body, as a default 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
var _2 = context['var'];
|
var _2 = context['var'];
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -922,7 +933,7 @@ exports[`t-esc literal 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
var _2 = 'ok';
|
var _2 = 'ok';
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -936,7 +947,7 @@ exports[`t-esc variable 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
var _2 = context['var'];
|
var _2 = context['var'];
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -950,7 +961,7 @@ exports[`t-if boolean value condition elif 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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\`});
|
||||||
@@ -972,9 +983,9 @@ exports[`t-if boolean value condition else 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
c2.push({text: \`begin\`});
|
c2.push({text: \`begin\`});
|
||||||
@@ -984,7 +995,7 @@ exports[`t-if boolean value condition else 1`] = `
|
|||||||
else {
|
else {
|
||||||
c1.push({text: \`ok-else\`});
|
c1.push({text: \`ok-else\`});
|
||||||
}
|
}
|
||||||
var c3 = [], p3 = {key:3};
|
let c3 = [], p3 = {key:3};
|
||||||
var vn3 = h('span', p3, c3);
|
var vn3 = h('span', p3, c3);
|
||||||
c1.push(vn3);
|
c1.push(vn3);
|
||||||
c3.push({text: \`end\`});
|
c3.push({text: \`end\`});
|
||||||
@@ -996,9 +1007,9 @@ exports[`t-if boolean value condition false else 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
c2.push({text: \`begin\`});
|
c2.push({text: \`begin\`});
|
||||||
@@ -1008,7 +1019,7 @@ exports[`t-if boolean value condition false else 1`] = `
|
|||||||
else {
|
else {
|
||||||
c1.push({text: \`fail-else\`});
|
c1.push({text: \`fail-else\`});
|
||||||
}
|
}
|
||||||
var c3 = [], p3 = {key:3};
|
let c3 = [], p3 = {key:3};
|
||||||
var vn3 = h('span', p3, c3);
|
var vn3 = h('span', p3, c3);
|
||||||
c1.push(vn3);
|
c1.push(vn3);
|
||||||
c3.push({text: \`end\`});
|
c3.push({text: \`end\`});
|
||||||
@@ -1020,7 +1031,7 @@ exports[`t-if boolean value condition missing 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
if (context['condition']) {
|
if (context['condition']) {
|
||||||
c1.push({text: \`fail\`});
|
c1.push({text: \`fail\`});
|
||||||
@@ -1033,7 +1044,7 @@ exports[`t-if boolean value false condition 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
if (context['condition']) {
|
if (context['condition']) {
|
||||||
c1.push({text: \`ok\`});
|
c1.push({text: \`ok\`});
|
||||||
@@ -1046,7 +1057,7 @@ exports[`t-if boolean value true condition 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
if (context['condition']) {
|
if (context['condition']) {
|
||||||
c1.push({text: \`ok\`});
|
c1.push({text: \`ok\`});
|
||||||
@@ -1059,7 +1070,7 @@ exports[`t-if can use some boolean operators in expressions 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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\`});
|
||||||
@@ -1093,7 +1104,7 @@ exports[`t-key can use t-key directive on a node 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:context['beer'].id};
|
let c1 = [], p1 = {key:context['beer'].id};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = context['beer'].name;
|
var _2 = context['beer'].name;
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -1108,7 +1119,7 @@ exports[`t-key t-key directive in a list 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key: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')}
|
||||||
@@ -1122,7 +1133,7 @@ exports[`t-key t-key directive in a list 1`] = `
|
|||||||
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];
|
||||||
var c5 = [], p5 = {key:context['beer'].id};
|
let c5 = [], p5 = {key:context['beer'].id};
|
||||||
var vn5 = h('li', p5, c5);
|
var vn5 = h('li', p5, c5);
|
||||||
c1.push(vn5);
|
c1.push(vn5);
|
||||||
var _6 = context['beer'].name;
|
var _6 = context['beer'].name;
|
||||||
@@ -1141,7 +1152,7 @@ exports[`t-on can bind event handler 1`] = `
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('button', p1, c1);
|
var vn1 = h('button', p1, c1);
|
||||||
extra.handlers['click' + 1] = extra.handlers['click' + 1] || (context['add'] || (function () {throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner);
|
extra.handlers['click' + 1] = extra.handlers['click' + 1] || (context['add'] || (function () {throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner);
|
||||||
p1.on['click'] = extra.handlers['click' + 1];
|
p1.on['click'] = extra.handlers['click' + 1];
|
||||||
@@ -1155,7 +1166,7 @@ exports[`t-on can bind handlers with arguments 1`] = `
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('button', p1, c1);
|
var vn1 = h('button', p1, c1);
|
||||||
p1.on['click'] = (context['add'] || (function () {throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, 5);
|
p1.on['click'] = (context['add'] || (function () {throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, 5);
|
||||||
c1.push({text: \`Click\`});
|
c1.push({text: \`Click\`});
|
||||||
@@ -1168,7 +1179,7 @@ exports[`t-on can bind handlers with empty object (with non empty inner string 1
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('button', p1, c1);
|
var vn1 = h('button', p1, c1);
|
||||||
p1.on['click'] = (context['doSomething'] || (function () {throw new Error('Missing handler \\\\'' + 'doSomething' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, {});
|
p1.on['click'] = (context['doSomething'] || (function () {throw new Error('Missing handler \\\\'' + 'doSomething' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, {});
|
||||||
c1.push({text: \`Click\`});
|
c1.push({text: \`Click\`});
|
||||||
@@ -1181,7 +1192,7 @@ exports[`t-on can bind handlers with empty object 1`] = `
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('button', p1, c1);
|
var vn1 = h('button', p1, c1);
|
||||||
p1.on['click'] = (context['doSomething'] || (function () {throw new Error('Missing handler \\\\'' + 'doSomething' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, {});
|
p1.on['click'] = (context['doSomething'] || (function () {throw new Error('Missing handler \\\\'' + 'doSomething' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, {});
|
||||||
c1.push({text: \`Click\`});
|
c1.push({text: \`Click\`});
|
||||||
@@ -1195,7 +1206,7 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
|
|||||||
let owner = context;
|
let owner = context;
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key: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')}
|
||||||
@@ -1209,10 +1220,10 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
|
|||||||
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];
|
||||||
var c5 = [], p5 = {key:context['action_index']};
|
let c5 = [], p5 = {key:context['action_index']};
|
||||||
var vn5 = h('li', p5, c5);
|
var vn5 = h('li', p5, c5);
|
||||||
c1.push(vn5);
|
c1.push(vn5);
|
||||||
var c6 = [], p6 = {key:6,on:{}};
|
let c6 = [], p6 = {key:6,on:{}};
|
||||||
var vn6 = h('a', p6, c6);
|
var vn6 = h('a', p6, c6);
|
||||||
c5.push(vn6);
|
c5.push(vn6);
|
||||||
p6.on['click'] = (context['activate'] || (function () {throw new Error('Missing handler \\\\'' + 'activate' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, context['action']);
|
p6.on['click'] = (context['activate'] || (function () {throw new Error('Missing handler \\\\'' + 'activate' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, context['action']);
|
||||||
@@ -1227,7 +1238,7 @@ exports[`t-on can bind handlers with object arguments 1`] = `
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('button', p1, c1);
|
var vn1 = h('button', p1, c1);
|
||||||
p1.on['click'] = (context['add'] || (function () {throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, {val: 5});
|
p1.on['click'] = (context['add'] || (function () {throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner, {val: 5});
|
||||||
c1.push({text: \`Click\`});
|
c1.push({text: \`Click\`});
|
||||||
@@ -1240,7 +1251,7 @@ exports[`t-on can bind two event handlers 1`] = `
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('button', p1, c1);
|
var vn1 = h('button', p1, c1);
|
||||||
extra.handlers['click' + 1] = extra.handlers['click' + 1] || (context['handleClick'] || (function () {throw new Error('Missing handler \\\\'' + 'handleClick' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner);
|
extra.handlers['click' + 1] = extra.handlers['click' + 1] || (context['handleClick'] || (function () {throw new Error('Missing handler \\\\'' + 'handleClick' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner);
|
||||||
p1.on['click'] = extra.handlers['click' + 1];
|
p1.on['click'] = extra.handlers['click' + 1];
|
||||||
@@ -1256,7 +1267,7 @@ exports[`t-on handler is bound to proper owner 1`] = `
|
|||||||
) {
|
) {
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1,on:{}};
|
let c1 = [], p1 = {key:1,on:{}};
|
||||||
var vn1 = h('button', p1, c1);
|
var vn1 = h('button', p1, c1);
|
||||||
extra.handlers['click' + 1] = extra.handlers['click' + 1] || (context['add'] || (function () {throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner);
|
extra.handlers['click' + 1] = extra.handlers['click' + 1] || (context['add'] || (function () {throw new Error('Missing handler \\\\'' + 'add' + \`\\\\' when evaluating template 'test'\`)})()).bind(owner);
|
||||||
p1.on['click'] = extra.handlers['click' + 1];
|
p1.on['click'] = extra.handlers['click' + 1];
|
||||||
@@ -1269,7 +1280,7 @@ exports[`t-raw literal 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
var _2 = 'ok';
|
var _2 = 'ok';
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -1288,7 +1299,7 @@ exports[`t-raw not escaping 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = context['var'];
|
var _2 = context['var'];
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -1307,9 +1318,9 @@ exports[`t-raw t-raw and another sibling node 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
c2.push({text: \`hello\`});
|
c2.push({text: \`hello\`});
|
||||||
@@ -1330,7 +1341,7 @@ exports[`t-raw variable 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('span', p1, c1);
|
var vn1 = h('span', p1, c1);
|
||||||
var _2 = context['var'];
|
var _2 = context['var'];
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -1349,15 +1360,17 @@ exports[`t-ref can get a dynamic ref on a node 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
const ref3 = 'myspan' + 3
|
const ref3 = 'myspan' + 3
|
||||||
p2.hook = {
|
p2.hook = {
|
||||||
create: (_, n) => context.refs[ref3] = n.elm,
|
create: (_, n) => {
|
||||||
};
|
context.refs[ref3] = n.elm;
|
||||||
|
},
|
||||||
|
};
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -1366,15 +1379,17 @@ exports[`t-ref can get a ref on a node 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
const ref3 = 'myspan'
|
const ref3 = 'myspan'
|
||||||
p2.hook = {
|
p2.hook = {
|
||||||
create: (_, n) => context.refs[ref3] = n.elm,
|
create: (_, n) => {
|
||||||
};
|
context.refs[ref3] = n.elm;
|
||||||
|
},
|
||||||
|
};
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -1384,7 +1399,7 @@ exports[`t-ref refs in a loop 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key: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')}
|
||||||
@@ -1398,12 +1413,14 @@ exports[`t-ref refs in a loop 1`] = `
|
|||||||
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];
|
||||||
var c5 = [], p5 = {key:context['item']};
|
let c5 = [], p5 = {key:context['item']};
|
||||||
var vn5 = h('div', p5, c5);
|
var vn5 = h('div', p5, c5);
|
||||||
c1.push(vn5);
|
c1.push(vn5);
|
||||||
const ref6 = context['item']
|
const ref6 = context['item']
|
||||||
p5.hook = {
|
p5.hook = {
|
||||||
create: (_, n) => context.refs[ref6] = n.elm,
|
create: (_, n) => {
|
||||||
|
context.refs[ref6] = n.elm;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
var _7 = context['item'];
|
var _7 = context['item'];
|
||||||
if (_7 || _7 === 0) {
|
if (_7 || _7 === 0) {
|
||||||
@@ -1418,7 +1435,7 @@ exports[`t-set evaluate value expression 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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) {
|
||||||
@@ -1432,7 +1449,7 @@ exports[`t-set evaluate value expression, part 2 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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) {
|
||||||
@@ -1446,7 +1463,7 @@ exports[`t-set set from attribute literal 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var _2 = 'ok';
|
var _2 = 'ok';
|
||||||
if (_2 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -1460,7 +1477,7 @@ exports[`t-set set from attribute lookup 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -1483,7 +1500,7 @@ exports[`t-set set from body lookup 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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 || _2 === 0) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -1497,7 +1514,7 @@ exports[`t-set set from empty body 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -1507,7 +1524,7 @@ exports[`t-set t-set and t-if 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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') {
|
||||||
@@ -1521,7 +1538,7 @@ exports[`t-set t-set evaluates an expression only once 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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) {
|
||||||
@@ -1539,7 +1556,7 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
|
|||||||
) {
|
) {
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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;
|
||||||
var _3 = context['list'];
|
var _3 = context['list'];
|
||||||
@@ -1554,10 +1571,10 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
|
|||||||
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];
|
||||||
var c6 = [], p6 = {key:context['elem_index']};
|
let c6 = [], p6 = {key:context['elem_index']};
|
||||||
var vn6 = h('div', p6, c6);
|
var vn6 = h('div', p6, c6);
|
||||||
c1.push(vn6);
|
c1.push(vn6);
|
||||||
var c7 = [], p7 = {key:7};
|
let c7 = [], p7 = {key:7};
|
||||||
var vn7 = h('span', p7, c7);
|
var vn7 = h('span', p7, c7);
|
||||||
c6.push(vn7);
|
c6.push(vn7);
|
||||||
c7.push({text: \`v\`});
|
c7.push({text: \`v\`});
|
||||||
@@ -1574,7 +1591,7 @@ exports[`t-set value priority 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var 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) {
|
if (_2 || _2 === 0) {
|
||||||
@@ -1588,7 +1605,7 @@ exports[`whitespace handling consecutives whitespaces are condensed into a singl
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
c1.push({text: \` abc \`});
|
c1.push({text: \` abc \`});
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -1599,7 +1616,7 @@ exports[`whitespace handling nothing is done in pre tags 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('pre', p1, c1);
|
var vn1 = h('pre', p1, c1);
|
||||||
c1.push({text: \` \`});
|
c1.push({text: \` \`});
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -1610,7 +1627,7 @@ exports[`whitespace handling nothing is done in pre tags 2`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('pre', p1, c1);
|
var vn1 = h('pre', p1, c1);
|
||||||
c1.push({text: \`
|
c1.push({text: \`
|
||||||
some text
|
some text
|
||||||
@@ -1623,7 +1640,7 @@ exports[`whitespace handling nothing is done in pre tags 3`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('pre', p1, c1);
|
var vn1 = h('pre', p1, c1);
|
||||||
c1.push({text: \`
|
c1.push({text: \`
|
||||||
|
|
||||||
@@ -1636,7 +1653,7 @@ exports[`whitespace handling white space only text nodes are condensed into a si
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
c1.push({text: \` \`});
|
c1.push({text: \` \`});
|
||||||
return vn1;
|
return vn1;
|
||||||
@@ -1647,9 +1664,9 @@ exports[`whitespace handling whitespace only text nodes with newlines are remove
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
var c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
var c2 = [], p2 = {key:2};
|
let c2 = [], p2 = {key:2};
|
||||||
var vn2 = h('span', p2, c2);
|
var vn2 = h('span', p2, c2);
|
||||||
c1.push(vn2);
|
c1.push(vn2);
|
||||||
c2.push({text: \`abc\`});
|
c2.push({text: \`abc\`});
|
||||||
|
|||||||
+506
-191
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -37,7 +37,7 @@ export function makeDeferred(): Deferred {
|
|||||||
return <Deferred>def;
|
return <Deferred>def;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeTestWEnv(): Env {
|
export function makeTestEnv(): Env {
|
||||||
return {
|
return {
|
||||||
qweb: new QWeb()
|
qweb: new QWeb()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -126,12 +126,12 @@ describe("observer", () => {
|
|||||||
arr.push({ kriek: 5 });
|
arr.push({ kriek: 5 });
|
||||||
expect(observer.rev).toBe(2);
|
expect(observer.rev).toBe(2);
|
||||||
expect(arr.__owl__.rev).toBe(2);
|
expect(arr.__owl__.rev).toBe(2);
|
||||||
expect(arr[0].__owl__.rev).toBe(1);
|
expect(arr[0].__owl__.rev).toBe(2);
|
||||||
|
|
||||||
arr[0].kriek = 6;
|
arr[0].kriek = 6;
|
||||||
expect(observer.rev).toBe(3);
|
expect(observer.rev).toBe(3);
|
||||||
expect(arr.__owl__.rev).toBe(2);
|
expect(arr.__owl__.rev).toBe(2);
|
||||||
expect(arr[0].__owl__.rev).toBe(2);
|
expect(arr[0].__owl__.rev).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("properly observe arrays in object", () => {
|
test("properly observe arrays in object", () => {
|
||||||
@@ -180,12 +180,14 @@ describe("observer", () => {
|
|||||||
obj.a = { b: 2 };
|
obj.a = { b: 2 };
|
||||||
expect(observer.rev).toBe(2);
|
expect(observer.rev).toBe(2);
|
||||||
expect(obj.__owl__.rev).toBe(2);
|
expect(obj.__owl__.rev).toBe(2);
|
||||||
expect(obj.a.__owl__.rev).toBe(1);
|
|
||||||
|
// we start at 2 because it is the new observer rev number
|
||||||
|
expect(obj.a.__owl__.rev).toBe(2);
|
||||||
|
|
||||||
obj.a.b = 3;
|
obj.a.b = 3;
|
||||||
expect(observer.rev).toBe(3);
|
expect(observer.rev).toBe(3);
|
||||||
expect(obj.__owl__.rev).toBe(2);
|
expect(obj.__owl__.rev).toBe(2);
|
||||||
expect(obj.a.__owl__.rev).toBe(2);
|
expect(obj.a.__owl__.rev).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("deep observe misc changes", () => {
|
test("deep observe misc changes", () => {
|
||||||
|
|||||||
+16
-8
@@ -100,7 +100,7 @@ describe("error handling", () => {
|
|||||||
|
|
||||||
test("loadTemplates throw if parser error", () => {
|
test("loadTemplates throw if parser error", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
qweb.loadTemplates("<templates><abc>></templates>");
|
qweb.addTemplates("<templates><abc>></templates>");
|
||||||
}).toThrow("Invalid XML in template");
|
}).toThrow("Invalid XML in template");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -511,6 +511,12 @@ describe("attributes", () => {
|
|||||||
expect(result).toBe(`<div foo="42"></div>`);
|
expect(result).toBe(`<div foo="42"></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("format expression, other format", () => {
|
||||||
|
qweb.addTemplate("test", `<div t-attf-foo="#{value + 37}"/>`);
|
||||||
|
const result = renderToString(qweb, "test", { value: 5 });
|
||||||
|
expect(result).toBe(`<div foo="42"></div>`);
|
||||||
|
});
|
||||||
|
|
||||||
test("format multiple", () => {
|
test("format multiple", () => {
|
||||||
qweb.addTemplate(
|
qweb.addTemplate(
|
||||||
"test",
|
"test",
|
||||||
@@ -748,7 +754,6 @@ describe("foreach", () => {
|
|||||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||||
console.warn = consoleWarn;
|
console.warn = consoleWarn;
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("misc", () => {
|
describe("misc", () => {
|
||||||
@@ -982,14 +987,17 @@ describe("t-ref", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("refs in a loop", () => {
|
test("refs in a loop", () => {
|
||||||
qweb.addTemplate("test", `
|
qweb.addTemplate(
|
||||||
|
"test",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-foreach="items" t-as="item">
|
<t t-foreach="items" t-as="item">
|
||||||
<div t-ref="item" t-key="item"><t t-esc="item"/></div>
|
<div t-ref="item" t-key="item"><t t-esc="item"/></div>
|
||||||
</t>
|
</t>
|
||||||
</div>`);
|
</div>`
|
||||||
|
);
|
||||||
let refs: any = {};
|
let refs: any = {};
|
||||||
renderToDOM(qweb, "test", { refs, items: [1,2,3] });
|
renderToDOM(qweb, "test", { refs, items: [1, 2, 3] });
|
||||||
expect(Object.keys(refs)).toEqual(["1", "2", "3"]);
|
expect(Object.keys(refs)).toEqual(["1", "2", "3"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1014,15 +1022,15 @@ describe("loading templates", () => {
|
|||||||
|
|
||||||
<ul t-name="main"><t t-call="items"/></ul>
|
<ul t-name="main"><t t-call="items"/></ul>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
qweb.loadTemplates(data);
|
qweb.addTemplates(data);
|
||||||
const result = renderToString(qweb, "main");
|
const result = renderToString(qweb, "main");
|
||||||
expect(result).toBe("<ul><li>ok</li><li>foo</li></ul>");
|
expect(result).toBe("<ul><li>ok</li><li>foo</li></ul>");
|
||||||
});
|
});
|
||||||
|
|
||||||
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.loadTemplates(data);
|
qweb.addTemplates(data);
|
||||||
expect(qweb.templates).toEqual({});
|
expect(Object.keys(qweb.templates)).toEqual(["default"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+115
-86
@@ -2,7 +2,7 @@ import { Component, Env } from "../src/component";
|
|||||||
import { connect, Store } from "../src/store";
|
import { connect, Store } from "../src/store";
|
||||||
import {
|
import {
|
||||||
makeTestFixture,
|
makeTestFixture,
|
||||||
makeTestWEnv,
|
makeTestEnv,
|
||||||
nextMicroTick,
|
nextMicroTick,
|
||||||
nextTick
|
nextTick
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
@@ -176,15 +176,15 @@ describe("basic use", () => {
|
|||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "bertinchamps",
|
name: "bertinchamps",
|
||||||
tasterID: 1,
|
tasterID: 1
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
tasters: {
|
tasters: {
|
||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'aaron',
|
name: "aaron"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
beerTasterName({ state }, beerID) {
|
beerTasterName({ state }, beerID) {
|
||||||
@@ -207,15 +207,15 @@ describe("basic use", () => {
|
|||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "bertinchamps",
|
name: "bertinchamps",
|
||||||
tasterID: 1,
|
tasterID: 1
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
tasters: {
|
tasters: {
|
||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'aaron',
|
name: "aaron"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
beerTasterName({ state }, beerID) {
|
beerTasterName({ state }, beerID) {
|
||||||
@@ -243,15 +243,15 @@ describe("basic use", () => {
|
|||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "bertinchamps",
|
name: "bertinchamps",
|
||||||
tasterID: 1,
|
tasterID: 1
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
tasters: {
|
tasters: {
|
||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'aaron',
|
name: "aaron"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
beerTasterName({ state }, beerID) {
|
beerTasterName({ state }, beerID) {
|
||||||
@@ -278,15 +278,15 @@ describe("basic use", () => {
|
|||||||
return `${getters.b()}${getters.c(1)}`;
|
return `${getters.b()}${getters.c(1)}`;
|
||||||
},
|
},
|
||||||
b() {
|
b() {
|
||||||
return 'b';
|
return "b";
|
||||||
},
|
},
|
||||||
c({}, i) {
|
c({}, i) {
|
||||||
return `c${i}`;
|
return `c${i}`;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ getters });
|
const store = new Store({ getters });
|
||||||
|
|
||||||
expect(store.getters.a()).toBe('bc1');
|
expect(store.getters.a()).toBe("bc1");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -401,27 +401,28 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = makeTestFixture();
|
fixture = makeTestFixture();
|
||||||
env = makeTestWEnv();
|
env = makeTestEnv();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
fixture.remove();
|
fixture.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
class App extends Component<any, any, any> {
|
test("connecting a component works", async () => {
|
||||||
inlineTemplate = `
|
env.qweb.addTemplate(
|
||||||
|
"App",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-foreach="props.todos" t-as="todo" t-key="todo">
|
<t t-foreach="props.todos" t-as="todo" t-key="todo">
|
||||||
<t t-widget="Todo" t-props="todo"/>
|
<t t-widget="Todo" t-props="todo"/>
|
||||||
</t>
|
</t>
|
||||||
</div>`;
|
</div>`
|
||||||
widgets = { Todo };
|
);
|
||||||
}
|
env.qweb.addTemplate("Todo", `<span><t t-esc="props.msg"/></span>`);
|
||||||
class Todo extends Component<any, any, any> {
|
class App extends Component<any, any, any> {
|
||||||
inlineTemplate = `<span><t t-esc="props.msg"/></span>`;
|
widgets = { Todo };
|
||||||
}
|
}
|
||||||
|
class Todo extends Component<any, any, any> {}
|
||||||
test("connecting a component works", async () => {
|
|
||||||
const state = { todos: [] };
|
const state = { todos: [] };
|
||||||
const mutations = {
|
const mutations = {
|
||||||
addTodo({ state }, msg) {
|
addTodo({ state }, msg) {
|
||||||
@@ -456,14 +457,16 @@ describe("connecting a component to store", () => {
|
|||||||
}
|
}
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, mutations });
|
||||||
|
|
||||||
class App extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `
|
"App",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<span t-foreach="props.todos" t-as="todo" t-key="todo">
|
<span t-foreach="props.todos" t-as="todo" t-key="todo">
|
||||||
<t t-esc="todo.title"/>
|
<t t-esc="todo.title"/>
|
||||||
</span>
|
</span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class App extends Component<any, any, any> {}
|
||||||
|
|
||||||
const DeepTodoApp = connect(
|
const DeepTodoApp = connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
@@ -493,8 +496,8 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
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/>`);
|
||||||
class Child extends Component<any, any, any> {
|
class Child extends Component<any, any, any> {
|
||||||
inlineTemplate = `<div/>`;
|
|
||||||
mounted() {
|
mounted() {
|
||||||
steps.push("child:mounted");
|
steps.push("child:mounted");
|
||||||
}
|
}
|
||||||
@@ -505,11 +508,14 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
const ConnectedChild = connect(s => s)(Child);
|
const ConnectedChild = connect(s => s)(Child);
|
||||||
|
|
||||||
class Parent extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `
|
"Parent",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-if="state.child" t-widget="ConnectedChild"/>
|
<t t-if="state.child" t-widget="ConnectedChild"/>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class Parent extends Component<any, any, any> {
|
||||||
widgets = { ConnectedChild };
|
widgets = { ConnectedChild };
|
||||||
|
|
||||||
constructor(env: Env) {
|
constructor(env: Env) {
|
||||||
@@ -540,20 +546,22 @@ describe("connecting a component to store", () => {
|
|||||||
};
|
};
|
||||||
const store = new Store({ state, mutations });
|
const store = new Store({ state, mutations });
|
||||||
|
|
||||||
class TodoItem extends Component<any, any, any> {
|
env.qweb.addTemplate("TodoItem", `<span><t t-esc="props.text"/></span>`);
|
||||||
inlineTemplate = `<span><t t-esc="props.text"/></span>`;
|
class TodoItem extends Component<any, any, any> {}
|
||||||
}
|
|
||||||
const ConnectedTodo = connect((state, props) => {
|
const ConnectedTodo = connect((state, props) => {
|
||||||
const todo = state.todos.find(t => t.id === props.id);
|
const todo = state.todos.find(t => t.id === props.id);
|
||||||
return todo;
|
return todo;
|
||||||
})(TodoItem);
|
})(TodoItem);
|
||||||
|
|
||||||
class TodoList extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"TodoList",
|
||||||
|
`<div>
|
||||||
<t t-foreach="props.todos" t-as="todo">
|
<t t-foreach="props.todos" t-as="todo">
|
||||||
<t t-widget="ConnectedTodo" t-props="todo"/>
|
<t t-widget="ConnectedTodo" t-props="todo"/>
|
||||||
</t>
|
</t>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class TodoList extends Component<any, any, any> {
|
||||||
widgets = { ConnectedTodo };
|
widgets = { ConnectedTodo };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,10 +586,7 @@ describe("connecting a component to store", () => {
|
|||||||
test("connect receives store getters as third argument", async () => {
|
test("connect receives store getters as third argument", async () => {
|
||||||
const state = {
|
const state = {
|
||||||
importantID: 1,
|
importantID: 1,
|
||||||
todos: [
|
todos: [{ id: 1, text: "jupiler" }, { id: 2, text: "bertinchamps" }]
|
||||||
{ id: 1, text: "jupiler" },
|
|
||||||
{ id: 2, text: "bertinchamps" },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
importantTodoText({ state }) {
|
importantTodoText({ state }) {
|
||||||
@@ -589,30 +594,35 @@ describe("connecting a component to store", () => {
|
|||||||
},
|
},
|
||||||
text({ state }, id) {
|
text({ state }, id) {
|
||||||
return state.todos.find(todo => todo.id === id).text;
|
return state.todos.find(todo => todo.id === id).text;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
const store = new Store({ state, getters });
|
const store = new Store({ state, getters });
|
||||||
|
|
||||||
class TodoItem extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"TodoItem",
|
||||||
|
`<div>
|
||||||
<span><t t-esc="props.activeTodoText"/></span>
|
<span><t t-esc="props.activeTodoText"/></span>
|
||||||
<span><t t-esc="props.importantTodoText"/></span>
|
<span><t t-esc="props.importantTodoText"/></span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class TodoItem extends Component<any, any, any> {}
|
||||||
const ConnectedTodo = connect((state, props, getters) => {
|
const ConnectedTodo = connect((state, props, getters) => {
|
||||||
const todo = state.todos.find(t => t.id === props.id);
|
const todo = state.todos.find(t => t.id === props.id);
|
||||||
return {
|
return {
|
||||||
activeTodoText: getters.text(todo.id),
|
activeTodoText: getters.text(todo.id),
|
||||||
importantTodoText: getters.importantTodoText(),
|
importantTodoText: getters.importantTodoText()
|
||||||
};
|
};
|
||||||
})(TodoItem);
|
})(TodoItem);
|
||||||
|
|
||||||
class TodoList extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"TodoList",
|
||||||
|
`<div>
|
||||||
<t t-foreach="props.todos" t-as="todo">
|
<t t-foreach="props.todos" t-as="todo">
|
||||||
<t t-widget="ConnectedTodo" t-props="todo"/>
|
<t t-widget="ConnectedTodo" t-props="todo"/>
|
||||||
</t>
|
</t>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class TodoList extends Component<any, any, any> {
|
||||||
widgets = { ConnectedTodo };
|
widgets = { ConnectedTodo };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,21 +635,25 @@ describe("connecting a component to store", () => {
|
|||||||
const app = new ConnectedTodoList(env);
|
const app = new ConnectedTodoList(env);
|
||||||
|
|
||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>jupiler</span><span>jupiler</span></div><div><span>bertinchamps</span><span>jupiler</span></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>jupiler</span><span>jupiler</span></div><div><span>bertinchamps</span><span>jupiler</span></div></div>"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("connected component is updated when props are updated", async () => {
|
test("connected component is updated when props are updated", async () => {
|
||||||
class Beer extends Component<any, any, any> {
|
env.qweb.addTemplate("Beer", `<span><t t-esc="props.name"/></span>`);
|
||||||
inlineTemplate = `<span><t t-esc="props.name"/></span>`;
|
class Beer extends Component<any, any, any> {}
|
||||||
}
|
|
||||||
const ConnectedBeer = connect((state, props) => {
|
const ConnectedBeer = connect((state, props) => {
|
||||||
return state.beers[props.id];
|
return state.beers[props.id];
|
||||||
})(Beer);
|
})(Beer);
|
||||||
|
|
||||||
|
env.qweb.addTemplate(
|
||||||
|
"App",
|
||||||
|
`<div>
|
||||||
|
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
class App extends Component<any, any, any> {
|
class App extends Component<any, any, any> {
|
||||||
inlineTemplate = `<div>
|
|
||||||
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
|
||||||
</div>`;
|
|
||||||
widgets = { ConnectedBeer };
|
widgets = { ConnectedBeer };
|
||||||
state = { beerId: 1 };
|
state = { beerId: 1 };
|
||||||
}
|
}
|
||||||
@@ -658,12 +672,14 @@ describe("connecting a component to store", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("connected component is updated when store is changed", async () => {
|
test("connected component is updated when store is changed", async () => {
|
||||||
class App extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `
|
"App",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<span t-foreach="props.beers" t-as="beer" t-key="beer.name"><t t-esc="beer.name"/></span>
|
<span t-foreach="props.beers" t-as="beer" t-key="beer.name"><t t-esc="beer.name"/></span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class App extends Component<any, any, any> {}
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
addBeer({ state }, name) {
|
addBeer({ state }, name) {
|
||||||
@@ -692,13 +708,15 @@ describe("connecting a component to store", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("connected component with undefined, null and string props", async () => {
|
test("connected component with undefined, null and string props", async () => {
|
||||||
class Beer extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"Beer",
|
||||||
|
`<div>
|
||||||
<span>taster:<t t-esc="props.taster"/></span>
|
<span>taster:<t t-esc="props.taster"/></span>
|
||||||
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span>
|
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span>
|
||||||
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span>
|
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class Beer extends Component<any, any, any> {}
|
||||||
const ConnectedBeer = connect((state, props) => {
|
const ConnectedBeer = connect((state, props) => {
|
||||||
return {
|
return {
|
||||||
selected: state.beers[props.id],
|
selected: state.beers[props.id],
|
||||||
@@ -707,10 +725,13 @@ describe("connecting a component to store", () => {
|
|||||||
};
|
};
|
||||||
})(Beer);
|
})(Beer);
|
||||||
|
|
||||||
class App extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"App",
|
||||||
|
`<div>
|
||||||
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class App extends Component<any, any, any> {
|
||||||
widgets = { ConnectedBeer };
|
widgets = { ConnectedBeer };
|
||||||
state = { beerId: 0 };
|
state = { beerId: 0 };
|
||||||
}
|
}
|
||||||
@@ -756,13 +777,15 @@ describe("connecting a component to store", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("connected component deeply reactive with undefined, null and string props", async () => {
|
test("connected component deeply reactive with undefined, null and string props", async () => {
|
||||||
class Beer extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"Beer",
|
||||||
|
`<div>
|
||||||
<span>taster:<t t-esc="props.taster"/></span>
|
<span>taster:<t t-esc="props.taster"/></span>
|
||||||
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span>
|
<span t-if="props.selected">selected:<t t-esc="props.selected.name"/></span>
|
||||||
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span>
|
<span t-if="props.consumed">consumed:<t t-esc="props.consumed.name"/></span>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
);
|
||||||
|
class Beer extends Component<any, any, any> {}
|
||||||
const ConnectedBeer = connect((state, props) => {
|
const ConnectedBeer = connect((state, props) => {
|
||||||
return {
|
return {
|
||||||
selected: state.beers[props.id],
|
selected: state.beers[props.id],
|
||||||
@@ -771,10 +794,13 @@ describe("connecting a component to store", () => {
|
|||||||
};
|
};
|
||||||
})(Beer);
|
})(Beer);
|
||||||
|
|
||||||
class App extends Component<any, any, any> {
|
env.qweb.addTemplate(
|
||||||
inlineTemplate = `<div>
|
"App",
|
||||||
|
`<div>
|
||||||
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
<t t-widget="ConnectedBeer" t-props="{id: state.beerId}"/>
|
||||||
</div>`;
|
</div>`
|
||||||
|
);
|
||||||
|
class App extends Component<any, any, any> {
|
||||||
widgets = { ConnectedBeer };
|
widgets = { ConnectedBeer };
|
||||||
state = { beerId: 0 };
|
state = { beerId: 0 };
|
||||||
}
|
}
|
||||||
@@ -845,12 +871,16 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
test("correct update order when parent/children are connected", async () => {
|
test("correct update order when parent/children are connected", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
class Parent extends Component<any, any, any> {
|
|
||||||
inlineTemplate = `
|
env.qweb.addTemplate(
|
||||||
|
"Parent",
|
||||||
|
`
|
||||||
<div>
|
<div>
|
||||||
<t t-widget="Child" t-ref="'child'" t-props="{key: props.current}"/>
|
<t t-widget="Child" t-ref="'child'" t-props="{key: props.current}"/>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
|
);
|
||||||
|
class Parent extends Component<any, any, any> {
|
||||||
widgets = { Child: ConnectedChild };
|
widgets = { Child: ConnectedChild };
|
||||||
}
|
}
|
||||||
const ConnectedParent = connect(function(s) {
|
const ConnectedParent = connect(function(s) {
|
||||||
@@ -858,9 +888,8 @@ describe("connecting a component to store", () => {
|
|||||||
return { current: s.current, isvisible: s.isvisible };
|
return { current: s.current, isvisible: s.isvisible };
|
||||||
})(Parent);
|
})(Parent);
|
||||||
|
|
||||||
class Child extends Component<any, any, any> {
|
env.qweb.addTemplate("Child", `<span><t t-esc="props.msg"/></span>`);
|
||||||
inlineTemplate = `<span><t t-esc="props.msg"/></span>`;
|
class Child extends Component<any, any, any> {}
|
||||||
}
|
|
||||||
|
|
||||||
const ConnectedChild = connect(function(s, props) {
|
const ConnectedChild = connect(function(s, props) {
|
||||||
steps.push("child");
|
steps.push("child");
|
||||||
@@ -889,8 +918,8 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
test("connected component willpatch/patch hooks are called on store updates", async () => {
|
test("connected component willpatch/patch hooks are called on store updates", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
|
env.qweb.addTemplate("App", `<div><t t-esc="props.msg"/></div>`);
|
||||||
class App extends Component<any, any, any> {
|
class App extends Component<any, any, any> {
|
||||||
inlineTemplate = `<div><t t-esc="props.msg"/></div>`;
|
|
||||||
willPatch() {
|
willPatch() {
|
||||||
steps.push("willpatch");
|
steps.push("willpatch");
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-80
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import { escape, debounce } from "../src/utils";
|
||||||
escape,
|
|
||||||
memoize,
|
|
||||||
debounce,
|
|
||||||
patch,
|
|
||||||
unpatch
|
|
||||||
} from "../src/utils";
|
|
||||||
|
|
||||||
describe("escape", () => {
|
describe("escape", () => {
|
||||||
test("normal strings", () => {
|
test("normal strings", () => {
|
||||||
@@ -18,26 +12,6 @@ describe("escape", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("memoize", () => {
|
|
||||||
test("return correct value", () => {
|
|
||||||
const f = memoize((a, b) => a + b);
|
|
||||||
expect(f(1, 3)).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("does not recompute if not needed", () => {
|
|
||||||
let nCalls = 0;
|
|
||||||
function origFunction(a: number, b: number): number {
|
|
||||||
nCalls++;
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
const memoized = memoize(origFunction);
|
|
||||||
|
|
||||||
expect(memoized(1, 3)).toBe(4);
|
|
||||||
expect(memoized(1, 3)).toBe(4);
|
|
||||||
expect(nCalls).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("debounce", () => {
|
describe("debounce", () => {
|
||||||
test("works as expected", () => {
|
test("works as expected", () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
@@ -54,56 +28,3 @@ describe("debounce", () => {
|
|||||||
expect(n).toBe(1);
|
expect(n).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("patch/unpatch", () => {
|
|
||||||
test("can monkey patch a class", () => {
|
|
||||||
class Test {
|
|
||||||
n = 1;
|
|
||||||
|
|
||||||
doSomething(): string {
|
|
||||||
return "hey";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
patch(Test, "some_custo", {
|
|
||||||
doSomething(): string {
|
|
||||||
this.n = this.n + 1;
|
|
||||||
return this._super();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const t = new Test();
|
|
||||||
expect(t.n).toBe(1);
|
|
||||||
expect(t.doSomething()).toBe("hey");
|
|
||||||
expect(t.n).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("cannot patch a class twice with same patch name", () => {
|
|
||||||
class Test {}
|
|
||||||
|
|
||||||
patch(Test, "some_custo", {});
|
|
||||||
expect(() => {
|
|
||||||
patch(Test, "some_custo", {});
|
|
||||||
}).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("can unpatch a class", () => {
|
|
||||||
class Test {
|
|
||||||
doSomething(): number {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
patch(Test, "some_custo", {
|
|
||||||
doSomething(): number {
|
|
||||||
return this._super() + 2;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const t = new Test();
|
|
||||||
expect(t.doSomething()).toBe(3);
|
|
||||||
|
|
||||||
unpatch(Test, "some_custo");
|
|
||||||
expect(t.doSomething()).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user