[REF] web: RelationalModel: commit changes in _updateConfig

This commit adds an option to _updateConfig which allows to specify
a callback to apply the new values obtained from the updated config
e.g.
 1) a dynamic list updates its offset (calls _updateConfig)
 2) a web_search_read is done and returns some records
 3) the new config (with the new offset) is applied on this.config
 4) record datapoints are created for the returned records and set
   on this.records of the list

Before this commit, step 3) was done in _updateConfig, and step 4)
was done by the called of _updateConfig. As _updateConfig is async,
step 4) was actually done in the next microTick after 3). As a
consequence, those two mutations could trigger owl renderings.

To avoid this situation, _updateConfig now takes a "commit"
function, which allows it to execute 3) and 4) in the same tick.

Part of~3179751

Part-of: odoo/odoo#114024
This commit is contained in:
Pierre Rousseau
2023-07-24 20:17:56 +02:00
committed by Aaron Bohy
parent 2b21fbe782
commit e02da4224a
4 changed files with 53 additions and 52 deletions
@@ -14,6 +14,10 @@ export class DynamicGroupList extends DynamicList {
setup(config, data) {
super.setup(...arguments);
this.isGrouped = true;
this._setData(data);
}
_setData(data) {
/** @type {import("./group").Group[]} */
this.groups = data.groups.map((g) => this._createGroupDatapoint(g));
this.count = data.length;
@@ -239,9 +243,11 @@ export class DynamicGroupList extends DynamicList {
for (const group of groups) {
delete configGroups[group.value];
}
const response = await this.model._updateConfig(this.config, { groups: configGroups });
this.groups = response.groups.map((group) => this._createGroupDatapoint(group));
this.count = response.length;
await this.model._updateConfig(
this.config,
{ groups: configGroups },
{ commit: this._setData.bind(this) }
);
} else {
for (const group of groups) {
this._removeGroup(group);
@@ -258,14 +264,11 @@ export class DynamicGroupList extends DynamicList {
}
async _load(offset, limit, orderBy, domain) {
const response = await this.model._updateConfig(this.config, {
offset,
limit,
orderBy,
domain,
});
this.groups = response.groups.map((group) => this._createGroupDatapoint(group));
this.count = response.length;
await this.model._updateConfig(
this.config,
{ offset, limit, orderBy, domain },
{ commit: this._setData.bind(this) }
);
}
_removeGroup(group) {
@@ -11,6 +11,10 @@ export class DynamicRecordList extends DynamicList {
*/
setup(config, data) {
super.setup(config);
this._setData(data);
}
_setData(data) {
/** @type {import("./record").Record[]} */
this.records = data.records.map((r) => this._createRecordDatapoint(r));
this._updateCount(data);
@@ -141,14 +145,11 @@ export class DynamicRecordList extends DynamicList {
}
async _load(offset, limit, orderBy, domain) {
const response = await this.model._updateConfig(this.config, {
offset,
limit,
orderBy,
domain,
});
this.records = response.records.map((r) => this._createRecordDatapoint(r));
this._updateCount(response);
await this.model._updateConfig(
this.config,
{ offset, limit, orderBy, domain },
{ commit: this._setData.bind(this) }
);
}
_removeRecords(recordIds) {
@@ -49,28 +49,35 @@ export class Record extends DataPoint {
}
const missingFields = this.fieldNames.filter((fieldName) => !(fieldName in data));
data = { ...this._getDefaultValues(missingFields), ...data };
const vals = this._parseServerValues(data);
if (this.resId) {
this._values = markRaw(vals);
this._changes = markRaw({});
} else {
this._values = markRaw({});
this._changes = markRaw(vals);
}
this.data = { ...this._values, ...this._changes };
// In db, char, text and html fields can be not set (NULL) and set to the empty string. In
// the UI, there's no difference, but in the eval context, it's not the same. The next
// structure keeps track of the server values we received for those fields (which can thus
// be false or a string). This allows us to properly build the eval context, and to always
// expose string values (false fallbacks on the empty string) in this.data.
this._textValues = markRaw({});
this._setTextValues(data);
this._setData(data);
}
_setData(data) {
if (this.resId) {
this._values = this._parseServerValues(data);
this._changes = markRaw({});
this._setTextValues(data);
} else {
this._values = markRaw({});
const allVals = { ...this._getDefaultValues(), ...data };
this._changes = this._parseServerValues(allVals);
this._setTextValues(allVals);
}
this.dirty = false;
this.data = { ...this._values, ...this._changes };
this._setEvalContext();
this._savePoint = markRaw({
dirty: false,
changes: { ...this._changes },
textValues: { ...this._textValues },
});
this._invalidFields.clear();
}
// -------------------------------------------------------------------------
@@ -535,27 +542,14 @@ export class Record extends DataPoint {
async _load(nextConfig = {}) {
// FIXME: do not allow to change resId? maybe add a new method on model to re-generate a
// new root for the new resId
const values = await this.model._updateConfig(this.config, nextConfig);
if (this.resId) {
this.model._updateSimilarRecords(this, values);
this._values = this._parseServerValues(values);
this._changes = markRaw({});
this._setTextValues(values);
} else {
this._values = markRaw({});
const allVals = { ...this._getDefaultValues(), ...values };
this._changes = this._parseServerValues(allVals);
this._setTextValues(allVals);
}
this.dirty = false;
this.data = { ...this._values, ...this._changes };
this._setEvalContext();
this._savePoint = markRaw({
dirty: false,
changes: { ...this._changes },
textValues: { ...this._textValues },
await this.model._updateConfig(this.config, nextConfig, {
commit: (values) => {
if (this.resId) {
this.model._updateSimilarRecords(this, values);
}
this._setData(values);
},
});
this._invalidFields.clear();
}
_parseServerValues(serverValues, currentValues = {}) {
@@ -603,15 +603,18 @@ export class RelationalModel extends Model {
* @param {Partial<Config>} patch
* @param {Object} [options]
* @param {boolean} [options.noReload=false]
* @param {Function} [options.commit] Function to call once the data has been loaded
*/
async _updateConfig(config, patch, options = {}) {
const tmpConfig = { ...config, ...patch };
let response;
let data;
if (!options.noReload) {
response = await this._loadData(tmpConfig);
data = await this._loadData(tmpConfig);
}
Object.assign(config, tmpConfig);
return response;
if (data && options.commit) {
options.commit(data);
}
}
/**