diff --git a/addons/base_automation/static/tests/base_automation_error_dialog.js b/addons/base_automation/static/tests/base_automation_error_dialog.js index be88ab2071d..5dfb772a0a3 100644 --- a/addons/base_automation/static/tests/base_automation_error_dialog.js +++ b/addons/base_automation/static/tests/base_automation_error_dialog.js @@ -16,7 +16,7 @@ import { BaseAutomationErrorDialog } from "../src/js/base_automation_error_dialo import { patchWithCleanup,getFixture, mount, nextTick } from "@web/../tests/helpers/utils"; import { DialogContainer } from "@web/core/dialog/dialog_container"; -const { onMounted } = owl; +const { onMounted, toRaw } = owl; const serviceRegistry = registry.category("services"); @@ -72,7 +72,11 @@ QUnit.module("base_automation", {}, function () { patchWithCleanup(BaseAutomationErrorDialog.prototype, { setup() { - assert.equal(this.props.data.context, errorContext, "Received the correct error context"); + assert.equal( + toRaw(this.props.data.context), + errorContext, + "Received the correct error context" + ); this._super(); }, }); diff --git a/addons/mail/static/src/widgets/form_renderer/form_renderer.js b/addons/mail/static/src/widgets/form_renderer/form_renderer.js index 89868f8ee16..993640de1f5 100644 --- a/addons/mail/static/src/widgets/form_renderer/form_renderer.js +++ b/addons/mail/static/src/widgets/form_renderer/form_renderer.js @@ -155,6 +155,13 @@ FormRenderer.include({ * @override */ _updateView() { + /** + * The chatter is detached before it's removed on the super._updateView function, + * this is done to avoid losing the event handlers. + */ + if (this._hasChatter()) { + this._chatterContainerTarget.remove(); + } this._super(...arguments); if (this._hasChatter()) { this.$chatterContainerHook.replaceWith(this._chatterContainerTarget); diff --git a/addons/point_of_sale/static/src/entry/chrome_adapter.js b/addons/point_of_sale/static/src/entry/chrome_adapter.js index bc5853144e9..f31874fbeae 100644 --- a/addons/point_of_sale/static/src/entry/chrome_adapter.js +++ b/addons/point_of_sale/static/src/entry/chrome_adapter.js @@ -49,7 +49,7 @@ export class ChromeAdapter extends Component { const updateUI = debounce(() => { if (this.env.isMobile !== currentIsMobile) { currentIsMobile = this.env.isMobile; - this.render(); + this.render(true); } }, 15); useExternalListener(window, "resize", updateUI); diff --git a/addons/point_of_sale/static/src/js/Chrome.js b/addons/point_of_sale/static/src/js/Chrome.js index 9eaa93f02fc..f86cc2baa1e 100644 --- a/addons/point_of_sale/static/src/js/Chrome.js +++ b/addons/point_of_sale/static/src/js/Chrome.js @@ -10,7 +10,7 @@ odoo.define('point_of_sale.Chrome', function(require) { const PopupControllerMixin = require('point_of_sale.PopupControllerMixin'); const Registries = require('point_of_sale.Registries'); const IndependentToOrderScreen = require('point_of_sale.IndependentToOrderScreen'); - const { identifyError } = require('point_of_sale.utils'); + const { identifyError, batched } = require('point_of_sale.utils'); const { odooExceptionTitleMap } = require("@web/core/errors/error_dialogs"); const { ConnectionLostError, ConnectionAbortedError, RPCError } = require('@web/core/network/rpc_service'); const { useBus } = require("@web/core/utils/hooks"); @@ -26,6 +26,7 @@ odoo.define('point_of_sale.Chrome', function(require) { useRef, useState, useSubEnv, + reactive, } = owl; /** @@ -74,7 +75,8 @@ odoo.define('point_of_sale.Chrome', function(require) { this.previous_touch_y_coordinate = -1; - useSubEnv({ pos: useState(this.env.pos) }); + const pos = reactive(this.env.pos, batched(() => this.render(true))) + useSubEnv({ pos }); onMounted(() => { // remove default webclient handlers that induce click delay @@ -200,7 +202,7 @@ odoo.define('point_of_sale.Chrome', function(require) { this.env.pos.loadPartnersBackground(); } if (this.env.pos.config.limited_products_loading && this.env.pos.config.product_load_background) { - this.env.pos.loadProductsBackground().then(() => this.render()); + this.env.pos.loadProductsBackground().then(() => this.render(true)); } } diff --git a/addons/point_of_sale/static/src/js/Screens/ClientListScreen/ClientDetailsEdit.js b/addons/point_of_sale/static/src/js/Screens/ClientListScreen/ClientDetailsEdit.js index 89afc6be087..1eacd4404fc 100644 --- a/addons/point_of_sale/static/src/js/Screens/ClientListScreen/ClientDetailsEdit.js +++ b/addons/point_of_sale/static/src/js/Screens/ClientListScreen/ClientDetailsEdit.js @@ -78,7 +78,7 @@ odoo.define('point_of_sale.ClientDetailsEdit', function(require) { const resizedImage = await this._resizeImage(loadedImage, 800, 600); this.changes.image_1920 = resizedImage.toDataURL(); // Rerender to reflect the changes in the screen - this.render(); + this.render(true); } } } diff --git a/addons/point_of_sale/static/src/js/Screens/ClientListScreen/ClientListScreen.js b/addons/point_of_sale/static/src/js/Screens/ClientListScreen/ClientListScreen.js index ae21fc4b1f7..0f64aac56ed 100644 --- a/addons/point_of_sale/static/src/js/Screens/ClientListScreen/ClientListScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ClientListScreen/ClientListScreen.js @@ -56,7 +56,7 @@ odoo.define('point_of_sale.ClientListScreen', function(require) { back() { if(this.state.detailIsShown) { this.state.detailIsShown = false; - this.render(); + this.render(true); } else { this.props.resolve({ confirmed: false, payload: false }); this.trigger('close-temp-screen'); @@ -110,7 +110,7 @@ odoo.define('point_of_sale.ClientListScreen', function(require) { this.state.selectedClient = clients[0]; this.clickNext(); } else { - this.render(); + this.render(true); } } clickClient(partner) { @@ -119,14 +119,14 @@ odoo.define('point_of_sale.ClientListScreen', function(require) { } else { this.state.selectedClient = partner; } - this.render(); + this.render(true); } editClient() { this.state.editModeProps = { partner: this.state.selectedClient, }; this.state.detailIsShown = true; - this.render(); + this.render(true); } clickNext() { this.state.selectedClient = this.nextButton.command === 'set' ? this.state.selectedClient : null; @@ -142,7 +142,7 @@ odoo.define('point_of_sale.ClientListScreen', function(require) { partner: this.state.selectedClient, }; } - this.render(); + this.render(true); } async saveChanges(event) { try { @@ -154,7 +154,7 @@ odoo.define('point_of_sale.ClientListScreen', function(require) { await this.env.pos.load_new_partners(); this.state.selectedClient = this.env.pos.db.get_partner_by_id(partnerId); this.state.detailIsShown = false; - this.render(); + this.render(true); } catch (error) { if (isConnectionError(error)) { await this.showPopup('OfflineErrorPopup', { @@ -175,7 +175,7 @@ odoo.define('point_of_sale.ClientListScreen', function(require) { body: this.env._t('No customer found'), }); } - this.render(); + this.render(true); } async getNewClient() { var domain = []; diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js index a33ef5d66a6..6f06c953e47 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js @@ -96,7 +96,7 @@ odoo.define('point_of_sale.PaymentScreen', function (require) { toggleIsToInvoice() { // click_invoice this.currentOrder.set_to_invoice(!this.currentOrder.is_to_invoice()); - this.render(); + this.render(true); } openCashbox() { this.env.proxy.printer.open_cashbox(); @@ -120,7 +120,7 @@ odoo.define('point_of_sale.PaymentScreen', function (require) { toggleIsToShip() { // click_ship this.currentOrder.set_to_ship(!this.currentOrder.is_to_ship()); - this.render(); + this.render(true); } deletePaymentLine(event) { var self = this; @@ -135,13 +135,13 @@ odoo.define('point_of_sale.PaymentScreen', function (require) { line.payment_method.payment_terminal.send_payment_cancel(this.currentOrder, cid).then(function() { self.currentOrder.remove_paymentline(line); NumberBuffer.reset(); - self.render(); + self.render(true); }) } else if (line.get_payment_status() !== 'waitingCancel') { this.currentOrder.remove_paymentline(line); NumberBuffer.reset(); - this.render(); + this.render(true); } } selectPaymentLine(event) { @@ -149,7 +149,7 @@ odoo.define('point_of_sale.PaymentScreen', function (require) { const line = this.paymentLines.find((line) => line.cid === cid); this.currentOrder.select_paymentline(line); NumberBuffer.reset(); - this.render(); + this.render(true); } async validateOrder(isForceValidate) { if(this.env.pos.config.cash_rounding) { diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js index 10f2cdb859d..bff9350d8d0 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js @@ -82,7 +82,7 @@ odoo.define('point_of_sale.ProductsWidget', function(require) { this.state.searchWord = ''; } _updateProductList(event) { - this.render(); + this.render(true); this.trigger('switch-category', 0); } } diff --git a/addons/web/static/src/legacy/js/control_panel/control_panel.js b/addons/web/static/src/legacy/js/control_panel/control_panel.js index 5b66c659d18..119f6074c63 100644 --- a/addons/web/static/src/legacy/js/control_panel/control_panel.js +++ b/addons/web/static/src/legacy/js/control_panel/control_panel.js @@ -11,7 +11,16 @@ odoo.define('web.ControlPanel', function (require) { const SearchBar = require('web.SearchBar'); const { useModel } = require('web.Model'); - const { Component, onMounted, onPatched, onWillDestroy, onWillUpdateProps, useRef, useSubEnv } = owl; + const { + Component, + onMounted, + onPatched, + onWillDestroy, + onWillUpdateProps, + toRaw, + useRef, + useSubEnv, + } = owl; /** * TODO: remove this whole mechanism as soon as `cp_content` is completely removed. @@ -122,7 +131,7 @@ odoo.define('web.ControlPanel', function (require) { searchViewButtons: useRef('searchViewButtons'), }; - this.fields = this._formatFields(this.props.fields); + this.fields = this._formatFields(toRaw(this.props.fields || {})); this.sprintf = _.str.sprintf; @@ -155,7 +164,7 @@ odoo.define('web.ControlPanel', function (require) { subEnvView = nextProps.view; } if ("fields" in nextProps) { - this.fields = this._formatFields(nextProps.fields); + this.fields = this._formatFields(toRaw(nextProps.fields)); } this.additionalContent = getAdditionalContent(nextProps); }); diff --git a/addons/web/static/src/legacy/js/control_panel/control_panel_model_extension.js b/addons/web/static/src/legacy/js/control_panel/control_panel_model_extension.js index b97b6f2fe65..7deedae7996 100644 --- a/addons/web/static/src/legacy/js/control_panel/control_panel_model_extension.js +++ b/addons/web/static/src/legacy/js/control_panel/control_panel_model_extension.js @@ -13,6 +13,7 @@ odoo.define("web/static/src/js/control_panel/control_panel_model_extension.js", const FAVORITE_SHARED_GROUP = 2; const DISABLE_FAVORITE = "search_disable_custom_filters"; + const { toRaw } = owl; /** * Control panel model * @@ -158,7 +159,7 @@ odoo.define("web/static/src/js/control_panel/control_panel_model_extension.js", this.actionContext = Object.assign({}, this.config.context); this.searchMenuTypes = this.config.searchMenuTypes || []; this.favoriteFilters = this.config.favoriteFilters || []; - this.fields = this.config.fields || {}; + this.fields = toRaw(this.config.fields || {}); this.searchDefaults = {}; for (const key in this.actionContext) { const match = /^search_default_(.*)$/.exec(key); diff --git a/addons/web/static/src/legacy/js/control_panel/search_bar.js b/addons/web/static/src/legacy/js/control_panel/search_bar.js index ac06f92852b..2f43b34b190 100644 --- a/addons/web/static/src/legacy/js/control_panel/search_bar.js +++ b/addons/web/static/src/legacy/js/control_panel/search_bar.js @@ -7,7 +7,7 @@ odoo.define('web.SearchBar', function (require) { const { useModel } = require('web.Model'); const { fuzzyTest } = require('@web/core/utils/search'); - const { Component, onMounted, onWillUnmount, useExternalListener, useState } = owl; + const { Component, onMounted, onWillUnmount, toRaw, useExternalListener, useState } = owl; const CHAR_FIELDS = ['char', 'html', 'many2many', 'many2one', 'one2many', 'text']; let sourceId = 0; @@ -91,7 +91,7 @@ odoo.define('web.SearchBar', function (require) { * @returns {Object} */ _createSource(filter) { - const field = this.props.fields[filter.fieldName]; + const field = toRaw(this.props.fields)[filter.fieldName]; const type = field.type === "reference" ? "char" : field.type; const source = { active: true, diff --git a/addons/web/static/src/legacy/js/fields/field_wrapper.js b/addons/web/static/src/legacy/js/fields/field_wrapper.js index 4f750ac516f..797bd8821b1 100644 --- a/addons/web/static/src/legacy/js/fields/field_wrapper.js +++ b/addons/web/static/src/legacy/js/fields/field_wrapper.js @@ -4,7 +4,7 @@ odoo.define('web.FieldWrapper', function (require) { const { ComponentWrapper } = require('web.OwlCompatibility'); const field_utils = require('web.field_utils'); - const { App } = owl; + const { App, reactive } = owl; /** * This file defines the FieldWrapper component, an extension of ComponentWrapper, @@ -178,7 +178,7 @@ odoo.define('web.FieldWrapper', function (require) { const fieldsInfo = this.props.record.fieldsInfo[viewType]; fieldsInfo[this.props.fieldName].modifiersValue = modifiers || {}; } - this.componentRef.comp.props = this.props; + this.componentRef.comp.props = reactive(this.props); } } diff --git a/addons/web/static/src/legacy/js/owl_compatibility.js b/addons/web/static/src/legacy/js/owl_compatibility.js index 4e4cefb411e..5288b691d70 100644 --- a/addons/web/static/src/legacy/js/owl_compatibility.js +++ b/addons/web/static/src/legacy/js/owl_compatibility.js @@ -596,11 +596,15 @@ odoo.define('web.OwlCompatibility', function (require) { if (options) { this.mountOptions = options; } - if (this.status === "unmounted") { + const remount = this.status === "unmounted"; + if (remount) { prepareForRemount(this.node); } this.status = "willMount"; const prom = this.app.mountNode(this.node, this.target, this.mountOptions); + if (remount) { + this.node.fiber.deep = true; + } prepareForFinish(this.node); await prom; if (document.contains(this.target)) { @@ -621,7 +625,7 @@ odoo.define('web.OwlCompatibility', function (require) { render() { if (this.renderProm) { - this.node.render(); + this.node.render(true); return this.renderProm; } this.renderProm = new Promise((resolve, reject) => { @@ -632,7 +636,7 @@ odoo.define('web.OwlCompatibility', function (require) { this.renderResolve = null; this.renderReject = null; }); - this.node.render(); + this.node.render(true); return this.renderProm; } diff --git a/addons/web/static/src/search/control_panel/control_panel.js b/addons/web/static/src/search/control_panel/control_panel.js index 552146c5ba3..65ea3c45a4c 100644 --- a/addons/web/static/src/search/control_panel/control_panel.js +++ b/addons/web/static/src/search/control_panel/control_panel.js @@ -8,7 +8,7 @@ import { FilterMenu } from "../filter_menu/filter_menu"; import { GroupByMenu } from "../group_by_menu/group_by_menu"; import { SearchBar } from "../search_bar/search_bar"; -const { Component } = owl; +const { Component, useState } = owl; const MAPPING = { filter: FilterMenu, @@ -20,6 +20,9 @@ const MAPPING = { export class ControlPanel extends Component { setup() { this.actionService = useService("action"); + this.pagerProps = this.env.config.pagerProps + ? useState(this.env.config.pagerProps) + : undefined; } /** diff --git a/addons/web/static/src/search/control_panel/control_panel.xml b/addons/web/static/src/search/control_panel/control_panel.xml index ac8b1382c45..2882a20c2ce 100644 --- a/addons/web/static/src/search/control_panel/control_panel.xml +++ b/addons/web/static/src/search/control_panel/control_panel.xml @@ -29,10 +29,7 @@