diff --git a/content/developer/tutorials/discover_js_framework/01_owl_components.rst b/content/developer/tutorials/discover_js_framework/01_owl_components.rst index e4e376c8c..9b02c448d 100644 --- a/content/developer/tutorials/discover_js_framework/01_owl_components.rst +++ b/content/developer/tutorials/discover_js_framework/01_owl_components.rst @@ -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:`` in the template of the `Todo` component. + #. Insert :code:`` 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 #. 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 title -

Some quick example text...

- Go somewhere -
+ + Card title +

Some quick example text...

+ Go somewhere +
#. 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 `_ -11. Go further -============== +11. Extensive props validation +============================== .. exercise:: diff --git a/content/developer/tutorials/discover_js_framework/02_web_framework.rst b/content/developer/tutorials/discover_js_framework/02_web_framework.rst index 1cc63c440..ad2ba4e77 100644 --- a/content/developer/tutorials/discover_js_framework/02_web_framework.rst +++ b/content/developer/tutorials/discover_js_framework/02_web_framework.rst @@ -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 ` 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 ` 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 `_). - #. 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 `_ 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 `) 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 `. 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 - `_ 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 `_. 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 diff --git a/content/developer/tutorials/master_odoo_web_framework/01_fields_and_views.rst b/content/developer/tutorials/master_odoo_web_framework/01_fields_and_views.rst index b9c18beb2..d630ca80a 100644 --- a/content/developer/tutorials/master_odoo_web_framework/01_fields_and_views.rst +++ b/content/developer/tutorials/master_odoo_web_framework/01_fields_and_views.rst @@ -99,31 +99,28 @@ views (namely: `form`, `list`, `kanban`) by using the `widget` attribute. +.. _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 `. - #. 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 + `. 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 ` 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 in a form view `_ - - `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 + `_ 7. Auto-reload the kanban view ============================== diff --git a/content/developer/tutorials/master_odoo_web_framework/02_miscellaneous.rst b/content/developer/tutorials/master_odoo_web_framework/02_miscellaneous.rst index ccb686919..0752e0177 100644 --- a/content/developer/tutorials/master_odoo_web_framework/02_miscellaneous.rst +++ b/content/developer/tutorials/master_odoo_web_framework/02_miscellaneous.rst @@ -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 ` + 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 - `_ - - `Code: The command service - <{GITHUB_PATH}/addons/web/static/src/core/commands/command_service.js>`_ + `Example: Using the useCommand hook + `_ 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 ` 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 ` 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 ` 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 `_ - - `Code: The ControlPanel component - <{GITHUB_PATH}/addons/web/static/src/search/control_panel/control_panel.js>`_ - `The Font Awesome website `_ - - `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: XPath with t-inherit-mode="primary" - `_ - - `Example: XPath with t-inherit-mode="extension" - `_ 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 `. - #. 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 `. 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 ` `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 + `_. + 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 ` - - `Code: LazyComponent - `_ + :ref:`Documentation on assets `