[IMP] web: update documentation of patch function

This commit updates the documentation of the patch function
with the changes done in https://github.com/odoo/odoo/pull/125716.

closes odoo/documentation#5017

Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
Michael (mcm) 2023-07-04 10:24:34 +00:00
parent dad4a47c9a
commit 775e80ed92

View File

@ -18,24 +18,18 @@ Description
The patch function is located in `@web/core/utils/patch`: The patch function is located in `@web/core/utils/patch`:
.. js:function:: patch(obj, patchName, patchValue, options) .. js:function:: patch(objToPatch, extension)
:param Object obj: object that should be patched :param object objToPatch: the object that should be patched
:param string patchName: unique string describing the patch :param object extension: an object mapping each key to an extension
:param Object patchValue: an object mapping each key to a patchValue :returns: a function to remove the patch
:param Object options: option object (see below)
The `patch` function modifies in place the `obj` object (or class) and The `patch` function modifies in place the `objToPatch` object (or class) and
applies all key/value described in the `patchValue` object. This operation applies all key/value described in the `extension` object. An unpatch
is registered under the `patchName` name, so it can be unpatched later if function is returned, so it can be used to remove the patch later if necessary.
necessary.
Most patch operations provide access to the parent value by using the 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 native `super` keyword (see below in the examples).
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 Patching a simple object
======================== ========================
@ -53,7 +47,7 @@ Here is a simple example of how an object can be patched:
}, },
}; };
patch(object, "patch name", { patch(object, {
fn() { fn() {
// do things // do things
}, },
@ -61,49 +55,45 @@ Here is a simple example of how an object can be patched:
When patching functions, we usually want to be able to access the ``parent`` 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 function. To do so, we can simply use the native ``super`` keyword:
use the native ``super`` keyword. So, Odoo provides a special method to simulate
this behaviour: ``this._super``:
.. code-block:: javascript .. code-block:: javascript
patch(object, "_super patch", { patch(object, {
fn() { fn() {
this._super(...arguments); super.fn(...arguments);
// do other things // do other things
}, },
}); });
.. warning:: .. warning::
``this._super`` is reassigned after each patched function is called. ``super`` can only be used in a method not a function. This means that the
This means that if you use an asynchronous function in the patch then you following constructs are invalid for javascript.
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 .. code-block:: javascript
patch(object, "async _super patch", { const obj = {
async myAsyncFn() { a: function () {
const _super = this._super.bind(this); // Throws: "Uncaught SyntaxError: 'super' keyword unexpected here"
await Promise.resolve(); super.a();
await _super(...arguments);
// await this._super(...arguments); // this._super is undefined.
}, },
}); b: () => {
// Throws: "Uncaught SyntaxError: 'super' keyword unexpected here"
super.b();
},
};
Getters and setters are supported too: Getters and setters are supported too:
.. code-block:: javascript .. code-block:: javascript
patch(object, "getter/setter patch", { patch(object, {
get number() { get number() {
return this._super() / 2; return super.number / 2;
}, },
set number(value) { set number(value) {
this._super(value * 2); super.number = value;
}, },
}); });
@ -124,12 +114,12 @@ the `prototype`:
} }
// this will patch static properties!!! // this will patch static properties!!!
patch(MyClass, "static patch", { patch(MyClass, {
myStaticFn() {...}, myStaticFn() {...},
}); });
// this is probably the usual case: patching a class method // this is probably the usual case: patching a class method
patch(MyClass.prototype, "prototype patch", { patch(MyClass.prototype, {
myPrototypeFn() {...}, myPrototypeFn() {...},
}); });
@ -149,9 +139,9 @@ constructor and patch that method instead:
} }
} }
patch(MyClass.prototype, "constructor", { patch(MyClass.prototype, {
setup() { setup() {
this._super(...arguments); super.setup(...arguments);
this.doubleNumber = this.number * 2; this.doubleNumber = this.number * 2;
}, },
}); });
@ -165,11 +155,11 @@ Patching a component
Components are defined by javascript classes, so all the information above still 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 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>`. can easily be patched as well (see the section on :ref:`best practices<frontend/owl/best_practices>`).
.. code-block:: javascript .. code-block:: javascript
patch(MyComponent.prototype, "my patch", { patch(MyComponent.prototype, {
setup() { setup() {
useMyHook(); useMyHook();
}, },
@ -178,19 +168,91 @@ can easily be patched as well (see the section on :ref:`best practices<frontend/
Removing a patch Removing a patch
================ ================
The `patch` function has a counterpart, `unpatch`, also located in `@web/core/utils/patch`. The `patch` function returns its counterpart. This is mostly useful for
.. 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 testing purposes, when we patch something at the beginning of a test, and
unpatch it at the end. unpatch it at the end.
.. code-block:: javascript .. code-block:: javascript
patch(object, "patch name", { ... }); const unpatch = patch(object, { ... });
// test stuff here // test stuff here
unpatch(object, "patch name"); unpatch();
Applying the same patch to multiple objects
===========================================
It could happen that one wants to apply the same patch to multiple objects but
because of the way the `super` keyword works, the `extension` can only be used
for patching once and cannot be copied/cloned (`check the documentation of the keyword <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super#description>`_).
A function returning the object used to patch can be used to make it unique.
.. code-block:: javascript
const obj1 = {
method() {
doSomething();
},
};
const obj2 = {
method() {
doThings();
},
};
function createExtensionObj() {
return {
method() {
super.method();
doCommonThings();
},
};
}
patch(obj1, createExtensionObj());
patch(obj2, createExtensionObj());
.. warning::
If an `extension` is based on another then the two extensions should
be applied separately. Do not copy/clone an extension.
.. code-block:: javascript
const object = {
method1() {
doSomething();
},
method2() {
doAnotherThing();
},
};
const ext1 = {
method1() {
super.method1();
doThings();
},
};
const invalid_ext2 = {
...ext1, // this will not work: super will not refer to the correct object in methods coming from ext1
method2() {
super.method2();
doOtherThings();
},
};
patch(object, invalid_ext2);
object.method1(); // throws: Uncaught TypeError: (intermediate value).method1 is not a function
const valid_ext2 = {
method2() {
super.method2();
doOtherThings();
},
};
patch(object, ext1); // first patch base extension
patch(object, valid_ext2); // then the new one
object.method1(); // works as expected