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:
+32
-30
@@ -91,22 +91,22 @@ find a template with the component name (or one of its ancestors).
|
||||
|
||||
### Reactive system
|
||||
|
||||
OWL components are normal javascript classes. So, changing a component internal
|
||||
OWL components are normal javascript classes. So, changing a component internal
|
||||
state does nothing more:
|
||||
|
||||
```js
|
||||
class Counter extends Component {
|
||||
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
|
||||
state = {value: 0};
|
||||
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
|
||||
state = { value: 0 };
|
||||
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clicking on the `Counter` component defined above will call the `increment`
|
||||
method, but it will not rerender the component. To fix that, one could add an
|
||||
method, but it will not rerender the component. To fix that, one could add an
|
||||
explicit call to `render` in `increment`:
|
||||
|
||||
```js
|
||||
@@ -122,7 +122,7 @@ method.
|
||||
|
||||
A better way is to use the reactive system: by using the `useState` hook (see the
|
||||
[hooks](hooks.md) section for more details), one can make Owl react to state
|
||||
changes. The `useState` hook generates a proxy version of an object
|
||||
changes. The `useState` hook generates a proxy version of an object
|
||||
(this is done by an [observer](observer.md)), which allows the component to
|
||||
react to any change. So, the `Counter` example above can be improved like this:
|
||||
|
||||
@@ -130,31 +130,32 @@ react to any change. So, the `Counter` example above can be improved like this:
|
||||
const { useState } = owl.hooks;
|
||||
|
||||
class Counter extends Component {
|
||||
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
|
||||
state = useState({value: 0});
|
||||
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
|
||||
state = useState({ value: 0 });
|
||||
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
increment() {
|
||||
this.state.value++;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Obviously, we can call the `useState` hook more than once:
|
||||
|
||||
```js
|
||||
const { useState } = owl.hooks;
|
||||
|
||||
class Counter extends Component {
|
||||
static template = xml`
|
||||
static template = xml`
|
||||
<div>
|
||||
<span t-on-click="increment(counter1)"><t t-esc="counter1.value"/></span>
|
||||
<span t-on-click="increment(counter2)"><t t-esc="counter2.value"/></span>
|
||||
</div>`;
|
||||
counter1 = useState({value: 0});
|
||||
counter2 = useState({value: 0});
|
||||
counter1 = useState({ value: 0 });
|
||||
counter2 = useState({ value: 0 });
|
||||
|
||||
increment(counter) {
|
||||
counter.value++;
|
||||
}
|
||||
increment(counter) {
|
||||
counter.value++;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -169,7 +170,7 @@ to be called in the constructor.
|
||||
- **`env`** (Object): the component environment, which contains a QWeb instance.
|
||||
|
||||
- **`props`** (Object): this is an object containing all the properties given by
|
||||
the parent to a child component. For example, in the following situation,
|
||||
the parent to a child component. For example, in the following situation,
|
||||
the parent component gives a `user` and a `color` value to the `ChildComponent`.
|
||||
|
||||
```xml
|
||||
@@ -182,7 +183,7 @@ to be called in the constructor.
|
||||
As such, it should not ever be modified by the component (otherwise you risk
|
||||
unintended effects, since the parent may not be aware of the change)!!
|
||||
|
||||
The `props` can be modified dynamically by the parent. In that case, the
|
||||
The `props` can be modified dynamically by the parent. In that case, the
|
||||
component will go through the following lifecycle methods: `willUpdateProps`,
|
||||
`willPatch` and `patched`.
|
||||
|
||||
@@ -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`).
|
||||
|
||||
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
|
||||
turn will cause other calls to patched. So, we need to be particularly
|
||||
One needs to be careful, because updates here will create an additional rendering, which in
|
||||
turn will cause other calls to the `patched` method. So, we need to be particularly
|
||||
careful at avoiding endless cycles.
|
||||
|
||||
#### `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
|
||||
to (rendering) errors that occur in some sub components. See the section on
|
||||
[error handling](#error-handling)
|
||||
[error handling](#error-handling).
|
||||
|
||||
### Root Component
|
||||
|
||||
@@ -783,7 +784,7 @@ Like event handling, the `t-model` directive accepts some modifiers:
|
||||
| Modifier | Description |
|
||||
| --------- | -------------------------------------------------------------------- |
|
||||
| `.lazy` | update the value on the `change` event (default is on `input` event) |
|
||||
| `.number` | tries to parse the value to a number (using `parseFloat`) |
|
||||
| `.number` | try to parse the value to a number (using `parseFloat`) |
|
||||
| `.trim` | trim the resulting value |
|
||||
|
||||
For example:
|
||||
@@ -799,9 +800,10 @@ Note: the online playground has an example to show how it works.
|
||||
|
||||
### `t-key` Directive
|
||||
|
||||
Even though Owl tries to be as declarative as possible, some DOM state is still
|
||||
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
|
||||
Even though Owl tries to be as declarative as possible, the DOM does not fully
|
||||
expose its state declaratively in the DOM tree. For example, the scrolling state,
|
||||
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
|
||||
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.
|
||||
@@ -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:
|
||||
|
||||
- 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
|
||||
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
|
||||
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.
|
||||
|
||||
For example, here is how we could implement an `ErrorBoundary` component:
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
## Overview
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
+24
-24
@@ -220,18 +220,18 @@ be ready whenever the component is rendered:
|
||||
|
||||
```js
|
||||
function useLoader() {
|
||||
const component = Component.current;
|
||||
const record = useState({});
|
||||
onWillStart(async () => {
|
||||
const recordId = component.props.id;
|
||||
Object.assign(record, await fetchSomeRecord(recordId));
|
||||
});
|
||||
return record;
|
||||
const component = Component.current;
|
||||
const record = useState({});
|
||||
onWillStart(async () => {
|
||||
const recordId = component.props.id;
|
||||
Object.assign(record, await fetchSomeRecord(recordId));
|
||||
});
|
||||
return record;
|
||||
}
|
||||
```
|
||||
|
||||
Note that this example does not update the record value whenever props are
|
||||
updated. For that situation, we need to use the `onWillUpdateProps` hook.
|
||||
updated. For that situation, we need to use the `onWillUpdateProps` hook.
|
||||
|
||||
### `onWillUpdateProps`
|
||||
|
||||
@@ -241,17 +241,17 @@ useful to perform some asynchronous task such as fetching updated data.
|
||||
|
||||
```js
|
||||
function useLoader() {
|
||||
const component = Component.current;
|
||||
const record = useState({});
|
||||
const component = Component.current;
|
||||
const record = useState({});
|
||||
|
||||
async function updateRecord(id) {
|
||||
Object.assign(record, await fetchSomeRecord(id));
|
||||
}
|
||||
async function updateRecord(id) {
|
||||
Object.assign(record, await fetchSomeRecord(id));
|
||||
}
|
||||
|
||||
onWillStart(() => updateRecord(component.props.id));
|
||||
onWillUpdateProps(nextProps => updateRecord(nextProps.id));
|
||||
onWillStart(() => updateRecord(component.props.id));
|
||||
onWillUpdateProps(nextProps => updateRecord(nextProps.id));
|
||||
|
||||
return record;
|
||||
return record;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -359,7 +359,7 @@ getters. See the [store documentation](store.md) for more information.
|
||||
### Making customized hooks
|
||||
|
||||
Hooks are a wonderful way to organize the code of a complex component by feature
|
||||
instead of by lifecycle methods. They are like mixins, except that they can be
|
||||
instead of by lifecycle methods. They are like mixins, except that they can be
|
||||
easily composed together.
|
||||
|
||||
But, like every good things in life, hooks should be used with moderation. They are
|
||||
@@ -373,16 +373,16 @@ not the solution to every problem.
|
||||
// maybe overkill
|
||||
class A extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
useMySpecificHook()
|
||||
super(...args);
|
||||
useMySpecificHook();
|
||||
}
|
||||
}
|
||||
|
||||
// ok
|
||||
class B extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.performSpecificTask()
|
||||
super(...args);
|
||||
this.performSpecificTask();
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -391,7 +391,7 @@ not the solution to every problem.
|
||||
|
||||
- they may be harder to test: if a customized hook injects some external side
|
||||
effect dependency, then it is harder to test without doing some non obvious
|
||||
manipulation. For example, assume that we want to give a reference to a
|
||||
manipulation. For example, assume that we want to give a reference to a
|
||||
router in a `useRouter` hook. We could do this:
|
||||
|
||||
```js
|
||||
@@ -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.
|
||||
|
||||
A better way would be to do something like this: get the reference from the
|
||||
@@ -410,7 +410,7 @@ not the solution to every problem.
|
||||
|
||||
```js
|
||||
function useRouter() {
|
||||
return Component.current.env.router;
|
||||
return Component.current.env.router;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+38
-6
@@ -1,10 +1,16 @@
|
||||
# 🦉 Observer 🦉
|
||||
|
||||
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
|
||||
an Observer class. Its job is to observe some object state, and react to any
|
||||
change. To do that, it recursively replaces all keys of the observed state by
|
||||
getters and setters.
|
||||
of a component is changed, Owl needs to rerender it. To help with that, there is
|
||||
an Observer class. Its job is to observe the state of an object (or array), and
|
||||
to react to any change. The observer is implemented with the native `Proxy`
|
||||
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:
|
||||
|
||||
@@ -16,5 +22,31 @@ const obj = observer.observe({ a: { b: 1 } });
|
||||
obj.a.b = 2;
|
||||
```
|
||||
|
||||
The observer is implemented with the native `Proxy` object. Note that this
|
||||
means that it will not work on older browsers.
|
||||
This example shows that an observer can observe nested properties.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
|
||||
Note that for convenience, the `useState` hook is also exported at the root of the `owl` object.
|
||||
|
||||
## Reference
|
||||
|
||||
+5
-7
@@ -38,14 +38,14 @@ const actions = {
|
||||
state.todos.push({
|
||||
id: state.nextId++,
|
||||
message,
|
||||
isCompleted: false,
|
||||
isCompleted: false
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const state = {
|
||||
todos: [],
|
||||
nextId: 1,
|
||||
nextId: 1
|
||||
};
|
||||
|
||||
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:
|
||||
|
||||
- `state`: the current state of the store content,
|
||||
- `dispatch`: a function that can be used to dispatch other actions,
|
||||
- `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.
|
||||
|
||||
Note that getters are cached if they don't take any argument, or their argument
|
||||
is a string or a number.
|
||||
Note that getters are not cached.
|
||||
|
||||
### 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,
|
||||
- `useGetters` to get a reference to the getters defined in the store.
|
||||
|
||||
|
||||
Assume we have this store:
|
||||
|
||||
```javascript
|
||||
@@ -223,7 +222,7 @@ const actions = {
|
||||
};
|
||||
|
||||
const state = {
|
||||
counter: {value: 0}
|
||||
counter: { value: 0 }
|
||||
};
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
+22
-22
@@ -12,7 +12,6 @@ functions are all available in the `owl.utils` namespace.
|
||||
- [`debounce`](#debounce): limiting rate of function calls
|
||||
- [`shallowEqual`](#shallowequal): shallow object comparison
|
||||
|
||||
|
||||
## `whenReady`
|
||||
|
||||
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).
|
||||
|
||||
```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 app = new App({ qweb });
|
||||
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.
|
||||
|
||||
For example, it is useful for lazy loading external libraries:
|
||||
|
||||
```js
|
||||
class MyComponent extends owl.Component {
|
||||
willStart() {
|
||||
@@ -55,7 +55,7 @@ class MyComponent extends owl.Component {
|
||||
|
||||
## `loadFile`
|
||||
|
||||
`loadFile` is a helper function to fetch a file. It simply
|
||||
`loadFile` is a helper function to fetch a file. It simply
|
||||
performs a `GET` request and returns the resulting string in a promise. The
|
||||
initial usecase for this function is to load a template file. For example:
|
||||
|
||||
@@ -73,38 +73,38 @@ string. It does not add a `script` tag or any other side effect.
|
||||
## `escape`
|
||||
|
||||
Sometimes, we need to display dynamic data (for example user-generated data) in
|
||||
the user interface. If this is done by a `QWeb` template, it is not an issue:
|
||||
the user interface. If this is done by a `QWeb` template, it is not an issue:
|
||||
|
||||
```xml
|
||||
<div><t t-esc="user.data"/></div>
|
||||
```
|
||||
|
||||
The `QWeb` engine will create a `div` node and add the content of the `user.data`
|
||||
string as a text node, so the web browser will not parse it as html. However,
|
||||
string as a text node, so the web browser will not parse it as html. However,
|
||||
it may be a problem if this is done with some javascript code like this:
|
||||
|
||||
```js
|
||||
class BadComponent extends Component {
|
||||
// some template with a ref to a div
|
||||
// some code ...
|
||||
// some template with a ref to a div
|
||||
// some code ...
|
||||
|
||||
mounted() {
|
||||
this.divRef.el.innerHTML = this.state.value;
|
||||
}
|
||||
mounted() {
|
||||
this.divRef.el.innerHTML = this.state.value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
by the browser, but which will not be parsed as html (for example, `"<ok>"` is
|
||||
escaped to the string: `"<ok>"`). So, the bad example above can be fixed
|
||||
with the following change:
|
||||
|
||||
```js
|
||||
this.divRef.el.innerHTML = owl.utils.escape(this.state.value);
|
||||
this.divRef.el.innerHTML = owl.utils.escape(this.state.value);
|
||||
```
|
||||
|
||||
|
||||
## `debounce`
|
||||
|
||||
The `debounce` function is useful when we want to limit the number of times some
|
||||
@@ -112,31 +112,31 @@ function/action is perfomed. For example, this may be useful to prevent issue
|
||||
with people double clicking on a button.
|
||||
|
||||
It takes three arguments:
|
||||
|
||||
- `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
|
||||
rate limit the function `func`
|
||||
rate limit the function `func`
|
||||
- `immediate` (optional, boolean, default=false): if `immediate` is true, the
|
||||
function will be triggered immediately (leading edge of the interval). If false,
|
||||
the function will be triggered at the end (trailing edge).
|
||||
|
||||
It returns a function. For example:
|
||||
It returns a function. For example:
|
||||
|
||||
```js
|
||||
const debounce = owl.utils.debounce
|
||||
window.addEventListener('mousemove', debounce(doSomething, 100));
|
||||
const debounce = owl.utils.debounce;
|
||||
window.addEventListener("mousemove", debounce(doSomething, 100));
|
||||
```
|
||||
|
||||
As this example shows, it is usualy useful for event handlers which are triggered
|
||||
very quickly, such as `scroll` or `mousemove` events.
|
||||
|
||||
|
||||
## `shallowEqual`
|
||||
|
||||
This function checks if two objects have the same values assigned to each keys:
|
||||
|
||||
```js
|
||||
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: 2 }); // true
|
||||
shallowEqual({ a: 1, b: 2 }, { a: 1, b: 3 }); // false
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```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