[FIX] web: kanban view's quick create flickering

Before this commit, when creating a new record using the quick create on
a kanban view, a flickering was visible. We can see the quick create
form disappear, the record list goes up, the quick create form re-appear
and the record list goes down again.

This occurs because, the record list contains an empty record that is
used on the quick create form. When clicking on the add button on the
quick create form, this record is directly saved, and as is already on
the list (the quick create form disappear and the record list goes up),
only after this action, a new empty record is created and added to the
list (the quick create form re-appear and the record list goes down
again).

Now, an empty record is created independently of the list, so when we
click on the add button on the quick create form, the record is saved,
a new empty record is created (the quick create form is emptied), and
the saved record is added to the list (the record list got the new saved
record), avoiding the flickering.

task-id=3085247

closes odoo/odoo#112367

X-original-commit: 9c82f5dca00921b6911671db3845ad593d2e1191
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
Signed-off-by: Jorge Pinna Puissant (jpp) <jpp@odoo.com>
This commit is contained in:
Jorge Pinna Puissant
2023-02-10 07:16:59 +01:00
parent 966d383e17
commit 58cc5662a9
5 changed files with 138 additions and 76 deletions
@@ -287,24 +287,30 @@ class KanbanGroup extends Group {
* @param {number} index
* @returns {Promise<Record | false>}
*/
async validateQuickCreate() {
const record = this.list.quickCreateRecord;
async validateQuickCreate(record, mode) {
let saved = false;
if (record) {
saved = await this.model.mutex.exec(async () => {
const saved = await record._save({ noReload: true, stayInEdition: true });
if (saved) {
this.count++;
if (record.parentActiveFields) {
record.setActiveFields(record.parentActiveFields);
record.parentActiveFields = false;
}
await this.model.reloadRecords(record);
record.switchMode("readonly");
this.addRecord(this.removeRecord(record), 0);
}
return saved;
return await record._save({ noReload: true, stayInEdition: true });
});
if (saved) {
if (mode === "add") {
await this.model.root.quickCreate(this);
} else {
this.quickCreateRecord = null;
}
if (record.parentActiveFields) {
record.setActiveFields(record.parentActiveFields);
record.parentActiveFields = false;
}
await this.model.reloadRecords(record);
record.switchMode("readonly");
this.addRecord(record, 0);
this.model.trigger("group-updated", {
group: this,
withProgressBars: true,
});
}
}
return saved ? record : false;
}
@@ -605,9 +611,7 @@ export class KanbanModel extends RelationalModel {
// example background. Return true so that we don't get sample data instead
return true;
}
return this.root.groups.some(
(group) => group.count > 0 || group.list.quickCreateRecord
);
return this.root.groups.some((group) => group.count > 0 || group.quickCreateRecord);
}
return this.root.records.length > 0;
}
@@ -378,10 +378,10 @@ export class KanbanRenderer extends Component {
}
async validateQuickCreate(mode, group) {
const values = group.list.quickCreateRecord.data;
let record;
const values = group.quickCreateRecord.data;
let record = group.quickCreateRecord;
try {
record = await group.validateQuickCreate();
record = await group.validateQuickCreate(record, mode);
} catch (e) {
// TODO: filter RPC errors more specifically (eg, for access denied, there is no point in opening a dialog)
if (!(e instanceof RPCError)) {
@@ -410,12 +410,8 @@ export class KanbanRenderer extends Component {
);
}
if (record) {
if (mode === "edit") {
await this.props.openRecord(record, "edit");
} else {
await this.quickCreate(group);
}
if (mode === "edit" && record) {
await this.props.openRecord(record, "edit");
}
}
@@ -60,30 +60,28 @@
</div>
</div>
<t t-if="!group.isFolded">
<t t-if="props.list.quickCreateInfo and group.quickCreateRecord">
<KanbanRecordQuickCreate
record="group.quickCreateRecord"
archInfo="props.list.quickCreateInfo"
onCancel="force => group.cancelQuickCreate(force)"
onValidate="mode => this.validateQuickCreate(mode, group)"
listIsGrouped="props.list.isGrouped"
/>
</t>
<t t-foreach="group.list.records" t-as="record" t-key="record.id">
<t t-if="record.isInQuickCreation and props.list.quickCreateInfo">
<KanbanRecordQuickCreate
record="record"
archInfo="props.list.quickCreateInfo"
onCancel="force => group.list.cancelQuickCreate(force)"
onValidate="mode => this.validateQuickCreate(mode, group)"
listIsGrouped="props.list.isGrouped"
/>
</t>
<t t-else="">
<KanbanRecord
archInfo="props.archInfo"
Compiler="props.Compiler"
canResequence="canResequenceRecords"
forceGlobalClick="props.forceGlobalClick"
group="group"
list="props.list"
openRecord="props.openRecord"
readonly="props.readonly"
record="record"
templates="props.archInfo.templateDocs"
/>
</t>
<KanbanRecord
archInfo="props.archInfo"
Compiler="props.Compiler"
canResequence="canResequenceRecords"
forceGlobalClick="props.forceGlobalClick"
group="group"
list="props.list"
openRecord="props.openRecord"
readonly="props.readonly"
record="record"
templates="props.archInfo.templateDocs"
/>
</t>
<t t-set="unloadedCount" t-value="getGroupUnloadedCount(group)" />
<div t-if="unloadedCount > 0" class="o_kanban_load_more">
+17 -18
View File
@@ -1893,10 +1893,6 @@ export class DynamicRecordList extends DynamicList {
};
}
get quickCreateRecord() {
return this.records.find((r) => r.isInQuickCreation);
}
// -------------------------------------------------------------------------
// Public
// -------------------------------------------------------------------------
@@ -1931,13 +1927,6 @@ export class DynamicRecordList extends DynamicList {
return record;
}
async cancelQuickCreate(force = false) {
const record = this.quickCreateRecord;
if (record && (force || !record.isDirty)) {
this.removeRecord(record);
}
}
/**
* @param {Object} [params={}]
* @param {boolean} [atFirstPosition]
@@ -1956,8 +1945,11 @@ export class DynamicRecordList extends DynamicList {
await this.model.keepLast.add(this.model.mutex.exec(() => newRecord.load()));
this.editedRecord = newRecord;
this.onRemoveNewRecord = await this.onCreateRecord(newRecord);
return this.addRecord(newRecord, atFirstPosition ? 0 : this.count);
if (params.isInQuickCreation) {
return newRecord;
} else {
return this.addRecord(newRecord, atFirstPosition ? 0 : this.count);
}
}
/**
@@ -2039,10 +2031,6 @@ export class DynamicRecordList extends DynamicList {
async quickCreate(activeFields, context) {
await this.model.mutex.getUnlockedDef();
const record = this.quickCreateRecord;
if (record) {
this.removeRecord(record);
}
const rawContext = {
parent: this.rawContext,
make: () => makeContext([context, {}]),
@@ -2352,7 +2340,11 @@ export class DynamicGroupList extends DynamicList {
if (isFolded) {
await group.toggle();
}
await group.quickCreate(this.quickCreateInfo.activeFields, this.context);
group.quickCreateRecord = await group.quickCreate(
this.quickCreateInfo.activeFields,
this.context
);
this.model.notify();
}
/**
@@ -2788,6 +2780,13 @@ export class Group extends DataPoint {
return this.list.quickCreate(activeFields, ctx);
}
async cancelQuickCreate(force = false) {
if (this.quickCreateRecord && (force || !this.quickCreateRecord.isDirty)) {
this.quickCreateRecord = null;
this.model.notify();
}
}
/**
* @see DynamicRecordList.removeRecord
*/
@@ -1544,8 +1544,8 @@ QUnit.module("Views", (hooks) => {
"web_search_read", // initial search_read (second column)
"onchange", // quick create
"name_create", // should perform a name_create to create the record
"read", // read the created record
"onchange", // reopen the quick create automatically
"read", // read the created record
]);
});
@@ -1621,11 +1621,76 @@ QUnit.module("Views", (hooks) => {
"get_views", // form view in quick create
"onchange", // quick create
"create", // should perform a create to create the record
"read", // read the created record
"onchange", // new quick create
"read", // read the created record
]);
});
QUnit.test("quick create record flickering", async (assert) => {
let def;
serverData.views["partner,some_view_ref,form"] =
"<form>" +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'<field name="state" widget="priority"/>' +
"</form>";
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch:
'<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
"</t></templates></kanban>",
groupBy: ["bar"],
async mockRPC(route, args) {
if (args.method === "create") {
assert.deepEqual(
args.args[0],
{
foo: "new partner",
int_field: 4,
state: "def",
},
"should send the correct values"
);
}
if (args.method === "onchange") {
await def;
}
},
});
// click on 'Create' -> should open the quick create in the first column
await createRecord();
assert.containsN(target, ".o_kanban_group:first-child .o_kanban_record", 1);
assert.containsOnce(target, ".o_kanban_group:first-child .o_kanban_quick_create");
const quickCreate = target.querySelector(
".o_kanban_group:first-child .o_kanban_quick_create"
);
assert.containsOnce(quickCreate, ".o_form_view.o_xxs_form_view");
assert.containsN(quickCreate, "input", 2);
assert.containsN(quickCreate, ".o_field_widget", 3, "should have rendered three widgets");
// fill the quick create and validate
await editQuickCreateInput("foo", "new partner");
await editQuickCreateInput("int_field", 4);
await click(quickCreate, ".o_field_widget[name=state] .o_priority_star:first-child");
def = makeDeferred();
await validateRecord();
assert.containsN(target, ".o_kanban_group:first-child .o_kanban_record", 1);
assert.containsOnce(target, ".o_kanban_group:first-child .o_kanban_quick_create");
def.resolve();
await nextTick();
assert.containsN(target, ".o_kanban_group:first-child .o_kanban_record", 2);
assert.containsOnce(target, ".o_kanban_group:first-child .o_kanban_quick_create");
});
QUnit.test(
"quick create record should focus default field [REQUIRE FOCUS]",
async function (assert) {
@@ -1779,8 +1844,8 @@ QUnit.module("Views", (hooks) => {
"web_search_read", // initial search_read (second column)
"onchange", // quick create
"name_create", // should perform a name_create to create the record
"read", // read the created record
"onchange", // reopen the quick create automatically
"read", // read the created record
]);
});
@@ -1847,8 +1912,8 @@ QUnit.module("Views", (hooks) => {
"get_views", // form view in quick create
"onchange", // quick create
"create", // should perform a create to create the record
"read", // read the created record
"onchange", // reopen the quick create automatically
"read", // read the created record
]);
});
@@ -1903,9 +1968,9 @@ QUnit.module("Views", (hooks) => {
"read", // read display_name of categories
"onchange", // quick create
"name_create", // should perform a name_create to create the record
"onchange", // reopen the quick create automatically
"read",
"read", // read the created record
"onchange", // reopen the quick create automatically
]);
});
@@ -1963,8 +2028,8 @@ QUnit.module("Views", (hooks) => {
"web_search_read", // read records when unfolding 'None'
"onchange", // quick create
"name_create", // should perform a name_create to create the record
"read", // read the created record
"onchange", // reopen the quick create automatically
"read", // read the created record
]);
});
@@ -2025,8 +2090,8 @@ QUnit.module("Views", (hooks) => {
"get_views", // get form view
"onchange", // quick create
"create", // should perform a name_create to create the record
"read", // read the created record
"onchange", // reopen the quick create automatically
"read", // read the created record
]);
});
@@ -2098,9 +2163,9 @@ QUnit.module("Views", (hooks) => {
"onchange", // quick create
"read",
"create", // should perform a create to create the record
"read",
"onchange",
"read",
"read",
]);
});
@@ -2295,7 +2360,7 @@ QUnit.module("Views", (hooks) => {
await editQuickCreateInput("foo", "new partner");
assert.verifySteps(["onchange"]);
await validateRecord();
assert.verifySteps(["create", "read", "onchange"]);
assert.verifySteps(["create", "onchange", "read"]);
});
QUnit.test("quick create record and change state in grouped mode", async (assert) => {