mirror of
https://github.com/odoo/documentation.git
synced 2026-01-04 10:46:04 +07:00
[IMP] tutorials/js_framework: reword, clarify and improve instructions
Part-of: odoo/documentation#3994
This commit is contained in:
@@ -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::
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user