[FIX] web: select all records should trigger only one rendered

Purpose:
--------
From the new RelationalModel (odoo/odoo#114024), in the list view, when
all the records are selected, the ListController is rendered once per record.
This causes performance problems and can cause a flicker where each record
is selected one by one.

Why:
----
When selecting all records, we will call record.togggleSelection() on each
record but this function uses a mutex. So each record will be selected
asynchronously. As the state is not modified by the same tick,
a rendering will be triggered for each call to record.togggleSelection.

Solution:
---------
Add a togggleSelection function to the DynamicList which will select all
the records in the same mutex. The state will therefore be modified in
the same tick and there will be a single rendering.

closes odoo/odoo#139859

Signed-off-by: Francois Georis (fge) <fge@odoo.com>
This commit is contained in:
FrancoisGe
2023-10-27 20:16:02 +00:00
parent 0c4d1aee26
commit 5696b5686e
4 changed files with 42 additions and 18 deletions
@@ -176,6 +176,21 @@ export class DynamicList extends DataPoint {
});
}
toggleSelection() {
return this.model.mutex.exec(() => {
if (this.selection.length === this.records.length) {
this.records.forEach((record) => {
record._toggleSelection(false);
});
this.isDomainSelected = false;
} else {
this.records.forEach((record) => {
record._toggleSelection(true);
});
}
});
}
unarchive(isSelected) {
return this.model.mutex.exec(() => this._toggleArchive(isSelected, false));
}
@@ -239,11 +239,7 @@ export class Record extends DataPoint {
toggleSelection(selected) {
return this.model.mutex.exec(() => {
if (typeof selected === "boolean") {
this.selected = selected;
} else {
this.selected = !this.selected;
}
this._toggleSelection(selected);
});
}
@@ -1102,6 +1098,14 @@ export class Record extends DataPoint {
}
}
_toggleSelection(selected) {
if (typeof selected === "boolean") {
this.selected = selected;
} else {
this.selected = !this.selected;
}
}
async _getOnchangeValues(changes) {
const onChangeFields = Object.keys(changes).filter(
(fieldName) => this.activeFields[fieldName] && this.activeFields[fieldName].onChange
@@ -1801,16 +1801,7 @@ export class ListRenderer extends Component {
if (!this.canSelectRecord) {
return;
}
if (list.selection.length === list.records.length) {
list.records.forEach((record) => {
record.toggleSelection(false);
list.selectDomain(false);
});
} else {
list.records.forEach((record) => {
record.toggleSelection(true);
});
}
return list.toggleSelection();
}
toggleRecordSelection(record, ev) {
@@ -1,6 +1,6 @@
/** @odoo-module **/
import { Component, markup, onWillStart, xml } from "@odoo/owl";
import { Component, markup, onRendered, onWillStart, xml } from "@odoo/owl";
import { browser } from "@web/core/browser/browser";
import { Domain } from "@web/core/domain";
import { currencies } from "@web/core/currency";
@@ -2546,7 +2546,7 @@ QUnit.module("Views", (hooks) => {
});
QUnit.test("many2one field rendering with many2one widget", async function (assert) {
serverData.models.bar.records[0].display_name = false
serverData.models.bar.records[0].display_name = false;
await makeView({
type: "list",
resModel: "foo",
@@ -2561,7 +2561,7 @@ QUnit.module("Views", (hooks) => {
});
QUnit.test("many2one field rendering when display_name is falsy", async function (assert) {
serverData.models.bar.records[0].display_name = false
serverData.models.bar.records[0].display_name = false;
await makeView({
type: "list",
resModel: "foo",
@@ -3270,6 +3270,15 @@ QUnit.module("Views", (hooks) => {
});
QUnit.test("selection changes are triggered correctly", async function (assert) {
patchWithCleanup(ListController.prototype, {
setup() {
super.setup(...arguments);
onRendered(() => {
assert.step("onRendered ListController");
});
},
});
const list = await makeView({
type: "list",
resModel: "foo",
@@ -3281,6 +3290,7 @@ QUnit.module("Views", (hooks) => {
assert.strictEqual(list.model.root.selection.length, 0, "no record should be selected");
assert.notOk(tbody_selector.checked, "selection checkbox should be checked");
assert.verifySteps(["onRendered ListController"]);
// tbody checkbox click
await click(tbody_selector);
@@ -3294,10 +3304,12 @@ QUnit.module("Views", (hooks) => {
"the correct record should be selected"
);
assert.ok(tbody_selector.checked, "selection checkbox should be checked");
assert.verifySteps(["onRendered ListController"]);
await click(tbody_selector);
assert.strictEqual(list.model.root.selection.length, 0, "no record should be selected");
assert.notOk(tbody_selector.checked, "selection checkbox should be checked");
assert.verifySteps(["onRendered ListController"]);
// head checkbox click
await click(thead_selector);
@@ -3308,6 +3320,7 @@ QUnit.module("Views", (hooks) => {
target.querySelectorAll("tbody tr").length,
"all selection checkboxes should be checked"
);
assert.verifySteps(["onRendered ListController"]);
await click(thead_selector);
assert.strictEqual(list.model.root.selection.length, 0, "no records should be selected");
@@ -3316,6 +3329,7 @@ QUnit.module("Views", (hooks) => {
"tbody .o_list_record_selector input:checked",
"no selection checkbox should be checked"
);
assert.verifySteps(["onRendered ListController"]);
});
QUnit.test(