diff --git a/addons/web/static/src/views/record.js b/addons/web/static/src/views/record.js
index abc652fe4df..c46c9f4db2e 100644
--- a/addons/web/static/src/views/record.js
+++ b/addons/web/static/src/views/record.js
@@ -5,21 +5,17 @@ import { useModel } from "@web/views/model";
import { RelationalModel } from "@web/views/relational_model";
import { Component, xml, onWillStart, onWillUpdateProps } from "@odoo/owl";
+const defaultActiveField = { attrs: {}, options: {}, domain: "[]", string: "" };
+
class _Record extends Component {
setup() {
- const activeFields =
- this.props.info.activeFields ||
- Object.fromEntries(
- this.props.info.fieldNames.map((f) => [f, { attrs: {}, options: {}, domain: "[]" }])
- );
-
this.model = useModel(RelationalModel, {
resId: this.props.info.resId,
resModel: this.props.info.resModel,
fields: this.props.fields,
viewMode: "form",
rootType: "record",
- activeFields,
+ activeFields: this.getActiveFields(),
mode: this.props.info.mode === "edit" ? "edit" : undefined,
initialValues: this.props.info.initialValues,
});
@@ -29,9 +25,36 @@ class _Record extends Component {
mode: nextProps.info.mode,
});
});
+
+ if (this.props.info.onRecordChanged) {
+ const load = this.model.load;
+ this.model.load = async (...args) => {
+ const res = await load.call(this.model, ...args);
+ const root = this.model.root;
+ root.onChanges = async () => {
+ const changes = root.getChanges();
+ this.props.info.onRecordChanged(root, changes);
+ };
+ return res;
+ };
+ }
+ }
+
+ getActiveFields() {
+ if (this.props.info.activeFields) {
+ const activeFields = {};
+ for (const [fName, fInfo] of Object.entries(this.props.info.activeFields)) {
+ activeFields[fName] = { ...defaultActiveField, ...fInfo };
+ }
+ return activeFields;
+ }
+ return Object.fromEntries(
+ this.props.info.fieldNames.map((f) => [f, { ...defaultActiveField }])
+ );
}
}
_Record.template = xml``;
+_Record.props = ["slots", "info", "fields"];
export class Record extends Component {
setup() {
@@ -61,4 +84,5 @@ Record.props = [
"resId?",
"mode?",
"initialValues?",
+ "onRecordChanged?",
];
diff --git a/addons/web/static/tests/views/record_tests.js b/addons/web/static/tests/views/record_tests.js
index 223e411bb87..a9dad462d48 100644
--- a/addons/web/static/tests/views/record_tests.js
+++ b/addons/web/static/tests/views/record_tests.js
@@ -7,6 +7,7 @@ import { click, getFixture, mount } from "../helpers/utils";
import { setupViewRegistries } from "../views/helpers";
import { Component, xml, useState } from "@odoo/owl";
+import { editInput } from "@web/../tests/helpers/utils";
let serverData;
let target;
@@ -183,4 +184,50 @@ QUnit.module("Record Component", (hooks) => {
assert.verifySteps([]);
assert.strictEqual(target.querySelector(".o_field_widget input").value, "abc");
});
+
+ QUnit.test("provides a way to handle changes in the record", async function (assert) {
+ class Parent extends Component {
+ setup() {
+ this.fields = {
+ foo: {
+ name: "foo",
+ type: "char",
+ },
+ bar: {
+ name: "bar",
+ type: "boolean",
+ },
+ };
+ this.values = {
+ foo: "abc",
+ bar: true,
+ };
+ }
+
+ onRecordChanged(record, changes) {
+ assert.step("record changed");
+ assert.strictEqual(record.model.constructor.name, "RelationalModel");
+ assert.deepEqual(changes, { foo: "753" });
+ }
+ }
+ Parent.components = { Record, Field };
+ Parent.template = xml`
+
+
+
+ `;
+
+ await mount(Parent, target, {
+ env: await makeTestEnv({
+ serverData,
+ mockRPC(route) {
+ assert.step(route);
+ },
+ }),
+ });
+ assert.strictEqual(target.querySelector("[name='foo'] input").value, "abc");
+ await editInput(target, "[name='foo'] input", "753");
+ assert.verifySteps(["record changed"]);
+ assert.strictEqual(target.querySelector("[name='foo'] input").value, "753");
+ });
});