[FIX] web,mail: Call on_attach_callback when appending content

Before this commit, the form renderer would call "on_attach_callback"
only when the whole _render process was over. The problem was that this
process is asynchronous and this callback should be invoked as soon as
the widgets are appended.

As such, any asynchronous operation is performed before appending the
content in a new function "__renderView".

Task 2346540

closes odoo/odoo#65991

X-original-commit: 953fb092636b9919aaa6c6a6cfaffd003ba951d5
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Julien Mougenot
2021-02-12 06:36:41 +00:00
committed by Aaron Bohy
parent 665e25c2de
commit 78e44a23c9
4 changed files with 87 additions and 3 deletions
@@ -145,7 +145,7 @@ FormRenderer.include({
*
* @override
*/
async _renderView() {
async __renderView() {
await this._super(...arguments);
if (this._hasChatter()) {
if (!this._chatterContainerComponent) {
@@ -684,7 +684,7 @@ var BasicRenderer = AbstractRenderer.extend(WidgetAdapterMixin, {
if (this._isInDom) {
for (const handle in this.allFieldWidgets) {
this.allFieldWidgets[handle].forEach(widget => {
if (widget.on_attach_callback) {
if (!utils.isComponent(widget.constructor) && widget.on_attach_callback) {
widget.on_attach_callback();
}
});
@@ -694,6 +694,8 @@ var BasicRenderer = AbstractRenderer.extend(WidgetAdapterMixin, {
widget.on_attach_callback();
}
});
// call on_attach_callback on child components (including field components)
WidgetAdapterMixin.on_attach_callback.call(this);
}
},
/**
@@ -1062,7 +1062,7 @@ var FormRenderer = BasicRenderer.extend({
var $form = this._renderNode(this.arch).addClass(this.className);
delete this.defs;
return Promise.all(defs).then(function () {
return Promise.all(defs).then(() => this.__renderView()).then(function () {
self._updateView($form.contents());
if (self.state.res_id in self.alertFields) {
self.displayTranslationAlert();
@@ -1075,6 +1075,14 @@ var FormRenderer = BasicRenderer.extend({
$form.remove();
});
},
/**
* Meant to be overridden if asynchronous work needs to be done when
* rendering the view. This is called right before attaching the new view
* content.
* @private
* @returns {Promise<any>}
*/
async __renderView() {},
/**
* This method is overridden to activate the first notebook page if the
* current active page is invisible due to modifiers. This is done after
@@ -1,12 +1,14 @@
odoo.define('web.form_tests', function (require) {
"use strict";
const AbstractField = require("web.AbstractField");
var AbstractStorageService = require('web.AbstractStorageService');
var BasicModel = require('web.BasicModel');
var concurrency = require('web.concurrency');
var core = require('web.core');
var fieldRegistry = require('web.field_registry');
const fieldRegistryOwl = require('web.field_registry_owl');
const FormRenderer = require('web.FormRenderer');
var FormView = require('web.FormView');
var mixins = require('web.mixins');
var NotificationService = require('web.NotificationService');
@@ -10851,6 +10853,78 @@ QUnit.module('Views', {
form.destroy();
});
QUnit.test("attach callbacks with long processing in __renderView", async function (assert) {
/**
* The main use case of this test is discuss, in which the FormRenderer
* __renderView method is overridden to perform asynchronous tasks (the
* update of the chatter Component) resulting in a delay between the
* appending of the new form content into its element and the
* "on_attach_callback" calls. This is the purpose of "__renderView"
* which is meant to do all the async work before the content is appended.
*/
assert.expect(11);
let testPromise = Promise.resolve();
const Renderer = FormRenderer.extend({
on_attach_callback() {
assert.step("form.on_attach_callback");
this._super(...arguments);
},
async __renderView() {
const _super = this._super.bind(this);
await testPromise;
return _super();
},
});
// Setup custom field widget
fieldRegistry.add("customwidget", AbstractField.extend({
className: "custom-widget",
on_attach_callback() {
assert.step("widget.on_attach_callback");
},
}));
const form = await createView({
arch: `<form><field name="bar" widget="customwidget"/></form>`,
data: this.data,
model: 'partner',
res_id: 1,
View: FormView.extend({
config: Object.assign({}, FormView.prototype.config, { Renderer }),
}),
});
assert.containsOnce(form, ".custom-widget");
assert.verifySteps([
"form.on_attach_callback", // Form attached
"widget.on_attach_callback", // Initial widget attached
]);
const initialWidget = form.$(".custom-widget")[0];
testPromise = testUtils.makeTestPromise();
await testUtils.form.clickEdit(form);
assert.containsOnce(form, ".custom-widget");
assert.strictEqual(initialWidget, form.$(".custom-widget")[0], "Widgets have yet to be replaced");
assert.verifySteps([]);
testPromise.resolve();
await testUtils.nextTick();
assert.containsOnce(form, ".custom-widget");
assert.notStrictEqual(initialWidget, form.$(".custom-widget")[0], "Widgets have been replaced");
assert.verifySteps([
"widget.on_attach_callback", // New widget attached
]);
form.destroy();
delete fieldRegistry.map.customwidget;
});
});
});