[REF] *: adapt code to owl2 with fine-grained reactivity
On most of the cases, the fine-grained reactivity is bypassed by:
- Setting the optional `deep` argument as `true` on the renderer
function. This will force a render to update all child components.
- Changing some objects from reactive to non-reactive using `toRaw`
function.
Part-of: odoo/odoo#84314
Co-authored-by: Géry Debongnie <ged@odoo.com>
This commit is contained in:
co-authored by
Géry Debongnie
parent
d90f481448
commit
09b4668c6b
@@ -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();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,10 +29,7 @@
|
||||
</t>
|
||||
|
||||
<div class="o_cp_pager" role="search">
|
||||
<t t-set="pagerProps" t-value="env.config.getPagerProps()" />
|
||||
<t t-if="pagerProps">
|
||||
<Pager t-if="pagerProps.total > 0" t-props="pagerProps" />
|
||||
</t>
|
||||
<Pager t-if="pagerProps and pagerProps.total > 0" t-props="pagerProps"/>
|
||||
</div>
|
||||
|
||||
<t t-if="env.config.viewSwitcherEntries.length">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
const { useEnv, useChildSubEnv } = owl;
|
||||
const { useEnv, useChildSubEnv, useState, onWillRender } = owl;
|
||||
|
||||
/**
|
||||
* @typedef PagerUpdateParams
|
||||
@@ -23,11 +23,15 @@ const { useEnv, useChildSubEnv } = owl;
|
||||
*/
|
||||
export function usePager(getProps) {
|
||||
const env = useEnv();
|
||||
const pagerState = useState(getProps());
|
||||
|
||||
useChildSubEnv({
|
||||
config: {
|
||||
...env.config,
|
||||
getPagerProps: getProps,
|
||||
pagerProps: pagerState,
|
||||
},
|
||||
});
|
||||
onWillRender(() => {
|
||||
Object.assign(pagerState, getProps());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "./utils/dates";
|
||||
import { FACET_ICONS } from "./utils/misc";
|
||||
|
||||
const { EventBus } = owl;
|
||||
const { EventBus, toRaw } = owl;
|
||||
const { DateTime } = luxon;
|
||||
|
||||
/**
|
||||
@@ -215,7 +215,7 @@ export class SearchModel extends EventBus {
|
||||
const { comparison, context, domain, groupBy, orderBy } = config;
|
||||
|
||||
this.globalComparison = comparison;
|
||||
this.globalContext = context || {};
|
||||
this.globalContext = toRaw(context || {});
|
||||
this.globalDomain = domain || [];
|
||||
this.globalGroupBy = groupBy || [];
|
||||
this.globalOrderBy = orderBy || [];
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import { SearchModel } from "@web/search/search_model";
|
||||
import { CallbackRecorder, useSetupAction } from "@web/webclient/actions/action_hook";
|
||||
|
||||
const { Component, onWillStart, onWillUpdateProps, useSubEnv, useChildSubEnv } = owl;
|
||||
const { Component, onWillStart, onWillUpdateProps, toRaw, useSubEnv } = owl;
|
||||
|
||||
export const SEARCH_KEYS = ["comparison", "context", "domain", "groupBy", "orderBy"];
|
||||
const OTHER_SEARCH_KEYS = ["irFilters", "searchViewArch", "searchViewFields", "searchViewId"];
|
||||
@@ -36,7 +36,7 @@ export class WithSearch extends Component {
|
||||
});
|
||||
|
||||
onWillStart(async () => {
|
||||
const config = { ...this.props };
|
||||
const config = { ...toRaw(this.props) };
|
||||
if (config.globalState && config.globalState.searchModel) {
|
||||
config.state = JSON.parse(config.globalState.searchModel);
|
||||
delete config.globalState;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { WithSearch } from "@web/search/with_search/with_search";
|
||||
import { useActionLinks } from "@web/views/helpers/view_hook";
|
||||
import { extractLayoutComponents } from "@web/views/layout";
|
||||
|
||||
const { Component, onWillUpdateProps, onWillStart, useSubEnv } = owl;
|
||||
const { Component, onWillUpdateProps, onWillStart, toRaw, useSubEnv } = owl;
|
||||
const viewRegistry = registry.category("views");
|
||||
|
||||
/** @typedef {Object} Config
|
||||
@@ -282,7 +282,7 @@ export class View extends Component {
|
||||
|
||||
// prepare the WithSearch component props
|
||||
this.withSearchProps = {
|
||||
...this.props,
|
||||
...toRaw(this.props),
|
||||
Component: ViewClass,
|
||||
componentProps: viewProps,
|
||||
};
|
||||
|
||||
@@ -107,7 +107,7 @@ QUnit.module("Search", (hooks) => {
|
||||
});
|
||||
|
||||
QUnit.test("pager", async (assert) => {
|
||||
let pagerInfo = {
|
||||
let pagerProps = {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total: 50,
|
||||
@@ -119,15 +119,13 @@ QUnit.module("Search", (hooks) => {
|
||||
resModel: "foo",
|
||||
Component: ControlPanel,
|
||||
config: {
|
||||
getPagerProps() {
|
||||
return pagerInfo;
|
||||
},
|
||||
pagerProps: pagerProps,
|
||||
},
|
||||
searchMenuTypes: [],
|
||||
});
|
||||
assert.containsOnce(controlPanel.el, ".o_pager");
|
||||
|
||||
pagerInfo.total = 0;
|
||||
pagerProps.total = 0;
|
||||
controlPanel.render();
|
||||
await nextTick();
|
||||
assert.containsNone(controlPanel.el, ".o_pager");
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { usePager } from "@web/search/pager_hook";
|
||||
import { click } from "../helpers/utils";
|
||||
import { click, nextTick } from "../helpers/utils";
|
||||
import { makeWithSearch, setupControlPanelServiceRegistry } from "./helpers";
|
||||
|
||||
const { Component, useState, xml } = owl;
|
||||
@@ -110,5 +110,22 @@ QUnit.module("Search", (hooks) => {
|
||||
offset: 10,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
comp.state.offset = 20;
|
||||
await nextTick();
|
||||
|
||||
assert.containsOnce(comp, ".o_pager");
|
||||
assert.strictEqual(
|
||||
comp.el.querySelector(`.o_pager_counter .o_pager_value`).textContent.trim(),
|
||||
"21-30"
|
||||
);
|
||||
assert.strictEqual(
|
||||
comp.el.querySelector(`.o_pager_counter span.o_pager_limit`).textContent.trim(),
|
||||
"50"
|
||||
);
|
||||
assert.deepEqual(comp.state, {
|
||||
offset: 20,
|
||||
limit: 10,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user