wip
This commit is contained in:
parent
4331177bb6
commit
fd28f72c68
@ -1,12 +1,66 @@
|
||||
:nosearch:
|
||||
|
||||
==================
|
||||
Javascript Modules
|
||||
==================
|
||||
====================
|
||||
Javascript Framework
|
||||
====================
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
|
||||
javascript/getting_started
|
||||
javascript/readme
|
||||
javascript/architecture
|
||||
javascript/assets_management
|
||||
javascript/browser
|
||||
javascript/bus
|
||||
javascript/crash_manager_and_errors
|
||||
javascript/environment
|
||||
javascript/global_odoo_object
|
||||
javascript/localization
|
||||
javascript/main_components
|
||||
javascript/python_interpreter
|
||||
javascript/systray
|
||||
javascript/testing
|
||||
|
||||
javascript/commands/readme
|
||||
javascript/commands/command_service
|
||||
|
||||
javascript/components/readme
|
||||
javascript/components/dialog
|
||||
javascript/components/dropdown
|
||||
javascript/components/popover
|
||||
|
||||
javascript/core/readme
|
||||
javascript/core/hooks
|
||||
|
||||
javascript/registries/readme
|
||||
javascript/registries/command_category_registry
|
||||
javascript/registries/company_menu
|
||||
javascript/registries/user_menu
|
||||
|
||||
javascript/services/readme
|
||||
javascript/services/action_manager
|
||||
javascript/services/crash_manager
|
||||
javascript/services/dialog_manager
|
||||
javascript/services/hotkey
|
||||
javascript/services/menus
|
||||
javascript/services/model
|
||||
javascript/services/notifications
|
||||
javascript/services/popover
|
||||
javascript/services/router
|
||||
javascript/services/rpc
|
||||
javascript/services/title
|
||||
javascript/services/ui
|
||||
javascript/services/user
|
||||
javascript/services/view_manager
|
||||
|
||||
javascript/views/readme
|
||||
javascript/views/calendar_view
|
||||
javascript/views/dashboard_view
|
||||
javascript/views/graph_view
|
||||
javascript/views/map_view
|
||||
javascript/views/pivot_view
|
||||
|
||||
javascript/javascript_cheatsheet
|
||||
javascript/javascript_reference
|
||||
javascript/mobile
|
||||
|
19
content/developer/reference/javascript/architecture.rst
Normal file
19
content/developer/reference/javascript/architecture.rst
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
Architecture
|
||||
============
|
||||
|
||||
This document explains how the Odoo web client is designed, from a high level
|
||||
point of view.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
.. code-block::
|
||||
|
||||
creating an environment => mounting web client
|
||||
|
||||
Services
|
||||
--------
|
||||
|
||||
`Services <services/readme.md>`_ are meant to be long lived processes which provides
|
||||
some features to the web client. They are alive for the duration of the application.
|
@ -0,0 +1,5 @@
|
||||
Assets Management
|
||||
=================
|
||||
|
||||
|
||||
|
34
content/developer/reference/javascript/browser.rst
Normal file
34
content/developer/reference/javascript/browser.rst
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
Browser
|
||||
=======
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The browser object is a part of the `environment <environment.md>`_. It contains
|
||||
all ``window`` API that perform some kind of side effects. This is useful when we
|
||||
need to disable/configure/modify/react to any call to these APIs. It is also
|
||||
necessary to be able to mock them properly in a test environment.
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
console.log(env.browser); // display the content of browser
|
||||
|
||||
Exported values
|
||||
---------------
|
||||
|
||||
Here is a list of all entities available in the ``browser`` object:
|
||||
|
||||
|
||||
* ``Date``
|
||||
* ``XMLHTTPRequest``
|
||||
* ``clearInterval``
|
||||
* ``clearTimeout``
|
||||
* ``console``
|
||||
* ``fetch``
|
||||
* ``localStorage``
|
||||
* ``location``
|
||||
* ``random``
|
||||
* ``requestAnimationFrame``
|
||||
* ``setInterval``
|
||||
* ``setTimeout``
|
56
content/developer/reference/javascript/bus.rst
Normal file
56
content/developer/reference/javascript/bus.rst
Normal file
@ -0,0 +1,56 @@
|
||||
|
||||
Bus
|
||||
===
|
||||
|
||||
The web client ``env`` contains an event bus, named ``bus``. Its purpose is to allow
|
||||
various parts of the system to properly coordinate themselves, without coupling
|
||||
them. The ``env.bus`` is an owl ``EventBus`` , that should be used for global events
|
||||
of interest.
|
||||
|
||||
Message List
|
||||
------------
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Message
|
||||
- Payload
|
||||
- Triggered when:
|
||||
- Addon
|
||||
* - ``ACTION_MANAGER:UI-UPDATED``
|
||||
- a mode indicating what part of the ui has been updated ('current', 'new' or 'fullscreen')
|
||||
- the rendering of the action requested to the action manager is done
|
||||
- wowl
|
||||
* - ``ACTION_MANAGER:UPDATE``
|
||||
- next rendering info
|
||||
- the action manager has finished computing the next interface
|
||||
- wowl
|
||||
* - ``MENUS:APP-CHANGED``
|
||||
- none
|
||||
- the menu service's current app has changed
|
||||
- wowl
|
||||
* - ``NOTIFICATIONS_CHANGE``
|
||||
- list of notifications
|
||||
- the list of notifications changes
|
||||
- wowl
|
||||
* - ``ROUTE_CHANGE``
|
||||
- none
|
||||
- the url hash was changed
|
||||
- wowl
|
||||
* - ``RPC_ERROR``
|
||||
- error data object
|
||||
- a rpc request (going through ``rpc`` service) fails
|
||||
- wowl
|
||||
* - ``RPC:REQUEST``
|
||||
- rpc id
|
||||
- a rpc request has just started
|
||||
- wowl
|
||||
* - ``RPC:RESPONSE``
|
||||
- rpc id
|
||||
- a rpc request is completed
|
||||
- wowl
|
||||
* - ``WEB_CLIENT_READY``
|
||||
- none
|
||||
- the web client has been mounted
|
||||
- wowl
|
||||
|
87
content/developer/reference/javascript/components/dialog.rst
Normal file
87
content/developer/reference/javascript/components/dialog.rst
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
Dialog
|
||||
======
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The Dialog component is one of the main bricks of the web client.
|
||||
|
||||
Here are its props
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Type
|
||||
- Default
|
||||
- Description
|
||||
* - ``contentClass``
|
||||
- string
|
||||
-
|
||||
- the classes contained in ``contentClass`` are added on the element "div.modal-content"
|
||||
* - ``fullscreen``
|
||||
- boolean
|
||||
- false
|
||||
- a class ``o_modal_full`` is added on the element "div.modal"
|
||||
* - ``renderFooter``
|
||||
- boolean
|
||||
- true
|
||||
- the footer contains a slot ``buttons`` and a default button ``OK``
|
||||
* - ``renderHeader``
|
||||
- boolean
|
||||
- true
|
||||
- the header contains a title and a button ``x`` for "closing" dialog
|
||||
* - ``size``
|
||||
- string
|
||||
- "modal-lg"
|
||||
- used to set the dialog size (available suffix: "xl", "lg", "md", sm")
|
||||
* - ``title``
|
||||
- string
|
||||
- "Odoo"
|
||||
-
|
||||
* - ``technical``
|
||||
- boolean
|
||||
- true
|
||||
- a class ``o_technical_modal`` is added on the element "div.modal". If set to false, the modal will have the standard frontend style (use this for non-editor frontend features).
|
||||
|
||||
|
||||
Slots
|
||||
-----
|
||||
|
||||
Beside the props, the configuration of a dialog is done via two slots:
|
||||
|
||||
The ``default`` slot should be used to define the main content of the dialog (display some text or subcomponents).
|
||||
|
||||
The slot ``buttons`` can be used to add custom buttons in the dialog footer.
|
||||
If the footer is displayed and that slot is not used, a default button ``OK`` is added to the footer.
|
||||
A click on that button triggers the event ``dialog-closed`` via the method ``_close`` (see section below).
|
||||
|
||||
So typically, the parent template could look like to
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<div>
|
||||
<!-- parent main content -->
|
||||
<Dialog t-if="state.displayDialog" t-on-dialog-closed="_onDialogClosed">
|
||||
<SubComponent t-on-subcomponent-clicked="_onSubcomponentClicked" />
|
||||
<t t-set="buttons">
|
||||
<button t-on-click="onConfirmClick">Confirm</button>
|
||||
<button t-on-click="onDiscardClick">Discard</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
<!-- ... -->
|
||||
</div>
|
||||
|
||||
Communication with parent
|
||||
-------------------------
|
||||
|
||||
The dialog never closes itself. The dialog parent is responsible of opening/closing the dialog.
|
||||
When the user click on the button ``x`` (in the header) or ``Ok`` (default button in the footer),
|
||||
a custom event ``dialog-closed`` is triggered, allowing the parent to take action or not.
|
||||
|
||||
Location in the dom
|
||||
-------------------
|
||||
|
||||
The Dialog class uses a portal to locate itself in a div with class ``o_dialog_container`` but the
|
||||
communication with the parent goes as usual: via props or custom/dom events.
|
445
content/developer/reference/javascript/components/dropdown.rst
Normal file
445
content/developer/reference/javascript/components/dropdown.rst
Normal file
@ -0,0 +1,445 @@
|
||||
|
||||
Dropdown Component
|
||||
==================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
As dropdowns are common in Odoo, we decided to make a generic dropdown component.
|
||||
|
||||
It contains all the logic you can usually expect a dropdown to behave.
|
||||
|
||||
Features
|
||||
^^^^^^^^
|
||||
|
||||
|
||||
* Toggle the list on click
|
||||
* Direct siblings dropdowns: when one is open, toggle others on hover
|
||||
* Close on outside click
|
||||
* Close the list when an item is selected
|
||||
* Emits an event to inform which list item is clicked
|
||||
* Infinite multi-level support
|
||||
* SIY: style it yourself
|
||||
* Configurable hotkey to open/close a dropdown or select a dropdown item
|
||||
* Keyboard navigation (arrows, enter...)
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
Behind the scenes
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
A ``<Dropdown/>`` component is simply a ``<div class="o_dropdown"/>`` having a ``<button class="o_dropdown_toggler"/>`` next to an unordered list (\ ``<ul class="o_dropdown_menu"/>`` ). The button is responsible for the list being present in the DOM or not.
|
||||
|
||||
A ``<DropdownItem/>`` is simply a list item (\ ``<li class="o_dropdown_item"/>`` ). On click, you can ask this item to return you a payload (which you'll receive back in a custom ``dropdown-item-selected`` event). This payload is an object, so feel free to put anything you want in it. Most likely, you will use ids as payloads to know which item was clicked.
|
||||
|
||||
Illustration of what the final DOM could look like:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<div class="o_dropdown">
|
||||
<button class="o_dropdown_toggler">
|
||||
<span>Click me to toggle the dropdown menu !</span>
|
||||
</button>
|
||||
<!-- following <ul/> list will or won't appear in the DOM depending on the state controlled by the button -->
|
||||
<ul class="o_dropdown_menu">
|
||||
<li class="o_dropdown_item">
|
||||
<span>Menu Item 1</span>
|
||||
</li>
|
||||
<li class="o_dropdown_item">
|
||||
<span>Menu Item 2</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Slots
|
||||
~~~~~
|
||||
|
||||
In order to properly use a ``<Dropdown/>`` component, you need to populate two `OWL slots <https://github.com/odoo/owl/blob/master/doc/reference/slots.md>`_\ :
|
||||
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<dl>
|
||||
<dt><strong>The <code>default</code> slot</strong></dt>
|
||||
<dd>It contains the <strong>toggler elements of your dropdown</strong> and will take place inside your dropdown <code><button><span/></button></code> elements.</dd>
|
||||
<dt><strong>The <code>menu</code> slot</strong></dt>
|
||||
<dd>
|
||||
It contains the <strong>elements of the dropdown menu itself</strong> and will take place inside your dropdown <code><ul/></code> element.<br/>
|
||||
Although it is not mandatory, you will usually place at least one <code><DropdownItem/></code> element in the <code>menu</code> slot.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
Manage items selection
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When a ``<DropdownItem/>`` gets selected, it emits a custom ``dropdown-item-selected`` event containing its payload. (see `OWL Business Events <https://github.com/odoo/owl/blob/master/doc/reference/event_handling.md#business-dom-events>`_\ )
|
||||
|
||||
If you want to react when a ``<DropdownItem/>`` gets selected, you need to define two things:
|
||||
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<dl>
|
||||
<dt>The <code>dropdown-item-selected</code> event listener</dt>
|
||||
<dd>It will receive the payload of the selected item.</dd>
|
||||
<dt>A <code>payload</code> for each <code><DropdownItem/></code> element</dt>
|
||||
<dd>They are just JS objects you declare the way you want. If a payload is not specified, it defaults to <code>null</code>.</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
Direct Siblings Dropdowns
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When many dropdowns share **a single parent in the DOM** , they will automatically notify each other about their state changes.
|
||||
|
||||
Doing so, **when one sibling dropdown is open** , the others will **automatically open themselves on hover**.
|
||||
|
||||
Available Properties
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``<Dropdown/>`` props
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Prop name
|
||||
- Default Value
|
||||
- Value type
|
||||
- Description
|
||||
* - ``startOpen``
|
||||
- ``false``
|
||||
- boolean
|
||||
- initial dropdown open state
|
||||
* - ``menuClass``
|
||||
- /
|
||||
- string
|
||||
- could be used to style the dropdown menu ``<ul/>``
|
||||
* - ``togglerClass``
|
||||
- /
|
||||
- string
|
||||
- could be used to style the toggler ``<button/>``
|
||||
* - ``hotkey``
|
||||
- /
|
||||
- string
|
||||
- could be used to toggle the opening through keyboard
|
||||
* - ``beforeOpen``
|
||||
- /
|
||||
- function
|
||||
- hook to execute logic just before opening
|
||||
* - ``manualOnly``
|
||||
- ``false``
|
||||
- boolean
|
||||
- if true, only toggle the dropdown when the button is clicked on
|
||||
|
||||
|
||||
``<DropdownItem/>`` props
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Prop name
|
||||
- Default Value
|
||||
- Value type
|
||||
- Description
|
||||
* - ``payload``
|
||||
- null
|
||||
- Object
|
||||
- item payload that will be part of the ``dropdown-item-selected`` event
|
||||
* - ``parentClosingMode``
|
||||
- ``all``
|
||||
- ``none`` | ``closest`` | ``all``
|
||||
- when item clicked, control which parent dropdown will get closed: none, closest or all
|
||||
* - ``hotkey``
|
||||
- /
|
||||
- string
|
||||
- click on the item via an hotkey activation
|
||||
|
||||
|
||||
Z-Index
|
||||
^^^^^^^
|
||||
|
||||
As Odoo previous dropdown menus made use of Bootstrap dropdowns, we added the same ``z-index`` value for the dropdown menu. See `Bootstrap documentation <https://getbootstrap.com/docs/4.5/layout/overview/#z-index>`_.
|
||||
|
||||
.. code-block:: scss
|
||||
|
||||
.o_dropdown_menu {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Step 1: make it appear on your app
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
So in your qweb template, you would write something like that:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<Dropdown>
|
||||
<!-- "default" slot content should be defined here -->
|
||||
Click me to toggle the dropdown menu !
|
||||
<t t-set-slot="menu">
|
||||
<!-- "dropdown" slot content should be defined here-->
|
||||
<DropdownItem>Menu Item 1</DropdownItem>
|
||||
<DropdownItem>Menu Item 2</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
|
||||
And in the DOM it would get translated similarly to:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<div class="o_dropdown">
|
||||
<button class="o_dropdown_toggler">
|
||||
<!-- "default" slot content will take place here -->
|
||||
<span>Click me to toggle the dropdown menu !</span>
|
||||
</button>
|
||||
|
||||
<ul class="o_dropdown_menu">
|
||||
<!-- "dropdown" slot content will take place here -->
|
||||
<li class="o_dropdown_item">
|
||||
<span>Menu Item 1</span>
|
||||
</li>
|
||||
<li class="o_dropdown_item">
|
||||
<span>Menu Item 2</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Step 2: make it react to clicks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
So in your qweb template you would write something like that:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<Dropdown t-on-dropdown-item-selected="onItemSelected">
|
||||
…
|
||||
<t t-set-slot="menu">
|
||||
…
|
||||
<DropdownItem payload="{a:15}">Menu Item</DropdownItem>
|
||||
…
|
||||
</t>
|
||||
</Dropdown>
|
||||
|
||||
And in your JS file, when an item is selected, you would receive the payload back like that:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
itemSelected(event) {
|
||||
const eventDetail = event.detail;
|
||||
const itemPayload = eventDetail.payload;
|
||||
console.log(itemPayload.a === 15);
|
||||
}
|
||||
|
||||
In this case, if you click on this menu item, the console will print « true ».
|
||||
|
||||
Step 3: make it shine
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Now that you understand the basics of the Dropdown Component, all you need to do is style it the way you want.
|
||||
|
||||
✨ Are you ready to make it shine? ✨
|
||||
|
||||
Default CSS classes are:
|
||||
|
||||
|
||||
* ``.o_dropdown`` : the whole dropdown
|
||||
* ``.o_dropdown_toggler`` : the dropdown button
|
||||
* ``.o_dropdown_menu`` : the dropdown menu list
|
||||
* ``.o_dropdown_item`` : a dropdown item
|
||||
|
||||
But you can go even further by extending them:
|
||||
|
||||
|
||||
- ``<Dropdown class="my_class"/>`` will become
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<div class="o_dropdown my_class">...</div>
|
||||
|
||||
- ``<Dropdown togglerClass="my_class"/>`` will become
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<div class="o_dropdown">
|
||||
<button class="o_dropdown_toggler my_class">
|
||||
<span>...</span>
|
||||
</button>
|
||||
...
|
||||
</div>
|
||||
|
||||
- ``<Dropdown menuClass="my_class"/>`` will become
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<div class="o_dropdown">
|
||||
<button>...</button>
|
||||
<ul class="o_dropdown_menu my_class">...</ul>
|
||||
</div>
|
||||
|
||||
- ``<DropdownItem class="my_class"/>`` will become
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<li class="o_dropdown_item my_class">
|
||||
<span>...</span>
|
||||
</li>
|
||||
|
||||
You can also make dropdown right aligned by passing 'o_dropdown_menu_right' in menuClass
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
* ``<Dropdown menuClass="'o_dropdown_menu_right'"/>`` will become
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<div class="o_dropdown">
|
||||
<button>...</button>
|
||||
<ul class="o_dropdown_menu o_dropdown_menu_right">...</ul>
|
||||
</div>
|
||||
|
||||
More Examples
|
||||
-------------
|
||||
|
||||
Direct Siblings Dropdown
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When one dropdown toggler is clicked (\ **File** , **Edit** or **About** ), the others will open themselves on hover.
|
||||
|
||||
This example uses the dropdown components without added style.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<div t-on-dropdown-item-selected="onItemSelected">
|
||||
<Dropdown>
|
||||
File
|
||||
<t t-set-slot="menu">
|
||||
<DropdownItem payload="'file-open'">Open</DropdownItem>
|
||||
<DropdownItem payload="'file-new-document'">New Document</DropdownItem>
|
||||
<DropdownItem payload="'file-new-spreadsheet'">New Spreadsheet</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
<Dropdown>
|
||||
Edit
|
||||
<t t-set-slot="menu">
|
||||
<DropdownItem payload="'edit-undo'">Undo</DropdownItem>
|
||||
<DropdownItem payload="'edit-redo'">Redo</DropdownItem>
|
||||
<DropdownItem payload="'edit-find'">Search</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
<Dropdown>
|
||||
About
|
||||
<t t-set-slot="menu">
|
||||
<DropdownItem payload="'about-help'">Help</DropdownItem>
|
||||
<DropdownItem payload="'about-update'">Check update</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
Multi-level Dropdown
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This example uses the dropdown components without added style.
|
||||
|
||||
Flat version
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<Dropdown t-on-dropdown-item-selected="onItemSelected" owl="1">
|
||||
File
|
||||
<t t-set-slot="menu">
|
||||
<DropdownItem payload="'file-open'">Open</DropdownItem>
|
||||
<t t-call="addon.Dropdown.File.New"/>
|
||||
<DropdownItem payload="'file-save'">Save</DropdownItem>
|
||||
<t t-call="addon.Dropdown.File.Save.As"/>
|
||||
</t>
|
||||
</Dropdown>
|
||||
|
||||
<Dropdown t-name="addon.Dropdown.File.New" owl="1">
|
||||
New
|
||||
<t t-set-slot="menu">
|
||||
<DropdownItem payload="'file-new-document'">Document</DropdownItem>
|
||||
<DropdownItem payload="'file-new-spreadsheet'">Spreadsheet</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
|
||||
<Dropdown t-name="addon.Dropdown.File.Save.As" owl="1">
|
||||
Save as...
|
||||
<t t-set-slot="menu">
|
||||
<DropdownItem payload="'file-save-as-csv'">CSV</DropdownItem>
|
||||
<DropdownItem payload="'file-save-as-pdf'">PDF</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
|
||||
Nested version
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<Dropdown t-on-dropdown-item-selected="onItemSelected" owl="1">
|
||||
File
|
||||
<t t-set-slot="menu">
|
||||
<DropdownItem payload="'file-open'">Open</DropdownItem>
|
||||
<Dropdown>
|
||||
New
|
||||
<t t-set-slot="menu">
|
||||
<DropdownItem payload="'file-new-document'">Document</DropdownItem>
|
||||
<DropdownItem payload="'file-new-spreadsheet'">Spreadsheet</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
<DropdownItem payload="'file-save'">Save</DropdownItem>
|
||||
<Dropdown>
|
||||
Save as...
|
||||
<t t-set-slot="menu">
|
||||
<DropdownItem payload="'file-save-as-csv'">CSV</DropdownItem>
|
||||
<DropdownItem payload="'file-save-as-pdf'">PDF</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</t>
|
||||
</Dropdown>
|
||||
|
||||
Recursive Multi-level Dropdown
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This example make use of inline style.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<div t-name="addon.MainTemplate" t-on-dropdown-item-selected="onItemSelected">
|
||||
<t t-call="addon.RecursiveDropdown">
|
||||
<t t-set="name" t-value="'Main Menu'" />
|
||||
<t t-set="items" t-value="state.menuItems" />
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<Dropdown t-name="addon.RecursiveDropdown" owl="1">
|
||||
<div style="display: inline-flex; color:white; background-color: red; padding: 2px; border: 1px white solid; opacity: 50%">
|
||||
<t t-esc="name" />
|
||||
</div>
|
||||
|
||||
<t t-set-slot="menu">
|
||||
<t t-foreach="items" t-as="item" t-key="item.id">
|
||||
<t t-if="!item.childrenTree.length">
|
||||
<!-- If this item has no child: make it a <DropdownItem/> -->
|
||||
<DropdownItem payload="item">
|
||||
<div style="display: inline-flex; color:white; background-color: blue; padding: 2px;border: 1px white solid; opacity: 50%;">
|
||||
<t t-esc="item.name" />
|
||||
</div>
|
||||
</DropdownItem>
|
||||
</t>
|
||||
|
||||
<!-- Else: recursively call the current dropdown template. -->
|
||||
<t t-else="" t-call="addon.RecursiveDropdown">
|
||||
<t t-set="name" t-value="item.name" />
|
||||
<t t-set="items" t-value="item.childrenTree" />
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</Dropdown>
|
@ -0,0 +1,97 @@
|
||||
|
||||
Popover
|
||||
=======
|
||||
|
||||
Props
|
||||
-----
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Type
|
||||
- Default
|
||||
- Description
|
||||
* - ``popoverClass``
|
||||
- string
|
||||
-
|
||||
- the classes contained in ``popoverClass`` are added on the element "div.o_popover"
|
||||
* - ``position``
|
||||
- "bottom" | "top" | "left" | "right"
|
||||
- "bottom"
|
||||
- determine the position of the popover
|
||||
* - ``target``
|
||||
- string
|
||||
-
|
||||
- if provided then the popover will be placed around this target. Selector can match multiple element
|
||||
* - ``trigger``
|
||||
- "manual" | "click" | "hover"
|
||||
- "click"
|
||||
- determine how the popover is triggered
|
||||
|
||||
|
||||
Slots
|
||||
-----
|
||||
|
||||
Popovers can be configured with slot
|
||||
|
||||
The ``default`` slot can be used to define the popover's content
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<Popover>
|
||||
Content
|
||||
</Popover>
|
||||
|
||||
Popover's target can be defined with props but there is also a slot to define it.
|
||||
|
||||
With props
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<Popover target="'.popover-target'">
|
||||
Content
|
||||
</Popover>
|
||||
|
||||
With slot
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<Popover>
|
||||
Content
|
||||
<t t-set-slot="target">
|
||||
<button>Click me to open the popover</button>
|
||||
</t>
|
||||
</Popover>
|
||||
|
||||
Events
|
||||
------
|
||||
|
||||
Popover can trigger a ``popover-closed`` event when it wants to close.
|
||||
This event is usually triggered by clicking outside the popover and the target
|
||||
but can be triggered manually inside the popover too.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<Popover>
|
||||
<header>
|
||||
<t t-esc="title" />
|
||||
<button t-on-click="trigger('popover-closed')">x</button>
|
||||
</header>
|
||||
<div>
|
||||
Popover's content
|
||||
</div>
|
||||
<t t-set-slot="target">
|
||||
<button>Click me to open the popover</button>
|
||||
</t>
|
||||
</Popover>
|
||||
|
||||
Popover also listens on the ``popover-compute`` event to re-compute it's
|
||||
position or size when children need to.
|
||||
|
||||
Location in the dom
|
||||
-------------------
|
||||
|
||||
The Popover class uses a portal to locate itself in a div with class
|
||||
``o_popover_container`` but the communication with the parent goes as
|
||||
usual: via props or custom/dom events.
|
14
content/developer/reference/javascript/components/readme.rst
Normal file
14
content/developer/reference/javascript/components/readme.rst
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
Various components
|
||||
==================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
This section regroups links to the components that aim to be useful throughout the user interface:
|
||||
|
||||
|
||||
* `Dialog <dialog.md>`_
|
||||
* `Dropdown <dropdown.md>`_
|
||||
* `Popover <popover.md>`_
|
||||
* ...
|
@ -0,0 +1,138 @@
|
||||
|
||||
Crash Manager and Errors
|
||||
========================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Odoo comes with a crash manager. It is a system that catch all errors that have bubbled up uncaught and show a special dialog with information to help debug the problem. It's the first and simplest way for clients to report a problem and provide useful information on the crash.
|
||||
|
||||
As a developer, there's very few chances you will have to touch to the crash manager code. Especially if you're a backend developer. However, a good understanding of it's inner workings and how to throw errors will help you right better code.
|
||||
|
||||
Note: if you're a backend dev just willing to display a different dialog from an RPC error, make sure to read the ``RPC Errors`` section.
|
||||
|
||||
The net and the dispatcher
|
||||
--------------------------
|
||||
|
||||
Any error that is not caugh (meaning that is is not handled by a try / catch block for example) will eventually bubble up all the way until it reaches what we call the net. The net is made of event listeners on the global window object, listening for errors and rejected promises. The net is an analogy to a safety net that would catch things at the last moment. Once caught, the error is cast if needed to an instance of OdooError and send with the bus to the error dispatcher.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
// this is pseudo code
|
||||
window.addEventListener((bubbledUpErrorEvent) => {
|
||||
const error: OdooError = someCodeThatCastTheError(bubbledUpErrorEvent.error);
|
||||
bus.trigger("ERROR_DISPATCH", error);
|
||||
});
|
||||
|
||||
The dispatcher will then use the error type to take action. Most of the time, it is just showing a different dialog.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
// this is pseudo code
|
||||
bus.on(ERROR_DISPATCH, (error) => {
|
||||
switch (error.type) {
|
||||
case "SERVER_ERROR":
|
||||
dialog_service.open(ServerErrorDialog, {
|
||||
// props ...
|
||||
});
|
||||
break;
|
||||
case "CORS_ERROR":
|
||||
dialog_service.open(ClientErrorDialog, {
|
||||
// props ...
|
||||
});
|
||||
break;
|
||||
default:
|
||||
dialog_service.open(ErrorDialog, {
|
||||
// props ...
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
OdooError
|
||||
---------
|
||||
|
||||
A new error class is introduced, the ``OdooError``. This class inherits ``Error`` , and add a few properties that let one customize what will be shown in an error dialog. Note that throwing an instance of the class ``Error`` in your javascript code is still valid and its message and stack argument will be correctly used. The name of the error will simply fallback to ``DEFAULT_ERROR`` , which is less explicit than another name.
|
||||
|
||||
An ``OdooError`` expects a name as constructor argument. The name should shortly and explicitly define the kind of error that is thrown.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
const error = new OdooError("XHR_NETWORK_ERROR");
|
||||
|
||||
Then, you can (and should!) add some more metadata:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
error.message = _lt("The server couldn't be reached, you may want to check your connection...");
|
||||
error.traceback = "Custom traceback";
|
||||
|
||||
Important note: you may not want to have a custom traceback. Javascript ``Error`` have a ``stack`` property that gets automatically populate. So unless you have a good reason to add a custom tracaback, simply let it empty, so the crash manager falls back to the special ``stack`` property.
|
||||
|
||||
With all that, your error will be shown in a dailog component. Which one ? It depends. By default, the ``ErrorDialog`` component is used. However, if the dispatcher has a case for this error name, it may be shown in a different dialog, like in this case, a ``ServerDialogError`` perhaps.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
// this is pseudo code
|
||||
bus.on(ERROR_DISPATCH, error => {
|
||||
// ...
|
||||
case 'XHR_NETWORK_ERROR':
|
||||
dialog_service.open(ServerErrorDialog, {
|
||||
// props ...
|
||||
})
|
||||
break;
|
||||
// ...
|
||||
})
|
||||
|
||||
What if you want a dialog that is not the simple ``ErrorDialog`` component but your error name is not part of the switch ? Let's say ``UNIQUE_JS_ERROR_THAT_ONLY_OCCURS_AT_ONE_PLACE``. Do not jump in the switch and add a case for this error. The switch should be kept for the most common errors.
|
||||
There's an alternative to map an OdooError to anoter dialog:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
error.component = SomeOtherDialogComponentClass;
|
||||
|
||||
With this, you instruct the crash manager to use this component that you may even have created yourself.
|
||||
So, the rule is:
|
||||
|
||||
|
||||
#. Any error: you don't mind it used the ``ErrorDialog`` ? Let it be, you're done.
|
||||
#. Common error: there should be a error name that fits your need, like ``SERVER_ERROR`` , ``CORS_ERROR`` , etc.
|
||||
#. New common error: it does not have a case in the switch yet. We should add it.
|
||||
#. Uncommon error: we shouldn't add it to the switch, use the ``component`` on the ``OdooError`` class.
|
||||
|
||||
RPC Errors
|
||||
----------
|
||||
|
||||
In odoo, most of the interactions client <=> server are made using the RPC service. The RPC service is taylored to accept and parse error from the python code. All the errors coming from the RPCs will be instances of ``RPCError`` that inherits directly from ``OdooError``. You will never have to instanciate an ``RPCError``. Just know it contains all the server metadata about an error.
|
||||
What is interesting to know is how can you, as a backend dev, show a custom dialog depending on python error that occured during the RPC.
|
||||
Most of the other sections of this doc are irrelevent to your case. Indeed, the dispatcher is already ready to get an ``RPCError`` error name, and will display correctly the ``RPCErrorDialog`` component.
|
||||
|
||||
However, the ``RPCError`` has a property called ``exception_class``. This would contain the full python exception class name. By example, ``odoo.exceptions.AccessError``. And there is a regestry mapping python error name to a dialog object.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
export const errorDialogRegistry: Registry<Type<Component>> = new Registry();
|
||||
errorDialogRegistry
|
||||
// ...
|
||||
.add("odoo.exceptions.AccessDenied", WarningDialog)
|
||||
.add("odoo.exceptions.AccessError", WarningDialog)
|
||||
.add("odoo.exceptions.RedirectWarning", RedirectWarningDialog)
|
||||
.add("odoo.http.SessionExpiredException", SessionExpiredDialog)
|
||||
.add("werkzeug.exceptions.Forbidden", SessionExpiredDialog)
|
||||
.add("504", Error504Dialog);
|
||||
// ...
|
||||
|
||||
You can therefore add to this registry a new mapping.
|
||||
|
||||
Little recap: you want to add a custom dialog from a python error happening during an RPC ?
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
errorDialogRegistry.add("odoo.exceptions.SomeServerError", SomeDialogError);
|
||||
|
||||
That's it. It should work.
|
||||
|
||||
Advanced case: what are _t or _lt is not easily available for the error message ?
|
||||
---------------------------------------------------------------------------------
|
||||
|
||||
In those cases, maybe it's best to have a dialog specificely for your error (or category of error). Because components have an env and have the method ``_t``.
|
32
content/developer/reference/javascript/environment.rst
Normal file
32
content/developer/reference/javascript/environment.rst
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
Environment
|
||||
===========
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Each Owl application runs in a specific environment, which contains many (mostly)
|
||||
global informations about the application. Odoo is no different, and adds a few
|
||||
important data in the environment. Here is a description of its content:
|
||||
|
||||
Content
|
||||
-------
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Description
|
||||
* - ``browser``
|
||||
- list of `browser <browser.md>`_ entities with side effects
|
||||
* - ``bus``
|
||||
- main application bus
|
||||
* - ``qweb``
|
||||
- application current ``QWeb`` rendering engine
|
||||
* - ``registries``
|
||||
- object containing all relevant registries
|
||||
* - ``services``
|
||||
- values of all deployed `services <services/readme.md>`_
|
||||
* - ``_t``
|
||||
- eager `translation function <localization.md#_t>`_
|
||||
|
169
content/developer/reference/javascript/getting_started.rst
Normal file
169
content/developer/reference/javascript/getting_started.rst
Normal file
@ -0,0 +1,169 @@
|
||||
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
The addon ``wowl`` is a self-contained web project: it has a ``package.json`` file
|
||||
(which makes it a proper ``npm`` application), a ``tsconfig.json`` (to configure
|
||||
typescript), a ``rollup.config.js`` (to configure the Rollup bundler).
|
||||
|
||||
It has typescript supports, xml templates detection, can run tests written in
|
||||
typescript, it features a livereload server, and more.
|
||||
|
||||
Initial setup
|
||||
-------------
|
||||
|
||||
The first and essential task is to install the dependencies defined in the project:
|
||||
to do that, one need to do the following:
|
||||
|
||||
|
||||
* have ``npm`` installed (the ``node`` package manager, which comes with ``node`` ),
|
||||
* open a terminal and move to the ``addons/wowl`` folder (because it is the root
|
||||
of the web project
|
||||
* type ``npm install``
|
||||
|
||||
Once this is done, the commands ``npm run build`` and ``npm run dev`` are available
|
||||
(see next section for more detail).
|
||||
|
||||
WARNING: the first and most common error is to run commands in the root of the
|
||||
odoo project. This will not work! The configuration files are located in the
|
||||
``addons/wowl`` folder, and each command should be run inside that path!
|
||||
|
||||
Configure your IDE
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The addon ``wowl`` is written in typescript. If you're using Sublime Text, install
|
||||
the TypeScript package from https://www.typescriptlang.org/ to enable type
|
||||
checking.
|
||||
|
||||
WARNING: other TypeScript packages may conflict with this one, so if it isn't
|
||||
working well, make sure no other is installed.
|
||||
|
||||
Main scripts
|
||||
------------
|
||||
|
||||
|
||||
* `npm run build`: build all the assets, which includes the following steps:
|
||||
|
||||
* compile the typescript ``src`` files into javascript (output: ``static/dist/js/src`` )
|
||||
* bundle the js files in a single iife bundle (output: ``static/dist/app.js`` )
|
||||
* compile the typescript ``tests`` files into javascript (output: ``static/dist/js/tests`` )
|
||||
* bundle the test files into a single iife bundle (output: ``static/dist/app_tests.ts`` )
|
||||
|
||||
* ``npm run dev`` : main command for developing on this project.
|
||||
|
||||
* build all the assets
|
||||
* watch the filesystem and rebuild assets if necessary
|
||||
* start a livereload server (port 8070, hardcoded) to make sure that each
|
||||
connected browser is refreshed whenever necessary
|
||||
|
||||
* ``npm run prettier`` : autoformat all the typescript, scss and markdown files
|
||||
located in ``static/src`` , ``static/tests`` and ``doc``.
|
||||
|
||||
Templates
|
||||
---------
|
||||
|
||||
Wowl introduces the support for a new key ``owl_qweb`` in the odoo manifest
|
||||
(\ ``__manifest__.py`` ). This works like ``qweb`` (but we could not use it because we
|
||||
want the current ``web/`` addon to keep working), except that it also support
|
||||
folders. So, if a folder such as ``static/src/components`` is added to that
|
||||
configuration, then each xml files inside (and inside each sub folders) will be
|
||||
considered a static template, that will be sent to the web client through the
|
||||
``/wowl/templates/...`` route.
|
||||
|
||||
In short, if Wowl cannot find a template, it is likely because the ``owl_qweb``
|
||||
key is not set, or incorrect.
|
||||
|
||||
Javascript bundles
|
||||
------------------
|
||||
|
||||
For javascript code, Wowl has its specific setup (typescript files are converted
|
||||
to javascript, then bundled). The bundler (rollup) will start with the ``main.ts``
|
||||
files (in ``src/`` and in ``tests/`` ) and use that as a starting point, then bundle
|
||||
all their dependencies.
|
||||
|
||||
So, they are detected automatically, but a file needs to be imported somewhere to
|
||||
be present in the final ``app.js`` or ``app_test.js``.
|
||||
|
||||
Styles
|
||||
------
|
||||
|
||||
Styles are handled in an unusual way for Odoo: there is a new ``style`` key in
|
||||
the manifest, that works like the ``owl_qweb`` key for templates. This key describes
|
||||
a list of files or folders. Then, each ``scss`` file that can be found is added
|
||||
in a dynamic asset bundle.
|
||||
|
||||
The main benefit is that we don't have to manually add all these scss files each
|
||||
time a new component is created.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Unit tests can be written in typescript, using the QUnit framework. As mentioned
|
||||
above, they should be imported in the ``main.ts`` file to be included in the
|
||||
test suite.
|
||||
|
||||
To run the test suite, one needs to open the ``/wowl/tests`` route in a browser.
|
||||
|
||||
Livereload
|
||||
----------
|
||||
|
||||
The WOWL addon provides a livereload feature: once activated, a browser tab will
|
||||
refresh itself automatically whenever some static assets was modified.
|
||||
|
||||
The ``npm`` script ``npm run livereload`` (also executed by ``npm run dev`` will start
|
||||
a node application that listen on a specific port. The client code will open a
|
||||
websocket and attempt to connect to the livereload server. If it manages to do
|
||||
this, you will see the message ``[livereload] connection established`` in your
|
||||
browser console.
|
||||
|
||||
Note that it works also for the unit `tests <#tests>`_.
|
||||
|
||||
Common issues
|
||||
-------------
|
||||
|
||||
My new code is not executed
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This commonly happens when one add a new typescript file into the source code.
|
||||
This file is properly compiled in JS, but then is not included in the main
|
||||
bundle ``app.js``.
|
||||
|
||||
The reason is that the way files are bundled is by starting at ``main.js`` , then
|
||||
following all its dependencies (and dependencies of dependencies, ...) to
|
||||
include them. If your new file is not imported anywhere, it will not be included
|
||||
in the final bundle.
|
||||
|
||||
System limit for file watchers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It may happen that you encounter an error while running some commands such as
|
||||
``npm run dev`` , related to file watchers:
|
||||
|
||||
.. code-block::
|
||||
|
||||
ENOSPC: System limit for number of file watchers reached...
|
||||
|
||||
This is probably caused by the livereload features, that need to watch the
|
||||
file system. Also, note that odoo started in ``dev=all`` mode also has its own
|
||||
watchers.
|
||||
|
||||
The only solution in that case is to increase the os limit. See
|
||||
https://howchoo.com/node/node-increase-file-watcher-system-limit for more info.
|
||||
|
||||
File in browser does not match TS code
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A common situation arises when one writes some typescript code, then notices that
|
||||
the javascript code executed on the browser is not the same. There are two
|
||||
probable causes for this issue:
|
||||
|
||||
|
||||
#.
|
||||
the typescript code was not built because we forgot to run the command
|
||||
``npm run build`` , or because we don't have a ``npm run dev`` command running.
|
||||
In that case, the solution is simple: just run one of these commands.
|
||||
|
||||
#.
|
||||
there is an error in the typescript code, in which case the typescript
|
||||
compiler simply does not output a JS file, and the previous file simply remains.
|
||||
Obviously, the solution is then to fix the typescript error.
|
@ -0,0 +1,46 @@
|
||||
|
||||
Global odoo object
|
||||
==================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Whenever the odoo web client is loaded on a page, a special global variable ``odoo``
|
||||
will be set in the page. This object contains a few useful methods/entities.
|
||||
These values are there for information, integration or debugging purposes, and
|
||||
are not to be used by business code.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
// in browser console (opened with F12)
|
||||
console.log(odoo);
|
||||
// will display an object looking like this:
|
||||
// {
|
||||
// info: {...},
|
||||
// __DEBUG__: {...}
|
||||
// }
|
||||
|
||||
Exported Values
|
||||
---------------
|
||||
|
||||
Here is an explanation of what each exported values are:
|
||||
|
||||
|
||||
*
|
||||
``info`` : this is an object which contains a few informations about the odoo
|
||||
server that we are connected to.
|
||||
|
||||
|
||||
* ``db (string)`` : the technical name of the current postgres database
|
||||
* ``server_version (string)`` : a short string describing the version of the odoo
|
||||
code currently running. It may look like this: ``14.1alpha1``.
|
||||
* ``server_version_info ((string|number)[])`` : the ``server_version`` string is not
|
||||
easy to parse/consume, so the ``server_version_info`` key is exported as well.
|
||||
It is an array looking like this: ``[14, 1, 0, "alpha", 1, ""]``
|
||||
|
||||
*
|
||||
``__DEBUG__`` : this object contains values that are useful to debug/play with the
|
||||
odoo application, but that should not be accessed in real code
|
||||
|
||||
|
||||
* ``root (Component)`` : this is the main web client instance
|
@ -0,0 +1,2 @@
|
||||
Action Hook: ``useAction``
|
||||
==========================
|
@ -0,0 +1,2 @@
|
||||
Autofocus Hook: ``useAutofocus``
|
||||
================================
|
@ -0,0 +1,2 @@
|
||||
Bus Hook: ``useBus``
|
||||
====================
|
@ -0,0 +1,2 @@
|
||||
Command Hook: ``useCommand``
|
||||
============================
|
@ -0,0 +1,2 @@
|
||||
Dropdown Navigation Hook: ``useDropdownNavigation``
|
||||
===================================================
|
@ -0,0 +1,2 @@
|
||||
Effect Hook: ``useEffect``
|
||||
==========================
|
49
content/developer/reference/javascript/hooks/hooks.rst
Normal file
49
content/developer/reference/javascript/hooks/hooks.rst
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
Hooks
|
||||
=====
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
|
||||
* `useBus <#usebus>`_
|
||||
|
||||
useBus
|
||||
------
|
||||
|
||||
This hook ensures a bus is properly used by a component:
|
||||
|
||||
|
||||
* each time the component is **mounted** , the callback registers on the bus,
|
||||
* each time the component is **unmounted** , the callback unregisters off the bus.
|
||||
|
||||
API
|
||||
^^^
|
||||
|
||||
..
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
useBus(bus: EventBus, eventName: string, callback: Callback): void
|
||||
|
||||
|
||||
where
|
||||
|
||||
|
||||
* ``bus`` is the event bus to use.
|
||||
* ``eventName`` is the event name to register a callback for.
|
||||
* ``callback`` gets called when the ``bus`` dispatches an ``eventName`` event.
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useBus(this.env.bus, "some-event", this.myCallback);
|
||||
}
|
||||
myCallback() {
|
||||
// ...
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
Hotkey Hook: ``useHotkey``
|
||||
==========================
|
@ -0,0 +1,2 @@
|
||||
Listener Hook: ``useListener``
|
||||
==============================
|
@ -0,0 +1,2 @@
|
||||
Popover Hook: ``usePopover``
|
||||
============================
|
@ -0,0 +1,2 @@
|
||||
Position Hook: ``usePosition``
|
||||
==============================
|
@ -0,0 +1,2 @@
|
||||
Service Hook: ``useService``
|
||||
============================
|
@ -0,0 +1,2 @@
|
||||
Tooltip Hook: ``useTooltip``
|
||||
============================
|
@ -0,0 +1,2 @@
|
||||
View Hook: ``useView``
|
||||
======================
|
63
content/developer/reference/javascript/localization.rst
Normal file
63
content/developer/reference/javascript/localization.rst
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
Localization
|
||||
============
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Each internal user has a specified language (default ``en_US`` ) according to which various parts
|
||||
of the interface can depend on: terms are translated, numbers are formated,... with respect to
|
||||
that language.
|
||||
|
||||
The localization module defines two functions ``_t`` and ``_lt`` used to translate terms
|
||||
and various other preference parameters.
|
||||
|
||||
_t
|
||||
--
|
||||
|
||||
Eager translation function, performs translation immediately at call (this requires the translations
|
||||
to be available). Used by owl to translate automatically text nodes.
|
||||
It is also available in the `environment <environment.md>`_ so that,
|
||||
for example, the components can use it to translate various terms:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
this.env._t("Hello");
|
||||
|
||||
_lt
|
||||
---
|
||||
|
||||
Lazy translation function, only performs the translation when actually
|
||||
printed (e.g. inserted into a template).
|
||||
Useful when defining translatable strings in code evaluated before the
|
||||
translations are loaded, as class attributes or at the top-level of
|
||||
an Odoo Web module, for example:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
import { _lt } from ./services/localization.ts
|
||||
const greeting = _lt("Hello");
|
||||
|
||||
The value returned by ``_lt`` is an object with a single property ``toString``.
|
||||
If the current user language is ``fr_FR`` and ``Hello`` has translation ``Bonjour`` ,
|
||||
``greeting`` takes the value
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
{
|
||||
toString: () => "Bonjour";
|
||||
}
|
||||
|
||||
Other localization parameters
|
||||
-----------------------------
|
||||
|
||||
The following localization parameters can be accessed via the `User Service <services/user.md>`_\ :
|
||||
|
||||
``dateFormat`` , ``decimalPoint`` , ``direction`` , ``grouping`` , ``multiLang`` , ``thousandsSep`` , ``timeFormat``.
|
||||
|
||||
Terms to translate
|
||||
------------------
|
||||
|
||||
Every string literal on which ``_t`` or ``_lt`` is applied to is automatically added to the list of
|
||||
terms to translate (if not already added).
|
||||
For more information on translations see `Translating Modules <../../../doc/reference/translations.rst>`_
|
32
content/developer/reference/javascript/main_components.rst
Normal file
32
content/developer/reference/javascript/main_components.rst
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
Main Components
|
||||
===============
|
||||
|
||||
A common need for a feature is the ability to interact with the DOM, somewhere
|
||||
at the root of the web client. For example, the notification service want to
|
||||
include a component somewhere to be able to display notifications. Or maybe the
|
||||
discuss code will need the ability to display chat windows.
|
||||
|
||||
The Odoo javascript framework provides a way to do that, with the idea of
|
||||
``main components``. These are component classes (NOT instances) that are registered
|
||||
in the ``mainComponentRegistry``. For example:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
class MyComponent extends Component {
|
||||
...
|
||||
}
|
||||
|
||||
componentRegistry.add("myaddon.MyComponent", MyComponent);
|
||||
|
||||
When the web client is rendered, it will iterate over all these Component and
|
||||
add them to a ``div`` inside its template.
|
||||
|
||||
Notes:
|
||||
|
||||
|
||||
* like usual, it is a convention to prefix the registry keys with the name of
|
||||
the odoo addon that register it, in order to lessen the risk of name collision.
|
||||
* Since these components are rendered when the Web client is started, they can
|
||||
actually delay the rendering (if they implement ``willStart`` ). Therefore, one
|
||||
need to be cautious. If possible, try to keep them synchronous.
|
@ -0,0 +1,39 @@
|
||||
|
||||
Python Interpreter
|
||||
==================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The Odoo web client features a built-in small python interpreter. Its purpose
|
||||
is to evaluate small python expressions. This is important, because views in
|
||||
Odoo have modifiers written in python, but they need to be evaluated by the
|
||||
browser.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
evaluate("1 + 2*{'a': 1}.get('b', 54) + v", { v: 33 }); // returns 142
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The ``py`` javascript code exports 5 functions:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Function
|
||||
- Description
|
||||
* - tokenize(expr: string) -> Token[]
|
||||
- convert a string into a list of tokens
|
||||
* - parse(tokens: Token[]) -> AST
|
||||
- parse a list of tokens into an AST
|
||||
* - parseExpr(expr: string) -> AST
|
||||
- tokenize and parse an expression
|
||||
* - evaluate(ast: AST) -> any
|
||||
- evaluate an AST
|
||||
* - evaluateExpr(expr: string) -> any
|
||||
- tokenize, parse and evaluate an expression
|
||||
|
30
content/developer/reference/javascript/readme.rst
Normal file
30
content/developer/reference/javascript/readme.rst
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
Web Client
|
||||
==========
|
||||
|
||||
Working on the WOWL project
|
||||
---------------------------
|
||||
|
||||
|
||||
* `Web Client Architecture <architecture.md>`_
|
||||
* `Getting Started <getting_started.md>`_
|
||||
* `Testing <testing.md>`_
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
||||
|
||||
* `Browser <browser.md>`_
|
||||
* `Bus <bus.md>`_
|
||||
* `Commands <commands/readme.md>`_
|
||||
* `Core <core/readme.md>`_
|
||||
* `Environment <environment.md>`_
|
||||
* `Global odoo object <global_odoo_object.md>`_
|
||||
* `Localization <localization.md>`_
|
||||
* `Main Components <main_components.md>`_
|
||||
* `Python interpreter <python_interpreter.md>`_
|
||||
* `Registries <registries/readme.md>`_
|
||||
* `Services <services/readme.md>`_
|
||||
* `Systray <systray.md>`_
|
||||
* `Various components <components/readme.md>`_
|
||||
* `Crash managers and errors <crash_manager_and_errors.md>`_
|
@ -0,0 +1,2 @@
|
||||
Action Handler Registry
|
||||
=======================
|
@ -0,0 +1,2 @@
|
||||
Action Registry
|
||||
===============
|
@ -0,0 +1,2 @@
|
||||
Burger Menu Registry
|
||||
====================
|
@ -0,0 +1,54 @@
|
||||
|
||||
Command Category Registry
|
||||
=========================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``commandCategoryRegistry`` gathers the command categories.
|
||||
|
||||
Key Usage
|
||||
---------
|
||||
|
||||
The keys in this registry can be used in two differents ways in order to organize the command palette:
|
||||
|
||||
|
||||
*
|
||||
when registering a new command: ``useCommand({ category: "key", ... })``.
|
||||
|
||||
*
|
||||
applied as an attribute in the document: ``[data-command-category="key"]``.
|
||||
N.B.: if an element should appear in the command palette
|
||||
(e.g. it has a ``[data-hotkey]`` attribute), the closest parent (including itself)
|
||||
having a ``[data-command-category]`` will provide the category key to seek for in the registry.
|
||||
|
||||
Value Type
|
||||
----------
|
||||
|
||||
``{ label?: string }`` where ``label`` is the displayed name of the category in the command palette. Can be undefined.
|
||||
|
||||
Available Categories
|
||||
--------------------
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Sequence
|
||||
- Description
|
||||
* - ``main``
|
||||
- 10
|
||||
- Main Commands
|
||||
* - ``app``
|
||||
- 20
|
||||
- Current App Commands
|
||||
* - ``actions``
|
||||
- 30
|
||||
- More Actions
|
||||
* - ``navbar``
|
||||
- 40
|
||||
- NavBar
|
||||
* - ``default``
|
||||
- 100
|
||||
- Other commands
|
||||
|
@ -0,0 +1,2 @@
|
||||
Command Empty List Registry
|
||||
===========================
|
@ -0,0 +1,2 @@
|
||||
Command Provider Registry
|
||||
=========================
|
@ -0,0 +1,2 @@
|
||||
Company Menu Registry
|
||||
=====================
|
@ -0,0 +1,2 @@
|
||||
Debug Registry
|
||||
==============
|
@ -0,0 +1,2 @@
|
||||
Error Dialogs registry
|
||||
======================
|
@ -0,0 +1,2 @@
|
||||
Error Handler registry
|
||||
======================
|
@ -0,0 +1,2 @@
|
||||
Error Notification Registry
|
||||
===========================
|
@ -0,0 +1,2 @@
|
||||
Favortite Menu Registry
|
||||
=======================
|
@ -0,0 +1,2 @@
|
||||
Field Registry
|
||||
==============
|
@ -0,0 +1,2 @@
|
||||
Formatter Registry
|
||||
==================
|
@ -0,0 +1,2 @@
|
||||
Main Component Registry
|
||||
=======================
|
@ -0,0 +1,2 @@
|
||||
Parser Registry
|
||||
===============
|
57
content/developer/reference/javascript/registries/readme.rst
Normal file
57
content/developer/reference/javascript/registries/readme.rst
Normal file
@ -0,0 +1,57 @@
|
||||
|
||||
Registries
|
||||
==========
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The Odoo web client provides many registries, which allow developers to extend
|
||||
the web client in a safe and structured way.
|
||||
|
||||
Here is a list of all registries:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Description
|
||||
* - ``actions``
|
||||
- definition of all available client actions
|
||||
* - ``Components``
|
||||
- components (class) that will be instantiated at root of web client
|
||||
* - ``errorDialogs``
|
||||
- dialogs (class) that will be instantiated by the crash manager to handle errors
|
||||
* - ``services``
|
||||
- definition of all services that will be deployed
|
||||
* - `systray <../systray.md#adding-a-systray-item>`_
|
||||
- components (class) that will be display in the systray menu (a part of the navbar)
|
||||
* - ``views``
|
||||
- definition of all available views
|
||||
* - `userMenu <user_menu.md>`_
|
||||
- definition of all user menu items
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
A registry is an owl `EventBus <https://github.com/odoo/owl/blob/master/doc/reference/event_bus.md#-event-bus->`_ with some additional methods (below ``T`` is the type of values to be found in the registry):
|
||||
|
||||
|
||||
* ``add: (key: string, value: T, force: boolean = false) => Registry<T>`` : add an entry ``(key, value)`` to the registry. By default, add
|
||||
a key already used results in an error. This can be prevented by using the parameter ``force`` , leading to the replacement of the old entry.
|
||||
The ``add`` method returns the registry itself to allow chaining.
|
||||
* ``get: (key: string) => T`` : returns a value from the registry. An error is thrown in case the key cannot be found in the registry.
|
||||
* ``contains: (key: string) => boolean`` : allow to check the presence of a key in the registry.
|
||||
* ``getAll: () => T[]`` : returns the registry values.
|
||||
* ``getEntries: () => [string, T][]`` : returns the registry entries.
|
||||
* ``remove: (key: string)`` : remove an entry from the registry.
|
||||
|
||||
Each time an item is added (deleted) via ``add`` (resp. ``delete`` ), an event "UPDATE" is triggered with a payload of type
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
interface Payload {
|
||||
operation: "add" | "delete";
|
||||
key: string;
|
||||
value: T;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
Sample Server Registry
|
||||
======================
|
@ -0,0 +1,2 @@
|
||||
Service Registry
|
||||
================
|
@ -0,0 +1,2 @@
|
||||
Systray Registry
|
||||
================
|
@ -0,0 +1,49 @@
|
||||
|
||||
User Menu registry
|
||||
==================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The registry ``userMenu`` gathers the ``user menu`` dropdown elements.
|
||||
|
||||
Value type
|
||||
----------
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
(env: OdooEnv) => UserMenuItem;
|
||||
|
||||
where
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
interface UserMenuItem {
|
||||
description: string;
|
||||
callback: () => void | Promise<any>;
|
||||
hide?: boolean;
|
||||
href?: string;
|
||||
sequence?: number;
|
||||
}
|
||||
|
||||
Thus each value of the registy is a function taking the `environment <../environment.md>`_ in entry
|
||||
and returning a plain object with some keys:
|
||||
|
||||
|
||||
* ``description`` : the item text,
|
||||
* ``href`` : (optional) if given (and truthy), the item text is put in a ``a`` tag with given attribute href,
|
||||
* ``callback`` : callback to call when the item is clicked on,
|
||||
* `hide`: (optional) indicates if the item should be hidden (default: false),
|
||||
* `sequence`: (optional) determines the rank of the item among the other dropwdown items (default: 100).
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
env.registry.userMenu.add("key", (env) => {
|
||||
return {
|
||||
description: env._t("Technical Settings"),
|
||||
callback: () => { env.services.action_manager.doAction(3); };
|
||||
hide: (env.browser.random() < 0.5),
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
View Registry
|
||||
=============
|
@ -0,0 +1,54 @@
|
||||
|
||||
Action Service
|
||||
==============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``action_manager``
|
||||
- ``notifications`` , ``rpc`` , ``router`` , ``user``
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The action manager handles every `\ ``ir.actions.actions`` <https://www.odoo.com/documentation/14.0/reference/actions.html>`_ triggered by user interaction.
|
||||
Clicking on a menu item, on a button, or changing of view type are all examples of
|
||||
interactions handled by the action manager.
|
||||
|
||||
The action manager gives priority to the most recent user request, and will drop
|
||||
a request if another one comes after. The second request obviously takes the state
|
||||
of the action manager as it was before the first request. When possible, unnecessary RPC
|
||||
and rendering must be canceled if another request is made by the user.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The action_manager service exports some methods, which are not all meant to be used by everyone:
|
||||
|
||||
|
||||
*
|
||||
`doAction(action: ActionRequest, options: ActionOptions): Promise<void>;`: probably the one thing to remember and use. It executes the action represented by the ActionRequest descriptor. An `ActionRequest` can be either its full XML id, its postgres id, the tag of the client action, or an object fully describing the action. `ActionOptions` is ....... . The moment when the Promise is resolved is guaranteed only in the following crucial cases:
|
||||
|
||||
|
||||
* ``ir.actions.report`` : when the report is downloaded, or when the report is displayed in the DOM.
|
||||
* ``ir.actions.act_window`` : when the action is visible in the DOM.
|
||||
* ``ir.actions.act_window_close`` : when the dialog has been closed. If there was no dialog, the Promise resolves immediately.
|
||||
For all other actions types, there are no guarantee of that precise moment.
|
||||
|
||||
*
|
||||
``switchView(viewType: viewType): void`` : only applicable when the current visible action is an ``ir.actions.act_window``. It switches the view to the target viewType. In principle, it shouldn't be used outside of frameworky developments.
|
||||
|
||||
*
|
||||
``restore(jsId: string): void;`` : restores the controller with ``jsId`` from the breadcrumbs stack back in the DOM. It shouldn't be used outside of frameworky developments.
|
||||
|
||||
*
|
||||
``loadState(state: Route["hash"], options: ActionOptions): Promise<boolean>;`` : an algorithm that decides what the action manager should do with the data present in the URL's hash. It returns a ``boolean`` wrapped in a Promise. The boolean indicates whether the action manager did handle the URL's state. This method must not be used.
|
||||
|
||||
Technical notes
|
||||
---------------
|
||||
|
||||
The action manager service tells the world that a rendering is necessary by triggering the
|
||||
event ``ACTION_MANAGER:UPDATE`` on the `main bus <./../bus.md>`_.
|
@ -0,0 +1,70 @@
|
||||
|
||||
Command Service
|
||||
===============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``command``
|
||||
- ``dialog`` , ``hotkey`` , ``ui``
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``command`` service offers a way to register commands.
|
||||
|
||||
A Command Palette could then be displayed through the hotkey ``Control+K``.
|
||||
|
||||
This palette displays a list including :
|
||||
|
||||
|
||||
* the commands registered in the service
|
||||
* any visible elements in the ``ui.activeElement`` that are accessible through an ``[data-hotkey]`` attribute.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The ``command`` service provides the following API:
|
||||
|
||||
|
||||
*
|
||||
``type Command = { name: string, action: ()=>void, category?: string, hotkey?: string, }``
|
||||
|
||||
*
|
||||
``registerCommand(command: Command): number``
|
||||
|
||||
*
|
||||
``unregisterCommand(token: number)``
|
||||
|
||||
In addition to that, you have access to some development helpers which are **greatly** recommended:
|
||||
|
||||
|
||||
* ``useCommand(command: Command): void`` :
|
||||
a hook that ensures your registration exist only when your component is mounted.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useCommand({
|
||||
name: "My Command 1",
|
||||
action: () => {
|
||||
// code when command 1 is executed
|
||||
}
|
||||
});
|
||||
useCommand({
|
||||
name: "My Super Command",
|
||||
hotkey: "shift-home",
|
||||
action: () => {
|
||||
// code when super command is executed
|
||||
// note that the super command can also get executed with the hotkey "shift-home"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
Company Service
|
||||
===============
|
@ -0,0 +1,2 @@
|
||||
Cookie Service
|
||||
==============
|
@ -0,0 +1,41 @@
|
||||
|
||||
Dialog Service
|
||||
==============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``dialog_manager``
|
||||
- None
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``dialog manager service`` offers a simple API that allows to open dialogs but with
|
||||
few interactions possible: when possible, it is better to instantiate a
|
||||
dialog by using a Dialog tag in a component template.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The dialog_manager service exports one method:
|
||||
|
||||
|
||||
* ``open(dialogClass: Type<Component>, props?: object): void`` : the ``dialog class`` given as
|
||||
first parameter is instantiated with the optional props given (or with ``{}`` ).
|
||||
|
||||
By ``dialog class`` , we mean a class extending ``owl.Component`` and having as root node ``Dialog`` :
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class CustomDialog extends owl.Component {
|
||||
static template = owl.tags.xml`
|
||||
<Dialog title="'Custom title'" size="'modal-xl'">
|
||||
...
|
||||
</Dialog
|
||||
`;
|
||||
...
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
Effect Service
|
||||
==============
|
@ -0,0 +1,71 @@
|
||||
|
||||
Error Service
|
||||
=============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``crash_manager``
|
||||
- `\ ``dialog_manager`` <dialog_manager.md>`_
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``crash manager service`` responsibility is to display a dialog
|
||||
when an rpc error or a client error occurs. No interaction with the
|
||||
crash manager is possible. If deployed, the crash manager simply listens
|
||||
to some event and opens the appropriate dialogs when needed.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The crash_manager service does not export anything.
|
||||
|
||||
Channels
|
||||
--------
|
||||
|
||||
The crash manager receives errors throught two channels:
|
||||
|
||||
|
||||
* it listens on ``env.bus`` the event ``RPC_ERROR`` ;
|
||||
* it listens on ``window`` the event ``error`` ;
|
||||
|
||||
RPC_ERROR event handling
|
||||
------------------------
|
||||
|
||||
When an event ``RPC_ERROR`` is triggerd on the bus, the crash manager processes the
|
||||
``RPCError`` in the following way:
|
||||
|
||||
|
||||
*
|
||||
look if the error's ``type`` is ``server`` ;
|
||||
|
||||
*
|
||||
if this is the case, the optional error's ``name`` indicates which `dialog class <./dialog_manager.md#api>`_
|
||||
(from the registry ``errorDialogs`` ) should be used to display the error details.
|
||||
If the error is unnamed or no `dialog class] <./dialog_manager.md#api>`_ corresponds to its name, the class
|
||||
``ErrorDialog`` is used by default.
|
||||
|
||||
*
|
||||
The `dialog class <./dialog_manager.md#api>`_ is instantiated with one prop: the error itself.
|
||||
|
||||
This is how a ``UserError`` , ``AccessError``... or a custom server error is handled.
|
||||
|
||||
ERROR event handlling
|
||||
---------------------
|
||||
|
||||
When an event ``error`` is triggered on window, the crash manager processes the ``ErrorEvent``
|
||||
received in the following way:
|
||||
|
||||
|
||||
*
|
||||
if some information on the file name where the error occurs, the error stack... is available
|
||||
an ``ErrorDialog`` is displayed showing that information.
|
||||
|
||||
*
|
||||
if such information is not available, an ``ErrorDialog`` is also displayed but with a generic message.
|
||||
|
||||
This is how client errors are treated.
|
@ -0,0 +1,244 @@
|
||||
|
||||
Hotkey Service
|
||||
==============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``hotkey``
|
||||
- ``ui``
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``hotkey`` service offers an easy way to react to a
|
||||
fine bounded subset of keyboard inputs: `hotkeys <#hotkey-definition>`_.
|
||||
|
||||
It provides some very special features:
|
||||
|
||||
|
||||
*
|
||||
awareness of the UI active element: no need to worry about that from your side.
|
||||
|
||||
*
|
||||
a clean subset of listenable hotkeys:
|
||||
it ensures a consistent experience through different OSes or browsers.
|
||||
|
||||
*
|
||||
a ``useHotkey`` hook: it ensures your JS code executes only
|
||||
when your component is alive and present in the DOM.
|
||||
|
||||
*
|
||||
a ``[data-hotkey]`` attribute: it gives a JS-free way to make
|
||||
any HTML element "clickable" through an hotkey press.
|
||||
|
||||
*
|
||||
a single key to display overlays over all HTML elements having ``[data-hotkey]`` attributes.
|
||||
|
||||
Good To Know - ALT is required
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By default, to trigger an hotkey, it is required to also press the ``ALT`` key:
|
||||
|
||||
..
|
||||
|
||||
*e.g.* **ALT+\ *C** would trigger the **C** hotkey.
|
||||
|
||||
|
||||
An option is available to make it optional in some cases. See the `API <#api>`_ section.
|
||||
|
||||
Good To Know - MAC users
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To ensure a similar experience to Mac users some keys had to get swapped :
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Standard Key is...
|
||||
- MacOS Corresponding Key is...
|
||||
* - ``alt``
|
||||
- ``control``
|
||||
* - ``control``
|
||||
- ``meta`` (known as *Command* by mac users)
|
||||
|
||||
|
||||
Hotkey Definition
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
An **hotkey** represents as a string a single keyboard
|
||||
input from a *single key* combined or not with *modifiers*.
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Authorized Single Keys
|
||||
* - **a-z**
|
||||
* - **0-9**
|
||||
* - **ArrowUp** , **ArrowLeft** , **ArrowDown** and **ArrowRight**
|
||||
* - **PageUp** , **PageDown** , **Home** and **End**
|
||||
* - **Backspace** , **Enter** and **Escape**
|
||||
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Authorized Modifiers
|
||||
* - **Control**
|
||||
* - **Shift**
|
||||
|
||||
|
||||
Hotkeys **must be** written following these rules:
|
||||
|
||||
|
||||
* they are not case sensitive.
|
||||
* the composition character is the plus sign: "\ **+** ".
|
||||
* each hotkey can have none or any modifier in the authorized subset.
|
||||
* order of their parts is important:
|
||||
|
||||
* modifiers must come first
|
||||
* modifiers must get alphabetically sorted (\ **Control** is always before **Shift** )
|
||||
* single key part must come last
|
||||
|
||||
E.g. following hotkeys are valid:
|
||||
|
||||
|
||||
* ``Control+Shift+5``
|
||||
* ``g``
|
||||
* ``Control+g`` (same as ``Control+G`` )
|
||||
|
||||
E.g. following hotkeys are **NOT** valid:
|
||||
|
||||
|
||||
* ``Alt+o`` : **alt** is neither a valid modifier nor a valid single key
|
||||
* ``o+d`` : combining two or more single keys is not valid
|
||||
* ``Shift-p`` : the composition character must be "+" and not "-"
|
||||
* ``Tab`` : it is not part of the list of valid single keys, nor modifiers
|
||||
|
||||
Hotkey Activation
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Hotkeys are activated through keyboard inputs.
|
||||
|
||||
By default, to activate an hotkey, ``ALT`` key should get pressed simultaneously.
|
||||
It is also possible to register an hotkey that will be fireable, even without pressing ALT key.
|
||||
|
||||
When the service detects an hotkey activation, it will:
|
||||
|
||||
|
||||
* execute **all matching registrations callbacks**.
|
||||
* click on **all visible elements having a matching ``[data-hotkey]`` attribute**.
|
||||
|
||||
The ``hotkey`` service will also **make sure that those
|
||||
registrations and elements belong to the correct UI active element** (see `\ ``ui`` service <ui.md>`_\ ).
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The ``hotkey`` service provides the following API:
|
||||
|
||||
|
||||
*
|
||||
``registerHotkey(hotkey: string, callback: ()=>void, options: { altIsOptional?: boolean, allowRepeat?: boolean }): number``
|
||||
|
||||
it asks the service to call the given callback when a matching hotkey is pressed.
|
||||
|
||||
``options.altIsOptional`` : default is false.
|
||||
|
||||
``options.allowRepeat`` : default is false.
|
||||
|
||||
This method returns a token you can use to unsubscribe later on.
|
||||
|
||||
*
|
||||
``unregisterHotkey(token: number): void``
|
||||
|
||||
it asks the service to forget about the token matching registration.
|
||||
|
||||
In addition to that, you have access to some development helpers which are **greatly** recommended:
|
||||
|
||||
|
||||
*
|
||||
``useHotkey(hotkey: string, callback: ()=>void, options: { altIsOptional?: boolean, allowRepeat?: boolean }): void``
|
||||
|
||||
a hook that ensures your registration exists only when your component is mounted.
|
||||
|
||||
*
|
||||
``[data-hotkey]``
|
||||
|
||||
an HTML attribute taking an hotkey definition.
|
||||
|
||||
When the defined hotkey is pressed, the element gets clicked.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
``useHotkey`` hook
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useHotkey("a", this.onAHotkey.bind(this));
|
||||
useHotkey("Home", () => this.onHomeHotkey());
|
||||
}
|
||||
onAHotkey() { ... }
|
||||
onHomeHotkey() { ... }
|
||||
}
|
||||
|
||||
``[data-hotkey]`` attribute
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.variableHotkey = "control+j";
|
||||
}
|
||||
onButton1Clicked() {
|
||||
console.log("clicked either with the mouse or with hotkey 'Shift+o'");
|
||||
}
|
||||
onButton2Clicked() {
|
||||
console.log(`clicked either with the mouse or with hotkey '${this.variableHotkey}'`);
|
||||
}
|
||||
}
|
||||
MyComponent.template = xml`
|
||||
<div>
|
||||
|
||||
<button t-on-click="onButton1Clicked" data-hotkey="Shift+o">
|
||||
One!
|
||||
</button>
|
||||
|
||||
<button t-on-click="onButton2Clicked" t-att-data-hotkey="variableHotkey">
|
||||
Two!
|
||||
</button>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
manual usage of the service
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.hotkey = useService("hotkey");
|
||||
}
|
||||
mounted() {
|
||||
this.hotkeyToken1 = this.hotkey.registerHotkey("backspace", () =>
|
||||
console.log("backspace has been pressed")
|
||||
);
|
||||
this.hotkeyToken2 = this.hotkey.registerHotkey("Shift+P", () =>
|
||||
console.log('Someone pressed on "shift+p"!')
|
||||
);
|
||||
}
|
||||
willUnmount() {
|
||||
// You need to manually unregister your registrations when needed!
|
||||
this.hotkey.unregisterHotkey(this.hotkeyToken1);
|
||||
this.hotkey.unregisterHotkey(this.hotkeyToken2);
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
Http Service
|
||||
============
|
@ -0,0 +1,2 @@
|
||||
Localization Service
|
||||
====================
|
@ -0,0 +1,30 @@
|
||||
|
||||
Menu Service
|
||||
============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``menus``
|
||||
- ``action_manager`` , ``router``
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``menus`` service is an asynchronous service: once the ``deploy`` method is
|
||||
called, it will call the server (using the ``/web/webclient/load_menus/...`` url) to fetch
|
||||
the data. Once it is done, the service is available and can be used to query
|
||||
informations on the menu items.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
Here is a description of all exported methods:
|
||||
|
||||
|
||||
* ``get(menuId)``
|
||||
* ``apps``
|
||||
* ``getMenusAsTree(...)``
|
@ -0,0 +1,73 @@
|
||||
|
||||
Notification Service
|
||||
====================
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``notifications``
|
||||
- None
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Like the name suggests, the ``notifications`` service allows the rest of the
|
||||
interface to display notifications (to inform the user of some interesting
|
||||
relevant facts).
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The ``notifications`` service provides two methods:
|
||||
|
||||
|
||||
*
|
||||
``create(message: string, options?: Options): number``. This method generates a
|
||||
new notification, and returns an ``id`` value.
|
||||
|
||||
Here is a list of the various options:
|
||||
|
||||
|
||||
* ``title (string)`` : if given, this string will be used as a title
|
||||
* ``sticky (boolean)`` : if true, this flag states that the notification should only close
|
||||
with an action of the user (not close itself automatically)
|
||||
* `type (string)`: can be one of the following: ``danger`` , ``warning`` , ``success`` , ``info``.
|
||||
These types will slightly alter the color and the icon that will be used
|
||||
to draw the notification
|
||||
* ``icon (string)`` : if no type is given, this string describes a css class that
|
||||
will be used. It is meant to use a font awesome class. For example, ``fa-cog``
|
||||
* ``className (string)`` : describes a css class that will be added to the
|
||||
notification. It is useful when one needs to add some special style to a
|
||||
notification.
|
||||
* ``messageIsHtml (boolean)`` : if true, the message won't be escaped (false by default)
|
||||
|
||||
*
|
||||
``close(id: string)`` : this method will simply close a notification with a specific ``id`` ,
|
||||
if it was not already closed.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Here is how one component can simply display a notification:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
...
|
||||
notifications = useService('notifications');
|
||||
|
||||
...
|
||||
|
||||
someHandler() {
|
||||
this.notifications.create('Look behind you!!!', { sticky: true });
|
||||
}
|
||||
}
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
|
||||
* whenever the list of notifications is updated, a ``NOTIFICATIONS_CHANGE`` event is
|
||||
triggered on the main bus.
|
@ -0,0 +1,89 @@
|
||||
|
||||
ORM Service
|
||||
=============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``model``
|
||||
- ``rpc`` , ``user``
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``model`` service is the standard way to interact with a python model, on the
|
||||
server. Obviously, each such interaction is asynchronous (and will be done by
|
||||
using the ``rpc`` service).
|
||||
|
||||
In short, the ``model`` service provides a simple API to call the most common orm
|
||||
methods, such as ``read`` , ``search_read`` , ``write`` ... It also has a generic ``call``
|
||||
method to call an arbitrary method from the model.
|
||||
|
||||
Another interesting point to mention is that the user context will automatically
|
||||
be added to each model request.
|
||||
|
||||
Here is a short example of a few possible ways to interact with the ``model``
|
||||
service:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
class MyComponent extends Component {
|
||||
model = useService("model");
|
||||
|
||||
async someMethod() {
|
||||
// return all fields from res.partner 3
|
||||
const result = await this.model("res.partner").read([3]);
|
||||
|
||||
// create a some.model record with a field name set to 'some name' and a
|
||||
// color key set in the context
|
||||
const id = await this.model("some.model").create({ name: "some name" }, { color: "red" });
|
||||
|
||||
// perform a read group with some parameters
|
||||
const groups = await this.model("sale.order").readGroup(
|
||||
[["user_id", "=", 2]],
|
||||
["amount_total:sum"],
|
||||
["date_order"]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Because the ``model`` service is a higher level service than ``rpc`` , easier to use,
|
||||
and with some additional features, it should be preferred above ``rpc``.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The ``model`` service exports a single function with the following signature:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
function model(modelName: string): Model {
|
||||
...
|
||||
}
|
||||
|
||||
A ``Model`` is here defined as an object linked to the ``modelName`` odoo model (for
|
||||
example ``res.partner`` or ``sale.order`` ) with the following five functions, each
|
||||
of them bound to ``modelName`` :
|
||||
|
||||
|
||||
* ``create(state: object, ctx?: Context): Promise<number>`` : call the ``create`` method
|
||||
for the ``modelName`` model defined above,
|
||||
* ``read(ids: number[], fields: string[], ctx?: Context): Promise<any>`` : read one
|
||||
or more records
|
||||
* ``readGroup(domain: any[], fields: string[], groupby: string[], options?: GroupByOptions, ctx?: Context): Promise<ReadGroupResult>;``
|
||||
* ``searchRead(domain: Domain, fields: string[], options?: SearchReadOptions, ctx?: Context): Promise<SearchReadResult>;``
|
||||
* ``unlink(ids: number[], ctx?: Context): Promise<void>``
|
||||
* ``write(ids: number[], data: object, context?: Context): Promise<boolean>``
|
||||
* ``call(method: string, args?: any[], kwargs?: KWargs): Promise<any>``
|
||||
|
||||
Additional notes
|
||||
----------------
|
||||
|
||||
|
||||
* since it uses the ``rpc`` service, it provides the same optimization when used
|
||||
by a component: an error will be thrown if a destroyed component attempts to
|
||||
initiate a model call, and requests will be left pending if a component is
|
||||
destroyed in the meantime.
|
@ -0,0 +1,78 @@
|
||||
|
||||
Popover Service
|
||||
===============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``popover``
|
||||
- None
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``popover`` service offers a simple API that allows to open popovers but with
|
||||
few interactions possible: when possible, it is better to instantiate a
|
||||
popover by using a Popover tag in a component template.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The ``popover`` service exports one method:
|
||||
|
||||
|
||||
*
|
||||
``add(params: Object): string``
|
||||
Signals the manager to add a popover.
|
||||
|
||||
``params`` can contain any of these options:
|
||||
|
||||
|
||||
* ``Component: Type<Component>`` : A class extending ``owl.Component`` and having
|
||||
as root node ``Popover``.
|
||||
* ``props: Object`` : The props passed to the component.
|
||||
* ``content: string`` : A text which is displayed in the popover.
|
||||
Cannot be used with ``Component``.
|
||||
* ``key: string`` : A key to retrieve the popover and remove it later.
|
||||
If no key is given then one will be generated.
|
||||
* ``onClose: (key: string) => void`` : A callback which is executed when the
|
||||
popover is closed.
|
||||
* ``keepOnClose: boolean = false`` : if true then the manager will keep the
|
||||
popover when it closes otherwise the manager removes the popover.
|
||||
|
||||
Returns the ``key`` given in ``params`` or the generated one.
|
||||
|
||||
*
|
||||
``remove(key: string): void``
|
||||
Signals the manager to remove the popover with key = ``key``.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class CustomPopover extends owl.Component {}
|
||||
CustomPopover.template = owl.tags.xml`
|
||||
<Popover target="props.target" trigger="'none'">
|
||||
<t t-set-slot="content">
|
||||
My popover
|
||||
</t>
|
||||
</Popover>
|
||||
`;
|
||||
|
||||
...
|
||||
|
||||
popoverService.add({
|
||||
key: "my-popover",
|
||||
Component: CustomPopover,
|
||||
props: {
|
||||
target: "#target",
|
||||
},
|
||||
keepOnClose: true,
|
||||
});
|
||||
|
||||
...
|
||||
|
||||
popoverService.remove("my-popover");
|
@ -0,0 +1,2 @@
|
||||
Profiling Service
|
||||
=================
|
144
content/developer/reference/javascript/services/readme.rst
Normal file
144
content/developer/reference/javascript/services/readme.rst
Normal file
@ -0,0 +1,144 @@
|
||||
|
||||
Services
|
||||
========
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The Odoo web client is organized in *components*. It is common for a component
|
||||
to have a need to perform tasks or obtain some information outside of itself.
|
||||
|
||||
For example:
|
||||
|
||||
|
||||
* performing an RPC
|
||||
* displaying a notification
|
||||
* asking the web client to change the current action/view
|
||||
* ...
|
||||
|
||||
These kind of features are represented in the web client under the name *service*.
|
||||
A service is basically a piece of code that is started with the web client, and
|
||||
available to the interface (and to other services).
|
||||
|
||||
List of all services
|
||||
--------------------
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Service
|
||||
- Purpose
|
||||
* - `\ ``action_manager`` <action_manager.md>`_
|
||||
- perform actions following user interactions
|
||||
* - `\ ``command`` <../commands/command_service.md>`_
|
||||
- manage commands
|
||||
* - `\ ``crash_manager`` <crash_manager.md>`_
|
||||
- listen errors and open error dialogs
|
||||
* - `\ ``dialog_manager`` <dialog_manager.md>`_
|
||||
- open dialogs
|
||||
* - `\ ``hotkey`` <hotkey.md>`_
|
||||
- manage all keyboard shortcuts in a single place
|
||||
* - `\ ``menus`` <menus.md>`_
|
||||
- keep track of all menu items (app and submenus)
|
||||
* - `\ ``model`` <model.md>`_
|
||||
- interact with (python) models
|
||||
* - `\ ``notifications`` <notifications.md>`_
|
||||
- display a notification (or error)
|
||||
* - `\ ``popover`` <popover.md>`_
|
||||
- add/remove popover
|
||||
* - `\ ``router`` <router.md>`_
|
||||
- manage the url
|
||||
* - `\ ``rpc`` <rpc.md>`_
|
||||
- perform a RPC (in other word, call the server)
|
||||
* - `\ ``title`` <title.md>`_
|
||||
- allow to read/modify the document title
|
||||
* - `\ ``ui`` <ui.md>`_
|
||||
- miscellaneous ui features: active element, block/unblock
|
||||
* - `\ ``user`` <user.md>`_
|
||||
- keep track of user main properties (lang, ...) and context
|
||||
* - `\ ``view_manager`` <view_manager.md>`_
|
||||
- load (and keep in cache) views information
|
||||
|
||||
|
||||
Defining a service
|
||||
------------------
|
||||
|
||||
A service needs to follow the following interface:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
export interface Service<T = any> {
|
||||
dependencies?: string[];
|
||||
deploy: (env: OdooEnv, odoo: Odoo) => Promise<T> | T;
|
||||
}
|
||||
|
||||
It may define some ``dependencies``. In that case, the dependent services will be
|
||||
started first, and ready when the current service is started.
|
||||
|
||||
The ``deploy`` method is the most important: the return value of the ``deploy``
|
||||
method will be the value of the service. This method can be asynchronous,
|
||||
in which case the value of the service will be the result of that promise.
|
||||
|
||||
Some services do not export any value. They may just do their work without a
|
||||
need to be directly called by other code. In that case, their value will be
|
||||
set to ``null`` in ``env.services``.
|
||||
|
||||
Once a service is defined, it needs then to be registered to the ``serviceRegistry`` ,
|
||||
to make sure it is properly deployed when the application is started.
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
serviceRegistry.add(myService.name, myService);
|
||||
|
||||
For example, imagine that we want to provide a service that manage a counter.
|
||||
It could be defined like this:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
const counterService = {
|
||||
start(env) {
|
||||
let value = 0;
|
||||
return {
|
||||
getValue() {
|
||||
return value;
|
||||
},
|
||||
increment() {
|
||||
value++;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
serviceRegistry.add("counter", counterService);
|
||||
|
||||
The services listed `above <./#list-of-all-services>`_ are deployed before the web client is mounted but it
|
||||
is allowed to add a service to the ``serviceRegistry`` after that moment. It will be automatically deployed.
|
||||
|
||||
Using a service
|
||||
---------------
|
||||
|
||||
To use a service, a component needs to call the ``useService`` hook. This will
|
||||
return a reference to the service value, that can then be used by the component.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
rpc = useService('rpc');
|
||||
|
||||
async willStart() {
|
||||
this.someValue = await this.rpc(...);
|
||||
}
|
||||
}
|
||||
|
||||
Note: If the value of the service is a function (for example, like the ``rpc``
|
||||
service), then the ``useService`` hook will bind it to the current component. This
|
||||
means that the code for the service can actually access the component reference.
|
||||
|
||||
A service that depends on other services (and having properly declared its ``dependencies`` )
|
||||
should use the other services by accessing them directly through the environment.
|
||||
For example, the service ``action_manager`` uses the service ``rpc`` in that way:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
action = await env.services.rpc(...);
|
@ -0,0 +1,105 @@
|
||||
|
||||
Router Service
|
||||
==============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``router``
|
||||
- None
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``router`` service provides three features:
|
||||
|
||||
|
||||
* information about the current route
|
||||
* provides a way for the application to update the url, depending on its state
|
||||
* listens to every hash change, and notifies the rest of the application
|
||||
|
||||
Current route
|
||||
-------------
|
||||
|
||||
The current route can be accessed with the ``current`` key. It contains the following
|
||||
information:
|
||||
|
||||
|
||||
* ``pathname (string)`` : the path for the current location (most likely ``/web`` )
|
||||
* ``search (object)`` : a dictionary mapping each search keyword from the url to
|
||||
its value. An empty string is the value if no value was explicitely given
|
||||
* ``hash (object)`` : same as above, but for values described in the hash.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
// url = /web?debug=assets#action=123&owl&menu_id=174
|
||||
|
||||
const { pathname, search, hash } = env.services.router.current;
|
||||
console.log(pathname); // /web
|
||||
console.log(search); // { debug="assets" }
|
||||
console.log(hash); // { action:123, owl: "", menu_id: 174 }
|
||||
|
||||
Updating the URL
|
||||
----------------
|
||||
|
||||
URL updates need to use the ``pushState`` method:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
pushState(hash: object, replace?: boolean)
|
||||
|
||||
The ``hash`` argument is an object containing a mapping from some key to some values.
|
||||
If a value is set to an empty string, the key will be simply added to the url
|
||||
without any value at all.
|
||||
|
||||
If true, the ``replace`` argument tells the router that the url hash should be
|
||||
completely replaced. Otherwise, the new values will be added to the current url.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
// url = /web#action_id=123
|
||||
|
||||
env.services.router.pushState({ menu_id: 321 });
|
||||
// url is now /web#action_id=123&menu_id=321
|
||||
|
||||
env.services.router.pushState({ yipyip: "" }, replace: true);
|
||||
// url is now /web#yipyip
|
||||
|
||||
Note that using ``pushState`` does not trigger a ``hashchange`` event, nor a
|
||||
``ROUTE_CHANGE`` in the main bus. This is because this method is intended to be
|
||||
used "from the inside", to update the url so that it matches the actual current
|
||||
displayed state.
|
||||
|
||||
Reacting to hash changes
|
||||
------------------------
|
||||
|
||||
This is mostly useful for the action manager, which needs to act when something
|
||||
in the url changed.
|
||||
|
||||
When created, the router listens to every (external) hash changes, and trigger a
|
||||
``ROUTE_CHANGE`` event on the main bus,
|
||||
|
||||
Redirect URL
|
||||
------------
|
||||
|
||||
The ``redirect`` method will redirect the browser to ``url``. If ``wait`` is true, sleep 1s and wait for the server (e.g. after a restart).
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
redirect(url: string, wait?: boolean)
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
// The complete url is "www.localhost/wowl"
|
||||
env.services.router.redirect("/wowl/tests");
|
||||
|
||||
// The complete url is "www.localhost/wowl/tests"
|
116
content/developer/reference/javascript/services/rpc_service.rst
Normal file
116
content/developer/reference/javascript/services/rpc_service.rst
Normal file
@ -0,0 +1,116 @@
|
||||
|
||||
RPC service
|
||||
===========
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``rpc``
|
||||
- ``notifications``
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The RPC service has a single purpose: send requests to the server. Its external
|
||||
API is a single function, with the following type:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
type RPC = (route: string, params?: { [key: string]: any }) => Promise<any>;
|
||||
|
||||
This makes it easy to use. For example, calling a controller ``/some/route`` can
|
||||
be done with the following code:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
class MyComponent extends Component {
|
||||
rpc = useService("rpc");
|
||||
|
||||
async someMethod() {
|
||||
const result = await this.rpc("/some/route");
|
||||
}
|
||||
}
|
||||
|
||||
Note that the ``rpc`` service is considered a low-level service. It should only be
|
||||
used to interact with Odoo controllers. To work with models (which is by far the
|
||||
most important usecase), one should use the `\ ``model`` <model.md>`_ service instead.
|
||||
|
||||
Calling a controller
|
||||
--------------------
|
||||
|
||||
As explained in the overview, calling a controller is very simple. The route
|
||||
should be the first argument, and optionally, a ``params`` object can be given as
|
||||
a second argument.
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
const result = await this.rpc("/my/route", { some: "value" });
|
||||
|
||||
Technical notes
|
||||
---------------
|
||||
|
||||
|
||||
* The ``rpc`` service communicates with the server by using a ``XMLHTTPRequest`` object,
|
||||
configured to work with ``application/json`` content type.
|
||||
* So clearly the content of the request should be JSON serializable.
|
||||
* Each request done by this service uses the ``POST`` http method
|
||||
* Server errors actually return the response with an http code 200. But the ``rpc``
|
||||
service will treat them as error (see below)
|
||||
|
||||
Error Handling
|
||||
--------------
|
||||
|
||||
An rpc can fail for two main reasons:
|
||||
|
||||
|
||||
* either the odoo server returns an error (so, we call this a ``server`` error).
|
||||
In that case the http request will return with am http code 200 BUT with a
|
||||
response object containing an ``error`` key.
|
||||
* or there is some other kind of network error
|
||||
|
||||
When a rpc fails, then:
|
||||
|
||||
|
||||
* the promise representing the rpc is rejected, so the calling code will crash,
|
||||
unless it handles the situation
|
||||
*
|
||||
an event ``RPC_ERROR`` is triggered on the main application bus. The event payload
|
||||
contains a description of the cause of the error:
|
||||
|
||||
If it is a server error (the server code threw an exception). In that case
|
||||
the event payload will be an object with the following keys:
|
||||
|
||||
|
||||
* ``type = 'server'``
|
||||
* ``message(string)``
|
||||
*
|
||||
``code(number)``
|
||||
|
||||
*
|
||||
``name(string)`` (optional, used by the crash manager to look for an appropriate
|
||||
dialog to use when handling the error)
|
||||
|
||||
* ``subType(string)`` (optional, often used to determine the dialog title)
|
||||
* ``data(object)`` (optional object that can contain various keys among which
|
||||
``debug`` : the main debug information, with the call stack)
|
||||
|
||||
If it is a network error, then the error description is simply an object
|
||||
``{type: 'network'}``.
|
||||
When a network error occurs, a notification is displayed and the server is regularly
|
||||
contacted until it responds. The notification is closed as soon as the server responds.
|
||||
|
||||
Specialized behaviour for components
|
||||
------------------------------------
|
||||
|
||||
The ``rpc`` service has a specific optimization to make using it with component safer. It
|
||||
does two things:
|
||||
|
||||
|
||||
* if a component is destroyed at the moment an rpc is initiated, an error will
|
||||
be thrown. This is considered an error, a destroyed component should be inert.
|
||||
* if a component is destroyed when a rpc is completed, which is a normal situation
|
||||
in an application, then the promise is simply left pending, to prevent any
|
||||
followup code to execute.
|
@ -0,0 +1,2 @@
|
||||
Scroller Service
|
||||
================
|
@ -0,0 +1,72 @@
|
||||
|
||||
Title Service
|
||||
=============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``title``
|
||||
- None
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``title service`` offers a simple API that allows to read/modify the document title.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The ``title service`` exports two methods and a value:
|
||||
|
||||
|
||||
* ``current (string)`` ,
|
||||
* ``getParts(): Parts`` ,
|
||||
* ``setParts(parts: Parts): void`` ,
|
||||
|
||||
where the type ``Parts`` is:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
interface Parts {
|
||||
[key: string]: string | null;
|
||||
}
|
||||
|
||||
The ``getParts`` method returns a copy of an object ``titleParts`` maintained by the tilte service.
|
||||
|
||||
The value ``current`` is structured in the following way: ``value_1 - ... - value_n`` where
|
||||
``value_1,...,value_n`` are the values (all not null) found in the object ``titleParts``.
|
||||
|
||||
The ``setParts`` method allow to add/replace/delete several parts of the title. Delete a part (a value) is done
|
||||
by setting the associated key value to null;
|
||||
|
||||
Example:
|
||||
|
||||
If the title is composed of the following parts:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
{
|
||||
odoo: "Odoo",
|
||||
action: "Import",
|
||||
}
|
||||
|
||||
with ``current`` value being ``Odoo - Import`` ,
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
setParts({
|
||||
odoo: "Open ERP",
|
||||
action: null,
|
||||
chat: "Sauron"
|
||||
});
|
||||
|
||||
will give the title ``Open ERP - Sauron`` and ``getParts`` will return
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
{
|
||||
odoo: "Open ERP",
|
||||
chat: "Sauron",
|
||||
}
|
193
content/developer/reference/javascript/services/ui_service.rst
Normal file
193
content/developer/reference/javascript/services/ui_service.rst
Normal file
@ -0,0 +1,193 @@
|
||||
|
||||
UI service
|
||||
==========
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``ui``
|
||||
- None
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``ui`` service offers miscellaneous UI features:
|
||||
|
||||
|
||||
* active element management.
|
||||
The default UI active element is the ``document`` element, but the ``ui`` service
|
||||
lets anyone become the UI active element. It is useful e.g. for dialogs.
|
||||
* block or unblock the UI.
|
||||
When the ui will be blocked, a loading screen blocking any action will cover the UI.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The ``ui`` service provides the following API:
|
||||
|
||||
|
||||
*
|
||||
``bus: EventBus`` : a bus, on which are triggered
|
||||
|
||||
|
||||
* ``active-element-changed (activeElement: DOMElement)`` when the UI active element has changed.
|
||||
|
||||
*
|
||||
``block(): void`` : this method will activate the loading screen to block the ui.
|
||||
|
||||
*
|
||||
``unblock(): void`` : This method will disable the loading screen in order to unblock the ui.
|
||||
if it was not already disable.
|
||||
|
||||
*
|
||||
``ìsBlocked (boolean)`` : informs on the UI blocked state
|
||||
|
||||
*
|
||||
``activateElement(activateElement: DOMElement): void`` : applies an UI active element.
|
||||
|
||||
*
|
||||
``activeElement: DOMElement`` : gives the actual UI active element
|
||||
|
||||
*
|
||||
``getVisibleElements(selector: string)`` : returns all elements matching the given selector that are displayed somewhere on the active element.
|
||||
|
||||
In addition to that, you have access to some development helpers which are **greatly** recommended:
|
||||
|
||||
|
||||
* ``useActiveElement(refName?:string)`` : a hook that ensures the UI active element will
|
||||
take place/get released each time your component gets mounted/unmounted.
|
||||
By default, the element that will be the UI active element is the component root's.
|
||||
It can be delegated to another element through the usage of a ``t-ref`` directive,
|
||||
providing its value to this hook. In that case, **it is mandatory** that the referenced
|
||||
element is fixed and not dynamically attached in/detached from the DOM (e.g. with t-if directive).
|
||||
|
||||
Good to know: UI blocking
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If the ``block()`` method is called several times simultaneously, the same number of times the ``unblock()`` function must be used to unblock the UI.
|
||||
|
||||
Good to know: UI Active Element
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Due to the way components are mounted by the Owl engine (from the bottom to the top), you should be aware that if nested components try to all become the UI active element, only the topmost of them will be.
|
||||
|
||||
E.g.:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class A extends Component {
|
||||
setup() {
|
||||
useActiveElement();
|
||||
}
|
||||
}
|
||||
A.components = { B };
|
||||
A.template = xml`<div id="a"><B/></div>`;
|
||||
|
||||
class B extends Component {
|
||||
setup() {
|
||||
useActiveElement();
|
||||
}
|
||||
}
|
||||
B.template = xml`<div id="b"/>`;
|
||||
|
||||
// When A will get mounted, all its children components will get mounted first
|
||||
// So B will get mounted first and div#b will become the active element.
|
||||
// Finally A will get mounted and div#a will become the active element.
|
||||
|
||||
Example: active element management
|
||||
----------------------------------
|
||||
|
||||
Listen to active element changes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
const ui = useService("ui");
|
||||
this.myActiveElement = ui.activeElement;
|
||||
useBus(ui.bus, "active-element-changed", (activeElement) => {
|
||||
if (activeElement !== this.myActiveElement) {
|
||||
// do some stuff, like changing my state or keeping myActiveElement in sync...
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
With ``useActiveElement`` hook
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Here is how one component could change the active element of the UI
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useActiveElement();
|
||||
}
|
||||
}
|
||||
|
||||
With ``useActiveElement`` hook: ref delegation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Here is how one component could change the active element of the UI
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useActiveElement("delegatedRef");
|
||||
}
|
||||
}
|
||||
MyComponent.template = owl.tags.xml`
|
||||
<div>
|
||||
<h1>My Component</h1>
|
||||
<div t-ref="delegatedRef"/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Manually
|
||||
^^^^^^^^
|
||||
|
||||
Here is how one component could change the active element of the UI
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
this.uiService = useService("ui");
|
||||
}
|
||||
mounted() {
|
||||
const activateElement = this.el;
|
||||
this.uiService.activateElement(activateElement);
|
||||
}
|
||||
willUnmount() {
|
||||
this.uiService.deactivateElement(activateElement);
|
||||
}
|
||||
}
|
||||
|
||||
Example: block/unblock
|
||||
----------------------
|
||||
|
||||
Here is how one component can block and unblock the UI:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MyComponent extends Component {
|
||||
...
|
||||
ui = useService('ui');
|
||||
|
||||
...
|
||||
|
||||
someHandlerBlock() {
|
||||
// The loading screen will be displayed and block the UI.
|
||||
this.ui.block();
|
||||
}
|
||||
|
||||
someHandlerUnblock() {
|
||||
// The loading screen is no longer displayed and the UI is unblocked.
|
||||
this.ui.unblock();
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
|
||||
User Service
|
||||
============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``user``
|
||||
- None
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``user`` service is a very simple service, that aims to just keep track of a
|
||||
few important values related to the current user. It simply provides an object
|
||||
with a few keys:
|
||||
|
||||
|
||||
* ``allowed_companies ({[id: number] : {id: number, name: string} })`` : the list of companies that can be
|
||||
accessed by the user. Each element is a pair ``id, name``
|
||||
* ``context (object)`` : the user main context (see below for a description)
|
||||
* ``current_company ({id: number, name: string})`` : the currently active company. It is a
|
||||
pair ``[id, name]``.
|
||||
* ``dateFormat`` : preferred format when formatting "dates"
|
||||
* ``decimalPoint`` : decimal separator
|
||||
* ``direction`` : "rtl" ("right to left") or "lrt" ("left to right")
|
||||
* ``grouping`` : ?
|
||||
* ``isAdmin (boolean)`` : if true, the user is an administrator of the current
|
||||
odoo database
|
||||
* ``lang (string)`` : a short description of the user language (such as ``en_us`` )
|
||||
* ``multiLang`` : if true, this means that several languages are installed on the database
|
||||
* ``partnerId (number)`` : the id for the partner (\ ``res.partner`` record) associated to the user
|
||||
* ``thousandsSep`` : thousands separator
|
||||
* ``timeFormat`` : preferred format when formatting "hours"
|
||||
* ``tz (string)`` : the user configured timezone (such as ``Europe/Brussels`` )
|
||||
* ``userId (number)`` : the user id (for the ``res.user`` model)
|
||||
* ``userName (string)`` : the user name (string that can be displayed)
|
||||
|
||||
User Context
|
||||
------------
|
||||
|
||||
The user context is an object that tracks a few important value. This context is
|
||||
mostly useful when talking to the server (it is added to every request).
|
||||
|
||||
Here is complete description of its content:
|
||||
|
||||
|
||||
* ``allowed_company_ids (number[])`` : the list of all ids for all available
|
||||
companies
|
||||
* ``lang (string)`` : a short description of the user language (same as above)
|
||||
* ``tz (string)`` : the user configured timezone (same as above)
|
||||
* ``uid (number)`` : the current user id (as a ``res.partner`` record). Same as the
|
||||
``userId`` value above
|
@ -0,0 +1,35 @@
|
||||
|
||||
View Service
|
||||
============
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Technical name
|
||||
- Dependencies
|
||||
* - ``view_manager``
|
||||
- ``model``
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The ``view_manager`` service is a low level service that helps with loading view
|
||||
informations (such as the arch, the ``id`` and other view informations).
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The ``view_manager`` service provide a single method:
|
||||
|
||||
|
||||
* ``loadView(model: string, type: ViewType, viewId?: number | false): Promise<ViewDefinition>``
|
||||
This method loads from the server the description for a view.
|
||||
|
||||
A ``ViewDefinition`` object contains the following information:
|
||||
|
||||
.. code-block::
|
||||
|
||||
- `arch (string)`
|
||||
- `type (ViewType)`
|
||||
- `viewId (number)`
|
43
content/developer/reference/javascript/systray.rst
Normal file
43
content/developer/reference/javascript/systray.rst
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
Systray
|
||||
=======
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The systray is the zone on the right of the navbar that contains various small
|
||||
components (called *systray items*\ ). These components usually display some sort
|
||||
of information (like the number of unread messages), notifications and/or let the
|
||||
user interact with them.
|
||||
|
||||
Systray items
|
||||
-------------
|
||||
|
||||
A systray item is simply a component, with a constraint: its root node should be
|
||||
a ``<li>`` tag! Otherwise, the systray item will not be styled properly.
|
||||
|
||||
Adding a systray item
|
||||
---------------------
|
||||
|
||||
Once a systray item is defined, adding it to the web client is only a matter of
|
||||
registering it to the ``systrayRegistry``.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
class MySystrayItem extends Component {
|
||||
// some component ...
|
||||
}
|
||||
|
||||
systrayRegistry.add("myaddon.some_description", MySystrayItem);
|
||||
|
||||
The systray registry is an ordered registry, so one can add a sequence number:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
systrayRegistry.add("myaddon.some_description", MySystrayItem, { sequence: 43 });
|
||||
|
||||
The sequence number defaults to 50. If given, this number will be used
|
||||
to order the items. The lowest sequence is on the right and the highest sequence
|
||||
is on the left in the systray menu.
|
141
content/developer/reference/javascript/testing.rst
Normal file
141
content/developer/reference/javascript/testing.rst
Normal file
@ -0,0 +1,141 @@
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
We take very seriously code quality. Tests is an important part of a solid and
|
||||
robust application. Each feature should be (if reasonable) tested properly, in
|
||||
the QUnit test suite (available in the route ``/wowl/tests`` )
|
||||
|
||||
In a few sentences, here is how the test suite is organized:
|
||||
|
||||
|
||||
* tests are written in the ``static/tests`` folder.
|
||||
* the main entry point for the suite is ``static/tests/main.ts`` , which is the
|
||||
code that setup the test suite environment.
|
||||
* some helpers are available in ``static/tests/helpers.ts``.
|
||||
* to access the test suite, one needs to have an odoo server running and then
|
||||
open ``/wowl/tests`` in a browser
|
||||
|
||||
The test suite file structure looks like this:
|
||||
|
||||
.. code-block::
|
||||
|
||||
static/
|
||||
...
|
||||
tests/
|
||||
components/
|
||||
navbar_tests.ts
|
||||
...
|
||||
services/
|
||||
router_tests.ts
|
||||
...
|
||||
helpers.ts <-- helpers that will probably be imported in each test file
|
||||
main.ts <-- main file, used to generate the test bundle
|
||||
qunit.ts <-- qunit config file. No test should import this
|
||||
|
||||
Test helpers
|
||||
------------
|
||||
|
||||
|
||||
*
|
||||
``mount(SomeComponent, {env, target})`` : create a component with the ``env`` provided,
|
||||
and mount it to the ``target`` html element. This method is asynchronous, and
|
||||
returns the instance of the component
|
||||
|
||||
*
|
||||
``makeTestEnv(params?: Params)`` : create asynchronously a test environment. By default, a test
|
||||
environment has no service, components or browser access. It can be optionally
|
||||
customized with the ``params`` argument:
|
||||
|
||||
|
||||
* ``services (optional, Registry<Service>)`` : a registry of services
|
||||
* ``Components (optional, Registry<Component>)`` ) main component registry
|
||||
* ``browser (optional, object)`` : the browser object that will be used for the
|
||||
test environment.
|
||||
|
||||
*
|
||||
``getFixture(): HTMLElement`` : return an html element to use as a DOM node for tests (only
|
||||
applies to code that needs to interact with the DOM, obviously).
|
||||
|
||||
*
|
||||
``nextTick(): Promise<void>`` : helper to wait for the next animation frame. This
|
||||
is extremely useful while writing tests involving components updating the DOM.
|
||||
|
||||
*
|
||||
``click(el: HTMLElement, selector?: string): Promise<void>`` : helper to click on
|
||||
an element, given the element itself, or an element and a selector that matches
|
||||
a single element inside it. The helper will crash when called with a selector
|
||||
that have no match, or more than 1 match. It returns a promise resolved after
|
||||
the next animation frame, i.e. after potential DOM updates have been applied.
|
||||
|
||||
QUnit custom assertions
|
||||
-----------------------
|
||||
|
||||
QUnit provides several `built-in assertions <https://api.qunitjs.com/assert/>`_\ ,
|
||||
available on the ``QUnit.assert`` object. Alongside them, we provide custom
|
||||
assertions.
|
||||
|
||||
|
||||
*
|
||||
``assert.containsN(el: HTMLElement, selector: string, n: number, msg?: string)`` :
|
||||
check that a target element ``el`` contains exactly ``n`` matches for the given
|
||||
``selector`` , with ``msg`` being an optional short description of the assertion
|
||||
|
||||
*
|
||||
``assert.containsNone(el: HTMLElement, selector: string, msg?: string)`` : check
|
||||
that a target element ``el`` contains no match for the given ``selector``
|
||||
|
||||
*
|
||||
``assert.containsOnce(el: HTMLElement, selector: string, msg?: string)`` : check
|
||||
that a target element ``el`` contains exactly one match for the given ``selector``
|
||||
|
||||
*
|
||||
``assert.hasClass(el: HTMLElement, classNames: string, msg?: string)`` : check
|
||||
that a target element ``el`` has the given ``classNames``
|
||||
|
||||
*
|
||||
``assert.doesNotHaveClass(el: HTMLElement, classNames: string, msg?: string)`` : check
|
||||
that a target element ``el`` does not have the given ``classNames``.
|
||||
|
||||
Adding a new test
|
||||
-----------------
|
||||
|
||||
To add a new test file to the QUnit suite, the following steps need to be done:
|
||||
|
||||
|
||||
#. create a new file named ``something_test.ts`` in the ``static/tests`` folder
|
||||
#. import your file in ``static/tests/main.ts`` (so it will be added to the test bundle)
|
||||
#. add your tests inside your new file.
|
||||
|
||||
Here is a simple example of a test file to test a component:
|
||||
|
||||
.. code-block:: ts
|
||||
|
||||
import * as QUnit from "qunit";
|
||||
import { MyComponent } from "../../src/components/...";
|
||||
import { getFixture, makeTestEnv, mount, OdooEnv } from "../helpers";
|
||||
|
||||
let target: HTMLElement;
|
||||
let env: OdooEnv;
|
||||
QUnit.module("MyComponent", {
|
||||
beforeEach() {
|
||||
target = getFixture();
|
||||
env = makeTestEnv();
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test("can be rendered", async (assert) => {
|
||||
assert.expect(1);
|
||||
const myComponent = await mount(MyComponent, { env, target });
|
||||
// perform some assertion/actions
|
||||
});
|
||||
|
||||
Debugging a test
|
||||
----------------
|
||||
|
||||
Sometimes, changes in the code make tests fail. Understanding why assertions
|
||||
fail reading the logs might be tedious. Hopefully, one can use our custom
|
||||
``QUnit.debug`` function instead of ``QUnit.test`` (basically, rename ``ŧest`` into
|
||||
``debug`` for the failing test). With this, the target returned by ``getFixture``
|
||||
will be ``document.body`` , so that what has been inserted into the DOM is visible,
|
||||
and can be directly interacted with.
|
@ -0,0 +1,2 @@
|
||||
Calendar View
|
||||
=============
|
@ -0,0 +1,2 @@
|
||||
Dashboard View
|
||||
==============
|
@ -0,0 +1,2 @@
|
||||
Graph View
|
||||
==========
|
@ -0,0 +1,2 @@
|
||||
Map View
|
||||
========
|
@ -0,0 +1,2 @@
|
||||
Pivot View
|
||||
==========
|
2
content/developer/reference/javascript/views/readme.rst
Normal file
2
content/developer/reference/javascript/views/readme.rst
Normal file
@ -0,0 +1,2 @@
|
||||
Views
|
||||
=====
|
Loading…
Reference in New Issue
Block a user