diff --git a/content/developer/reference/frontend/patching_code.rst b/content/developer/reference/frontend/patching_code.rst index e74fb4d7f..99e0a3f77 100644 --- a/content/developer/reference/frontend/patching_code.rst +++ b/content/developer/reference/frontend/patching_code.rst @@ -18,24 +18,18 @@ Description 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 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) + :param object objToPatch: the object that should be patched + :param object extension: an object mapping each key to an extension + :returns: a function to remove the patch - 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. + The `patch` function modifies in place the `objToPatch` object (or class) and + applies all key/value described in the `extension` object. An unpatch + function is returned, so it can be used to remove the patch 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. + native `super` keyword (see below in the examples). 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() { // 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`` -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``: +function. To do so, we can simply use the native ``super`` keyword: .. code-block:: javascript - patch(object, "_super patch", { + patch(object, { fn() { - this._super(...arguments); + super.fn(...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: + ``super`` can only be used in a method not a function. This means that the + following constructs are invalid for javascript. - .. 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. - }, - }); + .. code-block:: javascript + const obj = { + a: function () { + // Throws: "Uncaught SyntaxError: 'super' keyword unexpected here" + super.a(); + }, + b: () => { + // Throws: "Uncaught SyntaxError: 'super' keyword unexpected here" + super.b(); + }, + }; Getters and setters are supported too: .. code-block:: javascript - patch(object, "getter/setter patch", { + patch(object, { get number() { - return this._super() / 2; + return super.number / 2; }, set number(value) { - this._super(value * 2); + super.number = value; }, }); @@ -124,12 +114,12 @@ the `prototype`: } // this will patch static properties!!! - patch(MyClass, "static patch", { + patch(MyClass, { myStaticFn() {...}, }); // this is probably the usual case: patching a class method - patch(MyClass.prototype, "prototype patch", { + patch(MyClass.prototype, { myPrototypeFn() {...}, }); @@ -149,9 +139,9 @@ constructor and patch that method instead: } } - patch(MyClass.prototype, "constructor", { + patch(MyClass.prototype, { setup() { - this._super(...arguments); + super.setup(...arguments); this.doubleNumber = this.number * 2; }, }); @@ -165,11 +155,11 @@ 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`. +can easily be patched as well (see the section on :ref:`best practices`). .. code-block:: javascript - patch(MyComponent.prototype, "my patch", { + patch(MyComponent.prototype, { setup() { useMyHook(); }, @@ -178,19 +168,91 @@ can easily be patched as well (see the section on :ref:`best practices`_). +A function returning the object used to patch can be used to make it unique. - patch(object, "patch name", { ... }); - // test stuff here - unpatch(object, "patch name"); +.. 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