[DOC] update the documentation

closes #334
closes #336
closes #338
closes #339
closes #341
closes #342
closes #343
closes #346
closes #348
This commit is contained in:
Géry Debongnie
2019-10-17 09:50:03 +02:00
parent 9e65b0686f
commit 1f40f113aa
7 changed files with 195 additions and 52 deletions
+4 -5
View File
@@ -30,7 +30,7 @@ btn {
}
```
will produce a nice flash effect whenever the user click (or activate with the
will produce a nice flash effect whenever the user clicks (or activates with the
keyboard) the button.
## CSS Transitions
@@ -47,7 +47,7 @@ the lifetime of a node. Since this is not easy to do by hand, Owl `t-transition`
directive is there to help.
Whenever a node has a `t-transition` directive, with a `name` value, the following
will happen:
sequence of events will happen:
At node insertion:
@@ -91,6 +91,5 @@ The `t-transition` directive can be applied on a node element or on a component.
Notes:
- more information on animations are available [here](animations.md).
- Owl does not support more than one transition on a single node, so the
`t-transition` expression must be a single value (i.e. no space allowed)
Owl does not support more than one transition on a single node, so the
`t-transition` expression must be a single value (i.e. no space allowed).
+87 -21
View File
@@ -29,7 +29,7 @@
OWL components are the building blocks for user interface. They are designed to be:
1. **declarative:** the user interface should be described in term of the state
1. **declarative:** the user interface should be described in terms of the state
of the application, not as a sequence of imperative steps.
2. **composable:** each component can seamlessly be created in a parent component by
@@ -72,7 +72,7 @@ class ClickCounter extends owl.Component {
Note that this code is written in ESNext style, so it will only run on the
latest browsers without a transpilation step.
This example show how a component should be defined: it simply subclasses the
This example shows how a component should be defined: it simply subclasses the
Component class. If no static `template` key is defined, then
Owl will use the component's name as template name. Here,
a state object is defined, by using the `useState` hook. It is not mandatory to use the state object, but it is certainly encouraged. The result of the `useState` call is
@@ -80,33 +80,86 @@ a state object is defined, by using the `useState` hook. It is not mandatory to
## Reference
An Owl component is a small class which represent a component or some UI element.
An Owl component is a small class which represents a component or some UI element.
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
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).
find a template with the component name (or one of its ancestors).
### Reactive system
OWL components can be made reactive by observing some part of their state. See the [hooks](hooks.md) section for more details.
OWL components are normal javascript classes. So, changing a component internal
state does nothing more:
The main idea is that the `useState` hook generate a proxy version of an object
(this is done by an [observer](observer.md)), which allows the component to
react to any change.
```js
class Counter extends Component {
static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
state = {value: 0};
```javascript
const { useState } = owl.hooks;
class SomeComponent extends owl.Component {
state = useState({ a: 0, b: 1 });
increment() {
this.state.value++;
}
}
```
Note that there is an important limitation: hooks need to be called in the
constructor.
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
explicit call to `render` in `increment`:
```js
increment() {
this.state.value++;
this.render();
}
```
However, it may be simple in this case, but it quickly become cumbersome, as a
component get more complex, and its internal state is modified by more than one
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
(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:
```js
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});
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`
<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});
increment(counter) {
counter.value++;
}
}
```
Note that hooks are subject to one important [rule](hooks.md#one-rule): they need
to be called in the constructor.
### Properties
@@ -115,10 +168,23 @@ constructor.
- **`env`** (Object): the component environment, which contains a QWeb instance.
- **`props`** (Object): this is an object given (in the constructor) by the parent
to configure the component. It can be dynamically changed later by the parent,
in some case. Note that `props` are owned by the parent, not by the component.
As such, it should not ever be modified by the component!!
- **`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 component gives a `user` and a `color` value to the `ChildComponent`.
```xml
<div>
<ChildComponent user="state.user" color="color">
</div>
```
Note that `props` are owned by the parent, not by the component.
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
component will go through the following lifecycle methods: `willUpdateProps`,
`willPatch` and `patched`.
### Static Properties
@@ -182,7 +248,7 @@ We explain here all the public methods of the `Component` class.
we know that its state (or something in the environment, or ...) has changed.
In that case, it should simply set to `true`.
- **`unmount()`**: in case a component need to be detached/removed from the DOM, this
- **`unmount()`**: in case a component needs to be detached/removed from the DOM, this
method can be used. Most applications should not call `unmount`, this is more
useful to the underlying component system.
@@ -338,7 +404,7 @@ with the DOM (for example, through an external library) whenever the
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 compoent 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
turn will cause other calls to patched. So, we need to be particularly
careful at avoiding endless cycles.
+2 -2
View File
@@ -1,9 +1,9 @@
# 🦉 Observer 🦉
Owl need 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
an Observer class. Its job is to observe some object state, and react to any
change. To do that, it recursively replace all keys of the observed state by
change. To do that, it recursively replaces all keys of the observed state by
getters and setters.
For example, this code will display `update` in the console:
+2 -5
View File
@@ -55,7 +55,7 @@ To build an application (or a sub-part of an application), we need two things:
- an environment: it is the global context in which we are working. It needs to
contain a QWeb instance (preloaded with templates), and anything else that we
need. In practice, it could context some user session information, some
need. In practice, it could be used to contain some user session information, some
configuration keys (for example, isMobile = true/false if we are in mobile mode).
- a description of the user interface: there should be a root component, which can
@@ -76,10 +76,7 @@ const useState = owl.hooks.useState;
class ClickCounter extends owl.Component {
static template = "clickcounter";
constructor() {
super(...arguments);
this.state = useState({ value: 0 });
}
state = useState({ value: 0 });
increment() {
this.state.value++;
+17 -17
View File
@@ -17,12 +17,12 @@
Managing the state in an application is not an easy task. In some cases, the
state of an application can be part of the component tree, in a natural way.
However, there are situations where some part of the state need to be displayed
However, there are situations where some parts of the state need to be displayed
in various parts of the user interface, and then, it is not obvious which
component should own which part of the state.
Owl's solution to this issue is a centralized store. It is a class that owns
some (or all) state, and let the developer update it in a structured way, with
some (or all) state, and lets the developer update it in a structured way, with
`actions`. Owl components can then connect to the store, and will be updated if
necessary.
@@ -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 });
@@ -115,7 +115,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
- `getters`: an object containing all getters defined in the store,
- `env`: the current environment. This is useful sometimes, in particular if
an action needs to apply some side effects (such as performing an rpc), and
the `rpc` method is located in the environment.
@@ -132,7 +132,7 @@ call.
Also, it is important to be aware that we need to be careful with asynchronous
logic. Each state change will potentially trigger a rerendering, so we need to
make sure that we do not have a partial corrupted state. Here is an example that
make sure that we do not have a partially corrupted state. Here is an example that
is likely not a good idea:
```javascript
@@ -185,7 +185,7 @@ transform the data contained in the store.
const getters = {
getPost({ state }, id) {
const post = state.posts.find(p => p.id === id);
const author = state.authors.find(a => (a.id = post.id));
const author = state.authors.find(a => a.id === post.id);
return {
id,
author,
@@ -209,7 +209,7 @@ At some point, we need a way to interact with the store from a component. This
can be done with the help of the three store hooks:
- `useStore` to subscribe a component to some part of the store state,
- `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.
@@ -252,14 +252,14 @@ two arguments:
- a selector function, which takes the store state as first argument (and the
component props as second argument) and returns
an object or an array (which will be then observed)
an object or an array (which will be then observed),
- 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
will only be rerendered whenever this part of the state changes. Otherwise, it
will perform a strict equality check and updates the component every time this
will perform a strict equality check and will update the component every time this
check fails.
### Semantics
@@ -268,23 +268,23 @@ The `Store` class and the `useStore` hook try to be smart and to optimize as muc
as possible the rendering and update process. What is important to know is:
- components are always updated in the order of their creation (so, parent
before children)
- they are updated only if they are in the DOM
before children),
- they are updated only if they are in the DOM,
- if a parent is asynchronous, the system will wait for it to complete its
update before updating other components.
update before updating other components,
- in general, updates are not coordinated. This is not a problem for synchronous
components, but if there are many asynchronous components, this could lead to
a situation where some part of the UI is updated and other parts of the UI is
a situation where some part of the UI is updated and some other part of the UI is
not updated.
### Good Practices
- avoid asynchronous components as much as possible. Asynchronous components
lead to situations where parts of the UI is not updated immediately.
lead to situations where parts of the UI is not updated immediately,
- do not be afraid to connect many components, parent or children if needed. For
example, a `MessageList` component could get a list of ids in its `useStore`
call and a `Message` component could get the data of its own
message
message,
- since the `useStore` function is called for each connected component,
for each state update, it is important to make sure that these functions are
as fast as possible.
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 Tags 🦉
Tags are very small helper to make it easy to write inline templates. There is
Tags are very small helpers to make it easy to write inline templates. There is
only one currently available tag: `xml`, but we plan to add other tags later,
such as a `css` tag, which will be used to write single file components.
+82 -1
View File
@@ -10,6 +10,8 @@ functions are all available in the `owl.utils` namespace.
- [`loadTemplates`](#loadtemplates): loading xml files
- [`escape`](#escape): sanitizing strings
- [`debounce`](#debounce): limiting rate of function calls
- [`shallowEqual`](#shallowequal): shallow object comparison
## `whenReady`
@@ -25,6 +27,8 @@ Promise.all([loadTemplates(), owl.utils.whenReady()]).then(function([templates])
});
```
or alternatively:
```js
owl.utils.whenReady(function() {
const qweb = new owl.QWeb();
@@ -36,7 +40,7 @@ owl.utils.whenReady(function() {
## `loadJS`
`loadJS` takes a url (string) for a javascript resource, and loads it. It returns
a promise, so the caller can properly react when it is ready. Also, it is smart:
a promise, so the caller can 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.
@@ -50,6 +54,10 @@ class MyComponent extends owl.Component {
## `loadTemplates`
`loadTemplates` is a helper function to fetch a template file. It simply
performs a `GET` request and return the resulting string in a promise. For
example:
```js
async function makeEnv() {
const templates = await owl.utils.loadTemplates("templates.xml");
@@ -60,4 +68,77 @@ async function makeEnv() {
## `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:
```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,
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 ...
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
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: `"&lt;ok&gt;"`). So, the bad example above can be fixed
with the following change:
```js
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
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`
- `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:
```js
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
```
However, for performance reasons, it assumes that the two objects have the same
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);
```