[IMP] tutorials/js_framework: reword, clarify and improve instructions
X-original-commit: b58dbb1fbb
Part-of: odoo/documentation#4282
This commit is contained in:
parent
08f472ef28
commit
b1a76aec63
@ -80,9 +80,9 @@ route with your browser.
|
|||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
#. 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. You will
|
||||||
need to use the `useState
|
need to use the `useState hook
|
||||||
<{OWL_PATH}/doc/reference/hooks.md#usestate>`_ function 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 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.
|
||||||
#. Modify the template in :file:`playground.xml` so that it displays your counter variable. Use
|
#. 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.
|
`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.
|
#. 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
|
#. 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.
|
`Counter` in its own folder and file. Import it relatively from `./counter/counter`. Make sure
|
||||||
#. Make sure the template is in its own file, with the same name.
|
the template is in its own file, with the same name.
|
||||||
|
|
||||||
.. important::
|
.. important::
|
||||||
Don't forget :code:`/** @odoo-module **/` in your JavaScript files. More information on this can
|
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**.
|
**3. buy milk**.
|
||||||
#. Add the Bootstrap classes `text-muted` and `text-decoration-line-through` on the task if it is
|
#. 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
|
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
|
#. Modify :file:`owl_playground/static/src/playground.js` and
|
||||||
:file:`owl_playground/static/src/playground.xml` to display your new `Todo` component with
|
:file:`owl_playground/static/src/playground.xml` to display your new `Todo` component with
|
||||||
some hard-coded props to test it first.
|
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
|
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
|
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
|
<{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.
|
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`
|
#. Add `props validation <{OWL_PATH}/doc/reference/props.md#props-validation>`_ to the `Todo`
|
||||||
component.
|
component.
|
||||||
#. Make sure it passes in dev mode which is activated by default in `owl_playground`. The dev
|
#. Open the :guilabel:`Console` tab of your browser's dev tools and make sure the props
|
||||||
mode can be activated and deactivated by modifying the `dev` attribute in the in the `config`
|
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
|
parameter of the `mount <{OWL_PATH}/doc/reference/app.md#mount-helper>`_ function in
|
||||||
:file:`owl_playground/static/src/main.js`.
|
:file:`owl_playground/static/src/main.js`.
|
||||||
#. Remove `done` from the props and reload the page. The validation should fail.
|
#. Remove `done` from the props and reload the page. The validation should fail.
|
||||||
@ -179,8 +180,9 @@ list.
|
|||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
#. Change the code to display a list of todos instead of just one, and use `t-foreach
|
#. Change the code to display a list of todos instead of just one. Create a new `TodoList`
|
||||||
<{OWL_PATH}/doc/reference/templates.md#loops>`_ in the template.
|
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.
|
#. Think about how it should be keyed with the `t-key` directive.
|
||||||
|
|
||||||
.. image:: 01_owl_components/todo_list.png
|
.. 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 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
|
#. 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
|
#. 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.
|
case, create a new todo with the current content of the input as the description and clear the
|
||||||
#. Make sure it has a unique id. It can be just a counter that increments at each todo.
|
input of all content.
|
||||||
#. Then, 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.
|
#. 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
|
.. code-block:: javascript
|
||||||
|
|
||||||
this.todos = useState([]);
|
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::
|
.. exercise::
|
||||||
|
|
||||||
#. Focus the `input` from the previous exercise when the dashboard is `mounted
|
#. 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>`_
|
#. 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::
|
.. seealso::
|
||||||
`Owl: Component lifecycle <{OWL_PATH}/doc/reference/component.md#lifecycle>`_
|
`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
|
#. 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.
|
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 callback props `toggleState`.
|
||||||
#. Add a `click` event handler on the input in the `Todo` component and make sure it calls the
|
#. Add a `click` event handler on the input in the `Todo` component and make sure it calls the
|
||||||
`toggleState` function with the todo id.
|
`toggleState` function with the todo id.
|
||||||
@ -265,12 +271,12 @@ The final touch is to let the user delete a todo.
|
|||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
#. Add a new callback prop `removeTodo`.
|
#. 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.
|
#. Whenever the user clicks on it, it should call the `removeTodo` method.
|
||||||
|
|
||||||
.. tip::
|
.. tip::
|
||||||
If you're using an array to store your todo list, you can use the JavaScript `splice` function
|
If you're using an array to store your todo list, you can use the JavaScript `splice`
|
||||||
to remove a todo from it.
|
function to remove a todo from it.
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
@ -285,15 +291,18 @@ The final touch is to let the user delete a todo.
|
|||||||
:scale: 70%
|
:scale: 70%
|
||||||
:align: center
|
: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
|
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.
|
components. This is useful to factorize the common layout between different parts of the interface.
|
||||||
|
|
||||||
.. exercise::
|
.. 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
|
.. code-block:: html
|
||||||
|
|
||||||
@ -310,10 +319,7 @@ components. This is useful to factorize the common layout between different part
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
#. This component should have two slots: one slot for the title, and one for the content (the
|
#. 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
|
||||||
|
|
||||||
@ -332,8 +338,8 @@ components. This is useful to factorize the common layout between different part
|
|||||||
.. seealso::
|
.. seealso::
|
||||||
`Bootstrap: documentation on cards <https://getbootstrap.com/docs/5.2/components/card/>`_
|
`Bootstrap: documentation on cards <https://getbootstrap.com/docs/5.2/components/card/>`_
|
||||||
|
|
||||||
11. Go further
|
11. Extensive props validation
|
||||||
==============
|
==============================
|
||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ about the Odoo JavaScript framework in its entirety, as used by the web client.
|
|||||||
:width: 50%
|
:width: 50%
|
||||||
|
|
||||||
For this chapter, we will start from the empty dashboard provided by the `awesome_tshirt`
|
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
|
.. 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,
|
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`.
|
<{GITHUB_PATH}/addons/web/static/src/search/layout.js>`_, available in `@web/search/layout`.
|
||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
Update the `AwesomeDashboard` component located in :file:`awesome_tshirt/static/src/` to use the
|
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
|
`Layout` component. You can use
|
||||||
`display` props of the `Layout` component.
|
: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
|
.. image:: 02_web_framework/new_layout.png
|
||||||
:align: center
|
: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
|
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
|
Let us now use the action service for an easy access to the common views in Odoo.
|
||||||
need to use the action service.
|
|
||||||
|
|
||||||
:ref:`Services <frontend/services>` is a notion defined by the Odoo JavaScript framework, it is a
|
: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
|
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()` hooks.
|
services, and components can import a service with the `useService()` hook.
|
||||||
|
|
||||||
.. example::
|
.. example::
|
||||||
|
|
||||||
@ -92,9 +95,17 @@ services, and components can import a service with the `useService()` hooks.
|
|||||||
exists, so you should use `its xml id
|
exists, so you should use `its xml id
|
||||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||||
odoo/addons/base/views/res_partner_views.xml#L525>`_).
|
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
|
#. 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
|
.. image:: 02_web_framework/navigation_buttons.png
|
||||||
:align: center
|
:align: center
|
||||||
@ -109,9 +120,10 @@ services, and components can import a service with the `useService()` hooks.
|
|||||||
3. Call the server, add some statistics
|
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
|
Let's improve the dashboard by adding a few cards (see the `Card` component :ref:`made in the
|
||||||
training) containing a few statistics. There is a route `/awesome_tshirt/statistics` that will
|
previous chapter <tutorials/discover_js_framework/generic_card>`) containing a few statistics. There
|
||||||
perform some computations and return an object containing some useful information.
|
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
|
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:
|
<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
|
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
|
`/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
|
`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
|
would prefer to do it only the first time, so we actually need to maintain some state outside of the
|
||||||
outside of the `Dashboard` component. This is a nice use case for a service!
|
`Dashboard` component. This is a nice use case for a service!
|
||||||
|
|
||||||
.. example::
|
.. example::
|
||||||
|
|
||||||
@ -188,13 +200,12 @@ outside of the `Dashboard` component. This is a nice use case for a service!
|
|||||||
|
|
||||||
.. exercise::
|
.. 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
|
#. It should provide a function `loadStatistics` that, once called, performs the actual rpc, and
|
||||||
always return the same information.
|
always return the same information.
|
||||||
#. Maybe use the `memoize
|
#. Use the `memoize <https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||||
<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`
|
`@web/core/utils/functions` that will allow 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
|
||||||
|
|
||||||
@ -206,13 +217,13 @@ outside of the `Dashboard` component. This is a nice use case for a service!
|
|||||||
5. Display a pie chart
|
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.
|
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
|
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
|
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
|
assets bundle, or lazy load it. Lazy loading is usually better since our users will not have to load
|
||||||
chartjs code every time if they don't need it).
|
the chartjs code every time if they don't need it.
|
||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
#. Load chartjs, you can use the `loadJs
|
#. 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"/>
|
<field name="preview_moves" widget="account_resequence_widget"/>
|
||||||
|
|
||||||
|
.. _tutorials/master_odoo_web_framework/image_preview_field:
|
||||||
|
|
||||||
1. An `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
|
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
|
`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.
|
to see the image itself 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.
|
|
||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
#. Create a new `ImagePreview` component and use the `CharField` component in your template. You
|
#. Create a new `ImagePreview` component and register it in the proper :ref:`registry
|
||||||
can use `t-props
|
<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
|
<{OWL_PATH}/doc/reference/props.md#dynamic-props>`_ to pass props received by `ImagePreview`
|
||||||
received by `ImagePreview` to `CharField`.
|
to `CharField`. Update the arch of the form view to use your new field by setting the `widget`
|
||||||
#. Register your field in the proper :ref:`registry <frontend/registries>`.
|
attribute.
|
||||||
#. 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::
|
.. note::
|
||||||
It is possible to solve this exercise by inheriting `CharField` , but the goal of this exercise
|
It is possible to solve this exercise by inheriting `CharField`, but the goal of this exercise is
|
||||||
is to create a field from scratch.
|
to create a field from scratch.
|
||||||
|
|
||||||
.. image:: 01_fields_and_views/image_field.png
|
.. image:: 01_fields_and_views/image_field.png
|
||||||
:align: center
|
:align: center
|
||||||
@ -136,11 +133,12 @@ field, so it can be edited.
|
|||||||
2. Improving the `image_preview` field
|
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::
|
.. exercise::
|
||||||
|
|
||||||
We want to improve the field of the previous task to help the staff recognize orders for which
|
Display a warning "MISSING TSHIRT DESIGN" in red if there is no image URL specified on the order.
|
||||||
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.
|
|
||||||
|
|
||||||
.. image:: 01_fields_and_views/missing_image.png
|
.. image:: 01_fields_and_views/missing_image.png
|
||||||
:align: center
|
:align: center
|
||||||
@ -150,9 +148,9 @@ field, so it can be edited.
|
|||||||
|
|
||||||
Let's see how to use inheritance to extend an existing component.
|
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
|
There is a `is_late`, readonly, boolean field on the order model. That would be useful information
|
||||||
see on the list/kanban/view. Then, let us say that we want to add a red word "Late!" next to it
|
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.
|
whenever it is set to `true`.
|
||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
@ -166,11 +164,8 @@ whenever it is set to true.
|
|||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
- `Example: A field inheriting another
|
||||||
- `Example: A field inheriting another (JS)
|
<{GITHUB_PATH}/addons/account/static/src/components/account_type_selection>`_
|
||||||
<{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>`_
|
|
||||||
- :ref:`Documentation on xpath <reference/views/inheritance>`
|
- :ref:`Documentation on xpath <reference/views/inheritance>`
|
||||||
|
|
||||||
4. Message for some customers
|
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::
|
.. exercise::
|
||||||
|
|
||||||
For a super efficient workflow, we would like to display a message/warning box with some
|
For a super efficient workflow, we would like to display an alert block with specific messages
|
||||||
information in the form view, with specific messages depending on some conditions:
|
depending on some conditions:
|
||||||
|
|
||||||
- If the `image_url` field is not set, it should display "No image".
|
- 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".
|
material".
|
||||||
- Make sure that your widget is updated in real time.
|
- 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
|
.. image:: 01_fields_and_views/warning_widget.png
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
@ -197,23 +195,23 @@ insert arbitrary components in the form view. Let us see how we can use it.
|
|||||||
- `Example: Using the tag <widget> in a form view
|
- `Example: Using the tag <widget> in a form view
|
||||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||||
addons/calendar/views/calendar_views.xml#L197>`_
|
addons/calendar/views/calendar_views.xml#L197>`_
|
||||||
- `Example: Implementation of a widget (JS)
|
- `Example: Implementation of a widget
|
||||||
<{GITHUB_PATH}/addons/web/static/src/views/widgets/week_days/week_days.js>`_
|
<{GITHUB_PATH}/addons/web/static/src/views/widgets/week_days>`_
|
||||||
- `Example: Implementation of a widget (XML)
|
|
||||||
<{GITHUB_PATH}/addons/web/static/src/views/widgets/week_days/week_days.xml>`_
|
|
||||||
|
|
||||||
5. Use `markup`
|
5. Use `markup`
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Let's see how we can display raw HTML in a template. Before, there was a `t-raw` directive that
|
Let’s see how we can display raw HTML in a template. The `t-out` directive can be used for that
|
||||||
would just output anything as HTML. This was unsafe, and has been replaced by a `t-out
|
propose. Indeed, `it generally acts like t-esc, unless the data has been marked explicitly with a
|
||||||
<{OWL_PATH}/doc/reference/templates.md#outputting-data>`_ directive that acts like a `t-esc` unless
|
markup function <{OWL_PATH}/doc/reference/templates.md#outputting-data>`_. In that case, its value
|
||||||
the data has been marked explicitly with a `markup` function.
|
is injected as HTML.
|
||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
#. Modify the previous exercise to put the `image` and `material` words in bold.
|
#. 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`.
|
#. 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::
|
.. 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.
|
||||||
@ -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
|
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
|
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
|
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.
|
(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`
|
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
|
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.
|
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
|
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
|
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
|
`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::
|
.. 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`.
|
`awesome_tshirt.order_form_view`.
|
||||||
#. Add a `js_class` attribute to the arch of the form view so Odoo will load it.
|
#. Add a `js_class="awesome_tshirt.order_form_view"` attribute to the arch of the form view so
|
||||||
#. Create a new template inheriting from the form controller template to add a button after the
|
that Odoo will load it.
|
||||||
create button.
|
#. Create a new template inheriting from the form controller template and add a "Print Label"
|
||||||
#. Add a button. Clicking on this button should call the method `print_label` from the model
|
button after the "New" button.
|
||||||
`awesome_tshirt.order` with the proper id. Note: `print_label` is a mock method, it only
|
#. Clicking on this button should call the method `print_label` from the model
|
||||||
displays a message in the logs.
|
`awesome_tshirt.order` with the proper id.
|
||||||
#. The button should be disabled if the current order is in `create` mode (i.e., it does not
|
|
||||||
|
.. 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).
|
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
|
#. 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.
|
#. Bonus point: clicking twice on the button should not trigger 2 RPCs.
|
||||||
|
|
||||||
.. image:: 01_fields_and_views/form_button.png
|
.. 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>`_
|
- `Code: orm service <{GITHUB_PATH}/addons/web/static/src/core/orm_service.js>`_
|
||||||
- `Example: Using the orm service
|
- `Example: Using the orm service
|
||||||
<{GITHUB_PATH}/addons/account/static/src/components/open_move_widget/open_move_widget.js>`_
|
<{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
|
7. Auto-reload the kanban view
|
||||||
==============================
|
==============================
|
||||||
|
@ -67,7 +67,7 @@ printer is not connected or ran out of paper).
|
|||||||
:scale: 60%
|
:scale: 60%
|
||||||
|
|
||||||
.. seealso::
|
.. 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>`_
|
<{GITHUB_PATH}/addons/web/static/src/views/fields/image_url/image_url_field.js>`_
|
||||||
|
|
||||||
2. Add a systray item
|
2. Add a systray item
|
||||||
@ -107,7 +107,6 @@ do that by calling periodically (for example, every minute) the server to reload
|
|||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
#. Modify the systray item code to get its data from the `tshirt` service.
|
|
||||||
#. The `tshirt` service should periodically reload its data.
|
#. 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
|
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::
|
.. 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
|
<{OWL_PATH}/doc/reference/reactivity.md#reactive>`_ object. Reloading data should update the
|
||||||
reactive object in place.
|
reactive object in place.
|
||||||
#. The systray item can then perform a `useState` on the service return value.
|
3. The systray item can then perform a `useState
|
||||||
#. This is not really necessary, but you can also *package* the calls to `useService` and
|
<{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`.
|
`useState` in a custom hook `useStatistics`.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
@ -137,48 +137,45 @@ by pressing `CTRL+K` in the Odoo interface.
|
|||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
Let us modify the image preview field (from a previous exercise) to add a command to the command
|
Modify the :ref:`image preview field <tutorials/master_odoo_web_framework/image_preview_field>`
|
||||||
palette to open the image in a new browser tab (or window).
|
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
|
.. image:: 02_miscellaneous/new_command.png
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
- `Example: Using the useCommand hook
|
`Example: Using the useCommand hook
|
||||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||||
addons/web/static/src/core/debug/debug_menu.js#L15>`_
|
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>`_
|
|
||||||
|
|
||||||
5. Monkey patching a component
|
5. Monkey patching a component
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
Often, it is possible to do what we want by using existing extension points that allow
|
Often, we can achieve what we want by using existing extension points that allow for customization,
|
||||||
customization, such as registering something in a registry. But it happens that we want to modify
|
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 have to fall back on a less safe form of
|
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.
|
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
|
Bafien, our beloved leader, heard about employees performing 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
|
watched. Since he cannot be there in person for each of his employees, he tasked you with updating
|
||||||
tasked you with the following: update the user interface to add a blinking red eye in the control
|
the user interface to add a blinking red eye in the control panel. Clicking on that eye should open
|
||||||
panel. Clicking on that eye should open a dialog with the following message: "Bafien is watching
|
a dialog with the following message: "Bafien is watching you. This interaction is recorded and may
|
||||||
you. This interaction is recorded and may be used in legal proceedings if necessary. Do you agree to
|
be used in legal proceedings if necessary. Do you agree to these terms?"
|
||||||
these terms?".
|
|
||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
#. Create the :file:`control_panel_patch.js` file, as well as corresponding CSS and XML files.
|
#. :ref:`Inherit <reference/qweb/template_inheritance>` the `web.Breadcrumbs` template of the
|
||||||
#. :doc:`Patch </developer/reference/frontend/patching_code>` the `ControlPanel` template to add
|
`ControlPanel component <{GITHUB_PATH}/addons/web/static/src/search/control_panel>`_ to add an
|
||||||
an icon next to the breadcrumbs. You might want to use the `fa-eye` or `fa-eyes` icons. Make
|
icon next to the breadcrumbs. You might want to use the `fa-eye` or `fa-eyes` icons.
|
||||||
sure it is visible in all views!
|
#. :doc:`Patch </developer/reference/frontend/patching_code>` the component to display the
|
||||||
|
message on click by using `the dialog service
|
||||||
.. tip::
|
<{GITHUB_PATH}/addons/web/static/src/core/dialog/dialog_service.js>`_. You can use
|
||||||
There are two ways to inherit a template using XPath: by specifying
|
`ConfirmationDialog
|
||||||
`t-inherit-mode="primary"`, which creates a new, independent template with the desired
|
<{GITHUB_PATH}/addons/web/static/src/core/confirmation_dialog/confirmation_dialog.js>`_.
|
||||||
modifications, or by using `t-inherit-mode="extension"`, which modifies the original
|
#. Add the CSS class `blink` to the element representing the eye and paste the following code in
|
||||||
template in place.
|
a new CSS file located in your patch's directory.
|
||||||
|
|
||||||
.. code-block:: css
|
.. 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
|
.. image:: 02_miscellaneous/bafien_eye.png
|
||||||
:align: center
|
:align: center
|
||||||
:scale: 60%
|
:scale: 60%
|
||||||
@ -213,21 +206,10 @@ these terms?".
|
|||||||
- `Code: The patch function
|
- `Code: The patch function
|
||||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||||
addons/web/static/src/core/utils/patch.js#L16>`_
|
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/>`_
|
- `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
|
- `Example: Using the dialog service
|
||||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
||||||
addons/board/static/src/board_controller.js#L88>`_
|
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
|
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
|
#. 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).
|
returns the list of all customers (and only performs the call once).
|
||||||
#. Import the `AutoComplete` component from `@web/core/autocomplete/autocomplete`.
|
#. Add the `AutoComplete component <{GITHUB_PATH}/addons/web/static/src/core/autocomplete>`_ to
|
||||||
#. Add it to the dashboard, next to the buttons in the control panel.
|
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
|
#. Fetch the list of customers with the tshirt service, and display it in the AutoComplete
|
||||||
autocomplete component, filtered by the `fuzzyLookup` method.
|
component, filtered by the `fuzzyLookup
|
||||||
|
<{GITHUB_PATH}/addons/web/static/src/core/utils/search.js>`_ method.
|
||||||
|
|
||||||
.. image:: 02_miscellaneous/autocomplete.png
|
.. image:: 02_miscellaneous/autocomplete.png
|
||||||
:align: center
|
:align: center
|
||||||
:scale: 60%
|
: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
|
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.
|
the background of Odoo, because we like kittens.
|
||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
#. Create a :file:`kitten_mode.js` file.
|
#. Create a `kitten` service, which should check the content of the active URL hash with the
|
||||||
#. 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,
|
||||||
help of the :ref:`router service <frontend/services/router>`.
|
add the class `o-kitten-mode` to the document body.
|
||||||
#. If `kitten` is set, we are in kitten mode. This should add a class `.o-kitten-mode` on the
|
#. Add the following SCSS in :file:`kitten_mode.scss`:
|
||||||
document body.
|
|
||||||
#. Add the following CSS in :file:`kitten_mode.scss`:
|
|
||||||
|
|
||||||
.. code-block:: css
|
.. 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
|
#. 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
|
.. image:: 02_miscellaneous/kitten_mode.png
|
||||||
:align: center
|
:align: center
|
||||||
@ -289,10 +266,10 @@ the background of Odoo, because we like kittens.
|
|||||||
8. Lazy loading our dashboard
|
8. Lazy loading our dashboard
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
This is not really necessary, but the exercise is interesting. Imagine that our awesome dashboard
|
This is not really necessary, but the exercise is interesting. Imagine that our awesome dashboard is
|
||||||
is a large application, with potentially multiple external libraries, lots of code/styles/templates.
|
a large application with potentially multiple external libraries and lots of code/styles/templates.
|
||||||
Also, suppose that the dashboard is only used by some users in some business flows, so we want to
|
Also, suppose that the dashboard is used only by some users in some business flows. It would be
|
||||||
lazy load it in order to speed up the loading of the web client in most cases.
|
interesting to lazy load it in order to speed up the loading of the web client in most cases.
|
||||||
|
|
||||||
So, let us do that!
|
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>`
|
#. Modify the manifest to create a new :ref:`bundle <reference/assets_bundle>`
|
||||||
`awesome_tshirt.dashboard`.
|
`awesome_tshirt.dashboard`.
|
||||||
#. Add the awesome dashboard code to this bundle. If needed you can create folders and move
|
#. Add the awesome dashboard code to this bundle. Create folders and move files if needed.
|
||||||
files.
|
#. Remove the code from the `web.assets_backend` bundle so that it is not loaded twice.
|
||||||
#. Remove the code from the `web.assets_backend` bundle so it is not loaded twice.
|
|
||||||
|
|
||||||
So far, we removed the dashboard from the main bundle, but it should now be lazily loaded. Right
|
So far, we only removed the dashboard from the main bundle; we now want to lazy load it. Currently,
|
||||||
now, there is no client action registered in the action registry.
|
no client action is registered in the action registry.
|
||||||
|
|
||||||
.. exercise::
|
.. exercise::
|
||||||
|
|
||||||
#. Create a new file :file:`dashboard_loader.js`.
|
4. Create a new file :file:`dashboard_loader.js`.
|
||||||
#. Copy the code registering `AwesomeDashboard` to the dashboard loader.
|
5. Copy the code registering `AwesomeDashboard` to the dashboard loader.
|
||||||
#. Register `AwesomeDashboard` as a `LazyComponent`.
|
6. Register `AwesomeDashboard` as a `LazyComponent
|
||||||
#. Modify the code in the dashboard loader to use the lazy component `AwesomeDashboard`.
|
<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::
|
.. seealso::
|
||||||
- :ref:`Documentation on assets <reference/assets>`
|
:ref:`Documentation on assets <reference/assets>`
|
||||||
- `Code: LazyComponent
|
|
||||||
<https://github.com/odoo/odoo/blob/1f4e583ba20a01f4c44b0a4ada42c4d3bb074273/
|
|
||||||
addons/web/static/src/core/assets.js#L255>`_
|
|
||||||
|
Loading…
Reference in New Issue
Block a user