diff options
4 files changed, 88 insertions, 4 deletions
diff --git a/addons/mail/static/src/widgets/form_renderer/form_renderer.js b/addons/mail/static/src/widgets/form_renderer/form_renderer.js index e4c3161c4da..cf147656d63 100644 --- a/addons/mail/static/src/widgets/form_renderer/form_renderer.js +++ b/addons/mail/static/src/widgets/form_renderer/form_renderer.js @@ -145,7 +145,7 @@ FormRenderer.include({ * * @override */ - async _renderView() { + async __renderView() { await this._super(...arguments); if (this._hasChatter()) { if (!this._chatterContainerComponent) { 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; + }); }); }); |
