summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/core/patch_mixin.js
blob: 99f06f565880f554780bb5e3cf1d03f9d98b1332 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
odoo.define("web.patchMixin", function () {
    "use strict";

    /**
     * This module defines and exports the 'patchMixin' function. This function
     * returns a 'monkey-patchable' version of the ES6 Class given in arguments.
     *
     *    const patchMixin = require('web.patchMixin');
     *    class MyClass {
     *        print() {
     *            console.log('MyClass');
     *        }
     *    }
     *    const MyPatchedClass = patchMixin(MyClass);
     *
     *
     * A patchable class has a 'patch' function, allowing to define a patch:
     *
     *    MyPatchedClass.patch("module_name.key", T =>
     *        class extends T {
     *            print() {
     *                console.log('MyPatchedClass');
     *                super.print();
     *            }
     *        }
     *    );
     *
     *    const myPatchedClass = new MyPatchedClass();
     *    myPatchedClass.print(); // displays "MyPatchedClass" and "MyClass"
     *
     *
     * The 'unpatch' function can be used to remove a patch, given its key:
     *
     *    MyPatchedClass.unpatch("module_name.key");
     */
    function patchMixin(OriginalClass) {
        let unpatchList = [];
        class PatchableClass extends OriginalClass {}

        PatchableClass.patch = function (name, patch) {
            if (unpatchList.find(x => x.name === name)) {
                throw new Error(`Class ${OriginalClass.name} already has a patch ${name}`);
            }
            if (!Object.prototype.hasOwnProperty.call(this, 'patch')) {
                throw new Error(`Class ${this.name} is not patchable`);
            }
            const SubClass = patch(Object.getPrototypeOf(this));
            unpatchList.push({
                name: name,
                elem: this,
                prototype: this.prototype,
                origProto: Object.getPrototypeOf(this),
                origPrototype: Object.getPrototypeOf(this.prototype),
                patch: patch,
            });
            Object.setPrototypeOf(this, SubClass);
            Object.setPrototypeOf(this.prototype, SubClass.prototype);
        };

        PatchableClass.unpatch = function (name) {
            if (!unpatchList.find(x => x.name === name)) {
                throw new Error(`Class ${OriginalClass.name} does not have any patch ${name}`);
            }
            const toUnpatch = unpatchList.reverse();
            unpatchList = [];
            for (let unpatch of toUnpatch) {
                Object.setPrototypeOf(unpatch.elem, unpatch.origProto);
                Object.setPrototypeOf(unpatch.prototype, unpatch.origPrototype);
            }
            for (let u of toUnpatch.reverse()) {
                if (u.name !== name) {
                    PatchableClass.patch(u.name, u.patch);
                }
            }
        };
        return PatchableClass;
    }

    return patchMixin;
});