[FIX] mail,project: remove chatter flickering

Before this commit, when a form view was re-rendered (e.g. when
switching to "edit" or "readonly" modes, when using the pager...),
the chatter briefly disappeared and then reappeared, which produced
a flickering. This has been introduced by the adaptation of the JS
codebase to owl 2 [1]: when the form view had to be re-rendered,
we reused the DOM element containing the chatter in the new
rendering (done in memory), which was thus detached from the DOM.
That element was re-inserted into the DOM later on, alongside the
rest of the newly rendered elements.

This commit fixes the issue by using a specific "hook" element when
rendering the form view, and replacing that hook by the actual
(unchanged) chatter container element when the rendering is done
and the DOM is patched.

This commit also removes unnecessary overrides in project:
- _renderNode was dead code, classname "oe_project_sharing_chatter"
 no longer exists in the codebase since [2]
- _makeChatterContainerTarget replicated what was originally
 already done in the mail FormRenderer (for appearently no valid
 reason), so obviously, that override wasn't synchronized with
 this fix.

[1] edd79ff560
[2] a28cc6333a

closes odoo/odoo#84429

Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Aaron Bohy
2022-02-11 18:19:57 +00:00
parent fc276359fc
commit 11cc762d7a
3 changed files with 88 additions and 29 deletions
@@ -27,6 +27,12 @@ FormRenderer.include({
* This is set when arch contains `div.oe_chatter`.
*/
this._chatterContainerTarget = undefined;
/**
* This jQuery element will be set when rendering the form view, and
* used as a hook to insert the ChatterContainer in the right place,
* when applying the rendering into the DOM.
*/
this.$chatterContainerHook = undefined;
// Do not load chatter in form view dialogs
this._isFromFormViewDialog = params.isFromFormViewDialog;
},
@@ -105,12 +111,12 @@ FormRenderer.include({
* @returns {jQuery.Element}
*/
_makeChatterContainerTarget() {
if (this._chatterContainerTarget) {
return $(this._chatterContainerTarget);
if (!this._chatterContainerTarget) {
this._chatterContainerTarget = document.createElement("div");
this._chatterContainerTarget.classList.add("o_FormRenderer_chatterContainer");
}
const $el = $('<div class="o_FormRenderer_chatterContainer"/>');
this._chatterContainerTarget = $el[0];
return $el;
this.$chatterContainerHook = $('<div class="o_FormRenderer_chatterContainer"/>');
return this.$chatterContainerHook;
},
/**
* Mount the chatter
@@ -142,6 +148,18 @@ FormRenderer.include({
}
return this._super(...arguments);
},
/**
* The last rendering of the form view has just been applied into the DOM,
* so we replace our chatter container hook by the actual chatter container.
*
* @override
*/
_updateView() {
this._super(...arguments);
if (this._hasChatter()) {
this.$chatterContainerHook.replaceWith(this._chatterContainerTarget);
}
},
/**
* Overrides the function to render the chatter once the form view is
* rendered.
@@ -10,8 +10,10 @@ import {
} from '@mail/utils/test_utils';
import config from 'web.config';
import fieldRegistry from 'web.field_registry';
import FormView from 'web.FormView';
import { dom } from 'web.test_utils';
import { dom, nextTick } from 'web.test_utils';
import { registerCleanup } from "@web/../tests/helpers/cleanup";
const { triggerEvent } = dom;
@@ -1048,6 +1050,68 @@ QUnit.test('[TECHNICAL] unfolded read more/less links should not fold on message
);
});
QUnit.test('chatter does not flicker when the form view is re-rendered', async function (assert) {
this.data['res.partner'].records.push(
{ display_name: "first partner", id: 11 },
{ display_name: "second partner", id: 12 }
);
// define an asynchronous field and use it in the form to ease testing
let def;
const FieldChar = fieldRegistry.get("char");
const AsyncWidget = FieldChar.extend({
willStart() {
return Promise.resolve(def);
},
});
fieldRegistry.add("async_widget", AsyncWidget);
registerCleanup(() => {
delete fieldRegistry.map.async_widget;
});
await this.createView({
data: this.data,
hasView: true,
// View params
View: FormView,
model: 'res.partner',
arch: `
<form>
<sheet>
<field name="name" widget="async_widget"/>
</sheet>
<div class="oe_chatter"></div>
</form>
`,
viewOptions: {
currentId: 11,
ids: [11, 12],
},
});
assert.strictEqual(
document.querySelectorAll(`.o_Chatter`).length,
1,
"there should be a chatter"
);
def = makeDeferred();
document.querySelector('.o_pager_next').click();
await nextTick();
assert.strictEqual(
document.querySelectorAll(`.o_Chatter`).length,
1,
"there should be a chatter"
);
def.resolve();
await nextTick();
assert.strictEqual(
document.querySelectorAll(`.o_Chatter`).length,
1,
"there should be a chatter"
);
});
});
});
});
@@ -25,30 +25,7 @@ export default FormRenderer.extend({
project_sharing_id: session.project_id,
};
},
_makeChatterContainerTarget() {
const $el = $('<div class="o_FormRenderer_chatterContainer"/>');
this._chatterContainerTarget = $el[0];
return $el;
},
_mountChatterContainerComponent() {
this._chatterContainerComponent.appendTo(this._chatterContainerTarget);
},
_renderNode(node) {
if (node.tag === 'div' && node.attrs.class === 'oe_project_sharing_chatter') {
let isVisible = true;
if (node.attrs.modifiers && node.attrs.modifiers.invisible) {
const record = this._getRecord(this.state.id);
if (record) {
isVisible = !record.evalModifiers(node.attrs.modifiers).invisible;
}
}
if (isVisible) {
if (this._isFromFormViewDialog) {
return $('<div/>');
}
return this._makeChatterContainerTarget();
}
}
return this._super(...arguments);
},
});