From 9211a6f33ff79fe6651173193c92e2acfc238bed Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Tue, 10 Oct 2023 09:23:14 +0200 Subject: [PATCH] [FIX] web: calculate specialData when record is updated The goal of this commit is to cause a recalculation of specialData when this depends on a record value. Currently, specialData are only calculated during onWillStart and onWillUpdateProps. But the specialData of certain fields do not depend only on the props, they can also depend on the values present in the record. We therefore want to recalculate them when the value of the record changes. How to reproduce: - Go to a form view with a field "x" and a field with specialData dependent on field "x". - Edit the field "x" Before this commit: The specialData have not changed After this commit: The specialData have been recalculated. closes odoo/odoo#138372 Signed-off-by: Aaron Bohy (aab) --- .../src/views/fields/many2many_attendee.js | 2 +- .../src/model/relational_model/utils.js | 4 +-- .../src/views/fields/relational_utils.js | 15 ++++---- .../views/fields/statusbar_field_tests.js | 1 + .../tests/views/form/form_view_tests.js | 36 +++++++++++++++++++ 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/addons/calendar/static/src/views/fields/many2many_attendee.js b/addons/calendar/static/src/views/fields/many2many_attendee.js index e1c6a474b01..095d9410577 100644 --- a/addons/calendar/static/src/views/fields/many2many_attendee.js +++ b/addons/calendar/static/src/views/fields/many2many_attendee.js @@ -11,7 +11,7 @@ export class Many2ManyAttendee extends Many2ManyTagsAvatarField { setup() { super.setup(); this.specialData = useSpecialData((orm, props) => { - const { context, name, record } = this.props; + const { context, name, record } = props; return orm.call( "res.partner", "get_attendee_detail", diff --git a/addons/web/static/src/model/relational_model/utils.js b/addons/web/static/src/model/relational_model/utils.js index 9ff071e6f4a..2e7608b48b4 100644 --- a/addons/web/static/src/model/relational_model/utils.js +++ b/addons/web/static/src/model/relational_model/utils.js @@ -614,7 +614,7 @@ export function useRecordObserver(callback) { async (record) => { if (firstCall) { firstCall = false; - await callback(record); + await callback(record, props); def.resolve(); } else { return batched( @@ -624,7 +624,7 @@ export function useRecordObserver(callback) { // We must do it manually. return; } - await callback(record); + await callback(record, props); def.resolve(); }, () => new Promise((resolve) => window.requestAnimationFrame(resolve)) diff --git a/addons/web/static/src/views/fields/relational_utils.js b/addons/web/static/src/views/fields/relational_utils.js index 310919c8b4c..629cbfca16d 100644 --- a/addons/web/static/src/views/fields/relational_utils.js +++ b/addons/web/static/src/views/fields/relational_utils.js @@ -18,7 +18,7 @@ import { createElement, parseXML } from "@web/core/utils/xml"; import { FormArchParser } from "@web/views/form/form_arch_parser"; import { loadSubViews } from "@web/views/form/form_controller"; import { FormRenderer } from "@web/views/form/form_renderer"; -import { extractFieldsFromArchInfo } from "@web/model/relational_model/utils"; +import { extractFieldsFromArchInfo, useRecordObserver } from "@web/model/relational_model/utils"; import { computeViewClassName, isNull } from "@web/views/utils"; import { ViewButton } from "@web/views/view_button/view_button"; import { useViewButtons } from "@web/views/view_button/view_button_hook"; @@ -39,11 +39,11 @@ import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog import { Component, - onWillStart, onWillUpdateProps, useComponent, useEffect, useEnv, + useState, useSubEnv, } from "@odoo/owl"; @@ -156,12 +156,15 @@ export function useSpecialData(loadFn) { } ormWithCache.call = (...args) => specialDataCaches[key].read(...args); - const result = {}; - onWillStart(async () => { - result.data = await loadFn(ormWithCache, component.props); + const result = useState({}); + useRecordObserver(async (record, props) => { + result.data = await loadFn(ormWithCache, { ...props, record }); }); onWillUpdateProps(async (props) => { - result.data = await loadFn(ormWithCache, props); + // useRecordObserver callback is not called when the record doesn't change + if (props.record.id === component.props.record.id) { + result.data = await loadFn(ormWithCache, props); + } }); return result; } diff --git a/addons/web/static/tests/views/fields/statusbar_field_tests.js b/addons/web/static/tests/views/fields/statusbar_field_tests.js index 2dc24e62d61..d936675e61c 100644 --- a/addons/web/static/tests/views/fields/statusbar_field_tests.js +++ b/addons/web/static/tests/views/fields/statusbar_field_tests.js @@ -407,6 +407,7 @@ QUnit.module("Fields", (hooks) => { ".o_statusbar_status button:not(.dropdown-toggle)" ); await click(buttons[buttons.length - 1]); + await nextTick(); assert.containsN(target, ".o_statusbar_status button:not(.dropdown-toggle)", 2); } ); diff --git a/addons/web/static/tests/views/form/form_view_tests.js b/addons/web/static/tests/views/form/form_view_tests.js index 045738aa365..cef6015a822 100644 --- a/addons/web/static/tests/views/form/form_view_tests.js +++ b/addons/web/static/tests/views/form/form_view_tests.js @@ -54,6 +54,7 @@ import { CharField } from "@web/views/fields/char/char_field"; import { DateTimeField } from "@web/views/fields/datetime/datetime_field"; import { Field } from "@web/views/fields/field"; import { IntegerField } from "@web/views/fields/integer/integer_field"; +import { useSpecialData } from "@web/views/fields/relational_utils"; import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field"; import { FormController } from "@web/views/form/form_controller"; import { companyService } from "@web/webclient/company_service"; @@ -14262,4 +14263,39 @@ QUnit.module("Views", (hooks) => { }); assert.strictEqual(target.querySelector(".o_breadcrumb").innerText, "Unnamed"); }); + + QUnit.test("field with special data", async function (assert) { + class MyWidget extends Component { + static template = xml`
MyWidget
`; + setup() { + this.specialData = useSpecialData((orm, props) => { + const { record } = props; + return orm.call("my.model", "get_special_data", [record.data.int_field]); + }); + } + } + widgetRegistry.add("my_widget", { + component: MyWidget, + }); + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + + `, + resId: 2, + async mockRPC(route, args) { + if (args.method === "get_special_data") { + assert.step(`get_special_data ${args.args[0]}`); + return {}; + } + }, + }); + + await editInput(target, "[name='int_field'] input", "42"); + assert.verifySteps(["get_special_data 9", "get_special_data 42"]); + }); });