[IMP] tutorials/js_framework: reword, clarify and improve instructions

X-original-commit: b58dbb1fbb
Part-of: odoo/documentation#4281
This commit is contained in:
Antoine Vandevenne (anv)
2023-04-03 15:13:27 +00:00
parent 1ab6782608
commit 4815658419
4 changed files with 208 additions and 202 deletions

View File

@@ -43,15 +43,15 @@ button.
import { Component, useState } from "@odoo/owl";
class Counter extends Component {
static template = "my_module.Counter";
static template = "my_module.Counter";
setup() {
state = useState({ value: 0 });
}
setup() {
state = useState({ value: 0 });
}
increment() {
this.state.value++;
}
increment() {
this.state.value++;
}
}
The `Counter` component specifies the name of the template to render. The template is written in XML
@@ -80,9 +80,9 @@ route with your browser.
.. exercise::
#. Modify :file:`playground.js` so that it acts as a counter like in the example above. You will
need to use the `useState
<{OWL_PATH}/doc/reference/hooks.md#usestate>`_ function so that the component is re-rendered
whenever any part of the state object has been read by this component is modified.
need to use the `useState hook
<{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.
#. In the same component, create an `increment` method.
#. Modify the template in :file:`playground.xml` so that it displays your counter variable. Use
`t-esc <{OWL_PATH}/doc/reference/templates.md#outputting-data>`_ to output the data.
@@ -109,8 +109,8 @@ For now we have the logic of a counter in the `Playground` component, let us see
#. Extract the counter code from the `Playground` component into a new `Counter` component.
#. You can do it in the same file first, but once it's done, update your code to move the
`Counter` in its own file.
#. Make sure the template is in its own file, with the same name.
`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.
.. important::
Don't forget :code:`/** @odoo-module **/` in your JavaScript files. More information on this can
@@ -129,7 +129,7 @@ todos. This will be done incrementally in multiple exercises that will introduce
**3. buy milk**.
#. Add the Bootstrap classes `text-muted` and `text-decoration-line-through` on the task if it is
done. To do that, you can use `dynamic attributes
<{OWL_PATH}/doc/reference/templates.md#dynamic-attributes>`_
<{OWL_PATH}/doc/reference/templates.md#dynamic-attributes>`_.
#. Modify :file:`owl_playground/static/src/playground.js` and
:file:`owl_playground/static/src/playground.xml` to display your new `Todo` component with
some hard-coded props to test it first.
@@ -157,7 +157,7 @@ The `Todo` component has an implicit API. It expects to receive in its props the
todo object in a specified format: `id`, `description` and `done`. Let us make that API more
explicit. We can add a props definition that will let Owl perform a validation step in `dev mode
<{OWL_PATH}/doc/reference/app.md#dev-mode>`_. You can activate the dev mode in the `App
configuration <{OWL_PATH}/doc/reference/app.md#configuration>`_
configuration <{OWL_PATH}/doc/reference/app.md#configuration>`_.
It is a good practice to do props validation for every component.
@@ -165,8 +165,9 @@ configuration <{OWL_PATH}/doc/reference/app.md#configuration>`_
#. Add `props validation <{OWL_PATH}/doc/reference/props.md#props-validation>`_ to the `Todo`
component.
#. Make sure it passes in dev mode which is activated by default in `owl_playground`. The dev
mode can be activated and deactivated by modifying the `dev` attribute in the in the `config`
#. Open the :guilabel:`Console` tab of your browser's dev tools and make sure the props
validation passes in dev mode, which is activated by default in `owl_playground`. The dev mode
can be activated and deactivated by modifying the `dev` attribute in the in the `config`
parameter of the `mount <{OWL_PATH}/doc/reference/app.md#mount-helper>`_ function in
:file:`owl_playground/static/src/main.js`.
#. Remove `done` from the props and reload the page. The validation should fail.
@@ -179,8 +180,9 @@ list.
.. exercise::
#. Change the code to display a list of todos instead of just one, and use `t-foreach
<{OWL_PATH}/doc/reference/templates.md#loops>`_ in the template.
#. Change the code to display a list of todos instead of just one. Create a new `TodoList`
component to hold the `Todo` components and use `t-foreach
<{OWL_PATH}/doc/reference/templates.md#loops>`_ in its template.
#. Think about how it should be keyed with the `t-key` directive.
.. image:: 01_owl_components/todo_list.png
@@ -197,17 +199,15 @@ a todo to the list.
#. Add an input above the task list with placeholder *Enter a new task*.
#. Add an `event handler <{OWL_PATH}/doc/reference/event_handling.md>`_ on the `keyup` event
named ``addTodo``.
named `addTodo`.
#. Implement `addTodo` to check if enter was pressed (:code:`ev.keyCode === 13`), and in that
case, create a new todo with the current content of the input as the description.
#. Make sure it has a unique id. It can be just a counter that increments at each todo.
#. Then, clear the input of all content.
case, create a new todo with the current content of the input as the description and clear the
input of all content.
#. Make sure the todo has a unique id. It can be just a counter that increments at each todo.
#. Wrap the todo list in a `useState` hook to let Owl know that it should update the UI when the
list is modified.
#. Bonus point: don't do anything if the input is empty.
.. note::
Notice that nothing updates in the UI: this is because Owl does not know that it should update
the UI. This can be fixed by wrapping the todo list in a `useState` hook.
.. code-block:: javascript
this.todos = useState([]);
@@ -228,9 +228,10 @@ Let's see how we can access the DOM with `t-ref <{OWL_PATH}/doc/reference/refs.m
.. exercise::
#. Focus the `input` from the previous exercise when the dashboard is `mounted
<{OWL_PATH}/doc/reference/component.md#mounted>`_.
<{OWL_PATH}/doc/reference/component.md#mounted>`_. This this should be done from the
`TodoList` component.
#. Bonus point: extract the code into a specialized `hook <{OWL_PATH}/doc/reference/hooks.md>`_
`useAutofocus`.
`useAutofocus` in a new :file:`owl_playground/utils.js` file.
.. seealso::
`Owl: Component lifecycle <{OWL_PATH}/doc/reference/component.md#lifecycle>`_
@@ -248,6 +249,11 @@ way to do this is by using a `callback prop
#. Add an input with the attribute :code:`type="checkbox"` before the id of the task, which must
be checked if the state `done` is true.
.. tip::
QWeb does not create attributes computed with the `t-att` directive it it evaluates to a
falsy value.
#. Add a callback props `toggleState`.
#. Add a `click` event handler on the input in the `Todo` component and make sure it calls the
`toggleState` function with the todo id.
@@ -265,12 +271,12 @@ The final touch is to let the user delete a todo.
.. exercise::
#. Add a new callback prop `removeTodo`.
#. Insert :code:`<span class="fa fa-remove">` in the template of the `Todo` component.
#. Insert :code:`<span class="fa fa-remove"/>` in the template of the `Todo` component.
#. Whenever the user clicks on it, it should call the `removeTodo` method.
.. tip::
If you're using an array to store your todo list, you can use the JavaScript `splice` function
to remove a todo from it.
.. tip::
If you're using an array to store your todo list, you can use the JavaScript `splice`
function to remove a todo from it.
.. code-block::
@@ -285,15 +291,18 @@ The final touch is to let the user delete a todo.
:scale: 70%
:align: center
10. Generic components with slots
=================================
.. _tutorials/discover_js_framework/generic_card:
10. Generic card with slots
===========================
Owl has a powerful `slot <{OWL_PATH}/doc/reference/slots.md>`_ system to allow you to write generic
components. This is useful to factorize the common layout between different parts of the interface.
.. exercise::
#. Write a `Card` component using the following Bootstrap HTML structure:
#. Insert a new `Card` component between the `Counter` and `Todolist` components. Use the
following Bootstrap HTML structure for the card:
.. code-block:: html
@@ -310,18 +319,15 @@ components. This is useful to factorize the common layout between different part
</div>
#. This component should have two slots: one slot for the title, and one for the content (the
default slot).
default slot). It should be possible to use the `Card` component as follows:
.. example::
Here is how one could use it:
.. code-block:: html
.. code-block:: html
<Card>
<t t-set-slot="title">Card title</t>
<p class="card-text">Some quick example text...</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</Card>
<Card>
<t t-set-slot="title">Card title</t>
<p class="card-text">Some quick example text...</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</Card>
#. Bonus point: if the `title` slot is not given, the `h5` should not be rendered at all.
@@ -332,8 +338,8 @@ components. This is useful to factorize the common layout between different part
.. seealso::
`Bootstrap: documentation on cards <https://getbootstrap.com/docs/5.2/components/card/>`_
11. Go further
==============
11. Extensive props validation
==============================
.. exercise::

View File

@@ -21,7 +21,7 @@ about the Odoo JavaScript framework in its entirety, as used by the web client.
:width: 50%
For this chapter, we will start from the empty dashboard provided by the `awesome_tshirt`
addon. We will progressively add features to it, using the odoo framework.
addon. We will progressively add features to it, using the Odoo JavaScript framework.
.. admonition:: Goal
@@ -38,14 +38,18 @@ addon. We will progressively add features to it, using the odoo framework.
===============
Most screens in the Odoo web client uses a common layout: a control panel on top, with some buttons,
and a main content zone just below. This is done using a `Layout component
and a main content zone just below. This is done using the `Layout component
<{GITHUB_PATH}/addons/web/static/src/search/layout.js>`_, available in `@web/search/layout`.
.. exercise::
Update the `AwesomeDashboard` component located in :file:`awesome_tshirt/static/src/` to use the
`Layout` component. You can use :code:`{ "top-right": false, "bottom-right": false }` for the
`display` props of the `Layout` component.
`Layout` component. You can use
:code:`{controlPanel: { "top-right": false, "bottom-right": false } }` for the `display` props of
the `Layout` component.
Open http://localhost:8069/web, then open the :guilabel:`Awesome T-Shirts` app, and see the
result.
.. image:: 02_web_framework/new_layout.png
:align: center
@@ -61,12 +65,11 @@ and a main content zone just below. This is done using a `Layout component
2. Add some buttons for quick navigation
========================================
Bafien Carpink want buttons for easy access to common views in Odoo. In order to do that, you will
need to use the action service.
Let us now use the action service for an easy access to the common views in Odoo.
:ref:`Services <frontend/services>` is a notion defined by the Odoo JavaScript framework, it is a
persistent piece of code that exports state and/or functions. Each service can depend on other
services, and components can import a service with the `useService()` hooks.
:ref:`Services <frontend/services>` is a notion defined by the Odoo JavaScript framework; it is a
persistent piece of code that exports a state and/or functions. Each service can depend on other
services, and components can import a service with the `useService()` hook.
.. example::
@@ -92,9 +95,17 @@ services, and components can import a service with the `useService()` hooks.
exists, so you should use `its xml id
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
odoo/addons/base/views/res_partner_views.xml#L525>`_).
#. A button `New Orders`, which opens a list view with all orders created in the last 7 days.
#. A button `New Orders`, which opens a list view with all orders created in the last 7 days. Use
the `Domain <https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
odoo/addons/web/static/src/core/domain.js#L19>`_ helper class to represent the domain.
.. tip::
One way to represent the desired domain could be
:code:`[('create_date','>=', (context_today() - datetime.timedelta(days=7)).strftime('%Y-%m-%d'))]`
#. A button `Cancelled Order`, which opens a list of all orders created in the last 7 days, but
already cancelled.
already cancelled. Rather than defining the action twice, factorize it in a new `openOrders`
method.
.. image:: 02_web_framework/navigation_buttons.png
:align: center
@@ -109,9 +120,10 @@ services, and components can import a service with the `useService()` hooks.
3. Call the server, add some statistics
=======================================
Let's improve the dashboard by adding a few cards (see the `Card` component made in the Owl
training) containing a few statistics. There is a route `/awesome_tshirt/statistics` that will
perform some computations and return an object containing some useful information.
Let's improve the dashboard by adding a few cards (see the `Card` component :ref:`made in the
previous chapter <tutorials/discover_js_framework/generic_card>`) containing a few statistics. There
is a route `/awesome_tshirt/statistics` that performs some computations and returns an object
containing some useful information.
Whenever we need to call a specific controller, we need to use the :ref:`rpc service
<frontend/services/rpc>`. It only exports a single function that perform the request:
@@ -162,11 +174,11 @@ Here is a short explanation on the various arguments:
4. Cache network calls, create a service
========================================
If you open your browser dev tools, in the network tabs, you will probably see that the call to
If you open the :guilabel:`Network` tab of your browser's dev tools, you will see that the call to
`/awesome_tshirt/statistics` is done every time the client action is displayed. This is because the
`onWillStart` hook is called each time the `Dashboard` component is mounted. But in this case, we
would probably prefer to do it only the first time, so we actually need to maintain some state
outside of the `Dashboard` component. This is a nice use case for a service!
would prefer to do it only the first time, so we actually need to maintain some state outside of the
`Dashboard` component. This is a nice use case for a service!
.. example::
@@ -188,13 +200,12 @@ outside of the `Dashboard` component. This is a nice use case for a service!
.. exercise::
#. Implements a new `awesome_tshirt.statistics` service.
#. Register and import a new `awesome_tshirt.statistics` service.
#. It should provide a function `loadStatistics` that, once called, performs the actual rpc, and
always return the same information.
#. Maybe 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
`@web/core/utils/functions`
`@web/core/utils/functions` that will allow caching the statistics.
#. Use this service in the `Dashboard` component.
#. Check that it works as expected
@@ -206,13 +217,13 @@ outside of the `Dashboard` component. This is a nice use case for a service!
5. Display a pie chart
======================
Everyone likes charts (!), so let us add a pie chart in our dashboard, which displays the
Everyone likes charts (!), so let us add a pie chart in our dashboard. It will display the
proportions of t-shirts sold for each size: S/M/L/XL/XXL.
For this exercise, we will use `Chart.js <https://www.chartjs.org/>`_. It is the chart library used
by the graph view. However, it is not loaded by default, so we will need to either add it to our
assets bundle, or lazy load it (it's usually better since our users will not have to load the
chartjs code every time if they don't need it).
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.
.. exercise::
#. Load chartjs, you can use the `loadJs