This commit is contained in:
Simon Genin (ges)
2021-10-11 17:07:52 +02:00
committed by Simon Genin
parent 4331177bb6
commit fd28f72c68
84 changed files with 3316 additions and 3 deletions

View File

@@ -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>`_.

View File

@@ -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"
}
});
}
}

View File

@@ -0,0 +1,2 @@
Company Service
===============

View File

@@ -0,0 +1,2 @@
Cookie Service
==============

View File

@@ -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
`;
...
}

View File

@@ -0,0 +1,2 @@
Effect Service
==============

View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -0,0 +1,2 @@
Http Service
============

View File

@@ -0,0 +1,2 @@
Localization Service
====================

View File

@@ -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(...)``

View File

@@ -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.

View File

@@ -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.

View File

@@ -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");

View File

@@ -0,0 +1,2 @@
Profiling Service
=================

View 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(...);

View File

@@ -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"

View 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.

View File

@@ -0,0 +1,2 @@
Scroller Service
================

View File

@@ -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",
}

View 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();
}
}

View File

@@ -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

View File

@@ -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)`