[IMP] tutorials/js_framework: reword, clarify and improve instructions
X-original-commit: b58dbb1fbb
Part-of: odoo/documentation#4280
This commit is contained in:
parent
99049dc9fb
commit
fd881b4533
@ -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
|
||||
|
@ -99,31 +99,28 @@ views (namely: `form`, `list`, `kanban`) by using the `widget` attribute.
|
||||
|
||||
<field name="preview_moves" widget="account_resequence_widget"/>
|
||||
|
||||
.. _tutorials/master_odoo_web_framework/image_preview_field:
|
||||
|
||||
1. An `image_preview` field
|
||||
===========================
|
||||
|
||||
Each new order on the website will be created as an `awesome_tshirt.order`. This model has a
|
||||
`image_url` field (of type `char`), which is currently only visible as a string. We want to be able
|
||||
to see it in the form view.
|
||||
|
||||
For this task, we need to create a new field component `image_preview`. This component is
|
||||
specified as follows: In readonly mode, it is only an image tag with the correct `src` if the field
|
||||
is set; In edit mode, it also behaves like classical `char` fields (you can use the `CharField` in
|
||||
your template by passing it in the props). An `input` should be displayed with the text value of the
|
||||
field, so it can be edited.
|
||||
to see the image itself in the form view.
|
||||
|
||||
.. exercise::
|
||||
|
||||
#. Create a new `ImagePreview` component and use the `CharField` component in your template. You
|
||||
can use `t-props
|
||||
<{OWL_PATH}/doc/reference/props.md#dynamic-props>`_ to pass props
|
||||
received by `ImagePreview` to `CharField`.
|
||||
#. Register your field in the proper :ref:`registry <frontend/registries>`.
|
||||
#. Update the arch of the form view to use your new field by setting the `widget` attribute.
|
||||
#. Create a new `ImagePreview` component and register it in the proper :ref:`registry
|
||||
<frontend/registries>`. Use the `CharField` component in your template. You can use `t-props
|
||||
<{OWL_PATH}/doc/reference/props.md#dynamic-props>`_ to pass props received by `ImagePreview`
|
||||
to `CharField`. Update the arch of the form view to use your new field by setting the `widget`
|
||||
attribute.
|
||||
#. Change the code of the `ImagePreview` component so that the image is displayed below the URL.
|
||||
#. When the field is readonly, only the image should be displayed and the URL should be hidden.
|
||||
|
||||
.. note::
|
||||
It is possible to solve this exercise by inheriting `CharField` , but the goal of this exercise
|
||||
is to create a field from scratch.
|
||||
It is possible to solve this exercise by inheriting `CharField`, but the goal of this exercise is
|
||||
to create a field from scratch.
|
||||
|
||||
.. image:: 01_fields_and_views/image_field.png
|
||||
:align: center
|
||||
@ -136,11 +133,12 @@ field, so it can be edited.
|
||||
2. Improving the `image_preview` field
|
||||
======================================
|
||||
|
||||
We want to improve the field of the previous task to help the staff recognize orders for which some
|
||||
action should be done.
|
||||
|
||||
.. exercise::
|
||||
|
||||
We want to improve the field of the previous task to help the staff recognize orders for which
|
||||
some action should be done. In particular, we want to display a warning "MISSING TSHIRT DESIGN"
|
||||
in red if there is no image URL specified on the order.
|
||||
Display a warning "MISSING TSHIRT DESIGN" in red if there is no image URL specified on the order.
|
||||
|
||||
.. image:: 01_fields_and_views/missing_image.png
|
||||
:align: center
|
||||
@ -150,9 +148,9 @@ field, so it can be edited.
|
||||
|
||||
Let's see how to use inheritance to extend an existing component.
|
||||
|
||||
There is a `is_late`, readonly, boolean field on the task model. That would be useful information to
|
||||
see on the list/kanban/view. Then, let us say that we want to add a red word "Late!" next to it
|
||||
whenever it is set to true.
|
||||
There is a `is_late`, readonly, boolean field on the order model. That would be useful information
|
||||
to see on the list/kanban/view. Then, let us say that we want to add a red word "Late!" next to it
|
||||
whenever it is set to `true`.
|
||||
|
||||
.. exercise::
|
||||
|
||||
@ -166,11 +164,8 @@ whenever it is set to true.
|
||||
:align: center
|
||||
|
||||
.. seealso::
|
||||
|
||||
- `Example: A field inheriting another (JS)
|
||||
<{GITHUB_PATH}/addons/account/static/src/components/account_type_selection/account_type_selection.js>`_
|
||||
- `Example: A field inheriting another (XML)
|
||||
<{GITHUB_PATH}/addons/account/static/src/components/account_type_selection/account_type_selection.xml>`_
|
||||
- `Example: A field inheriting another
|
||||
<{GITHUB_PATH}/addons/account/static/src/components/account_type_selection>`_
|
||||
- :ref:`Documentation on xpath <reference/views/inheritance>`
|
||||
|
||||
4. Message for some customers
|
||||
@ -181,14 +176,17 @@ insert arbitrary components in the form view. Let us see how we can use it.
|
||||
|
||||
.. exercise::
|
||||
|
||||
For a super efficient workflow, we would like to display a message/warning box with some
|
||||
information in the form view, with specific messages depending on some conditions:
|
||||
For a super efficient workflow, we would like to display an alert block with specific messages
|
||||
depending on some conditions:
|
||||
|
||||
- If the `image_url` field is not set, it should display "No image".
|
||||
- If the amount of the order is higher than 100 euros, it should display "Add promotional
|
||||
- If the `amount` of the order is higher than 100 euros, it should display "Add promotional
|
||||
material".
|
||||
- Make sure that your widget is updated in real time.
|
||||
|
||||
.. tip::
|
||||
Try to evaluate `props.record` in the :guilabel:`Console` tab of your browser's dev tools.
|
||||
|
||||
.. image:: 01_fields_and_views/warning_widget.png
|
||||
:align: center
|
||||
|
||||
@ -197,26 +195,26 @@ insert arbitrary components in the form view. Let us see how we can use it.
|
||||
- `Example: Using the tag <widget> in a form view
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
addons/calendar/views/calendar_views.xml#L197>`_
|
||||
- `Example: Implementation of a widget (JS)
|
||||
<{GITHUB_PATH}/addons/web/static/src/views/widgets/week_days/week_days.js>`_
|
||||
- `Example: Implementation of a widget (XML)
|
||||
<{GITHUB_PATH}/addons/web/static/src/views/widgets/week_days/week_days.xml>`_
|
||||
- `Example: Implementation of a widget
|
||||
<{GITHUB_PATH}/addons/web/static/src/views/widgets/week_days>`_
|
||||
|
||||
5. Use `markup`
|
||||
===============
|
||||
|
||||
Let's see how we can display raw HTML in a template. Before, there was a `t-raw` directive that
|
||||
would just output anything as HTML. This was unsafe, and has been replaced by a `t-out
|
||||
<{OWL_PATH}/doc/reference/templates.md#outputting-data>`_ directive that acts like a `t-esc` unless
|
||||
the data has been marked explicitly with a `markup` function.
|
||||
Let’s see how we can display raw HTML in a template. The `t-out` directive can be used for that
|
||||
propose. Indeed, `it generally acts like t-esc, unless the data has been marked explicitly with a
|
||||
markup function <{OWL_PATH}/doc/reference/templates.md#outputting-data>`_. In that case, its value
|
||||
is injected as HTML.
|
||||
|
||||
.. exercise::
|
||||
|
||||
#. Modify the previous exercise to put the `image` and `material` words in bold.
|
||||
#. The warnings should be markuped, and the template should be modified to use `t-out`.
|
||||
#. Import the `markup` function from Owl and, for each message, replace it with a call of the
|
||||
function with the message passed as argument.
|
||||
|
||||
.. note::
|
||||
This is an example of a safe use of `t-out` , since the string is static.
|
||||
This is an example of a safe use of `t-out`, since the string is static.
|
||||
|
||||
.. image:: 01_fields_and_views/warning_widget2.png
|
||||
:align: center
|
||||
@ -311,18 +309,19 @@ The view description can define a `props` function, which receives the standard
|
||||
the base props of the concrete view. The `props` function is executed only once, and can be thought
|
||||
of as being some kind of factory. It is useful to parse the `arch` XML document, and to allow the
|
||||
view to be parameterized (for example, it can return a Renderer component that will be used as
|
||||
Renderer), but then it makes it easy to customize the specific renderer used by a sub view.
|
||||
Renderer). Then, it is easy to customize the specific renderer used by a sub view.
|
||||
|
||||
These props will be extended before being given to the Controller. In particular, the search props
|
||||
The props will be extended before being given to the Controller. In particular, the search props
|
||||
(domain/context/groupby) will be added.
|
||||
|
||||
Then, the root component, commonly called the `Controller`, coordinates everything. It uses the
|
||||
Finally, the root component, commonly called the `Controller`, coordinates everything. It uses the
|
||||
generic `Layout` component (to add a control panel), instantiates a `Model`, and uses a `Renderer`
|
||||
component in the `Layout` default slot. The `Model` is tasked with loading and updating data, and
|
||||
the `Renderer` is supposed to handle all rendering work, along with all user interactions.
|
||||
|
||||
In practice, once the t-shirt order is printed, we need to print a label to put on the package. To
|
||||
do that, let us add a button in the order form view control panel which will call a model method.
|
||||
do that, let us add a button in the order's form view's control panel, which will call a model
|
||||
method.
|
||||
|
||||
There is a service dedicated to calling models methods: `orm_service`, located in
|
||||
`core/orm_service.js`. It provides a way to call common model methods, as well as a generic
|
||||
@ -343,18 +342,27 @@ There is a service dedicated to calling models methods: `orm_service`, located i
|
||||
|
||||
.. exercise::
|
||||
|
||||
#. Create a customized form view extending the web form view and register it as
|
||||
#. Create a customized form view extending the `web` form view and register it as
|
||||
`awesome_tshirt.order_form_view`.
|
||||
#. Add a `js_class` attribute to the arch of the form view so Odoo will load it.
|
||||
#. Create a new template inheriting from the form controller template to add a button after the
|
||||
create button.
|
||||
#. Add a button. Clicking on this button should call the method `print_label` from the model
|
||||
`awesome_tshirt.order` with the proper id. Note: `print_label` is a mock method, it only
|
||||
displays a message in the logs.
|
||||
#. The button should be disabled if the current order is in `create` mode (i.e., it does not
|
||||
#. Add a `js_class="awesome_tshirt.order_form_view"` attribute to the arch of the form view so
|
||||
that Odoo will load it.
|
||||
#. Create a new template inheriting from the form controller template and add a "Print Label"
|
||||
button after the "New" button.
|
||||
#. Clicking on this button should call the method `print_label` from the model
|
||||
`awesome_tshirt.order` with the proper id.
|
||||
|
||||
.. note::
|
||||
`print_label` is a mock method; it only displays a message in the logs.
|
||||
|
||||
#. The button should not be disabled if the current order is in `create` mode (i.e., it does not
|
||||
exist yet).
|
||||
|
||||
.. tip::
|
||||
Log `this.props.resId` and `this.model.root.resId` and compare the two values before and
|
||||
after entering `create` mode.
|
||||
|
||||
#. The button should be displayed as a primary button if the customer is properly set and if the
|
||||
task stage is `printed`. Otherwise, it is displayed as a secondary button.
|
||||
task stage is `printed`. Otherwise, it should be displayed as a secondary button.
|
||||
#. Bonus point: clicking twice on the button should not trigger 2 RPCs.
|
||||
|
||||
.. image:: 01_fields_and_views/form_button.png
|
||||
@ -371,6 +379,9 @@ There is a service dedicated to calling models methods: `orm_service`, located i
|
||||
- `Code: orm service <{GITHUB_PATH}/addons/web/static/src/core/orm_service.js>`_
|
||||
- `Example: Using the orm service
|
||||
<{GITHUB_PATH}/addons/account/static/src/components/open_move_widget/open_move_widget.js>`_
|
||||
- `Code: useDebounced hook
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
addons/web/static/src/core/utils/timing.js#L117>`_
|
||||
|
||||
7. Auto-reload the kanban view
|
||||
==============================
|
||||
|
@ -67,7 +67,7 @@ printer is not connected or ran out of paper).
|
||||
:scale: 60%
|
||||
|
||||
.. seealso::
|
||||
`Example: Code using the notification service
|
||||
`Example: Using the notification service
|
||||
<{GITHUB_PATH}/addons/web/static/src/views/fields/image_url/image_url_field.js>`_
|
||||
|
||||
2. Add a systray item
|
||||
@ -107,7 +107,6 @@ do that by calling periodically (for example, every minute) the server to reload
|
||||
|
||||
.. exercise::
|
||||
|
||||
#. Modify the systray item code to get its data from the `tshirt` service.
|
||||
#. The `tshirt` service should periodically reload its data.
|
||||
|
||||
Now, the question arises: how is the systray item notified that it should re-render itself? It can
|
||||
@ -115,11 +114,12 @@ be done in various ways but, for this training, we choose to use the most *decla
|
||||
|
||||
.. exercise::
|
||||
|
||||
#. Modify the `tshirt` service to return a `reactive
|
||||
2. Modify the `tshirt` service to return a `reactive
|
||||
<{OWL_PATH}/doc/reference/reactivity.md#reactive>`_ object. Reloading data should update the
|
||||
reactive object in place.
|
||||
#. The systray item can then perform a `useState` on the service return value.
|
||||
#. This is not really necessary, but you can also *package* the calls to `useService` and
|
||||
3. The systray item can then perform a `useState
|
||||
<{OWL_PATH}/doc/reference/reactivity.md#usestate>`_ on the service return value.
|
||||
4. This is not really necessary, but you can also *package* the calls to `useService` and
|
||||
`useState` in a custom hook `useStatistics`.
|
||||
|
||||
.. seealso::
|
||||
@ -137,48 +137,45 @@ by pressing `CTRL+K` in the Odoo interface.
|
||||
|
||||
.. exercise::
|
||||
|
||||
Let us modify the image preview field (from a previous exercise) to add a command to the command
|
||||
palette to open the image in a new browser tab (or window).
|
||||
Modify the :ref:`image preview field <tutorials/master_odoo_web_framework/image_preview_field>`
|
||||
to add a command to the command palette to open the image in a new browser tab (or window).
|
||||
|
||||
Make sure that the command is only active whenever a field preview is visible in the screen.
|
||||
Ensure the command is only active whenever a field preview is visible on the screen.
|
||||
|
||||
.. image:: 02_miscellaneous/new_command.png
|
||||
:align: center
|
||||
|
||||
.. seealso::
|
||||
- `Example: Using the useCommand hook
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
addons/web/static/src/core/debug/debug_menu.js#L15>`_
|
||||
- `Code: The command service
|
||||
<{GITHUB_PATH}/addons/web/static/src/core/commands/command_service.js>`_
|
||||
`Example: Using the useCommand hook
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
addons/web/static/src/core/debug/debug_menu.js#L15>`_
|
||||
|
||||
5. Monkey patching a component
|
||||
==============================
|
||||
|
||||
Often, it is possible to do what we want by using existing extension points that allow
|
||||
customization, such as registering something in a registry. But it happens that we want to modify
|
||||
something that has no such mechanism. In that case, we have to fall back on a less safe form of
|
||||
Often, we can achieve what we want by using existing extension points that allow for customization,
|
||||
such as registering something in a registry. Sometimes, however, it happens that we want to modify
|
||||
something that has no such mechanism. In that case, we must fall back on a less safe form of
|
||||
customization: monkey patching. Almost everything in Odoo can be monkey patched.
|
||||
|
||||
Bafien, our beloved leader, heard that employees perform better if they are constantly being
|
||||
watched. Since he is not able to be there in person for each and every one of his employees, he
|
||||
tasked you with the following: update the user interface to add a blinking red eye in the control
|
||||
panel. Clicking on that eye should open a dialog with the following message: "Bafien is watching
|
||||
you. This interaction is recorded and may be used in legal proceedings if necessary. Do you agree to
|
||||
these terms?".
|
||||
Bafien, our beloved leader, heard about employees performing better if they are constantly being
|
||||
watched. Since he cannot be there in person for each of his employees, he tasked you with updating
|
||||
the user interface to add a blinking red eye in the control panel. Clicking on that eye should open
|
||||
a dialog with the following message: "Bafien is watching you. This interaction is recorded and may
|
||||
be used in legal proceedings if necessary. Do you agree to these terms?"
|
||||
|
||||
.. exercise::
|
||||
|
||||
#. Create the :file:`control_panel_patch.js` file, as well as corresponding CSS and XML files.
|
||||
#. :doc:`Patch </developer/reference/frontend/patching_code>` the `ControlPanel` template to add
|
||||
an icon next to the breadcrumbs. You might want to use the `fa-eye` or `fa-eyes` icons. Make
|
||||
sure it is visible in all views!
|
||||
|
||||
.. tip::
|
||||
There are two ways to inherit a template using XPath: by specifying
|
||||
`t-inherit-mode="primary"`, which creates a new, independent template with the desired
|
||||
modifications, or by using `t-inherit-mode="extension"`, which modifies the original
|
||||
template in place.
|
||||
#. :ref:`Inherit <reference/qweb/template_inheritance>` the `web.Breadcrumbs` template of the
|
||||
`ControlPanel component <{GITHUB_PATH}/addons/web/static/src/search/control_panel>`_ to add an
|
||||
icon next to the breadcrumbs. You might want to use the `fa-eye` or `fa-eyes` icons.
|
||||
#. :doc:`Patch </developer/reference/frontend/patching_code>` the component to display the
|
||||
message on click by using `the dialog service
|
||||
<{GITHUB_PATH}/addons/web/static/src/core/dialog/dialog_service.js>`_. You can use
|
||||
`ConfirmationDialog
|
||||
<{GITHUB_PATH}/addons/web/static/src/core/confirmation_dialog/confirmation_dialog.js>`_.
|
||||
#. Add the CSS class `blink` to the element representing the eye and paste the following code in
|
||||
a new CSS file located in your patch's directory.
|
||||
|
||||
.. code-block:: css
|
||||
|
||||
@ -197,10 +194,6 @@ these terms?".
|
||||
}
|
||||
}
|
||||
|
||||
#. Import the ControlPanel component and the `patch` function.
|
||||
#. Update the code to display the message on click by using the dialog service. You can use
|
||||
`ConfirmationDialog`.
|
||||
|
||||
.. image:: 02_miscellaneous/bafien_eye.png
|
||||
:align: center
|
||||
:scale: 60%
|
||||
@ -213,21 +206,10 @@ these terms?".
|
||||
- `Code: The patch function
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
addons/web/static/src/core/utils/patch.js#L16>`_
|
||||
- `Code: The ControlPanel component
|
||||
<{GITHUB_PATH}/addons/web/static/src/search/control_panel/control_panel.js>`_
|
||||
- `The Font Awesome website <https://fontawesome.com/>`_
|
||||
- `Code: The dialog service <{GITHUB_PATH}/addons/web/static/src/core/dialog/dialog_service.js>`_
|
||||
- `Code: ConfirmationDialog
|
||||
<{GITHUB_PATH}/addons/web/static/src/core/confirmation_dialog/confirmation_dialog.js>`_
|
||||
- `Example: Using the dialog service
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
addons/board/static/src/board_controller.js#L88>`_
|
||||
- `Example: XPath with t-inherit-mode="primary"
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
addons/account/static/src/components/account_move_form/account_move_form_notebook.xml#L4>`_
|
||||
- `Example: XPath with t-inherit-mode="extension"
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
calendar/static/src/components/activity/activity.xml#L4>`_
|
||||
|
||||
6. Fetching orders from a customer
|
||||
==================================
|
||||
@ -240,33 +222,28 @@ from a given customer.
|
||||
|
||||
#. Update :file:`tshirt_service.js` to add a `loadCustomers` method, which returns a promise that
|
||||
returns the list of all customers (and only performs the call once).
|
||||
#. Import the `AutoComplete` component from `@web/core/autocomplete/autocomplete`.
|
||||
#. Add it to the dashboard, next to the buttons in the control panel.
|
||||
#. Update the code to fetch the list of customers with the tshirt service, and display it in the
|
||||
autocomplete component, filtered by the `fuzzyLookup` method.
|
||||
#. Add the `AutoComplete component <{GITHUB_PATH}/addons/web/static/src/core/autocomplete>`_ to
|
||||
the dashboard, next to the buttons in the control panel.
|
||||
#. Fetch the list of customers with the tshirt service, and display it in the AutoComplete
|
||||
component, filtered by the `fuzzyLookup
|
||||
<{GITHUB_PATH}/addons/web/static/src/core/utils/search.js>`_ method.
|
||||
|
||||
.. image:: 02_miscellaneous/autocomplete.png
|
||||
:align: center
|
||||
:scale: 60%
|
||||
|
||||
.. seealso::
|
||||
- `Code: AutoComplete <{GITHUB_PATH}/addons/web/static/src/core/autocomplete/autocomplete.js>`_
|
||||
- `Code: fuzzyLookup <{GITHUB_PATH}/addons/web/static/src/core/utils/search.js>`_
|
||||
|
||||
7. Reintroduce Kitten Mode
|
||||
==========================
|
||||
|
||||
Let us add a special mode to Odoo: whenever the url contains `kitten=1`, we will display a kitten in
|
||||
Let us add a special mode to Odoo: whenever the URL contains `kitten=1`, we will display a kitten in
|
||||
the background of Odoo, because we like kittens.
|
||||
|
||||
.. exercise::
|
||||
|
||||
#. Create a :file:`kitten_mode.js` file.
|
||||
#. Create a `kitten` service, which should check the content of the active url hash with the
|
||||
help of the :ref:`router service <frontend/services/router>`.
|
||||
#. If `kitten` is set, we are in kitten mode. This should add a class `.o-kitten-mode` on the
|
||||
document body.
|
||||
#. Add the following CSS in :file:`kitten_mode.scss`:
|
||||
#. Create a `kitten` service, which should check the content of the active URL hash with the
|
||||
help of the :ref:`router service <frontend/services/router>`. If `kitten` is set in the URL,
|
||||
add the class `o-kitten-mode` to the document body.
|
||||
#. Add the following SCSS in :file:`kitten_mode.scss`:
|
||||
|
||||
.. code-block:: css
|
||||
|
||||
@ -281,7 +258,7 @@ the background of Odoo, because we like kittens.
|
||||
}
|
||||
|
||||
#. Add a command to the command palette to toggle the kitten mode. Toggling the kitten mode
|
||||
should toggle the `.o-kitten-mode` class and update the current URL accordingly.
|
||||
should toggle the class `o-kitten-mode` and update the current URL accordingly.
|
||||
|
||||
.. image:: 02_miscellaneous/kitten_mode.png
|
||||
:align: center
|
||||
@ -289,10 +266,10 @@ the background of Odoo, because we like kittens.
|
||||
8. Lazy loading our dashboard
|
||||
=============================
|
||||
|
||||
This is not really necessary, but the exercise is interesting. Imagine that our awesome dashboard
|
||||
is a large application, with potentially multiple external libraries, lots of code/styles/templates.
|
||||
Also, suppose that the dashboard is only used by some users in some business flows, so we want to
|
||||
lazy load it in order to speed up the loading of the web client in most cases.
|
||||
This is not really necessary, but the exercise is interesting. Imagine that our awesome dashboard is
|
||||
a large application with potentially multiple external libraries and lots of code/styles/templates.
|
||||
Also, suppose that the dashboard is used only by some users in some business flows. It would be
|
||||
interesting to lazy load it in order to speed up the loading of the web client in most cases.
|
||||
|
||||
So, let us do that!
|
||||
|
||||
@ -300,22 +277,23 @@ So, let us do that!
|
||||
|
||||
#. Modify the manifest to create a new :ref:`bundle <reference/assets_bundle>`
|
||||
`awesome_tshirt.dashboard`.
|
||||
#. Add the awesome dashboard code to this bundle. If needed you can create folders and move
|
||||
files.
|
||||
#. Remove the code from the `web.assets_backend` bundle so it is not loaded twice.
|
||||
#. Add the awesome dashboard code to this bundle. Create folders and move files if needed.
|
||||
#. Remove the code from the `web.assets_backend` bundle so that it is not loaded twice.
|
||||
|
||||
So far, we removed the dashboard from the main bundle, but it should now be lazily loaded. Right
|
||||
now, there is no client action registered in the action registry.
|
||||
So far, we only removed the dashboard from the main bundle; we now want to lazy load it. Currently,
|
||||
no client action is registered in the action registry.
|
||||
|
||||
.. exercise::
|
||||
|
||||
#. Create a new file :file:`dashboard_loader.js`.
|
||||
#. Copy the code registering `AwesomeDashboard` to the dashboard loader.
|
||||
#. Register `AwesomeDashboard` as a `LazyComponent`.
|
||||
#. Modify the code in the dashboard loader to use the lazy component `AwesomeDashboard`.
|
||||
4. Create a new file :file:`dashboard_loader.js`.
|
||||
5. Copy the code registering `AwesomeDashboard` to the dashboard loader.
|
||||
6. Register `AwesomeDashboard` as a `LazyComponent
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
addons/web/static/src/core/assets.js#L265-L282>`_.
|
||||
7. Modify the code in the dashboard loader to use the lazy component `AwesomeDashboard`.
|
||||
|
||||
If you open the :guilabel:`Network` tab of your browser's dev tools, you should see that
|
||||
:file:`awesome_tshirt.dashboard.min.js` is now loaded only when the Dashboard is first accessed.
|
||||
|
||||
.. seealso::
|
||||
- :ref:`Documentation on assets <reference/assets>`
|
||||
- `Code: LazyComponent
|
||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||
addons/web/static/src/core/assets.js#L255>`_
|
||||
:ref:`Documentation on assets <reference/assets>`
|
||||
|
Loading…
Reference in New Issue
Block a user