[ADD] developer: add a tutorial on Owl
This commit adds the introduction and the chapter 1 of the new Owl/JavaScript framework tutorial. This new tutorial allows people to discover Owl and the building blocks of the Odoo JavaScript framework. closes odoo/documentation#2845 Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com> Co-authored-by: Géry Debongnie <ged@odoo.com> Co-authored-by: Antoine Vandevenne (anv) <anv@odoo.com>
1
conf.py
@ -63,6 +63,7 @@ add_function_parentheses = True
|
|||||||
source_read_replace_vals = {
|
source_read_replace_vals = {
|
||||||
'GITHUB_PATH': f'https://github.com/odoo/odoo/blob/{version}',
|
'GITHUB_PATH': f'https://github.com/odoo/odoo/blob/{version}',
|
||||||
'GITHUB_ENT_PATH': f'https://github.com/odoo/enterprise/blob/{version}',
|
'GITHUB_ENT_PATH': f'https://github.com/odoo/enterprise/blob/{version}',
|
||||||
|
'OWL_PATH': f'https://github.com/odoo/owl/blob/master',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add extensions directory to PYTHONPATH
|
# Add extensions directory to PYTHONPATH
|
||||||
|
@ -8,6 +8,7 @@ Tutorials
|
|||||||
:titlesonly:
|
:titlesonly:
|
||||||
|
|
||||||
howtos/rdtraining
|
howtos/rdtraining
|
||||||
|
howtos/discover_js_framework
|
||||||
howtos/website
|
howtos/website
|
||||||
howtos/backend
|
howtos/backend
|
||||||
howtos/web_services
|
howtos/web_services
|
||||||
|
53
content/developer/howtos/discover_js_framework.rst
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
:show-content:
|
||||||
|
|
||||||
|
=================================
|
||||||
|
Discover the JavaScript Framework
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:titlesonly:
|
||||||
|
:glob:
|
||||||
|
|
||||||
|
discover_js_framework/*
|
||||||
|
|
||||||
|
For this training, we will step into the shoes of the IT staff at the fictional company Awesome
|
||||||
|
T-Shirt, which is dedicated to printing custom t-shirts for online customers. The Awesome T-Shirt
|
||||||
|
company uses Odoo to manage orders and has created a dedicated Odoo module to manage their workflow.
|
||||||
|
The project is currently a simple kanban view, with a few columns.
|
||||||
|
|
||||||
|
The usual process is as follows: a customer looking for a nice t-shirt can simply order it from the
|
||||||
|
Awesome T-Shirt site and give the url for any image they want. They must also fill in some basic
|
||||||
|
information, such as the desired size and quantity of t-shirts. Once they have confirmed their
|
||||||
|
order, and once the payment has been validated, the system will create a task in our application.
|
||||||
|
|
||||||
|
The big boss of Awesome T-shirt, Bafien Carpink, is unhappy with our implementation. He believes
|
||||||
|
that by micromanaging more, he will be able to get more revenue from his employees. As the IT staff
|
||||||
|
for Awesome T-shirt, we are responsible with improving the system. Various independent tasks must be
|
||||||
|
performed.
|
||||||
|
|
||||||
|
Let us now practice our Odoo skills!
|
||||||
|
|
||||||
|
.. _howtos/discover_js_framework/setup:
|
||||||
|
|
||||||
|
Setup
|
||||||
|
=====
|
||||||
|
|
||||||
|
To follow the training, it is necessary to have basic knowledge on Git and a recent version of Odoo
|
||||||
|
installed. If you have not installed it yet, we recommend installing it from :ref:`source
|
||||||
|
<setup/install/source>` (:dfn:`running Odoo from source code`).
|
||||||
|
|
||||||
|
To setup your development environment, you can also follow the dedicated chapter in :doc:`Getting
|
||||||
|
Started: Development environment setup <rdtraining/02_setup>` tutorial.
|
||||||
|
|
||||||
|
The last things to do are:
|
||||||
|
|
||||||
|
- Clone the `official Odoo tutorials repository <https://github.com/odoo/tutorials>`_ and switch to
|
||||||
|
the branch `{BRANCH}`.
|
||||||
|
- Add the cloned repository to the :option:`--addons-path <odoo-bin --addons-path>`.
|
||||||
|
- Start a new Odoo database and install the modules `owl_playground`, `awesome_tshirt`, and
|
||||||
|
`awesome_gallery`.
|
||||||
|
|
||||||
|
Exercises
|
||||||
|
=========
|
||||||
|
|
||||||
|
* :doc:`discover_js_framework/01_components`
|
319
content/developer/howtos/discover_js_framework/01_components.rst
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
=====================
|
||||||
|
Chapter 1: Components
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This chapter introduces the `Owl framework <https://github.com/odoo/owl>`_, a tailor-made component
|
||||||
|
system for Odoo. The main building blocks of OWL are `components
|
||||||
|
<{OWL_PATH}/doc/reference/component.md>`_ and `templates
|
||||||
|
<{OWL_PATH}/doc/reference/templates.md>`_.
|
||||||
|
|
||||||
|
In Owl, every part of user interface is managed by a component: they hold the logic and define the
|
||||||
|
templates that are used to render the user interface. In practice, a component is represented by a
|
||||||
|
small JavaScript class subclassing the `Component` class.
|
||||||
|
|
||||||
|
.. _jstraining/chapter1/intro_example:
|
||||||
|
|
||||||
|
.. example::
|
||||||
|
The `Counter` class implements a component that holds the internal state of a counter and defines
|
||||||
|
how it should be incremented.
|
||||||
|
|
||||||
|
.. code-block:: js
|
||||||
|
|
||||||
|
const { Component, useState } = owl;
|
||||||
|
|
||||||
|
class Counter extends Component {
|
||||||
|
static template = "my_module.Counter";
|
||||||
|
|
||||||
|
state = useState({ value: 0 });
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.state.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The `Counter` class specifies the name of the template to render. The template is written in XML
|
||||||
|
and defines a part of user interface.
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<templates xml:space="preserve">
|
||||||
|
<t t-name="my_module.Counter" owl="1">
|
||||||
|
<p>Counter: <t t-esc="state.value"/></p>
|
||||||
|
<button class="btn btn-primary" t-on-click="increment">Increment</button>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
|
||||||
|
You maybe noticed the `owl="1"` temporary attribute, it allows Odoo to differentiate Owl
|
||||||
|
templates from the old JavaScript framework templates.
|
||||||
|
|
||||||
|
Let us take some time to get used to Owl itself. Below, you will find a series of exercises
|
||||||
|
intended to quickly understand and practice the basics of Owl.
|
||||||
|
|
||||||
|
.. todo:: update screenshot
|
||||||
|
|
||||||
|
.. admonition:: Goal
|
||||||
|
|
||||||
|
Here is an overview of what we are going to achieve in this chapter.
|
||||||
|
|
||||||
|
.. image:: 01_components/overview.png
|
||||||
|
:scale: 50%
|
||||||
|
:align: center
|
||||||
|
:alt: Overview of chapter 1.
|
||||||
|
|
||||||
|
.. spoiler:: Solutions
|
||||||
|
|
||||||
|
The solutions for each exercise of the chapter are hosted on the `official Odoo tutorials
|
||||||
|
repository <https://github.com/odoo/tutorials/commits/{BRANCH}-solutions/owl_playground>`_.
|
||||||
|
|
||||||
|
1. Displaying a counter
|
||||||
|
=======================
|
||||||
|
|
||||||
|
As a first exercise, let us implement a counter in the `Playground` component located in
|
||||||
|
:file:`owl_playground/static/src/`.
|
||||||
|
|
||||||
|
.. exercise::
|
||||||
|
|
||||||
|
#. Modify :file:`playground.js` so that it acts as a counter like in :ref:`the example above
|
||||||
|
<jstraining/chapter1/intro_example>`. 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.
|
||||||
|
#. 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.
|
||||||
|
#. Add a button in the template and specify a `t-on-click
|
||||||
|
<{OWL_PATH}/doc/reference/event_handling.md#event-handling>`_ attribute in the button to
|
||||||
|
trigger the `increment` method whenever the button is clicked.
|
||||||
|
|
||||||
|
.. image:: 01_components/counter.png
|
||||||
|
:scale: 70%
|
||||||
|
:align: center
|
||||||
|
:alt: A counter component.
|
||||||
|
|
||||||
|
2. Extract counter in a component
|
||||||
|
=================================
|
||||||
|
|
||||||
|
For now we have the logic of a counter in the `Playground` component, let us see how to create a
|
||||||
|
sub-component from it.
|
||||||
|
|
||||||
|
.. exercise::
|
||||||
|
|
||||||
|
#. 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.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
Don't forget :code:`/** @odoo-module **/` in your JavaScript files. More information on this can
|
||||||
|
be found :ref:`here <frontend/modules/native_js>`.
|
||||||
|
|
||||||
|
3. A todo component
|
||||||
|
===================
|
||||||
|
|
||||||
|
We will create new components in :file:`owl_playground/static/src/` to keep track of a list of
|
||||||
|
todos. This will be done incrementally in multiple exercises that will introduce various concepts.
|
||||||
|
|
||||||
|
.. exercise::
|
||||||
|
|
||||||
|
#. Create a `Todo` component that receive a `todo` object in `props
|
||||||
|
<{OWL_PATH}/doc/reference/props.md>`_, and display it. It should show something like
|
||||||
|
**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>`_
|
||||||
|
#. 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.
|
||||||
|
|
||||||
|
.. example::
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
...
|
||||||
|
this.todo = { id: 3, description: "buy milk", done: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
.. image:: 01_components/todo.png
|
||||||
|
:scale: 70%
|
||||||
|
:align: center
|
||||||
|
:alt: A Todo component
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
`Owl: Dynamic class attributes <{OWL_PATH}/doc/reference/templates.md#dynamic-class-attribute>`_
|
||||||
|
|
||||||
|
4. Props validation
|
||||||
|
===================
|
||||||
|
|
||||||
|
The `Todo` component has an implicit API. It expects to receive in its props the description of a
|
||||||
|
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 :ref:`dev
|
||||||
|
mode <developer-mode>`. It is a good practice to do that for every component.
|
||||||
|
|
||||||
|
.. exercise::
|
||||||
|
|
||||||
|
#. Add `props validation <{OWL_PATH}/doc/reference/props.md#props-validation>`_ to the `Todo`
|
||||||
|
component.
|
||||||
|
#. Make sure it passes in :ref:`dev mode <developer-mode>`.
|
||||||
|
#. Remove `done` from the props and reload the page. The validation should fail.
|
||||||
|
|
||||||
|
5. A list of todos
|
||||||
|
==================
|
||||||
|
|
||||||
|
Now, let us display a list of todos instead of just one todo. For now, we can still hard-code the
|
||||||
|
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
|
||||||
|
#. Think about how it should be keyed with the `t-key` directive.
|
||||||
|
|
||||||
|
.. image:: 01_components/todo_list.png
|
||||||
|
:scale: 70%
|
||||||
|
:align: center
|
||||||
|
:alt: A TodoList
|
||||||
|
|
||||||
|
6. Adding a todo
|
||||||
|
================
|
||||||
|
|
||||||
|
So far, the todos in our list are hard-coded. Let us make it more useful by allowing the user to add
|
||||||
|
a todo to the list.
|
||||||
|
|
||||||
|
.. exercise::
|
||||||
|
|
||||||
|
#. 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``.
|
||||||
|
#. 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.
|
||||||
|
#. 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([]);
|
||||||
|
|
||||||
|
.. image:: 01_components/create_todo.png
|
||||||
|
:scale: 70%
|
||||||
|
:align: center
|
||||||
|
:alt: Creating todos
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
`Owl: Reactivity <{OWL_PATH}/doc/reference/reactivity.md>`_
|
||||||
|
|
||||||
|
7. Focusing the input
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Let's see how we can access the DOM with `t-ref <{OWL_PATH}/doc/reference/refs.md>`_ and `useRef
|
||||||
|
<{OWL_PATH}/doc/reference/hooks.md#useref>`_.
|
||||||
|
|
||||||
|
.. exercise::
|
||||||
|
|
||||||
|
#. Focus the `input` from the previous exercise when the dashboard is `mounted
|
||||||
|
<{OWL_PATH}/doc/reference/component.md#mounted>`_.
|
||||||
|
#. Bonus point: extract the code into a specialized `hook <{OWL_PATH}/doc/reference/hooks.md>`_
|
||||||
|
`useAutofocus`.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
`Owl: Component lifecycle <{OWL_PATH}/doc/reference/component.md#lifecycle>`_
|
||||||
|
|
||||||
|
8. Toggling todos
|
||||||
|
=================
|
||||||
|
|
||||||
|
Now, let's add a new feature: mark a todo as completed. This is actually trickier than one might
|
||||||
|
think. The owner of the state is not the same as the component that displays it. So, the `Todo`
|
||||||
|
component needs to communicate to its parent that the todo state needs to be toggled. One classic
|
||||||
|
way to do this is by using a `callback prop
|
||||||
|
<{OWL_PATH}/doc/reference/props.md#binding-function-props>`_ `toggleState`.
|
||||||
|
|
||||||
|
.. exercise::
|
||||||
|
|
||||||
|
#. 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.
|
||||||
|
#. 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.
|
||||||
|
#. Make it work!
|
||||||
|
|
||||||
|
.. image:: 01_components/toggle_todo.png
|
||||||
|
:scale: 70%
|
||||||
|
:align: center
|
||||||
|
:alt: Toggling todos
|
||||||
|
|
||||||
|
9. Deleting todos
|
||||||
|
=================
|
||||||
|
|
||||||
|
The final touch is to let the user delete a todo.
|
||||||
|
|
||||||
|
.. exercise::
|
||||||
|
|
||||||
|
#. Add a new callback prop `removeTodo`.
|
||||||
|
#. Insert :code:`<span class="fa fa-remove">` in the template of the `Todo` component.
|
||||||
|
#. Whenever the user clicks on it, it should call the `removeTodo` method.
|
||||||
|
|
||||||
|
.. image:: 01_components/delete_todo.png
|
||||||
|
:scale: 70%
|
||||||
|
:align: center
|
||||||
|
:alt: Deleting todos
|
||||||
|
|
||||||
|
10. Generic components 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:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<div class="card" style="width: 18rem;">
|
||||||
|
<img src="..." class="card-img-top" alt="..." />
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Card title</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
Some quick example text to build on the card title and make up the bulk
|
||||||
|
of the card's content.
|
||||||
|
</p>
|
||||||
|
<a href="#" class="btn btn-primary">Go somewhere</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
#. This component should have two slots: one slot for the title, and one for the content (the
|
||||||
|
default slot).
|
||||||
|
|
||||||
|
.. example::
|
||||||
|
Here is how one could use it:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<t t-set-slot="title">Card title</t>
|
||||||
|
<p class="card-text">Some quick example text...</p>
|
||||||
|
<a href="#" class="btn btn-primary">Go somewhere</a>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
#. Bonus point: if the `title` slot is not given, the `h5` should not be rendered at all.
|
||||||
|
|
||||||
|
.. image:: 01_components/card.png
|
||||||
|
:scale: 70%
|
||||||
|
:align: center
|
||||||
|
:alt: Creating card with slots
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
`Bootstrap: documentation on cards <https://getbootstrap.com/docs/5.2/components/card/>`_
|
||||||
|
|
||||||
|
11. Go further
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. exercise::
|
||||||
|
|
||||||
|
#. Add prop validation on the `Card` component.
|
||||||
|
#. Try to express in the props validation system that it requires a `default` slot, and an
|
||||||
|
optional `title` slot.
|
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 6.2 KiB |