mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[DOC] improve the documentation
closes #340 closes #344 closes #352 closes #355
This commit is contained in:
+15
-13
@@ -97,7 +97,7 @@ state does nothing more:
|
|||||||
```js
|
```js
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
|
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
|
||||||
state = {value: 0};
|
state = { value: 0 };
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
this.state.value++;
|
this.state.value++;
|
||||||
@@ -131,7 +131,7 @@ const { useState } = owl.hooks;
|
|||||||
|
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
|
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
|
||||||
state = useState({value: 0});
|
state = useState({ value: 0 });
|
||||||
|
|
||||||
increment() {
|
increment() {
|
||||||
this.state.value++;
|
this.state.value++;
|
||||||
@@ -140,6 +140,7 @@ class Counter extends Component {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Obviously, we can call the `useState` hook more than once:
|
Obviously, we can call the `useState` hook more than once:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { useState } = owl.hooks;
|
const { useState } = owl.hooks;
|
||||||
|
|
||||||
@@ -149,8 +150,8 @@ class Counter extends Component {
|
|||||||
<span t-on-click="increment(counter1)"><t t-esc="counter1.value"/></span>
|
<span t-on-click="increment(counter1)"><t t-esc="counter1.value"/></span>
|
||||||
<span t-on-click="increment(counter2)"><t t-esc="counter2.value"/></span>
|
<span t-on-click="increment(counter2)"><t t-esc="counter2.value"/></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
counter1 = useState({value: 0});
|
counter1 = useState({ value: 0 });
|
||||||
counter2 = useState({value: 0});
|
counter2 = useState({ value: 0 });
|
||||||
|
|
||||||
increment(counter) {
|
increment(counter) {
|
||||||
counter.value++;
|
counter.value++;
|
||||||
@@ -405,8 +406,8 @@ component was patched. Note that this hook will not be called if the compoent is
|
|||||||
not in the DOM (this can happen with components with `t-keepalive`).
|
not in the DOM (this can happen with components with `t-keepalive`).
|
||||||
|
|
||||||
Updating the component state in this hook is possible, but not encouraged.
|
Updating the component state in this hook is possible, but not encouraged.
|
||||||
One need to be careful, because updates here will cause rerender, which in
|
One needs to be careful, because updates here will create an additional rendering, which in
|
||||||
turn will cause other calls to patched. So, we need to be particularly
|
turn will cause other calls to the `patched` method. So, we need to be particularly
|
||||||
careful at avoiding endless cycles.
|
careful at avoiding endless cycles.
|
||||||
|
|
||||||
#### `willUnmount()`
|
#### `willUnmount()`
|
||||||
@@ -429,7 +430,7 @@ This is the opposite method of `mounted`.
|
|||||||
|
|
||||||
The `catchError` method is useful when we need to intercept and properly react
|
The `catchError` method is useful when we need to intercept and properly react
|
||||||
to (rendering) errors that occur in some sub components. See the section on
|
to (rendering) errors that occur in some sub components. See the section on
|
||||||
[error handling](#error-handling)
|
[error handling](#error-handling).
|
||||||
|
|
||||||
### Root Component
|
### Root Component
|
||||||
|
|
||||||
@@ -783,7 +784,7 @@ Like event handling, the `t-model` directive accepts some modifiers:
|
|||||||
| Modifier | Description |
|
| Modifier | Description |
|
||||||
| --------- | -------------------------------------------------------------------- |
|
| --------- | -------------------------------------------------------------------- |
|
||||||
| `.lazy` | update the value on the `change` event (default is on `input` event) |
|
| `.lazy` | update the value on the `change` event (default is on `input` event) |
|
||||||
| `.number` | tries to parse the value to a number (using `parseFloat`) |
|
| `.number` | try to parse the value to a number (using `parseFloat`) |
|
||||||
| `.trim` | trim the resulting value |
|
| `.trim` | trim the resulting value |
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
@@ -799,9 +800,10 @@ Note: the online playground has an example to show how it works.
|
|||||||
|
|
||||||
### `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, the DOM does not fully
|
||||||
locked inside the DOM: for example, the scrolling state, the current user selection,
|
expose its state declaratively in the DOM tree. For example, the scrolling state,
|
||||||
the focused element or the state of an input. This is why we use a virtual dom
|
the current user selection, the focused element or the state of an input are not
|
||||||
|
set as attribute in the DOM tree. 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.
|
||||||
@@ -913,7 +915,7 @@ Here is what Owl will do:
|
|||||||
As an application becomes complex, it may be quite unsafe to define props in an informal way. This leads to two issues:
|
As an application becomes complex, it may be quite unsafe to define props in an informal way. This leads to two issues:
|
||||||
|
|
||||||
- hard to tell how a component should be used, by looking at its code.
|
- hard to tell how a component should be used, by looking at its code.
|
||||||
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parent.
|
- unsafe, it is easy to send wrong props into a component, either by refactoring a component, or one of its parents.
|
||||||
|
|
||||||
A props type system would solve both issues, by describing the types and shapes
|
A props type system would solve both issues, by describing the types and shapes
|
||||||
of the props. Here is how it works in Owl:
|
of the props. Here is how it works in Owl:
|
||||||
@@ -1169,7 +1171,7 @@ from lifecycle hooks): the `catchError` hook.
|
|||||||
|
|
||||||
Whenever the `catchError` lifecycle hook is implemented, all errors coming from
|
Whenever the `catchError` lifecycle hook is implemented, all errors coming from
|
||||||
sub components rendering and/or lifecycle method calls will be caught and given
|
sub components rendering and/or lifecycle method calls will be caught and given
|
||||||
to the `catchError` method. This allow us to properly handle the error, and to
|
to the `catchError` method. This allows us to properly handle the error, and to
|
||||||
not break the application.
|
not break the application.
|
||||||
|
|
||||||
For example, here is how we could implement an `ErrorBoundary` component:
|
For example, here is how we could implement an `ErrorBoundary` component:
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The `Context` object provides a way to share data between an arbitrary number
|
The `Context` object provides a way to share data between an arbitrary number
|
||||||
of component. Usually, data is passed from a parent to its children component,
|
of components. Usually, data is passed from a parent to its children component,
|
||||||
but when we have to deal with some mostly global information, this can be
|
but when we have to deal with some mostly global information, this can be
|
||||||
annoying, since each component will need to pass the information to each children,
|
annoying, since each component will need to pass the information to each children,
|
||||||
even though some or most of them will not use the information.
|
even though some or most of them will not use the information.
|
||||||
|
|||||||
+3
-3
@@ -374,7 +374,7 @@ not the solution to every problem.
|
|||||||
class A extends Component {
|
class A extends Component {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
useMySpecificHook()
|
useMySpecificHook();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ not the solution to every problem.
|
|||||||
class B extends Component {
|
class B extends Component {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
this.performSpecificTask()
|
this.performSpecificTask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -402,7 +402,7 @@ not the solution to every problem.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
As you can see, this does not *hook* into the internal of the component. It
|
As you can see, this does not _hook_ into the internal of the component. It
|
||||||
simply returns a global object, which is difficult to mock.
|
simply returns a global object, which is difficult to mock.
|
||||||
|
|
||||||
A better way would be to do something like this: get the reference from the
|
A better way would be to do something like this: get the reference from the
|
||||||
|
|||||||
+38
-6
@@ -1,10 +1,16 @@
|
|||||||
# 🦉 Observer 🦉
|
# 🦉 Observer 🦉
|
||||||
|
|
||||||
Owl needs to be able to react to state changes. For example, whenever the state
|
Owl needs to be able to react to state changes. For example, whenever the state
|
||||||
of a component is changed, we need to rerender it. To help with that, we have
|
of a component is changed, Owl needs to rerender it. To help with that, there is
|
||||||
an Observer class. Its job is to observe some object state, and react to any
|
an Observer class. Its job is to observe the state of an object (or array), and
|
||||||
change. To do that, it recursively replaces all keys of the observed state by
|
to react to any change. The observer is implemented with the native `Proxy`
|
||||||
getters and setters.
|
object. Note that this means that it will not work on older browsers.
|
||||||
|
|
||||||
|
Note that the `Observer` is used by the `useState` and `useContext` hooks. This
|
||||||
|
is the way most Owl applications will create observers. For the majority of
|
||||||
|
use cases, there is no need to directly instantiate an observer.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
For example, this code will display `update` in the console:
|
For example, this code will display `update` in the console:
|
||||||
|
|
||||||
@@ -16,5 +22,31 @@ const obj = observer.observe({ a: { b: 1 } });
|
|||||||
obj.a.b = 2;
|
obj.a.b = 2;
|
||||||
```
|
```
|
||||||
|
|
||||||
The observer is implemented with the native `Proxy` object. Note that this
|
This example shows that an observer can observe nested properties.
|
||||||
means that it will not work on older browsers.
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
**observe** An observer can observe multiple values with the `observe` method.
|
||||||
|
This method takes an object or an array as its argument and will return a proxy
|
||||||
|
(which is mapped to the initial object/array). With this proxy, the observer
|
||||||
|
can detect whenever any internal value is changed.
|
||||||
|
|
||||||
|
**Registering a callback** Whenever an observer sees a state change, it will
|
||||||
|
call its `notifyCB` method. No additional information is given to the callback.
|
||||||
|
|
||||||
|
**deepRevNumber** Each observed value has an internal revision number, which
|
||||||
|
is incremented every time the value is observed. Sometimes, it can be useful
|
||||||
|
to obtain that number:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const observer = new owl.Observer();
|
||||||
|
const obj = observer.observe({ a: { b: 1 } });
|
||||||
|
|
||||||
|
observer.deepRevNumber(obj.a); // 1
|
||||||
|
obj.a.b = 2;
|
||||||
|
|
||||||
|
observer.deepRevNumber(obj.a); // 2
|
||||||
|
```
|
||||||
|
|
||||||
|
The `deepRevNumber` can also return 0, which indicates that the value is not
|
||||||
|
observed.
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ owl
|
|||||||
whenReady
|
whenReady
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Note that for convenience, the `useState` hook is also exported at the root of the `owl` object.
|
Note that for convenience, the `useState` hook is also exported at the root of the `owl` object.
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|||||||
+5
-7
@@ -38,14 +38,14 @@ const actions = {
|
|||||||
state.todos.push({
|
state.todos.push({
|
||||||
id: state.nextId++,
|
id: state.nextId++,
|
||||||
message,
|
message,
|
||||||
isCompleted: false,
|
isCompleted: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
todos: [],
|
todos: [],
|
||||||
nextId: 1,
|
nextId: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new owl.Store({ state, actions });
|
const store = new owl.Store({ state, actions });
|
||||||
@@ -113,6 +113,7 @@ const actions = {
|
|||||||
```
|
```
|
||||||
|
|
||||||
The first argument to an action method is an object with four keys:
|
The first argument to an action method is an object with four keys:
|
||||||
|
|
||||||
- `state`: the current state of the store content,
|
- `state`: the current state of the store content,
|
||||||
- `dispatch`: a function that can be used to dispatch other actions,
|
- `dispatch`: a function that can be used to dispatch other actions,
|
||||||
- `getters`: an object containing all getters defined in the store,
|
- `getters`: an object containing all getters defined in the store,
|
||||||
@@ -200,8 +201,7 @@ const post = store.getters.getPost(id);
|
|||||||
|
|
||||||
Getters take _at most_ one argument.
|
Getters take _at most_ one argument.
|
||||||
|
|
||||||
Note that getters are cached if they don't take any argument, or their argument
|
Note that getters are not cached.
|
||||||
is a string or a number.
|
|
||||||
|
|
||||||
### Connecting a Component
|
### Connecting a Component
|
||||||
|
|
||||||
@@ -212,7 +212,6 @@ can be done with the help of the three store hooks:
|
|||||||
- `useDispatch` to get a reference to a dispatch function,
|
- `useDispatch` to get a reference to a dispatch function,
|
||||||
- `useGetters` to get a reference to the getters defined in the store.
|
- `useGetters` to get a reference to the getters defined in the store.
|
||||||
|
|
||||||
|
|
||||||
Assume we have this store:
|
Assume we have this store:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@@ -223,7 +222,7 @@ const actions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
counter: {value: 0}
|
counter: { value: 0 }
|
||||||
};
|
};
|
||||||
const store = new owl.Store({ state, actions });
|
const store = new owl.Store({ state, actions });
|
||||||
```
|
```
|
||||||
@@ -256,7 +255,6 @@ two arguments:
|
|||||||
- optionally, an object with a `store` key (if we want to override the default
|
- optionally, an object with a `store` key (if we want to override the default
|
||||||
store) and an equality function (if we want to specialize the comparison).
|
store) and an equality function (if we want to specialize the comparison).
|
||||||
|
|
||||||
|
|
||||||
If the `useStore` callback selects a sub part of the store state, the component
|
If the `useStore` callback selects a sub part of the store state, the component
|
||||||
will only be rerendered whenever this part of the state changes. Otherwise, it
|
will only be rerendered whenever this part of the state changes. Otherwise, it
|
||||||
will perform a strict equality check and will update the component every time this
|
will perform a strict equality check and will update the component every time this
|
||||||
|
|||||||
+10
-10
@@ -12,7 +12,6 @@ functions are all available in the `owl.utils` namespace.
|
|||||||
- [`debounce`](#debounce): limiting rate of function calls
|
- [`debounce`](#debounce): limiting rate of function calls
|
||||||
- [`shallowEqual`](#shallowequal): shallow object comparison
|
- [`shallowEqual`](#shallowequal): shallow object comparison
|
||||||
|
|
||||||
|
|
||||||
## `whenReady`
|
## `whenReady`
|
||||||
|
|
||||||
The function `whenReady` returns a `Promise` resolved when the DOM is ready (if
|
The function `whenReady` returns a `Promise` resolved when the DOM is ready (if
|
||||||
@@ -20,7 +19,7 @@ not ready yet, resolved directly otherwise). If called with a callback as
|
|||||||
argument, it executes it as soon as the DOM ready (or directly).
|
argument, it executes it as soon as the DOM ready (or directly).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
Promise.all([loadFile('templates.xml'), owl.utils.whenReady()]).then(function([templates]) {
|
Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function([templates]) {
|
||||||
const qweb = new owl.QWeb(templates);
|
const qweb = new owl.QWeb(templates);
|
||||||
const app = new App({ qweb });
|
const app = new App({ qweb });
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
@@ -45,6 +44,7 @@ properly reacts when it is ready. Also, it is smart: it maintains a list of urls
|
|||||||
previously loaded (or currently being loaded), and prevent doing twice the work.
|
previously loaded (or currently being loaded), and prevent doing twice the work.
|
||||||
|
|
||||||
For example, it is useful for lazy loading external libraries:
|
For example, it is useful for lazy loading external libraries:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class MyComponent extends owl.Component {
|
class MyComponent extends owl.Component {
|
||||||
willStart() {
|
willStart() {
|
||||||
@@ -93,6 +93,7 @@ class BadComponent extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this case, the content of the `div` will be parsed as html, which may inject
|
In this case, the content of the `div` will be parsed as html, which may inject
|
||||||
unwanted behaviour. To fix this, the `escape` function will simply transform a
|
unwanted behaviour. To fix this, the `escape` function will simply transform a
|
||||||
string into an escaped version of the same string, which will be properly displayed
|
string into an escaped version of the same string, which will be properly displayed
|
||||||
@@ -101,10 +102,9 @@ escaped to the string: `"<ok>"`). So, the bad example above can be fixed
|
|||||||
with the following change:
|
with the following change:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
this.divRef.el.innerHTML = owl.utils.escape(this.state.value);
|
this.divRef.el.innerHTML = owl.utils.escape(this.state.value);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## `debounce`
|
## `debounce`
|
||||||
|
|
||||||
The `debounce` function is useful when we want to limit the number of times some
|
The `debounce` function is useful when we want to limit the number of times some
|
||||||
@@ -112,6 +112,7 @@ function/action is perfomed. For example, this may be useful to prevent issue
|
|||||||
with people double clicking on a button.
|
with people double clicking on a button.
|
||||||
|
|
||||||
It takes three arguments:
|
It takes three arguments:
|
||||||
|
|
||||||
- `func` (function): this is the function that will be rate limited
|
- `func` (function): this is the function that will be rate limited
|
||||||
- `wait` (number): this is the number of milliseconds that we want to use to
|
- `wait` (number): this is the number of milliseconds that we want to use to
|
||||||
rate limit the function `func`
|
rate limit the function `func`
|
||||||
@@ -122,21 +123,20 @@ It takes three arguments:
|
|||||||
It returns a function. For example:
|
It returns a function. For example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const debounce = owl.utils.debounce
|
const debounce = owl.utils.debounce;
|
||||||
window.addEventListener('mousemove', debounce(doSomething, 100));
|
window.addEventListener("mousemove", debounce(doSomething, 100));
|
||||||
```
|
```
|
||||||
|
|
||||||
As this example shows, it is usualy useful for event handlers which are triggered
|
As this example shows, it is usualy useful for event handlers which are triggered
|
||||||
very quickly, such as `scroll` or `mousemove` events.
|
very quickly, such as `scroll` or `mousemove` events.
|
||||||
|
|
||||||
|
|
||||||
## `shallowEqual`
|
## `shallowEqual`
|
||||||
|
|
||||||
This function checks if two objects have the same values assigned to each keys:
|
This function checks if two objects have the same values assigned to each keys:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
shallowEqual({a:1, b: 2}, {a:1, b:2}); // true
|
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
|
||||||
shallowEqual({a:1, b: 2}, {a:1, b:3}); // false
|
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 3 }); // false
|
||||||
```
|
```
|
||||||
|
|
||||||
However, for performance reasons, it assumes that the two objects have the same
|
However, for performance reasons, it assumes that the two objects have the same
|
||||||
@@ -144,5 +144,5 @@ keys. If we are in a situation where this is not guaranteed, the following code
|
|||||||
will work:
|
will work:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const completeShallowEqual = (a,b) => shallowEqual(a,b) && shallowEqual(b,a);
|
const completeShallowEqual = (a, b) => shallowEqual(a, b) && shallowEqual(b, a);
|
||||||
```
|
```
|
||||||
Reference in New Issue
Block a user