[FIX] tutorials/discover_js_framework: clarify instructions
closes odoo/documentation#8065 Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
parent
d36e0b095c
commit
632add350d
@ -22,8 +22,7 @@ into the exercises, make sure you have followed all the steps described in this
|
|||||||
|
|
||||||
In this chapter, we use the `awesome_owl` addon, which provides a simplified environment that
|
In this chapter, we use the `awesome_owl` addon, which provides a simplified environment that
|
||||||
only contains Owl and a few other files. The goal is to learn Owl itself, without relying on Odoo
|
only contains Owl and a few other files. The goal is to learn Owl itself, without relying on Odoo
|
||||||
web client code. To get started, open the `/awesome_owl` route with your browser: it
|
web client code.
|
||||||
should display an Owl component with the text *hello world*.
|
|
||||||
|
|
||||||
.. spoiler:: Solutions
|
.. spoiler:: Solutions
|
||||||
|
|
||||||
@ -41,9 +40,11 @@ button.
|
|||||||
|
|
||||||
.. code-block:: js
|
.. code-block:: js
|
||||||
|
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
import { Component, useState } from "@odoo/owl";
|
import { Component, useState } from "@odoo/owl";
|
||||||
|
|
||||||
class Counter extends Component {
|
export class Counter extends Component {
|
||||||
static template = "my_module.Counter";
|
static template = "my_module.Counter";
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
@ -79,8 +80,8 @@ As a first exercise, let us modify the `Playground` component located in
|
|||||||
`/awesome_owl` route with your browser.
|
`/awesome_owl` route with your browser.
|
||||||
|
|
||||||
|
|
||||||
#. Modify :file:`playground.js` so that it acts as a counter like in the example above. You will
|
#. Modify :file:`playground.js` so that it acts as a counter like in the example above.
|
||||||
need to use the `useState hook
|
Keep `Playground` for the class name. You will need to use the `useState hook
|
||||||
<{OWL_PATH}/doc/reference/hooks.md#usestate>`_ so that the component is re-rendered
|
<{OWL_PATH}/doc/reference/hooks.md#usestate>`_ so that the component is re-rendered
|
||||||
whenever any part of the state object that has been read by this component is modified.
|
whenever any part of the state object that has been read by this component is modified.
|
||||||
#. In the same component, create an `increment` method.
|
#. In the same component, create an `increment` method.
|
||||||
@ -90,6 +91,10 @@ As a first exercise, let us modify the `Playground` component located in
|
|||||||
<{OWL_PATH}/doc/reference/event_handling.md#event-handling>`_ attribute in the button to
|
<{OWL_PATH}/doc/reference/event_handling.md#event-handling>`_ attribute in the button to
|
||||||
trigger the `increment` method whenever the button is clicked.
|
trigger the `increment` method whenever the button is clicked.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
Don't forget :code:`/** @odoo-module **/` in your JavaScript files. More information on this can
|
||||||
|
be found :ref:`here <frontend/modules/native_js>`.
|
||||||
|
|
||||||
.. tip::
|
.. tip::
|
||||||
The Odoo JavaScript files downloaded by the browser are minified. For debugging purpose, it's
|
The Odoo JavaScript files downloaded by the browser are minified. For debugging purpose, it's
|
||||||
easier when the files are not minified. Switch to
|
easier when the files are not minified. Switch to
|
||||||
@ -110,7 +115,8 @@ see how to create a `sub-component <{OWL_PATH}/doc/reference/component.md#sub-co
|
|||||||
#. You can do it in the same file first, but once it's done, update your code to move the
|
#. You can do it in the same file first, but once it's done, update your code to move the
|
||||||
`Counter` in its own folder and file. Import it relatively from `./counter/counter`. Make sure
|
`Counter` in its own folder and file. Import it relatively from `./counter/counter`. Make sure
|
||||||
the template is in its own file, with the same name.
|
the template is in its own file, with the same name.
|
||||||
#. Add two counters in your playground.
|
#. Use `<Counter/>` in the template of the `Playground` component to add two counters in your
|
||||||
|
playground.
|
||||||
|
|
||||||
.. image:: 01_owl_components/double_counter.png
|
.. image:: 01_owl_components/double_counter.png
|
||||||
:align: center
|
:align: center
|
||||||
@ -120,10 +126,6 @@ see how to create a `sub-component <{OWL_PATH}/doc/reference/component.md#sub-co
|
|||||||
as the component. For example, if we have a `TodoList` component, its code should be in
|
as the component. For example, if we have a `TodoList` component, its code should be in
|
||||||
`todo_list.js`, `todo_list.xml` and if necessary, `todo_list.scss`
|
`todo_list.js`, `todo_list.xml` and if necessary, `todo_list.scss`
|
||||||
|
|
||||||
.. important::
|
|
||||||
Don't forget :code:`/** @odoo-module **/` in your JavaScript files. More information on this can
|
|
||||||
be found :ref:`here <frontend/modules/native_js>`.
|
|
||||||
|
|
||||||
.. _tutorials/discover_js_framework/simple_card:
|
.. _tutorials/discover_js_framework/simple_card:
|
||||||
|
|
||||||
3. A simple `Card` component
|
3. A simple `Card` component
|
||||||
@ -163,7 +165,7 @@ The above example should produce some html using bootstrap that look like this:
|
|||||||
4. Using `markup` to display html
|
4. Using `markup` to display html
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
If you used `t-esc` in the previous exercise, then you may have noticed that Owl will automatically escape
|
If you used `t-esc` in the previous exercise, then you may have noticed that Owl automatically escapes
|
||||||
its content. For example, if you try to display some html like this: `<Card title="'my title'" content="this.html"/>`
|
its content. For example, if you try to display some html like this: `<Card title="'my title'" content="this.html"/>`
|
||||||
with `this.html = "<div>some content</div>""`,
|
with `this.html = "<div>some content</div>""`,
|
||||||
the resulting output will simply display the html as a string.
|
the resulting output will simply display the html as a string.
|
||||||
@ -233,7 +235,7 @@ be called whenever the `Counter` component is incremented.
|
|||||||
.. important::
|
.. important::
|
||||||
|
|
||||||
There is a subtlety with callback props: they usually should be defined with the `.bind`
|
There is a subtlety with callback props: they usually should be defined with the `.bind`
|
||||||
suffix. See the `documentation <{OWL_PATH}/doc/reference/props.md#binding-function-props>`_
|
suffix. See the `documentation <{OWL_PATH}/doc/reference/props.md#binding-function-props>`_.
|
||||||
|
|
||||||
7. A todo list
|
7. A todo list
|
||||||
==============
|
==============
|
||||||
@ -249,7 +251,7 @@ For this tutorial, a `todo` is an object that contains three values: an `id` (nu
|
|||||||
|
|
||||||
{ id: 3, description: "buy milk", isCompleted: false }
|
{ id: 3, description: "buy milk", isCompleted: false }
|
||||||
|
|
||||||
#. Create a `TodoList` and a `TodoItem` components
|
#. Create a `TodoList` and a `TodoItem` components.
|
||||||
#. The `TodoItem` component should receive a `todo` as a prop, and display its `id` and `description` in a `div`.
|
#. The `TodoItem` component should receive a `todo` as a prop, and display its `id` and `description` in a `div`.
|
||||||
#. For now, hardcode the list of todos:
|
#. For now, hardcode the list of todos:
|
||||||
|
|
||||||
@ -258,20 +260,20 @@ For this tutorial, a `todo` is an object that contains three values: an `id` (nu
|
|||||||
// in TodoList
|
// in TodoList
|
||||||
this.todos = useState([{ id: 3, description: "buy milk", isCompleted: false }]);
|
this.todos = useState([{ id: 3, description: "buy milk", isCompleted: false }]);
|
||||||
|
|
||||||
#. Use `t-foreach <{OWL_PATH}/doc/reference/templates.md#loops>`_ to display each todo in a `TodoItem`
|
#. Use `t-foreach <{OWL_PATH}/doc/reference/templates.md#loops>`_ to display each todo in a `TodoItem`.
|
||||||
#. Display a `TodoList` in the playground
|
#. Display a `TodoList` in the playground.
|
||||||
#. Add props validation to `TodoItem`
|
#. Add props validation to `TodoItem`.
|
||||||
|
|
||||||
.. image:: 01_owl_components/todo_list.png
|
.. image:: 01_owl_components/todo_list.png
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
Note that the `t-foreach` directive is not exactly the same in Owl as the QWeb python implementation: it
|
|
||||||
requires a `t-key` unique value, so Owl can properly reconciliate each element.
|
|
||||||
|
|
||||||
.. tip::
|
.. tip::
|
||||||
|
|
||||||
Since the `TodoList` and `TodoItem` components are so tightly coupled, it makes
|
Since the `TodoList` and `TodoItem` components are so tightly coupled, it makes
|
||||||
sense to put them in the same folder
|
sense to put them in the same folder.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The `t-foreach` directive is not exactly the same in Owl as the QWeb python implementation: it
|
||||||
|
requires a `t-key` unique value, so that Owl can properly reconcile each element.
|
||||||
|
|
||||||
8. Use dynamic attributes
|
8. Use dynamic attributes
|
||||||
=========================
|
=========================
|
||||||
@ -281,7 +283,7 @@ using a `dynamic attributes <{OWL_PATH}/doc/reference/templates.md#dynamic-attri
|
|||||||
|
|
||||||
#. Add the Bootstrap classes `text-muted` and `text-decoration-line-through` on the `TodoItem` root element
|
#. Add the Bootstrap classes `text-muted` and `text-decoration-line-through` on the `TodoItem` root element
|
||||||
if it is completed.
|
if it is completed.
|
||||||
#. Change the hardcoded `todo` value to check that it is properly displayed.
|
#. Change the hardcoded `this.todos` value to check that it is properly displayed.
|
||||||
|
|
||||||
Even though the directive is named `t-att` (for attribute), it can be used to set a `class` value (and
|
Even though the directive is named `t-att` (for attribute), it can be used to set a `class` value (and
|
||||||
html properties such as the `value` of an input).
|
html properties such as the `value` of an input).
|
||||||
@ -305,7 +307,7 @@ html properties such as the `value` of an input).
|
|||||||
So far, the todos in our list are hard-coded. Let us make it more useful by allowing the user to add
|
So far, the todos in our list are hard-coded. Let us make it more useful by allowing the user to add
|
||||||
a todo to the list.
|
a todo to the list.
|
||||||
|
|
||||||
#. Remove the hardcoded values in the `TodoList` component
|
#. Remove the hardcoded values in the `TodoList` component:
|
||||||
|
|
||||||
.. code-block:: javascript
|
.. code-block:: javascript
|
||||||
|
|
||||||
@ -381,7 +383,7 @@ hook functions have to be called in the `setup` method, and no later!
|
|||||||
|
|
||||||
|
|
||||||
An Owl component goes through a lot of phases: it can be instantiated, rendered,
|
An Owl component goes through a lot of phases: it can be instantiated, rendered,
|
||||||
mounted, updated, detached, destroyed, ... This is the `component lifecycle <{OWL_PATH}/doc/reference/component.md#lifecycle>`_.
|
mounted, updated, detached, destroyed... This is the `component lifecycle <{OWL_PATH}/doc/reference/component.md#lifecycle>`_.
|
||||||
The figure above show the most important events in the life of a component (hooks are shown in purple).
|
The figure above show the most important events in the life of a component (hooks are shown in purple).
|
||||||
Roughly speaking, a component is created, then updated (potentially many times), then is destroyed.
|
Roughly speaking, a component is created, then updated (potentially many times), then is destroyed.
|
||||||
|
|
||||||
@ -444,7 +446,7 @@ component is mounted.
|
|||||||
|
|
||||||
.. code-block:: js
|
.. code-block:: js
|
||||||
|
|
||||||
this.inputRef = useRef('refname');
|
this.inputRef = useRef('input');
|
||||||
|
|
||||||
11. Toggling todos
|
11. Toggling todos
|
||||||
==================
|
==================
|
||||||
@ -452,7 +454,7 @@ component is mounted.
|
|||||||
Now, let's add a new feature: mark a todo as completed. This is actually trickier than one might
|
Now, let's add a new feature: mark a todo as completed. This is actually trickier than one might
|
||||||
think. The owner of the state is not the same as the component that displays it. So, the `TodoItem`
|
think. The owner of the state is not the same as the component that displays it. So, the `TodoItem`
|
||||||
component needs to communicate to its parent that the todo state needs to be toggled. One classic
|
component needs to communicate to its parent that the todo state needs to be toggled. One classic
|
||||||
way to do this is by using a `callback prop
|
way to do this is by adding a `callback prop
|
||||||
<{OWL_PATH}/doc/reference/props.md#binding-function-props>`_ `toggleState`.
|
<{OWL_PATH}/doc/reference/props.md#binding-function-props>`_ `toggleState`.
|
||||||
|
|
||||||
#. Add an input with the attribute :code:`type="checkbox"` before the id of the task, which must
|
#. Add an input with the attribute :code:`type="checkbox"` before the id of the task, which must
|
||||||
@ -463,7 +465,7 @@ way to do this is by using a `callback prop
|
|||||||
falsy value.
|
falsy value.
|
||||||
|
|
||||||
#. Add a callback props `toggleState` to `TodoItem`.
|
#. Add a callback props `toggleState` to `TodoItem`.
|
||||||
#. Add a `click` event handler on the input in the `TodoItem` component and make sure it calls the
|
#. Add a `change` event handler on the input in the `TodoItem` component and make sure it calls the
|
||||||
`toggleState` function with the todo id.
|
`toggleState` function with the todo id.
|
||||||
#. Make it work!
|
#. Make it work!
|
||||||
|
|
||||||
@ -503,19 +505,19 @@ The final touch is to let the user delete a todo.
|
|||||||
|
|
||||||
In a :ref:`previous exercise <tutorials/discover_js_framework/simple_card>`, we built
|
In a :ref:`previous exercise <tutorials/discover_js_framework/simple_card>`, we built
|
||||||
a simple `Card` component. But it is honestly quite limited. What if we want
|
a simple `Card` component. But it is honestly quite limited. What if we want
|
||||||
to display some arbitrary content inside a card, such as a sub component? Well,
|
to display some arbitrary content inside a card, such as a sub-component? Well,
|
||||||
it does not work, since the content of the card is described by a string. It would
|
it does not work, since the content of the card is described by a string. It would
|
||||||
however be very convenient if we could describe the content as a piece of template.
|
however be very convenient if we could describe the content as a piece of template.
|
||||||
|
|
||||||
This is exactly what Owl `slot <{OWL_PATH}/doc/reference/slots.md>`_ system is designed
|
This is exactly what Owl's `slot <{OWL_PATH}/doc/reference/slots.md>`_ system is designed
|
||||||
for: allowing to write generic components.
|
for: allowing to write generic components.
|
||||||
|
|
||||||
Let us modify the `Card` component to use slots:
|
Let us modify the `Card` component to use slots:
|
||||||
|
|
||||||
#. Remove the `content` prop
|
#. Remove the `content` prop.
|
||||||
#. Use the default slot to define the body
|
#. Use the default slot to define the body.
|
||||||
#. Insert a few cards with arbitrary content, such as a `Counter` component
|
#. Insert a few cards with arbitrary content, such as a `Counter` component.
|
||||||
#. (bonus) Add prop validation
|
#. (bonus) Add prop validation.
|
||||||
|
|
||||||
.. image:: 01_owl_components/generic_card.png
|
.. image:: 01_owl_components/generic_card.png
|
||||||
:align: center
|
:align: center
|
||||||
@ -526,6 +528,8 @@ Let us modify the `Card` component to use slots:
|
|||||||
14. Minimizing card content
|
14. Minimizing card content
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
|
.. TODO: This exercise shows no new concept; it should probably be removed.
|
||||||
|
|
||||||
Finally, let's add a feature to the `Card` component, to make it more interesting: we
|
Finally, let's add a feature to the `Card` component, to make it more interesting: we
|
||||||
want a button to toggle its content (show it or hide it)
|
want a button to toggle its content (show it or hide it)
|
||||||
|
|
||||||
|
@ -74,10 +74,10 @@ Theory: Services
|
|||||||
In practice, every component (except the root component) may be destroyed at any time and replaced
|
In practice, every component (except the root component) may be destroyed at any time and replaced
|
||||||
(or not) with another component. This means that each component internal state is not persistent.
|
(or not) with another component. This means that each component internal state is not persistent.
|
||||||
This is fine in many cases, but there certainly are situations where we want to keep some data around.
|
This is fine in many cases, but there certainly are situations where we want to keep some data around.
|
||||||
For example, all discuss messages should not be reloaded every time we display a channel.
|
For example, all Discuss messages should not be reloaded every time we display a channel.
|
||||||
|
|
||||||
Also, it may happen that we need to write some code that is not a component. Maybe something that
|
Also, it may happen that we need to write some code that is not a component. Maybe something that
|
||||||
process all barcodes, or that manages the user configuration (context, ...).
|
process all barcodes, or that manages the user configuration (context, etc.).
|
||||||
|
|
||||||
The Odoo framework defines the idea of a :ref:`service <frontend/services>`, which is a persistent
|
The Odoo framework defines the idea of a :ref:`service <frontend/services>`, which is a persistent
|
||||||
piece of code that exports state and/or functions. Each service can depend on other services, and
|
piece of code that exports state and/or functions. Each service can depend on other services, and
|
||||||
@ -90,13 +90,13 @@ The following example registers a simple service that displays a notification ev
|
|||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
|
|
||||||
const myService = {
|
const myService = {
|
||||||
dependencies: ["notification"],
|
dependencies: ["notification"],
|
||||||
start(env, { notification }) {
|
start(env, { notification }) {
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
notification.add(`Tick Tock ${counter++}`);
|
notification.add(`Tick Tock ${counter++}`);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("services").add("myService", myService);
|
registry.category("services").add("myService", myService);
|
||||||
@ -110,18 +110,17 @@ state:
|
|||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
|
|
||||||
const sharedStateService = {
|
const sharedStateService = {
|
||||||
start(env) {
|
start(env) {
|
||||||
let state = {};
|
let state = {};
|
||||||
|
return {
|
||||||
return {
|
getValue(key) {
|
||||||
getValue(key) {
|
return state[key];
|
||||||
return state[key];
|
},
|
||||||
},
|
setValue(key, value) {
|
||||||
setValue(key, value) {
|
state[key] = value;
|
||||||
state[key] = value;
|
},
|
||||||
},
|
};
|
||||||
};
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("services").add("shared_state", sharedStateService);
|
registry.category("services").add("shared_state", sharedStateService);
|
||||||
@ -141,6 +140,8 @@ Then, any component can do this:
|
|||||||
2. Add some buttons for quick navigation
|
2. Add some buttons for quick navigation
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
|
.. TODO: Add ref to the action service when it's documented.
|
||||||
|
|
||||||
One important service provided by Odoo is the `action` service: it can execute
|
One important service provided by Odoo is the `action` service: it can execute
|
||||||
all kind of standard actions defined by Odoo. For example, here is how one
|
all kind of standard actions defined by Odoo. For example, here is how one
|
||||||
component could execute an action by its xml id:
|
component could execute an action by its xml id:
|
||||||
@ -163,39 +164,39 @@ Let us now add two buttons to our control panel:
|
|||||||
exists, so you should use `its xml id
|
exists, so you should use `its xml id
|
||||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/odoo/addons/base/views/res_partner_views.xml#L510>`_).
|
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/odoo/addons/base/views/res_partner_views.xml#L510>`_).
|
||||||
|
|
||||||
#. A button `Leads`, which opens a dynamic action on the `crm.lead` model with a list and a form view.
|
#. A button `Leads`, which opens a dynamic action on the `crm.lead` model with a list and a form
|
||||||
|
view. Follow the example of `this use of the action service
|
||||||
|
<https://github.com/odoo/odoo/blob/ef424a9dc22a5abbe7b0a6eff61cf113826f04c0/addons/account
|
||||||
|
/static/src/components/journal_dashboard_activity/journal_dashboard_activity.js#L28-L35>`_.
|
||||||
|
|
||||||
.. image:: 02_web_framework/navigation_buttons.png
|
.. image:: 02_web_framework/navigation_buttons.png
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
- `Example: doAction use
|
`Code: action service
|
||||||
<{GITHUB_PATH}/addons/account/static/src/components/journal_dashboard_activity
|
<{GITHUB_PATH}/addons/web/static/src/webclient/actions/action_service.js>`_
|
||||||
/journal_dashboard_activity.js#L35>`_
|
|
||||||
- `Code: action service
|
|
||||||
<{GITHUB_PATH}/addons/web/static/src/webclient/actions/action_service.js>`_
|
|
||||||
|
|
||||||
3. Add a DashboardItem
|
3. Add a dashboard item
|
||||||
======================
|
=======================
|
||||||
|
|
||||||
Let us now improve our content.
|
Let us now improve our content.
|
||||||
|
|
||||||
#. Create a generic `DashboardItem` component that display its default slot in a nice card layout
|
#. Create a generic `DashboardItem` component that display its default slot in a nice card layout.
|
||||||
It should take an optional `size` number props, that default to `1`
|
It should take an optional `size` number props, that default to `1`. The width should be
|
||||||
The width should be hardcoded to `(18*size)rem`.
|
hardcoded to `(18*size)rem`.
|
||||||
#. Add a few cards in the dashboard, with no size and a size of 2.
|
#. Add two cards to the dashboard. One with no size, and the other with a size of 2.
|
||||||
|
|
||||||
.. image:: 02_web_framework/dashboard_item.png
|
.. image:: 02_web_framework/dashboard_item.png
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
- `Owl slot system <{OWL_PATH}/doc/reference/slots.md>`_
|
`Owl's slot system <{OWL_PATH}/doc/reference/slots.md>`_
|
||||||
|
|
||||||
4. Call the server, add some statistics
|
4. Call the server, add some statistics
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
Let's improve the dashboard by adding a few dashboard items to display *real* business data.
|
Let's improve the dashboard by adding a few dashboard items to display *real* business data.
|
||||||
The *awesome_dashboard* addon provides a `/awesome_dashboard/statistics` route that is meant
|
The `awesome_dashboard` addon provides a `/awesome_dashboard/statistics` route that is meant
|
||||||
to return some interesting information.
|
to return some interesting information.
|
||||||
|
|
||||||
To call a specific controller, we need to use the :ref:`rpc service <frontend/services/rpc>`.
|
To call a specific controller, we need to use the :ref:`rpc service <frontend/services/rpc>`.
|
||||||
@ -226,11 +227,7 @@ A basic request could look like this:
|
|||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
`Code: rpc service <{GITHUB_PATH}/addons/web/static/src/core/network/rpc_service.js>`_
|
||||||
- `Code: rpc service <{GITHUB_PATH}/addons/web/static/src/core/network/rpc_service.js>`_
|
|
||||||
- `Example: calling a route in onWillStart
|
|
||||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
|
||||||
addons/lunch/static/src/views/search_model.js#L21>`_
|
|
||||||
|
|
||||||
5. Cache network calls, create a service
|
5. Cache network calls, create a service
|
||||||
========================================
|
========================================
|
||||||
@ -246,9 +243,9 @@ would prefer to do it only the first time, so we actually need to maintain some
|
|||||||
always return the same information.
|
always return the same information.
|
||||||
#. Use the `memoize <https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
#. Use the `memoize <https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||||
addons/web/static/src/core/utils/functions.js#L11>`_ utility function from
|
addons/web/static/src/core/utils/functions.js#L11>`_ utility function from
|
||||||
`@web/core/utils/functions` that will allow caching the statistics.
|
`@web/core/utils/functions` that allows caching the statistics.
|
||||||
#. Use this service in the `Dashboard` component.
|
#. Use this service in the `Dashboard` component.
|
||||||
#. Check that it works as expected
|
#. Check that it works as expected.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
- `Example: simple service <{GITHUB_PATH}/addons/web/static/src/core/network/http_service.js>`_
|
- `Example: simple service <{GITHUB_PATH}/addons/web/static/src/core/network/http_service.js>`_
|
||||||
@ -266,15 +263,15 @@ by the graph view. However, it is not loaded by default, so we will need to eith
|
|||||||
assets bundle, or lazy load it. Lazy loading is usually better since our users will not have to load
|
assets bundle, or lazy load it. Lazy loading is usually better since our users will not have to load
|
||||||
the chartjs code every time if they don't need it.
|
the chartjs code every time if they don't need it.
|
||||||
|
|
||||||
#. Create a `PieChart` component
|
#. Create a `PieChart` component.
|
||||||
#. In its `onWillStart` method, load chartjs, you can use the `loadJs
|
#. In its `onWillStart` method, load chartjs, you can use the `loadJs
|
||||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||||
addons/web/static/src/core/assets.js#L23>`_ function to load
|
addons/web/static/src/core/assets.js#L23>`_ function to load
|
||||||
:file:`/web/static/lib/Chart/Chart.js`.
|
:file:`/web/static/lib/Chart/Chart.js`.
|
||||||
#. Use the `PieChart` component in a `DashboardItem` to display a `pie chart
|
#. Use the `PieChart` component in a `DashboardItem` to display a `pie chart
|
||||||
<https://www.chartjs.org/docs/2.8.0/charts/doughnut.html>`_ that shows the
|
<https://www.chartjs.org/docs/2.8.0/charts/doughnut.html>`_ that shows the
|
||||||
correct quantity for each sold t-shirts in each size (that information is available in the
|
quantity for each sold t-shirts in each size (that information is available in the
|
||||||
statistics route). Note that you can use the `size` property to make it look larger
|
`/statistics` route). Note that you can use the `size` property to make it look larger.
|
||||||
#. The `PieChart` component will need to render a canvas, and draw on it using `chart.js`.
|
#. The `PieChart` component will need to render a canvas, and draw on it using `chart.js`.
|
||||||
#. Make it work!
|
#. Make it work!
|
||||||
|
|
||||||
@ -293,11 +290,11 @@ the chartjs code every time if they don't need it.
|
|||||||
7. Real life update
|
7. Real life update
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Since we moved the data loading in a cache, it does not ever updates. But let us say that we
|
Since we moved the data loading in a cache, it never updates. But let us say that we
|
||||||
are looking at fast moving data, so we want to periodically (for example, every 10min) reload
|
are looking at fast moving data, so we want to periodically (for example, every 10min) reload
|
||||||
fresh data.
|
fresh data.
|
||||||
|
|
||||||
This is quite simple to implement, with a `setTimeout` or `setInterval` in the dashboard service.
|
This is quite simple to implement, with a `setTimeout` or `setInterval` in the statistics service.
|
||||||
However, here is the tricky part: if the dashboard is currently being displayed, it should be
|
However, here is the tricky part: if the dashboard is currently being displayed, it should be
|
||||||
updated immediately.
|
updated immediately.
|
||||||
|
|
||||||
@ -306,7 +303,7 @@ but not linked to any component. A component can then do a `useState` on it to s
|
|||||||
changes.
|
changes.
|
||||||
|
|
||||||
|
|
||||||
#. Update the dashboard service to reload data every 10 minutes (to test it, use 10s instead!)
|
#. Update the statistics service to reload data every 10 minutes (to test it, use 10s instead!)
|
||||||
#. Modify it to return a `reactive <{OWL_PATH}/doc/reference/reactivity.md#reactive>`_ object.
|
#. Modify it to return a `reactive <{OWL_PATH}/doc/reference/reactivity.md#reactive>`_ object.
|
||||||
Reloading data should update the reactive object in place.
|
Reloading data should update the reactive object in place.
|
||||||
#. The `Dashboard` component can now use it with a `useState`
|
#. The `Dashboard` component can now use it with a `useState`
|
||||||
@ -328,13 +325,13 @@ look at it.
|
|||||||
To do that, we will need to create a new bundle containing all our dashboard assets,
|
To do that, we will need to create a new bundle containing all our dashboard assets,
|
||||||
then use the `LazyComponent` (located in `@web/core/assets`).
|
then use the `LazyComponent` (located in `@web/core/assets`).
|
||||||
|
|
||||||
#. Move all dashboard assets into a sub folder `/dashboard` to make it easier to
|
#. Move all dashboard assets into a sub folder :file:`/dashboard` to make it easier to
|
||||||
add to a bundle.
|
add to a bundle.
|
||||||
#. Create a `awesome_dashboard.dashboard` assets bundle containing all content of
|
#. Create a `awesome_dashboard.dashboard` assets bundle containing all content of
|
||||||
the `/dashboard` folder
|
the `/dashboard` folder.
|
||||||
#. Modify `dashboard.js` to register itself in the `lazy_components` registry, and not
|
#. Modify :file:`dashboard.js` to register itself in the `lazy_components` registry, and not
|
||||||
in the `action` registry.
|
in the `action` registry.
|
||||||
#. Add in `src/` a file `dashboard_action` that import `LazyComponent` and register
|
#. Add in :file:`src/` a file :file:`dashboard_action` that imports `LazyComponent` and registers
|
||||||
it to the `action` registry
|
it to the `action` registry
|
||||||
|
|
||||||
9. Making our dashboard generic
|
9. Making our dashboard generic
|
||||||
@ -342,15 +339,15 @@ then use the `LazyComponent` (located in `@web/core/assets`).
|
|||||||
|
|
||||||
So far, we have a nice working dashboard. But it is currently hardcoded in the dashboard
|
So far, we have a nice working dashboard. But it is currently hardcoded in the dashboard
|
||||||
template. What if we want to customize our dashboard? Maybe some users have different
|
template. What if we want to customize our dashboard? Maybe some users have different
|
||||||
needs, and want to see some other data.
|
needs and want to see other data.
|
||||||
|
|
||||||
So, the next step is then to make our dashboard generic: instead of hardcoding its content
|
So, the next step is to make our dashboard generic: instead of hard-coding its content
|
||||||
in the template, it can just iterate over a list of dashboard items. But then, many
|
in the template, it can just iterate over a list of dashboard items. But then, many
|
||||||
questions comes up: how to represent a dashboard item, how to register it, what data
|
questions come up: how to represent a dashboard item, how to register it, what data
|
||||||
should it receive, and so on. There are many different ways to design such a system,
|
should it receive, and so on. There are many different ways to design such a system,
|
||||||
with different trade offs.
|
with different trade-offs.
|
||||||
|
|
||||||
For this tutorial, we will say that a dashboard item is an object with the folowing structure:
|
For this tutorial, we will say that a dashboard item is an object with the following structure:
|
||||||
|
|
||||||
.. code-block:: js
|
.. code-block:: js
|
||||||
|
|
||||||
@ -367,7 +364,7 @@ For this tutorial, we will say that a dashboard item is an object with the folow
|
|||||||
};
|
};
|
||||||
|
|
||||||
The `description` value will be useful in a later exercise to show the name of items that the
|
The `description` value will be useful in a later exercise to show the name of items that the
|
||||||
user can choose to add to his dashboard. The `size` number is optional, and simply describes
|
user can add to their dashboard. The `size` number is optional, and simply describes
|
||||||
the size of the dashboard item that will be displayed. Finally, the `props` function is optional.
|
the size of the dashboard item that will be displayed. Finally, the `props` function is optional.
|
||||||
If not given, we will simply give the `statistics` object as data. But if it is defined, it will
|
If not given, we will simply give the `statistics` object as data. But if it is defined, it will
|
||||||
be used to compute specific props for the component.
|
be used to compute specific props for the component.
|
||||||
@ -383,16 +380,16 @@ The goal is to replace the content of the dashboard with the following snippet:
|
|||||||
</DashboardItem>
|
</DashboardItem>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
Note that the above example features two advanced features of Owl: dynamic components, and dynamic props.
|
Note that the above example features two advanced features of Owl: dynamic components and dynamic props.
|
||||||
|
|
||||||
We currently have two kinds of item components: number cards, with a title and a number, and pie cards, with
|
We currently have two kinds of item components: number cards with a title and a number, and pie cards with
|
||||||
some label and a pie chart.
|
some label and a pie chart.
|
||||||
|
|
||||||
#. create and implement two components: `NumberCard` and `PieChartCard`, with the corresponding props
|
#. Create and implement two components: `NumberCard` and `PieChartCard`, with the corresponding props.
|
||||||
#. create a file `dashboard_items.js` in which you define and export a list of items, using `NumberCard`
|
#. Create a file :file:`dashboard_items.js` in which you define and export a list of items, using `NumberCard`
|
||||||
and `PieChartCard` to recreate our current dashboard
|
and `PieChartCard` to recreate our current dashboard.
|
||||||
#. import that list of items in our `Dashboard` component, add it to the component, and update the template
|
#. Import that list of items in our `Dashboard` component, add it to the component, and update the template
|
||||||
to use a `t-foreach` like shown above
|
to use a `t-foreach` like shown above.
|
||||||
|
|
||||||
.. code-block:: js
|
.. code-block:: js
|
||||||
|
|
||||||
@ -410,27 +407,26 @@ However, the content of our item list is still hardcoded. Let us fix that by usi
|
|||||||
#. Instead of exporting a list, register all dashboard items in a `awesome_dashboard` registry
|
#. Instead of exporting a list, register all dashboard items in a `awesome_dashboard` registry
|
||||||
#. Import all the items of the `awesome_dashboard` registry in the `Dashboard` component
|
#. Import all the items of the `awesome_dashboard` registry in the `Dashboard` component
|
||||||
|
|
||||||
The dashboard is now easily extensible. Any other odoo addon that want to register a new item to the
|
The dashboard is now easily extensible. Any other Odoo addon that wants to register a new item to the
|
||||||
dashboard can just add it to the registry.
|
dashboard can just add it to the registry.
|
||||||
|
|
||||||
11. Add and remove dashboard items
|
11. Add and remove dashboard items
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
Let us see how we can make our dashboard customizable. To make it simple, we will save the user
|
Let us see how we can make our dashboard customizable. To make it simple, we will save the user
|
||||||
dashboard configuration in the local storage, so it is persistent, but we don't have to deal
|
dashboard configuration in the local storage so that it is persistent, but we don't have to deal
|
||||||
with the server for now.
|
with the server for now.
|
||||||
|
|
||||||
The dashboard configuration will be saved as a list of removed item ids.
|
The dashboard configuration will be saved as a list of removed item ids.
|
||||||
|
|
||||||
#. Add a button in the control panel with a gear icon, to indicate that it is a settings button
|
#. Add a button in the control panel with a gear icon to indicate that it is a settings button.
|
||||||
#. Clicking on that button should open a dialog
|
#. Clicking on that button should open a dialog.
|
||||||
#. In that dialog, we want to see a list of all existing dashboard items, each with a checkbox
|
#. In that dialog, we want to see a list of all existing dashboard items, each with a checkbox.
|
||||||
#. There should be a `Apply` button in the footer. Clicking on it will build a list of all item ids
|
#. There should be a `Apply` button in the footer. Clicking on it will build a list of all item ids
|
||||||
that are unchecked
|
that are unchecked.
|
||||||
#. We want to store that value in the local storage
|
#. We want to store that value in the local storage.
|
||||||
#. And modify the `Dashboard` component to filter the current items by removing the ids of items
|
#. And modify the `Dashboard` component to filter the current items by removing the ids of items
|
||||||
from the configuration
|
from the configuration.
|
||||||
|
|
||||||
|
|
||||||
.. image:: 02_web_framework/items_configuration.png
|
.. image:: 02_web_framework/items_configuration.png
|
||||||
:width: 80%
|
:width: 80%
|
||||||
@ -443,10 +439,10 @@ Here is a list of some small improvements you could try to do if you have the ti
|
|||||||
|
|
||||||
#. Make sure your application can be :ref:`translated <reference/translations>` (with
|
#. Make sure your application can be :ref:`translated <reference/translations>` (with
|
||||||
`env._t`).
|
`env._t`).
|
||||||
#. Clicking on a section of the pie chart should open a list view of all orders which have the
|
#. Clicking on a section of the pie chart should open a list view of all orders that have the
|
||||||
corresponding size.
|
corresponding size.
|
||||||
#. Save the content of the dashboard in a user settings on the server!
|
#. Save the content of the dashboard in a user setting on the server!
|
||||||
#. Make it responsive: in mobile mode, each card should take 100% of the width
|
#. Make it responsive: in mobile mode, each card should take 100% of the width.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
- `Example: use of env._t function
|
- `Example: use of env._t function
|
||||||
|
Loading…
Reference in New Issue
Block a user