diff --git a/addons/mail/static/src/component_hooks/use_models/use_models.js b/addons/mail/static/src/component_hooks/use_models/use_models.js
index 25583efaa37..c555afa8e14 100644
--- a/addons/mail/static/src/component_hooks/use_models/use_models.js
+++ b/addons/mail/static/src/component_hooks/use_models/use_models.js
@@ -7,10 +7,18 @@ const { useComponent } = owl.hooks;
/**
* This hook provides support for automatically re-rendering when used records
* or fields changed.
+ *
+ * Components that use this hook must be instantiated after messaging service is
+ * started. However there is no restriction on the messaging record (coming from
+ * the modelManager of the messaging service) being already initialized or even
+ * created.
*/
export function useModels() {
const component = useComponent();
const { modelManager } = component.env.services.messaging;
+ Object.defineProperty(component, 'messaging', {
+ get: () => modelManager.messaging,
+ });
const listener = new Listener({
isLocking: false, // unfortunately __render has side effects such as children components updating their reference to their corresponding model
name: `useModels() of ${component}`,
diff --git a/addons/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js b/addons/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js
index 930f16ecb29..525e00efe6f 100644
--- a/addons/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js
+++ b/addons/mail/static/src/component_hooks/use_rendered_values/use_rendered_values.js
@@ -1,5 +1,7 @@
/** @odoo-module **/
+import { Listener } from '@mail/model/model_listener';
+
const { Component } = owl;
const { onMounted, onPatched } = owl.hooks;
@@ -16,10 +18,16 @@ export function useRenderedValues(selector) {
const component = Component.current;
let renderedValues;
let patchedValues;
-
+ const { modelManager } = component.env.services.messaging;
+ const listener = new Listener({
+ name: `useRenderedValues() of ${component}`,
+ onChange: () => component.render(),
+ });
const __render = component.__render.bind(component);
component.__render = function () {
+ modelManager.startListening(listener);
renderedValues = selector();
+ modelManager.stopListening(listener);
return __render(...arguments);
};
onMounted(onUpdate);
@@ -27,5 +35,10 @@ export function useRenderedValues(selector) {
function onUpdate() {
patchedValues = renderedValues;
}
+ const __destroy = component.__destroy;
+ component.__destroy = parent => {
+ modelManager.removeListener(listener);
+ __destroy.call(component, parent);
+ };
return () => patchedValues;
}
diff --git a/addons/mail/static/src/components/messaging_menu/messaging_menu.js b/addons/mail/static/src/components/messaging_menu/messaging_menu.js
index c1519c3de4c..f183b4e2640 100644
--- a/addons/mail/static/src/components/messaging_menu/messaging_menu.js
+++ b/addons/mail/static/src/components/messaging_menu/messaging_menu.js
@@ -18,16 +18,12 @@ export class MessagingMenu extends Component {
* item is not considered as a click away from messaging menu in mobile.
*/
this.id = _.uniqueId('o_messagingMenu_');
-
// bind since passed as props
this._onMobileNewMessageInputSelect = this._onMobileNewMessageInputSelect.bind(this);
this._onMobileNewMessageInputSource = this._onMobileNewMessageInputSource.bind(this);
this._onClickCaptureGlobal = this._onClickCaptureGlobal.bind(this);
this._onHideMobileNewMessage = this._onHideMobileNewMessage.bind(this);
this._onSelectMobileNavbarTab = this._onSelectMobileNavbarTab.bind(this);
- // for now, the legacy env is needed for internal functions such as
- // `useModels` to work
- this.env = owl.Component.env;
onMounted(() => this._mounted());
onWillUnmount(() => this._willUnmount());
}
@@ -48,7 +44,7 @@ export class MessagingMenu extends Component {
* @returns {mail.messaging_menu}
*/
get messagingMenu() {
- return this.messaging && this.messaging.messagingMenu;
+ return this.messaging && this.messaging.models['mail.messaging_menu'].get(this.props.localId);
}
/**
@@ -187,7 +183,7 @@ export class MessagingMenu extends Component {
}
Object.assign(MessagingMenu, {
- props: {},
+ props: { localId: String },
template: 'mail.MessagingMenu',
});
diff --git a/addons/mail/static/src/components/messaging_menu/tests/messaging_menu_tests.js b/addons/mail/static/src/components/messaging_menu/tests/messaging_menu_tests.js
index 6a717ce7a45..6c21de3398f 100644
--- a/addons/mail/static/src/components/messaging_menu/tests/messaging_menu_tests.js
+++ b/addons/mail/static/src/components/messaging_menu/tests/messaging_menu_tests.js
@@ -52,61 +52,19 @@ QUnit.test('[technical] messaging not created then becomes created', async funct
await createMessagingMenuComponent();
assert.containsOnce(
document.body,
- '.o_MessagingMenu',
- "should have messaging menu even when messaging is not yet created"
+ '.o_MessagingMenuContainer_spinner',
+ "messaging menu container should have spinner when messaging is not yet created"
);
// simulate messaging becoming created
- messagingBeforeCreationDeferred.resolve();
- await nextAnimationFrame();
+ await afterNextRender(() => messagingBeforeCreationDeferred.resolve());
assert.containsOnce(
document.body,
'.o_MessagingMenu',
- "should still contain messaging menu after messaging has been created"
+ "messaging menu container should contain messaging menu after messaging has been created"
);
});
-QUnit.test('[technical] no crash on attempting opening messaging menu when messaging not created', async function (assert) {
- /**
- * Creation of messaging in env is async due to generation of models being
- * async. Generation of models is async because it requires parsing of all
- * JS modules that contain pieces of model definitions.
- *
- * Time of having no messaging is very short, almost imperceptible by user
- * on UI, but the display should not crash during this critical time period.
- *
- * Messaging menu is not expected to be open on click because state of
- * messaging menu requires messaging being created.
- */
- assert.expect(2);
-
- const { createMessagingMenuComponent } = await this.start({
- messagingBeforeCreationDeferred: new Promise(() => {}), // keep messaging not created
- waitUntilMessagingCondition: 'none',
- });
- await createMessagingMenuComponent();
- assert.containsOnce(
- document.body,
- '.o_MessagingMenu',
- "should have messaging menu even when messaging is not yet created"
- );
-
- let error;
- try {
- document.querySelector('.o_MessagingMenu_toggler').click();
- await nextAnimationFrame();
- } catch (err) {
- error = err;
- }
- assert.notOk(
- !!error,
- "Should not crash on attempt to open messaging menu when messaging not created"
- );
- if (error) {
- throw error;
- }
-});
-
QUnit.test('messaging not initialized', async function (assert) {
assert.expect(2);
diff --git a/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.js b/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.js
new file mode 100644
index 00000000000..bacdb041c6c
--- /dev/null
+++ b/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.js
@@ -0,0 +1,26 @@
+/** @odoo-module **/
+
+import { useModels } from "@mail/component_hooks/use_models/use_models";
+import { getMessagingComponent } from "@mail/utils/messaging_component";
+
+const { Component } = owl;
+
+export class MessagingMenuContainer extends Component {
+
+ /**
+ * @override
+ */
+ setup() {
+ // for now, the legacy env is needed for internal functions such as
+ // `useModels` to work
+ this.env = owl.Component.env;
+ useModels();
+ super.setup();
+ }
+
+}
+
+Object.assign(MessagingMenuContainer, {
+ components: { MessagingMenu: getMessagingComponent('MessagingMenu') },
+ template: 'mail.MessagingMenuContainer',
+});
diff --git a/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.xml b/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.xml
new file mode 100644
index 00000000000..13d4b86b73d
--- /dev/null
+++ b/addons/mail/static/src/components/messaging_menu_container/messaging_menu_container.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/addons/mail/static/src/components/rtc_activity_notice/rtc_activity_notice.js b/addons/mail/static/src/components/rtc_activity_notice/rtc_activity_notice.js
index 695a7e6a193..09f02c40d19 100644
--- a/addons/mail/static/src/components/rtc_activity_notice/rtc_activity_notice.js
+++ b/addons/mail/static/src/components/rtc_activity_notice/rtc_activity_notice.js
@@ -6,16 +6,6 @@ const { Component } = owl;
export class RtcActivityNotice extends Component {
- /**
- * @override
- */
- setup() {
- // for now, the legacy env is needed for internal functions such as
- // `useModels` to work
- this.env = owl.Component.env;
- super.setup();
- }
-
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
diff --git a/addons/mail/static/src/components/rtc_activity_notice/rtc_activity_notice.xml b/addons/mail/static/src/components/rtc_activity_notice/rtc_activity_notice.xml
index 871fd7151e4..9ed259b04a9 100644
--- a/addons/mail/static/src/components/rtc_activity_notice/rtc_activity_notice.xml
+++ b/addons/mail/static/src/components/rtc_activity_notice/rtc_activity_notice.xml
@@ -7,7 +7,7 @@
Open conference:
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mail/static/src/models/messaging/messaging.js b/addons/mail/static/src/models/messaging/messaging.js
index 973a3713334..d2d38248399 100644
--- a/addons/mail/static/src/models/messaging/messaging.js
+++ b/addons/mail/static/src/models/messaging/messaging.js
@@ -27,7 +27,10 @@ registerModel({
*/
async start() {
this.env.services['bus_service'].on('window_focus', null, this._handleGlobalWindowFocus);
- await this.async(() => this.initializer.start());
+ await this.initializer.start();
+ if (!this.exists()) {
+ return;
+ }
this.notificationHandler.start();
this.update({ isInitialized: true });
this.initializedPromise.resolve();
diff --git a/addons/mail/static/src/services/systray_service/systray_service.js b/addons/mail/static/src/services/systray_service/systray_service.js
index c2644575938..2ae1fd6ea2b 100644
--- a/addons/mail/static/src/services/systray_service/systray_service.js
+++ b/addons/mail/static/src/services/systray_service/systray_service.js
@@ -1,6 +1,7 @@
/** @odoo-module **/
-import { getMessagingComponent } from '@mail/utils/messaging_component';
+import { MessagingMenuContainer } from '@mail/components/messaging_menu_container/messaging_menu_container';
+import { RtcActivityNoticeContainer } from '@mail/components/rtc_activity_notice_container/rtc_activity_notice_container';
import AbstractService from 'web.AbstractService';
import { registry } from '@web/core/registry';
@@ -13,12 +14,7 @@ export const SystrayService = AbstractService.extend({
* @override {web.AbstractService}
*/
async start() {
- await owl.Component.env.services.messaging.modelManager.messagingCreatedPromise;
- systrayRegistry.add('mail.MessagingMenu', {
- Component: getMessagingComponent('MessagingMenu'),
- });
- systrayRegistry.add('mail.RtcActivityNotice', {
- Component: getMessagingComponent('RtcActivityNotice'),
- });
+ systrayRegistry.add('mail.MessagingMenuContainer', { Component: MessagingMenuContainer });
+ systrayRegistry.add('mail.RtcActivityNoticeContainer', { Component: RtcActivityNoticeContainer });
},
});
diff --git a/addons/mail/static/src/utils/messaging_component.js b/addons/mail/static/src/utils/messaging_component.js
index 415b1898b46..a8cc1718f53 100644
--- a/addons/mail/static/src/utils/messaging_component.js
+++ b/addons/mail/static/src/utils/messaging_component.js
@@ -28,14 +28,11 @@ export function registerMessagingComponent(ComponentClass, { propsCompareDepth =
// Defining the class in an object and immediately taking it out so that it
// has "decoratedName" as its class name in stack traces and stuff.
const MessagingClass = { [decoratedName]: class extends ComponentClass {
- setup(...args) {
+ setup() {
this.root = useRef('root');
- super.setup(...args);
- // useModels must be defined after useRenderedValues, indeed records and
- // fields accessed during useRenderedValues should be observed by
- // useModels as if they were part of the OWL rendering itself.
useModels();
useShouldUpdateBasedOnProps({ propsCompareDepth });
+ super.setup();
}
get className() {
let res = '';
@@ -51,9 +48,6 @@ export function registerMessagingComponent(ComponentClass, { propsCompareDepth =
}
return res;
}
- get messaging() {
- return this.env.services.messaging.modelManager.messaging;
- }
/**
* @returns {string}
*/
diff --git a/addons/mail/static/src/utils/test_utils.js b/addons/mail/static/src/utils/test_utils.js
index 959ec5d22a1..50aba2eb99a 100644
--- a/addons/mail/static/src/utils/test_utils.js
+++ b/addons/mail/static/src/utils/test_utils.js
@@ -2,6 +2,7 @@
import BusService from 'bus.BusService';
+import { MessagingMenuContainer } from '@mail/components/messaging_menu_container/messaging_menu_container';
import {
addMessagingToEnv,
addTimeControlToEnv,
@@ -373,8 +374,28 @@ function getAfterEvent({ messagingBus }) {
}
/**
- * Creates and returns a new root Component with the given props and mounts it
- * on target.
+ * Creates a new root Component, with the given props, and mounts it on target.
+ * Assumes that self.env is set to the correct value.
+ * Components created this way are automatically registered for clean up after
+ * the test, which will happen when `afterEach` is called.
+ *
+ * @param {Object} self the current QUnit instance
+ * @param {Object} Component the class of the component to create
+ * @param {Object} param2
+ * @param {Object} [param2.props={}] forwarded to component constructor
+ * @param {DOM.Element} param2.target mount target for the component
+ */
+async function createRootComponent(self, Component, { props = {}, target }) {
+ Component.env = self.env;
+ const component = new Component(null, props);
+ delete Component.env;
+ self.components.push(component);
+ await afterNextRender(() => component.mount(target));
+}
+
+/**
+ * Creates and returns a new root messaging component, based on the given
+ * componentName and with the given props, and mounts it on target.
* Assumes that self.env is set to the correct value.
* Components created this way are automatically registered for clean up after
* the test, which will happen when `afterEach` is called.
@@ -384,16 +405,9 @@ function getAfterEvent({ messagingBus }) {
* @param {Object} param2
* @param {Object} [param2.props={}] forwarded to component constructor
* @param {DOM.Element} param2.target mount target for the component
- * @returns {owl.Component} the new component instance
*/
async function createRootMessagingComponent(self, componentName, { props = {}, target }) {
- const Component = getMessagingComponent(componentName);
- Component.env = self.env;
- const component = new Component(null, props);
- delete Component.env;
- self.components.push(component);
- await afterNextRender(() => component.mount(target));
- return component;
+ await createRootComponent(self, getMessagingComponent(componentName), { props, target });
}
function getCreateComposerComponent({ components, env, modelManager, widget }) {
@@ -439,10 +453,7 @@ function getCreateMessageComponent({ components, env, modelManager, widget }) {
function getCreateMessagingMenuComponent({ components, env, widget }) {
return async function createMessagingMenuComponent() {
- await createRootMessagingComponent({ components, env }, 'MessagingMenu', {
- props: {},
- target: widget.el,
- });
+ await createRootComponent({ components, env }, MessagingMenuContainer, { target: widget.el });
};
}