summaryrefslogtreecommitdiff
path: root/addons/web
diff options
context:
space:
mode:
authorJulien Mougenot <jum@odoo.com>2020-12-22 16:59:46 +0000
committerAaron Bohy <aab@odoo.com>2021-02-11 07:44:15 +0000
commit953fb092636b9919aaa6c6a6cfaffd003ba951d5 (patch)
treef1af3b961f3475c31a7de4560467774135541714 /addons/web
parent1f99f5df22b3d36775a7386af170c126a481279b (diff)
[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#60791 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
Diffstat (limited to 'addons/web')
-rw-r--r--addons/web/static/src/js/views/basic/basic_renderer.js4
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js10
-rw-r--r--addons/web/static/tests/views/form_tests.js76
3 files changed, 87 insertions, 3 deletions
diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js
index 16ecc473915..a03b9b41a77 100644
--- a/addons/web/static/src/js/views/basic/basic_renderer.js
+++ b/addons/web/static/src/js/views/basic/basic_renderer.js
@@ -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);
}
},
/**
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index b6e1593b215..c758c6a03fd 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -1029,7 +1029,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();
@@ -1043,6 +1043,14 @@ var FormRenderer = BasicRenderer.extend({
});
},
/**
+ * 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
* all modifiers are applied on all page elements.
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index e0f179a4939..89d1797589d 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -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');
@@ -9666,9 +9668,81 @@ QUnit.module('Views', {
form.destroy();
delete fieldRegistryOwl.map.custom;
-
+
assert.verifySteps(['mounted', 'willUnmount']);
});
+
+ 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;
+ });
});
});