[MOV] developer: move section on patching javascript
This commit is a move, but the content was also slightly
updated/reformatted.
closes odoo/documentation#1288
X-original-commit: f206233ebc
Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
This commit is contained in:
parent
aec3e2d9d9
commit
efb327479f
@ -14,6 +14,7 @@ Frontend
|
|||||||
frontend/registries
|
frontend/registries
|
||||||
frontend/services
|
frontend/services
|
||||||
frontend/hooks
|
frontend/hooks
|
||||||
|
frontend/patching_code
|
||||||
frontend/javascript_cheatsheet
|
frontend/javascript_cheatsheet
|
||||||
frontend/javascript_reference
|
frontend/javascript_reference
|
||||||
frontend/mobile
|
frontend/mobile
|
||||||
|
@ -2246,160 +2246,3 @@ For more information, look into the `control_panel_renderer.js <https://github.c
|
|||||||
.. _datepicker: https://github.com/Eonasdan/bootstrap-datetimepicker
|
.. _datepicker: https://github.com/Eonasdan/bootstrap-datetimepicker
|
||||||
|
|
||||||
|
|
||||||
Patching code
|
|
||||||
=============
|
|
||||||
|
|
||||||
Sometimes we need to modify an object or a class in place. To achieve that, Odoo
|
|
||||||
provides the utility function ``patch``. It is mostly useful to override/update
|
|
||||||
the behaviour of some other component/piece of code that one does not control.
|
|
||||||
Obviously, it is not the primary tool of working with Odoo. It should be used
|
|
||||||
with care.
|
|
||||||
|
|
||||||
Patching a simple object
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
Here is a simple example of how an object can be patched:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
const { patch } = require("web.utils");
|
|
||||||
|
|
||||||
const object = {
|
|
||||||
field: "a field",
|
|
||||||
fn() {
|
|
||||||
// do something
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
patch(object, "patch name", {
|
|
||||||
fn() {
|
|
||||||
// do things
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
When patching functions, we usually want to be able to access the ``parent``
|
|
||||||
function. Since we are working with patch objects, not ES6 classes, we cannot
|
|
||||||
use the native ``super`` keyword. So, Odoo provides a special method to simulate
|
|
||||||
this behaviour: ``this._super``:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
patch(object, "_super patch", {
|
|
||||||
fn() {
|
|
||||||
this._super(...arguments);
|
|
||||||
// do other things
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
``this._super`` is reassigned after each patched function is called.
|
|
||||||
This means that if you use an asynchronous function in the patch then you
|
|
||||||
cannot call ``this._super`` after an ``await``, because it may or may not be
|
|
||||||
the function that you expect. The correct way to do that is to keep a reference
|
|
||||||
to the initial ``_super`` method:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
patch(object, "async _super patch", {
|
|
||||||
async myAsyncFn() {
|
|
||||||
const _super = this._super;
|
|
||||||
await Promise.resolve();
|
|
||||||
await _super(...arguments);
|
|
||||||
// await this._super(...arguments); // this._super is undefined.
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Getters and setters are supported too!
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
patch(object, "getter/setter patch", {
|
|
||||||
get number() {
|
|
||||||
return this._super() / 2;
|
|
||||||
},
|
|
||||||
set number(value) {
|
|
||||||
this._super(value * 2);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Native JS class
|
|
||||||
---------------
|
|
||||||
|
|
||||||
|
|
||||||
The ``patch`` function is similar to ``OdooClass.include``, but it is designed
|
|
||||||
to work with anything: object, Odoo class, ES6 class.
|
|
||||||
|
|
||||||
However, since ES6 classes work with the javascript prototypal inheritance, when
|
|
||||||
one wishes to patch a standard method from a class, then we actually need to patch
|
|
||||||
the ``prototype``:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
class MyClass {
|
|
||||||
static myStaticFn() {...}
|
|
||||||
myPrototypeFn() {...}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this will patch static properties!!!
|
|
||||||
patch(MyClass, "static patch", {
|
|
||||||
myStaticFn() {...},
|
|
||||||
});
|
|
||||||
|
|
||||||
// this is probably the usual case: patching a class method
|
|
||||||
patch(MyClass.prototype, "prototype patch", {
|
|
||||||
myPrototypeFn() {...},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Also, Javascript handle the constructor in a special native way which makes it
|
|
||||||
impossible to be patched. The only workaround is to call a method in the original
|
|
||||||
constructor and patch that method instead:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
class MyClass {
|
|
||||||
constructor() {
|
|
||||||
this.setup();
|
|
||||||
}
|
|
||||||
setup() {
|
|
||||||
this.number = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
patch(MyClass.prototype, "constructor", {
|
|
||||||
setup() {
|
|
||||||
this._super(...arguments);
|
|
||||||
this.doubleNumber = this.number * 2;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Note that for this reason, Owl component have all the ``setup`` method builtin.
|
|
||||||
Therefore, patching a component constructor is always possible:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
patch(MyComponent.prototype, "my patch", {
|
|
||||||
setup() {
|
|
||||||
useMyHook();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Removing a patch
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
In tests, it is sometimes useful to remove a patch for a specific test, for instance,
|
|
||||||
to remove a patch applied when another addon is installed, which overrides the behavior
|
|
||||||
implemented in the addon where the test is defined. The function ``unpatch`` exists for
|
|
||||||
that purpose. It returns the removed patch, such that it can be re-applied at the end
|
|
||||||
of the test.
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
const p = unpatch(object, "patch name");
|
|
||||||
// test stuff here
|
|
||||||
patch(object, "patch name", p);
|
|
||||||
|
@ -105,6 +105,36 @@ the ``owl`` attribute set to 1.
|
|||||||
.. seealso::
|
.. seealso::
|
||||||
- `Owl Repository <https://github.com/odoo/owl>`_
|
- `Owl Repository <https://github.com/odoo/owl>`_
|
||||||
|
|
||||||
|
.. _frontend/owl/best_practices:
|
||||||
|
|
||||||
|
Best practices
|
||||||
|
==============
|
||||||
|
|
||||||
|
First of all, components are classes, so they have a constructor. But constructors
|
||||||
|
are special methods in javascript that are not overridable in any way. Since this
|
||||||
|
is an occasionally useful pattern in Odoo, we need to make sure that no component
|
||||||
|
in Odoo directly uses the constructor method. Instead, components should use the
|
||||||
|
`setup` method:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
// correct:
|
||||||
|
class MyComponent extends Component {
|
||||||
|
setup() {
|
||||||
|
// initialize component here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// incorrect. Do not do that!
|
||||||
|
class IncorrectComponent extends Component {
|
||||||
|
constructor(parent, props) {
|
||||||
|
// initialize component here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Another good practice is to use a consistent convention for template names:
|
||||||
|
`addon_name.ComponentName`. This prevents name collision between odoo addons.
|
||||||
|
|
||||||
Reference List
|
Reference List
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
196
content/developer/reference/frontend/patching_code.rst
Normal file
196
content/developer/reference/frontend/patching_code.rst
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
=============
|
||||||
|
Patching code
|
||||||
|
=============
|
||||||
|
|
||||||
|
Sometimes, we need to customize the way the UI works. Many common needs are
|
||||||
|
covered by some supported API. For example, all registries are good extension
|
||||||
|
points: the field registry allows adding/removing specialized field components,
|
||||||
|
or the main component registry allows adding components that should be displayed
|
||||||
|
all the time.
|
||||||
|
|
||||||
|
However, there are situations for which it is not sufficient. In those cases, we
|
||||||
|
may need to modify an object or a class in place. To achieve that, Odoo
|
||||||
|
provides the utility function `patch`. It is mostly useful to override/update
|
||||||
|
the behavior of some other component/piece of code that one does not control.
|
||||||
|
|
||||||
|
Description
|
||||||
|
===========
|
||||||
|
|
||||||
|
The patch function is located in `@web/core/utils/patch`:
|
||||||
|
|
||||||
|
.. js:function:: patch(obj, patchName, patchValue, options)
|
||||||
|
|
||||||
|
:param Object obj: object that should be patched
|
||||||
|
:param string patchName: unique string describing the patch
|
||||||
|
:param Object patchValue: an object mapping each key to a patchValue
|
||||||
|
:param Object options: option object (see below)
|
||||||
|
|
||||||
|
The `patch` function modifies in place the `obj` object (or class) and
|
||||||
|
applies all key/value described in the `patchValue` object. This operation
|
||||||
|
is registered under the `patchName` name, so it can be unpatched later if
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
Most patch operations provide access to the parent value by using the
|
||||||
|
`_super` property (see below in the examples). To do that, the `patch` method
|
||||||
|
wraps each pair key/value in a getter that dynamically binds `_super`.
|
||||||
|
|
||||||
|
The only option is `pure (boolean)`. If set to `true`, the patch operation
|
||||||
|
does not bind the `_super` property.
|
||||||
|
|
||||||
|
Patching a simple object
|
||||||
|
========================
|
||||||
|
|
||||||
|
Here is a simple example of how an object can be patched:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
|
||||||
|
const object = {
|
||||||
|
field: "a field",
|
||||||
|
fn() {
|
||||||
|
// do something
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
patch(object, "patch name", {
|
||||||
|
fn() {
|
||||||
|
// do things
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
When patching functions, we usually want to be able to access the ``parent``
|
||||||
|
function. Since we are working with patch objects, not ES6 classes, we cannot
|
||||||
|
use the native ``super`` keyword. So, Odoo provides a special method to simulate
|
||||||
|
this behaviour: ``this._super``:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
patch(object, "_super patch", {
|
||||||
|
fn() {
|
||||||
|
this._super(...arguments);
|
||||||
|
// do other things
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
``this._super`` is reassigned after each patched function is called.
|
||||||
|
This means that if you use an asynchronous function in the patch then you
|
||||||
|
cannot call ``this._super`` after an ``await``, because it may or may not be
|
||||||
|
the function that you expect. The correct way to do that is to keep a reference
|
||||||
|
to the initial ``_super`` method:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
patch(object, "async _super patch", {
|
||||||
|
async myAsyncFn() {
|
||||||
|
const _super = this._super.bind(this);
|
||||||
|
await Promise.resolve();
|
||||||
|
await _super(...arguments);
|
||||||
|
// await this._super(...arguments); // this._super is undefined.
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Getters and setters are supported too:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
patch(object, "getter/setter patch", {
|
||||||
|
get number() {
|
||||||
|
return this._super() / 2;
|
||||||
|
},
|
||||||
|
set number(value) {
|
||||||
|
this._super(value * 2);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Patching a javascript class
|
||||||
|
===========================
|
||||||
|
|
||||||
|
The ``patch`` function is designed to work with anything: object or ES6 class.
|
||||||
|
|
||||||
|
However, since javascript classes work with the prototypal inheritance, when
|
||||||
|
one wishes to patch a standard method from a class, then we actually need to patch
|
||||||
|
the `prototype`:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
class MyClass {
|
||||||
|
static myStaticFn() {...}
|
||||||
|
myPrototypeFn() {...}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this will patch static properties!!!
|
||||||
|
patch(MyClass, "static patch", {
|
||||||
|
myStaticFn() {...},
|
||||||
|
});
|
||||||
|
|
||||||
|
// this is probably the usual case: patching a class method
|
||||||
|
patch(MyClass.prototype, "prototype patch", {
|
||||||
|
myPrototypeFn() {...},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Also, Javascript handles the constructor in a special native way which makes it
|
||||||
|
impossible to be patched. The only workaround is to call a method in the original
|
||||||
|
constructor and patch that method instead:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
class MyClass {
|
||||||
|
constructor() {
|
||||||
|
this.setup();
|
||||||
|
}
|
||||||
|
setup() {
|
||||||
|
this.number = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
patch(MyClass.prototype, "constructor", {
|
||||||
|
setup() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.doubleNumber = this.number * 2;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
It is impossible to patch directly the `constructor` of a class!
|
||||||
|
|
||||||
|
Patching a component
|
||||||
|
====================
|
||||||
|
|
||||||
|
Components are defined by javascript classes, so all the information above still
|
||||||
|
holds. For these reasons, Owl components should use the `setup` method, so they
|
||||||
|
can easily be patched as well (see the section on :ref:`best practices<frontend/owl/best_practices>`.
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
patch(MyComponent.prototype, "my patch", {
|
||||||
|
setup() {
|
||||||
|
useMyHook();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Removing a patch
|
||||||
|
================
|
||||||
|
|
||||||
|
The `patch` function has a counterpart, `unpatch`, also located in `@web/core/utils/patch`.
|
||||||
|
|
||||||
|
.. js:function:: unpatch(obj, patchName)
|
||||||
|
|
||||||
|
:param Object obj: object that should be unpatched
|
||||||
|
:param string patchName: string describing the patch that should be removed
|
||||||
|
|
||||||
|
Removes an existing patch from an object `obj`. This is mostly useful for
|
||||||
|
testing purposes, when we patch something at the beginning of a test, and
|
||||||
|
unpatch it at the end.
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
patch(object, "patch name", { ... });
|
||||||
|
// test stuff here
|
||||||
|
unpatch(object, "patch name");
|
Loading…
Reference in New Issue
Block a user