diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index 1c413c422a2..5a3e6dba6c2 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -511,6 +511,7 @@ odoo.define('account.reconciliation_tests', function (require) { var ReconciliationClientAction = require('account.ReconciliationClientAction'); var demoData = require('account.reconciliation_tests.data'); + var testUtils = require('web.test_utils'); QUnit.module('account', { @@ -738,7 +739,7 @@ QUnit.module('account', { // We also create a line which is the open balance. testUtils.intercept(clientAction, 'call_service', function (event) { assert.deepEqual(event.data.args[1].args, - [[5],[{partner_id: 8, + [[5],[{partner_id: 8, counterpart_aml_dicts: [{ counterpart_aml_id: 109, credit: 650, @@ -746,7 +747,7 @@ QUnit.module('account', { name: 'INV/2017/0002', analytic_tag_ids: [[6, null, []]] }], - payment_aml_ids: [], + payment_aml_ids: [], new_aml_dicts: [{ account_id: 287, credit: 525, @@ -898,7 +899,7 @@ QUnit.module('account', { var widget = clientAction.widgets[0]; assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Agrolait", "the partner many2one should display agrolait"); assert.strictEqual(widget.$('.match table tr').length, 2, "agrolait should have 2 propositions for reconciliation"); - + // Adding the two propositions // This is in order to try that after changing partner the propositions are emptied widget.$('.match .cell_account_code:first').trigger('click'); diff --git a/addons/bus/static/src/js/services/bus_service.js b/addons/bus/static/src/js/services/bus_service.js index 78be7a9740a..c6e9517acc6 100644 --- a/addons/bus/static/src/js/services/bus_service.js +++ b/addons/bus/static/src/js/services/bus_service.js @@ -4,6 +4,7 @@ odoo.define('bus.BusService', function (require) { var bus = require('bus.bus').bus; var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var BusService = AbstractService.extend({ name: 'bus_service', @@ -23,7 +24,7 @@ var BusService = AbstractService.extend({ /** * Get the bus * - * @return {web.Bus} + * @return {web.Bus} the longpoll bus */ getBus: function () { return this.bus; @@ -91,6 +92,8 @@ var BusService = AbstractService.extend({ }); +core.serviceRegistry.add('bus_service', BusService); + return BusService; }); diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js index ba2433abafc..e9d23227202 100644 --- a/addons/im_livechat/static/src/js/im_livechat.js +++ b/addons/im_livechat/static/src/js/im_livechat.js @@ -64,17 +64,19 @@ var LivechatButton = Widget.extend(ServiceProviderMixin, { var cookie = utils.get_cookie('im_livechat_session'); var ready; if (!cookie) { - ready = session.rpc("/im_livechat/init", {channel_id: this.options.channel_id}).then(function (result) { - if (!result.available_for_me) { - return $.Deferred().reject(); - } - self.rule = result.rule; - }); + ready = session.rpc("/im_livechat/init", {channel_id: this.options.channel_id}) + .then(function (result) { + if (!result.available_for_me) { + return $.Deferred().reject(); + } + self.rule = result.rule; + }); } else { var channel = JSON.parse(cookie); - ready = session.rpc("/mail/chat_history", {uuid: channel.uuid, limit: 100}).then(function (history) { - self.history = history; - }); + ready = session.rpc("/mail/chat_history", {uuid: channel.uuid, limit: 100}) + .then(function (history) { + self.history = history; + }); } return ready.then(this.load_qweb_template.bind(this)); }, @@ -87,7 +89,8 @@ var LivechatButton = Widget.extend(ServiceProviderMixin, { } else if (!config.device.isMobile && this.rule.action === 'auto_popup') { var auto_popup_cookie = utils.get_cookie('im_livechat_auto_popup'); if (!auto_popup_cookie || JSON.parse(auto_popup_cookie)) { - this.auto_popup_timeout = setTimeout(this.open_chat.bind(this), this.rule.auto_popup_timer*1000); + this.auto_popup_timeout = + setTimeout(this.open_chat.bind(this), this.rule.auto_popup_timer*1000); } } this.busBus.on('notification', this, function (notifications) { diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index 06675ec3902..c4ad1b53306 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -790,11 +790,11 @@ var BasicComposer = Widget.extend({ var def = $.Deferred(); clearTimeout(this.canned_timeout); this.canned_timeout = setTimeout(function () { - var canned_responses = self.call('chat_manager', 'getCannedResponses'); - var matches = fuzzy.filter(utils.unaccent(search), _.pluck(canned_responses, 'source')); + var cannedResponses = self.call('chat_manager', 'getCannedResponses'); + var matches = fuzzy.filter(utils.unaccent(search), _.pluck(cannedResponses, 'source')); var indexes = _.pluck(matches.slice(0, self.options.mention_fetch_limit), 'index'); def.resolve(_.map(indexes, function (i) { - return canned_responses[i]; + return cannedResponses[i]; })); }, 500); return def; diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js index 52bf2b75822..3b405761169 100644 --- a/addons/mail/static/src/js/discuss.js +++ b/addons/mail/static/src/js/discuss.js @@ -63,13 +63,14 @@ var PartnerInviteDialog = Dialog.extend({ return $('').text(item.text).prepend(status); }, query: function (query) { - self.call('chat_manager', 'searchPartner', query.term, 20).then(function (partners) { - query.callback({ - results: _.map(partners, function (partner) { - return _.extend(partner, { text: partner.label }); - }), + self.call('chat_manager', 'searchPartner', query.term, 20) + .then(function (partners) { + query.callback({ + results: _.map(partners, function (partner) { + return _.extend(partner, { text: partner.label }); + }), + }); }); - }); } }); return this._super.apply(this, arguments); @@ -152,15 +153,13 @@ var Discuss = Widget.extend(ControlPanelMixin, { .then(function (fields_view) { self.fields_view = fields_view; }); - var chatReady = this.call('chat_manager', 'isReady'); - return $.when(this._super(), chatReady, def); + return $.when(this._super(), this.call('chat_manager', 'isReady'), def); }, /** * @override */ start: function () { var self = this; - var defaultChannel = this.call('chat_manager', 'getChannel', this.defaultChannelID) || this.call('chat_manager', 'getChannel', 'channel_inbox'); @@ -214,8 +213,7 @@ var Discuss = Widget.extend(ControlPanelMixin, { * @override */ on_attach_callback: function () { - var chatBus = this.call('chat_manager', 'getChatBus'); - chatBus.trigger('discuss_open', true); + this.call('chat_manager', 'getChatBus').trigger('discuss_open', true); if (this.channel) { this.thread.scroll_to({offset: this.channelsScrolltop[this.channel.id]}); } @@ -224,8 +222,7 @@ var Discuss = Widget.extend(ControlPanelMixin, { * @override */ on_detach_callback: function () { - var chatBus = this.call('chat_manager', 'getChatBus'); - chatBus.trigger('discuss_open', false); + this.call('chat_manager', 'getChatBus').trigger('discuss_open', false); this.channelsScrolltop[this.channel.id] = this.thread.get_scrolltop(); }, @@ -582,7 +579,7 @@ var Discuss = Widget.extend(ControlPanelMixin, { chatBus.on('new_channel', this, this._onNewChannel); chatBus.on('anyone_listening', this, function (channel, query) { query.is_displayed = query.is_displayed || - (channel.id === this.channel.id && this.thread.is_at_bottom()); + (channel.id === this.channel.id && this.thread.is_at_bottom()); }); chatBus.on('unsubscribe_from_channel', this, this._onChannelLeft); chatBus.on('update_needaction', this, this.throttledUpdateChannels); @@ -642,7 +639,7 @@ var Discuss = Widget.extend(ControlPanelMixin, { needaction_counter: this.call('chat_manager', 'getNeedactionCounter'), starred_counter: this.call('chat_manager', 'getStarredCounter'), }); - self.$(".o_mail_chat_sidebar").html($sidebar.contents()); + this.$(".o_mail_chat_sidebar").html($sidebar.contents()); _.each(['dm', 'public', 'private'], function (type) { var $input = self.$('.o_mail_add_channel[data-type=' + type + '] input'); self._prepareAddChannelInput($input, type); @@ -791,10 +788,10 @@ var Discuss = Widget.extend(ControlPanelMixin, { domain: this.domain }).then(function (messages) { var options = self._getThreadRenderingOptions(messages); - self.thread.remove_message_and_render(message.id, messages, options).then(function () { - self._updateButtonStatus(messages.length === 0, type); - - }); + self.thread.remove_message_and_render(message.id, messages, options) + .then(function () { + self._updateButtonStatus(messages.length === 0, type); + }); }); } else if (_.contains(message.channel_ids, currentChannelID)) { this._fetchAndRenderThread(); diff --git a/addons/mail/static/src/js/extended_chat_window.js b/addons/mail/static/src/js/extended_chat_window.js index a8aa5673b81..a85740ac5e1 100644 --- a/addons/mail/static/src/js/extended_chat_window.js +++ b/addons/mail/static/src/js/extended_chat_window.js @@ -48,8 +48,7 @@ return ChatWindow.extend({ event.stopPropagation(); }, on_reverse_breadcrumb: function () { - var chatBus = this.call('chat_manager', 'getChatBus'); - chatBus.trigger('discuss_open', false); + this.call('chat_manager', 'getChatBus').trigger('discuss_open', false); }, on_click_expand: _.debounce(function (event) { event.preventDefault(); diff --git a/addons/mail/static/src/js/services/chat_manager.js b/addons/mail/static/src/js/services/chat_manager.js index b7c46f2d4c1..b90cc0263fb 100644 --- a/addons/mail/static/src/js/services/chat_manager.js +++ b/addons/mail/static/src/js/services/chat_manager.js @@ -16,11 +16,11 @@ var _lt = core._lt; var LIMIT = 30; // max number of fetched messages from the server var PREVIEW_MSG_MAX_SIZE = 350; // optimal for native english speakers -var ODOOBOT_ID = "ODOOBOT"; // default author_id for messages +var ODOOBOT_ID = "ODOOBOT"; // default author_id for messages /** * This service handles everything about chat channels and messages. - * + * * There are basically two points of entry: * * 1. Calling a public method by means of 'this.call' @@ -60,7 +60,7 @@ var ChatManager = AbstractService.extend({ this.chatBus.on('discuss_open', null, function (open) { self.discussOpen = open; }); - + this.busBus.on('notification', this, this._onNotification); this.busBus.on('window_focus', this, this._onWindowFocus); @@ -77,12 +77,12 @@ var ChatManager = AbstractService.extend({ this._isReady = this._initMessaging(); + // Add static channels this._addChannel({ id: "channel_inbox", name: _lt("Inbox"), type: "static", }, { displayNeedactions: true }); - this._addChannel({ id: "channel_starred", name: _lt("Starred"), @@ -181,7 +181,7 @@ var ChatManager = AbstractService.extend({ * Returns a channel corresponding to the given id. * * @param {string|integer} id e.g. 'channel_inbox', 'channel_starred' - * @return {Object|undefined} channel + * @return {Object|undefined} the channel, if it exists */ getChannel: function (id) { return _.findWhere(this.channels, {id: id}); @@ -189,7 +189,7 @@ var ChatManager = AbstractService.extend({ /** * Returns a list of channels * - * @return {Object[]} + * @return {Object[]} list of channels */ getChannels: function () { return _.clone(this.channels); @@ -282,12 +282,13 @@ var ChatManager = AbstractService.extend({ * Show the list of available commands next to a message (e.g. star) * * @param {Object} channel - * @return {Array} + * @return {Array} list of commands */ getCommands: function (channel) { - return _.filter(this.commands, function (command) { + var commands = _.filter(this.commands, function (command) { return !command.channel_types || _.contains(command.channel_types, channel.server_type); }); + return commands; }, /** * Returns the record id of ir.ui.menu for Discuss @@ -309,7 +310,7 @@ var ChatManager = AbstractService.extend({ /** * Returns list of emojis Objects * - * @return {Object[]} + * @return {Object[]} list of emojis * ['id', 'source', 'unicode_source', 'substitution', 'description'] */ getEmojis: function () { @@ -319,9 +320,10 @@ var ChatManager = AbstractService.extend({ * Get the last seen message for a given channel * * @param {Object} channel - * @return {Object} Message Object + * @return {Object|undefined} last seen Message Object (if any) */ getLastSeenMessage: function (channel) { + var result; if (channel.last_seen_message_id) { var messages = channel.cache['[]'].messages; var msg = _.findWhere(messages, {id: channel.last_seen_message_id}); @@ -332,9 +334,10 @@ var ChatManager = AbstractService.extend({ msg = messages[i]; i++; } - return msg; + result = msg; } } + return result; }, /** * Get all listeners of a channel. @@ -378,22 +381,22 @@ var ChatManager = AbstractService.extend({ * Gets message from its id * * @param {integer} msgID - * @return {Object|undefined} Message Object + * @return {Object|undefined} Message Object (if any) */ getMessage: function (msgID) { return _.findWhere(this.messages, {id: msgID}); }, /** * Gets messages from channel or ids or record (model and res_id): - * + * * 1. From channel if we have 'channelID' in options * 2. From ids if we have 'ids' in options * 3. From model if we have 'model' and 'res_id' in options - * + * * Rule of precedence: - * + * * 'channelID' < 'ids' < 'model' and 'res_id' - * + * * If we have none of the cases above, we return an empty list. * * @param {Object} [options] @@ -451,7 +454,7 @@ var ChatManager = AbstractService.extend({ * Returns the number of messages received from followed channels * + all messages where the current user is notified. * - * @return {integer} + * @return {integer} needaction counter */ getNeedactionCounter: function () { return this.needactionCounter; @@ -459,7 +462,7 @@ var ChatManager = AbstractService.extend({ /** * Gets the number of starred message * - * @return {integer} + * @return {integer} starred counter */ getStarredCounter: function () { return this.starredCounter; @@ -467,7 +470,7 @@ var ChatManager = AbstractService.extend({ /** * Gets the number of conversation which contains unread messages * - * @return {integer} + * @return {integer} unread conversation counter */ getUnreadConversationCounter: function () { return this.unreadConversationCounter; @@ -491,7 +494,7 @@ var ChatManager = AbstractService.extend({ /** * join an existing channel * See @createChannel to join a new channel - * + * * @param {integer} channelID * @param {Object} [options] * @return {$.Promise} resolved with channel object @@ -593,7 +596,7 @@ var ChatManager = AbstractService.extend({ }, /** * Open the channel: - * + * * 1. If discuss is opened, asks discuss to open the channel * 2. Otherwise, asks the chat_window_manager to detach the channel * @@ -604,14 +607,14 @@ var ChatManager = AbstractService.extend({ }, /** * Prepares and sends a message to the server: - * + * * 1. Either the message is posted on a channel * 2. Or the message is posted in a model's record (chatter) * * Rule of precedence: - * + * * 'channelID' < 'model' & 'res_id' - * + * * If options as none of these parameters, do nothing and return * a promise no resolved item. * @@ -856,7 +859,7 @@ var ChatManager = AbstractService.extend({ * @param {string} [data.state] e.g. 'open', 'folded' * @param {Object|integer} [options=undefined] * @param {boolean} [options.silent] - * @return {Object} the newly or already exisiting channel + * @return {Object} the newly or already existing channel */ _addChannel: function (data, options) { options = typeof options === "object" ? options : {}; @@ -909,7 +912,7 @@ var ChatManager = AbstractService.extend({ * @param {Object} data message data * @param {integer} data.id * @param {Object} [options] - * @param {Array} [options.domain] + * @param {Array} [options.domain] * @param {boolean} [options.increment_unread] whether we should increment * the unread_counter of channel. * @param {boolean} [options.silent] whether it should inform in the chatBus @@ -1134,6 +1137,7 @@ var ChatManager = AbstractService.extend({ } } }); + self.busBus.start_polling(); }); }, @@ -1466,7 +1470,7 @@ var ChatManager = AbstractService.extend({ } }, /** - * Updates channel_inbox when a message has marked as read. + * Updates channel_inbox when a message has marked as read. * * @private * @param {Object} data @@ -1746,6 +1750,8 @@ var ChatManager = AbstractService.extend({ }); +core.serviceRegistry.add('chat_manager', ChatManager); + return ChatManager; }); diff --git a/addons/mail/static/src/js/services/chat_window_manager.js b/addons/mail/static/src/js/services/chat_window_manager.js index f82da369ecf..bab6fbf7729 100644 --- a/addons/mail/static/src/js/services/chat_window_manager.js +++ b/addons/mail/static/src/js/services/chat_window_manager.js @@ -40,8 +40,7 @@ var ChatWindowManager = AbstractService.extend({ spaceLeft: 0, windowsDropdownIsOpen: false, // used to keep dropdown open when closing chat windows }; - var chatReady = this.call('chat_manager', 'isReady'); - chatReady.then(function () { + this.call('chat_manager', 'isReady').then(function () { var channels = self.call('chat_manager', 'getChannels'); _.each(channels, function (channel) { if (channel.is_detached) { @@ -88,7 +87,7 @@ var ChatWindowManager = AbstractService.extend({ * @param {boolean} [options.passively] if set to true, open the chat window * without focusing the input and marking messages as read if it is not * open yet, and do nothing otherwise - */ + */ openChat: function (session, options) { var self = this; if (!session) { @@ -188,7 +187,7 @@ var ChatWindowManager = AbstractService.extend({ }, /** * Called when unfolding the chat window - * + * * @param {Object} channel * @param {integer} channel.id * @param {boolean} [channel.is_folded] @@ -326,12 +325,13 @@ var ChatWindowManager = AbstractService.extend({ var channel = self.call('chat_manager', 'getChannel', session.id); self.call('chat_manager', 'markChannelAsSeen', channel); } - self.call('chat_manager', 'getMessages', {channelID: session.id}).then(function (messages) { - session.window.render(messages); - if (scrollBottom && messageVisible) { - session.window.thread.scroll_to(); - } - }); + self.call('chat_manager', 'getMessages', {channelID: session.id}) + .then(function (messages) { + session.window.render(messages); + if (scrollBottom && messageVisible) { + session.window.thread.scroll_to(); + } + }); } }); }, @@ -386,7 +386,7 @@ var ChatWindowManager = AbstractService.extend({ }); this.displayState.hiddenSessions = hiddenSessions; this.displayState.hiddenUnreadCounter = hiddenUnreadCounter; - + if (this.displayState.$hidden_windows_dropdown) { this.displayState.$hidden_windows_dropdown.remove(); } @@ -397,7 +397,7 @@ var ChatWindowManager = AbstractService.extend({ .appendTo($('body')); this._repositionHiddenSessionsDropdown(); this.displayState.windowsDropdownIsOpen = false; - + $hiddenWindowsDropdown.on('click', '.o_chat_header', function (event) { var sessionID = $(event.currentTarget).data('session-id'); var session = _.findWhere(hiddenSessions, {id: sessionID}); @@ -527,6 +527,8 @@ var ChatWindowManager = AbstractService.extend({ }); +core.serviceRegistry.add('chat_window_manager', ChatWindowManager); + return ChatWindowManager; }); diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js index f2dfdc631ab..d874c7756e8 100644 --- a/addons/mail/static/src/js/systray.js +++ b/addons/mail/static/src/js/systray.js @@ -36,8 +36,7 @@ var MessagingMenu = Widget.extend({ var chatBus = this.call('chat_manager', 'getChatBus'); chatBus.on("update_needaction", this, this.update_counter); chatBus.on("update_channel_unread_counter", this, this.update_counter); - var chatReady = this.call('chat_manager', 'isReady'); - chatReady.then(this.update_counter.bind(this)); + this.call('chat_manager', 'isReady').then(this.update_counter.bind(this)); return this._super(); }, is_open: function () { @@ -58,8 +57,7 @@ var MessagingMenu = Widget.extend({ // Display spinner while waiting for channels preview this.$channels_preview.html(QWeb.render('Spinner')); - var chatReady = this.call('chat_manager', 'isReady'); - chatReady.then(function () { + this.call('chat_manager', 'isReady').then(function () { var allChannels = self.call('chat_manager', 'getChannels'); var channels = _.filter(allChannels, function (channel) { if (self.filter === 'chat') { @@ -70,23 +68,28 @@ var MessagingMenu = Widget.extend({ return channel.type !== 'static'; } }); - self.call('chat_manager', 'getMessages', {channelID: 'channel_inbox'}).then(function (result) { - var res = []; - _.each(result, function (message) { - message.unread_counter = 1; - var duplicatedMessage = _.findWhere(res, {model: message.model, 'res_id': message.res_id}); - if (message.model && message.res_id && duplicatedMessage) { - message.unread_counter = duplicatedMessage.unread_counter + 1; - res[_.findIndex(res, duplicatedMessage)] = message; - } else { - res.push(message); + self.call('chat_manager', 'getMessages', {channelID: 'channel_inbox'}) + .then(function (messages) { + var res = []; + _.each(messages, function (message) { + message.unread_counter = 1; + var duplicatedMessage = _.findWhere(res, { + model: message.model, + 'res_id': message.res_id + }); + if (message.model && message.res_id && duplicatedMessage) { + message.unread_counter = duplicatedMessage.unread_counter + 1; + res[_.findIndex(res, duplicatedMessage)] = message; + } else { + res.push(message); + } + }); + if (self.filter === 'channel_inbox' || !self.filter) { + channels = _.union(channels, res); } + self.call('chat_manager', 'getChannelsPreview', channels) + .then(self._render_channels_preview.bind(self)); }); - if (self.filter === 'channel_inbox' || !self.filter) { - channels = _.union(channels, res); - } - self.call('chat_manager', 'getChannelsPreview', channels).then(self._render_channels_preview.bind(self)); - }); }); }, _render_channels_preview: function (channels_preview) { @@ -142,7 +145,7 @@ var MessagingMenu = Widget.extend({ } else { var channel = this.call('chat_manager', 'getChannel', channelID); if (channel) { - self.call('chat_manager', 'openChannel', channel); + this.call('chat_manager', 'openChannel', channel); } } }, @@ -159,10 +162,8 @@ var ActivityMenu = Widget.extend({ }, start: function () { this.$activities_preview = this.$('.o_mail_navbar_dropdown_channels'); - var chatBus = this.call('chat_manager', 'getChatBus'); - chatBus.on("activity_updated", this, this._updateCounter); - var chatReady = this.call('chat_manager', 'isReady'); - chatReady.then(this._updateCounter.bind(this)); + this.call('chat_manager', 'getChatBus').on("activity_updated", this, this._updateCounter); + this.call('chat_manager', 'isReady').then(this._updateCounter.bind(this)); this._updateActivityPreview(); return this._super(); }, diff --git a/addons/mail/static/src/js/thread_field.js b/addons/mail/static/src/js/thread_field.js index 0a745b69a96..6d036208eb2 100644 --- a/addons/mail/static/src/js/thread_field.js +++ b/addons/mail/static/src/js/thread_field.js @@ -21,8 +21,7 @@ var ThreadField = AbstractField.extend({ this.msgIDs = this.value.res_ids; }, willStart: function () { - var chatReady = this.call('chat_manager', 'isReady'); - return this.alive(chatReady); + return this.alive(this.call('chat_manager', 'isReady')); }, start: function () { var self = this; @@ -78,7 +77,7 @@ var ThreadField = AbstractField.extend({ /** * @param {Object} message * @param {integer[]} message.partner_ids - * @return {Deferred} + * @return {$.Promise} */ postMessage: function (message) { var self = this; diff --git a/addons/mail/static/tests/chat_window_tests.js b/addons/mail/static/tests/chat_window_tests.js index a892d8e26e6..ab5a9f03538 100644 --- a/addons/mail/static/tests/chat_window_tests.js +++ b/addons/mail/static/tests/chat_window_tests.js @@ -11,7 +11,11 @@ var Widget = require('web.Widget'); var createBusService = mailTestUtils.createBusService; -QUnit.module('mail', {}, function () { +QUnit.module('mail', { + beforeEach: function () { + this.services = [ChatManager, createBusService()]; + }, +}, function () { QUnit.module('chat_window'); @@ -39,6 +43,7 @@ QUnit.module('mail', {}, function () { }]; var parent = createParent({ data: {}, + services: this.services, }); var chatWindow = new ChatWindow(parent, 1, "user", false, messages, {}); @@ -67,6 +72,7 @@ QUnit.module('mail', {}, function () { } var parent = createParent({ data: {}, + services: this.services, }); var $dom = $('#qunit-fixture'); @@ -93,8 +99,6 @@ QUnit.module('mail', {}, function () { QUnit.test('emoji popover should open correctly in chat windows', function (assert) { assert.expect(1); - var BusService = createBusService(); - function createParent(params) { var widget = new Widget(); @@ -107,7 +111,7 @@ QUnit.module('mail', {}, function () { } var parent = createParent({ data: {}, - services: [ChatManager, BusService], + services: this.services, }); var $dom = $('#qunit-fixture'); diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js index f3e4b1c571c..4ef63b70615 100644 --- a/addons/mail/static/tests/chatter_tests.js +++ b/addons/mail/static/tests/chatter_tests.js @@ -21,7 +21,7 @@ QUnit.module('mail', {}, function () { QUnit.module('Chatter', { beforeEach: function () { - this.BusService = createBusService(); + this.services = [ChatManager, createBusService()]; this.data = { partner: { fields: { @@ -98,6 +98,7 @@ QUnit.test('basic rendering', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '
' + '' + '' + @@ -121,7 +122,6 @@ QUnit.test('basic rendering', function (assert) { } return this._super(route, args); }, - services: [ChatManager, this.BusService], res_id: 2, }); @@ -150,6 +150,7 @@ QUnit.test('chatter in create mode', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -165,7 +166,6 @@ QUnit.test('chatter in create mode', function (assert) { } return this._super(route, args); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_chatter').length, 1, @@ -213,6 +213,7 @@ QUnit.test('chatter rendering inside the sheet', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -232,7 +233,6 @@ QUnit.test('chatter rendering inside the sheet', function (assert) { } return this._super(route, args); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_chatter').length, 1, @@ -241,7 +241,7 @@ QUnit.test('chatter rendering inside the sheet', function (assert) { form.$buttons.find('.o_form_button_create').click(); assert.ok(form.$el.find('.o_form_view').hasClass('o_form_editable'), "we should be in create mode"); - + assert.strictEqual(form.$('.o_chatter').length, 1, "chatter should be displayed"); @@ -396,6 +396,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) { View: FormView, model: 'partner', data: this.data, + services: [ChatManager, BusService], arch: '' + '' + '' + @@ -455,7 +456,6 @@ QUnit.test('chatter: post, receive and star messages', function (assert) { return this._super(route, args); }, session: {}, - services: [ChatManager, BusService], }); assert.ok(form.$('.o_chatter_topbar .o_chatter_button_log_note').length, @@ -546,6 +546,7 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert) View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -582,10 +583,9 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert) }); return $.when(42); } - + return this._super(route, args); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_thread_message').length, 0, "thread should not contain messages"); @@ -652,6 +652,7 @@ QUnit.test('chatter: Attachment viewer', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -675,7 +676,6 @@ QUnit.test('chatter: Attachment viewer', function (assert) { } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_thread_message .o_attachment').length, 4, "there should be three attachment on message"); @@ -720,6 +720,7 @@ QUnit.test('form activity widget: schedule next activity', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -746,7 +747,6 @@ QUnit.test('form activity widget: schedule next activity', function (assert) { } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], intercepts: { do_action: function (event) { assert.deepEqual(event.data.action, { @@ -784,6 +784,7 @@ QUnit.test('form activity widget: schedule activity does not discard changes', f View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -800,7 +801,6 @@ QUnit.test('form activity widget: schedule activity does not discard changes', f } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], intercepts: { do_action: function (event) { event.data.options.on_close(); @@ -852,6 +852,7 @@ QUnit.test('form activity widget: mark as done and remove', function (assert) { View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -903,7 +904,6 @@ QUnit.test('form activity widget: mark as done and remove', function (assert) { } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], }); assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 2, @@ -951,6 +951,7 @@ QUnit.test('followers widget: follow/unfollow, edit subtypes', function (assert) View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -1071,6 +1072,7 @@ QUnit.test('followers widget: do not display follower duplications', function (a View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '
' + @@ -1129,6 +1131,7 @@ QUnit.test('does not render and crash when destroyed before chat system is ready View: FormView, model: 'partner', data: this.data, + services: this.services, arch: '' + '' + '' + @@ -1159,7 +1162,6 @@ QUnit.test('does not render and crash when destroyed before chat system is ready } return this._super(route, args); }, - services: [ChatManager, this.BusService], intercepts: { get_session: function (event) { event.stopPropagation(); diff --git a/addons/mail/static/tests/discuss_mobile_tests.js b/addons/mail/static/tests/discuss_mobile_tests.js index 431a433c19f..4e96c87f512 100644 --- a/addons/mail/static/tests/discuss_mobile_tests.js +++ b/addons/mail/static/tests/discuss_mobile_tests.js @@ -11,7 +11,7 @@ QUnit.module('mail', {}, function () { QUnit.module('Discuss client action in mobile', { beforeEach: function () { - this.BusService = createBusService(); + this.services = [ChatManager, createBusService()]; this.data = { 'mail.message': { fields: {}, @@ -33,17 +33,17 @@ QUnit.test('mobile basic rendering', function (assert) { id: 1, context: {}, params: {}, + data: this.data, + services: this.services, mockRPC: function (route, args) { if (args.method === 'message_fetch') { return $.when([]); } return this._super.apply(this, arguments); }, - services: [ChatManager, this.BusService], }); - var chatReady = discuss.call('chat_manager', 'isReady'); - chatReady.then(function () { + discuss.call('chat_manager', 'isReady').then(function () { // test basic rendering in mobile assert.strictEqual(discuss.$('.o_mail_chat_mobile_control_panel').length, 1, "should have rendered a control panel"); diff --git a/addons/mail/static/tests/helpers/test_utils.js b/addons/mail/static/tests/helpers/test_utils.js index 3d107688537..e0a8a0a13de 100644 --- a/addons/mail/static/tests/helpers/test_utils.js +++ b/addons/mail/static/tests/helpers/test_utils.js @@ -17,43 +17,41 @@ var Widget = require('web.Widget'); /** * Create a mock bus_service, using 'bus' instead of bus.bus - * + * * @param {web.bus} bus * @return {AbstractService} the mock bus_service */ function createBusService(bus) { - var BusService = AbstractService.extend({ name: 'bus_service', /** * @override */ - init: function (parent) { + init: function () { this._super.apply(this, arguments); if (!bus) { bus = new Bus(); } this.bus = new _.extend(bus, { /** - * Do nothing + * Do nothing */ start_polling: function () {}, }); }, - + //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- - + /** * Get the bus */ getBus: function () { return this.bus; }, - }); - + return BusService; } diff --git a/addons/mail/static/tests/systray_tests.js b/addons/mail/static/tests/systray_tests.js index 2481c4d6550..4ac60c7e123 100644 --- a/addons/mail/static/tests/systray_tests.js +++ b/addons/mail/static/tests/systray_tests.js @@ -13,7 +13,7 @@ QUnit.module('mail', {}, function () { QUnit.module('ActivityMenu', { beforeEach: function () { - this.BusService = createBusService(); + this.services = [ChatManager, createBusService()]; this.data = { 'mail.activity.menu': { fields: { @@ -58,13 +58,13 @@ QUnit.test('activity menu widget: menu with no records', function (assert) { var activityMenu = new systray.ActivityMenu(); testUtils.addMockEnvironment(activityMenu, { + services: this.services, mockRPC: function (route, args) { if (args.method === 'activity_user_count') { return $.when([]); } return this._super(route, args); }, - services: [ChatManager, this.BusService] }); activityMenu.appendTo($('#qunit-fixture')); assert.ok(activityMenu.$('.o_no_activity').hasClass('o_no_activity'), "should not have instance of widget"); @@ -76,13 +76,13 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser var self = this; var activityMenu = new systray.ActivityMenu(); testUtils.addMockEnvironment(activityMenu, { + services: this.services, mockRPC: function (route, args) { if (args.method === 'activity_user_count') { return $.when(self.data['mail.activity.menu']['records']); } return this._super(route, args); }, - services: [ChatManager, this.BusService], }); activityMenu.appendTo($('#qunit-fixture')); assert.ok(activityMenu.$el.hasClass('o_mail_navbar_item'), 'should be the instance of widget'); diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js index 2dd69d7426c..b4b192c1ea9 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -245,6 +245,10 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, { * identified by strings. Each time a title part is changed, all parts * are gathered, ordered by alphabetical order and displayed in the title * of the window separated by ``-``. + * + * @private + * @param {string} part + * @param {string} title */ set_title_part: function (part, title) { var tmp = _.clone(this.get("title_part")); diff --git a/addons/web/static/src/js/chrome/root_widget.js b/addons/web/static/src/js/chrome/root_widget.js new file mode 100644 index 00000000000..9cf0515b134 --- /dev/null +++ b/addons/web/static/src/js/chrome/root_widget.js @@ -0,0 +1,7 @@ +odoo.define('root.widget', function (require) { +"use strict"; + +var webClient = require('web.web_client'); + +return webClient; +}); diff --git a/addons/web/static/src/js/core/abstract_service.js b/addons/web/static/src/js/core/abstract_service.js index cbd6c1322ca..14efef09b39 100644 --- a/addons/web/static/src/js/core/abstract_service.js +++ b/addons/web/static/src/js/core/abstract_service.js @@ -6,7 +6,6 @@ var Mixins = require('web.mixins'); var ServicesMixin = require('web.ServicesMixin'); var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { - Services: [], dependencies: [], name: null, init: function (parent) { @@ -15,13 +14,5 @@ var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { }, }); -var realExtend = AbstractService.extend; - -AbstractService.extend = function () { - var Service = realExtend.apply(this, arguments); - AbstractService.prototype.Services.push(Service); - return Service; -}; - return AbstractService; }); diff --git a/addons/web/static/src/js/core/registry.js b/addons/web/static/src/js/core/registry.js index d70c09e4ae0..f925b0ea30a 100644 --- a/addons/web/static/src/js/core/registry.js +++ b/addons/web/static/src/js/core/registry.js @@ -25,6 +25,7 @@ var Registry = Class.extend({ */ init: function (mapping) { this.map = Object.create(mapping || null); + this.listeners = []; // listening callbacks on newly added items. }, //-------------------------------------------------------------------------- @@ -34,12 +35,17 @@ var Registry = Class.extend({ /** * Add a key (and a value) to the registry. * + * Notify the listeners on newly added item in the registry. + * * @param {string} key * @param {any} value * @returns {Registry} can be used to chain add calls. */ add: function (key, value) { this.map[key] = value; + _.each(this.listeners, function (callback) { + callback(key, value); + }); return this; }, /** @@ -89,6 +95,14 @@ var Registry = Class.extend({ } return null; }, + /** + * Register a callback to execute when items are added to the registry. + * + * @param {function} callback function with parameters (key, value). + */ + onAdd: function (callback) { + this.listeners.push(callback); + }, }); return Registry; diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index d5abf727704..4ab0a361988 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -1,58 +1,86 @@ odoo.define('web.ServiceProviderMixin', function (require) { "use strict"; -var AbstractService = require('web.AbstractService'); -var utils = require('web.utils'); +var core = require('web.core'); var ServiceProviderMixin = { - services: {}, - init: function () { + services: {}, // dict containing deployed service instances + UndeployedServices: {}, // dict containing classes of undeployed services + /** + * @override + */ + init: function (parent) { + var self = this; // to properly instantiate services with this as parent, this mixin // assumes that it is used along the EventDispatcherMixin, and that // EventDispatchedMixin's init is called first - var self = this; - // as EventDispatcherMixin's init is already called, this handler has to - // be bound manually + // as EventDispatcherMixin's init is already called, this handler has + // to be bound manually this.on('call_service', this, this._call_service.bind(this)); - var sortedServices = this._sortServices(AbstractService.prototype.Services); - _.each(sortedServices, function (Service) { - var service = new Service(self); - self.services[service.name] = service; + // add already registered services from the service registry + _.each(core.serviceRegistry.map, function (Service, serviceName) { + self.UndeployedServices[serviceName] = Service; + }); + this._deployServices(); + + // listen on newly added services + core.serviceRegistry.onAdd(function (serviceName, Service) { + self.UndeployedServices[serviceName] = Service; + self._deployServices(); }); }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _deployServices: function () { + var self = this; + var done = false; + while (!done) { + var Service = _.find(this.UndeployedServices, function (Service) { + // no missing dependency + return !_.some(Service.prototype.dependencies, function (depName) { + return !self.services[depName]; + }); + }); + if (Service) { + this.services[Service.prototype.name] = new Service(this); + delete this.UndeployedServices[Service.prototype.name]; + } else { + done = true; + } + } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Call the 'service', using data from the 'event' that + * has triggered the service call. + * + * For the ajax service, the arguments are extended with + * the target so that it can call back the caller. + * + * @private + * @param {OdooEvent} event + */ _call_service: function (event) { - var service = this.services[event.data.service]; var args = event.data.args || []; - // ajax service uses an extra 'target' argument for rpc if (event.data.service === 'ajax' && event.data.method === 'rpc') { + // ajax service uses an extra 'target' argument for rpc args = args.concat(event.target); } + var service = this.services[event.data.service]; var result = service[event.data.method].apply(service, args); event.data.callback(result); }, - _sortServices: function (services) { - var nodes = {}; - // Create nodes (services) - _.each(services, function (Service) { - nodes[Service.prototype.name] = Service.prototype.dependencies; - }); - var sorted; - try { - sorted = utils.topologicalSort(nodes); - } catch (err) { - console.warn('topologicalSort Error:', err.message); - sorted = nodes; - } - // Sort services based on sorted - // Note: we convert sorted to an object key=>index for efficiency - var sortedObj = _.invert(_.object(_.pairs(sorted))); - sorted = _.sortBy(services, function (Service) { - return sortedObj[Service.prototype.name]; - }); - - return sorted; - }, }; return ServiceProviderMixin; @@ -69,6 +97,11 @@ var rpc = require('web.rpc'); * @name ServicesMixin */ var ServicesMixin = { + /** + * @param {string} service + * @param {string} method + * @return {any} result of the service called + */ call: function (service, method) { var args = Array.prototype.slice.call(arguments, 2); var result; diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js index 0b4af722203..1bdd9e70a69 100644 --- a/addons/web/static/src/js/core/utils.js +++ b/addons/web/static/src/js/core/utils.js @@ -300,37 +300,6 @@ var utils = { } return fn(mod, Math.floor(x)); }, - /** - * Topological sort of nodes - * - * @param {Object} nodes where key is name of node, and value is list of - * names of dependent nodes. - * @param {string[]} nodes[name] list of named dependencies of node having - * 'name' as its name. - * @return {string[]} topological sort of nodes by their names. - * @throws {Error} if there is at least one circular - * dependency between nodes - */ - topologicalSort: function (nodes) { - var sorted = []; - var unresolved = nodes; - while (!_.isEmpty(unresolved)) { - // Get all nodes that have no dependency - var resolvable = _.pick(unresolved, function (dependencies, name) { - return _.isEmpty(_.difference(dependencies, sorted)); - }); - if (_.isEmpty(resolvable)) { - throw new Error("Circular dependency detected"); - } - // Append them to sorted - sorted = _.union(sorted, _.keys(resolvable)); - // Remove these nodes from unresolved - _.each(resolvable, function (dependencies, name) { - unresolved = _.omit(unresolved, name); - }); - } - return sorted; - }, /** * performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision * See the corresponding float_round() in server/tools/float_utils.py for more info diff --git a/addons/web/static/src/js/services/ajax_service.js b/addons/web/static/src/js/services/ajax_service.js index 480549347d4..3446f651a59 100644 --- a/addons/web/static/src/js/services/ajax_service.js +++ b/addons/web/static/src/js/services/ajax_service.js @@ -2,6 +2,7 @@ odoo.define('web.AjaxService', function (require) { "use strict"; var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var session = require('web.session'); var AjaxService = AbstractService.extend({ @@ -21,6 +22,8 @@ var AjaxService = AbstractService.extend({ }, }); +core.serviceRegistry.add('ajax', AjaxService); + return AjaxService; }); diff --git a/addons/web/static/src/js/services/core.js b/addons/web/static/src/js/services/core.js index 73071a2a65d..522d6b07302 100644 --- a/addons/web/static/src/js/services/core.js +++ b/addons/web/static/src/js/services/core.js @@ -41,6 +41,7 @@ return { crash_registry: new Registry(), search_filters_registry: new Registry(), search_widgets_registry: new Registry(), + serviceRegistry: new Registry(), /** * @type {String} */ diff --git a/addons/web/static/src/js/services/local_storage_service.js b/addons/web/static/src/js/services/local_storage_service.js index a4527026238..7a8c803a0f9 100644 --- a/addons/web/static/src/js/services/local_storage_service.js +++ b/addons/web/static/src/js/services/local_storage_service.js @@ -2,6 +2,7 @@ odoo.define('web.LocalStorageService', function (require) { 'use strict'; var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var localStorage = require('web.local_storage'); var LocalStorageService = AbstractService.extend({ @@ -20,6 +21,8 @@ var LocalStorageService = AbstractService.extend({ } }); +core.serviceRegistry.add('local_storage', LocalStorageService); + return LocalStorageService; }); diff --git a/addons/web/static/src/js/services/report_service.js b/addons/web/static/src/js/services/report_service.js index a2bb8759a24..cfd62c0499f 100644 --- a/addons/web/static/src/js/services/report_service.js +++ b/addons/web/static/src/js/services/report_service.js @@ -6,6 +6,7 @@ odoo.define('web.ReportService', function (require) { */ var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var ReportService = AbstractService.extend({ name: 'report', @@ -28,6 +29,8 @@ var ReportService = AbstractService.extend({ }, }); +core.serviceRegistry.add('report', ReportService); + return ReportService; }); diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index 1ee50daaf65..6c580e6b9dc 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -1333,6 +1333,7 @@ QUnit.module('ActionManager', { actions: this.actions, archs: this.archs, data: this.data, + services: [ReportService], mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { @@ -1340,7 +1341,6 @@ QUnit.module('ActionManager', { } return this._super.apply(this, arguments); }, - services: [ReportService], session: { get_file: function (params) { assert.step(params.url); @@ -1372,6 +1372,7 @@ QUnit.module('ActionManager', { actions: this.actions, archs: this.archs, data: this.data, + services: [ReportService], mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { @@ -1379,7 +1380,6 @@ QUnit.module('ActionManager', { } return this._super.apply(this, arguments); }, - services: [ReportService], session: { get_file: function (params) { assert.step(params.url); @@ -1425,6 +1425,7 @@ QUnit.module('ActionManager', { actions: this.actions, archs: this.archs, data: this.data, + services: [ReportService], mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { @@ -1435,7 +1436,6 @@ QUnit.module('ActionManager', { } return this._super.apply(this, arguments); }, - services: [ReportService], session: { get_file: function (params) { assert.step(params.url); // should not be called diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 0a1fd9128e6..ecedd4c5bc5 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -6271,7 +6271,7 @@ QUnit.module('relational_fields', { } if (args.method === 'write') { assert.deepEqual(args.args[1].timmy, [[6, false, [12]]], - 'should properly write ids') + 'should properly write ids'); } return this._super.apply(this, arguments); }, @@ -9354,7 +9354,7 @@ QUnit.module('relational_fields', { $input = form.$('.o_field_many2manytags span[data-id=13]'); // selects 'red' tag $input.click(); // opens the colorpicker dropdown assert.ok(form.$('.o_field_many2manytags span[data-id=13] .o_colorpicker .o_checkbox'), "should have a checkbox in the colorpicker dropdown menu"); - + $input.click(); var $checkBox = form.$('.o_field_many2manytags span[data-id=13] .o_colorpicker .o_checkbox input'); assert.notOk($checkBox.is(':checked'), "should have unticked checkbox in colorpicker dropdown menu"); diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index 6b2c09d2631..7070716ed27 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -19,7 +19,6 @@ var core = require('web.core'); var dom = require('web.dom'); var session = require('web.session'); var MockServer = require('web.MockServer'); -var utils = require('web.utils'); var Widget = require('web.Widget'); var DebouncedField = basic_fields.DebouncedField; @@ -405,7 +404,7 @@ function addMockEnvironment(widget, params) { // Dispatch service calls // Note: some services could call other services at init, // Which is why we have to init services after that - var services = {}; + var services = {ajax: null}; // mocked ajax service already loaded intercept(widget, 'call_service', function (ev) { var args, result; if (ev.data.service === 'ajax') { @@ -420,36 +419,22 @@ function addMockEnvironment(widget, params) { } ev.data.callback(result); }); - // Instantiate services - // Note: ensure topological sort of services based on their dependencies - var sortServices = function (services) { - // Create nodes (services), with ajax already loaded - var nodes = { ajax: [] }; - _.each(services, function (Service) { - nodes[Service.prototype.name] = Service.prototype.dependencies; + + // Deploy services + var done = false; + while (!done) { + var index = _.findIndex(params.services, function (Service) { + return !_.some(Service.prototype.dependencies, function (depName) { + return !_.has(services, depName); + }); }); - var sorted; - try { - sorted = utils.topologicalSort(nodes); - } catch (err) { - console.warn('topologicalSort Error:', err.message); - sorted = nodes; + if (index !== -1) { + var Service = params.services.splice(index, 1)[0]; + services[Service.prototype.name] = new Service(widget); + } else { + done = true; } - // Remove ajax from sorted - sorted = _.without(sorted, 'ajax'); - // Sort services based on sorted - // Note: we convert sorted to an object key=>index for efficiency - var sortedObj = _.invert(_.object(_.pairs(sorted))); - sorted = _.sortBy(services, function (Service) { - return sortedObj[Service.prototype.name]; - }); - return sorted; - }; - var sortedServices = sortServices(params.services); - _.each(sortedServices, function (Service) { - var service = new Service(widget); - services[service.name] = service; - }); + } intercept(widget, 'load_action', function (event) { mockServer.performRpc('/web/action/load', { diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js index 08ddd2c91c2..014435194f3 100644 --- a/addons/web/static/tests/views/view_dialogs_tests.js +++ b/addons/web/static/tests/views/view_dialogs_tests.js @@ -21,7 +21,6 @@ QUnit.module('Views', { ], }, }; - }, }, function () { diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 2742871c790..f54755e1266 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -155,6 +155,7 @@ + diff --git a/addons/web_editor/static/src/js/iframe.js b/addons/web_editor/static/src/js/iframe.js index 949a141c333..f2c5e3be661 100644 --- a/addons/web_editor/static/src/js/iframe.js +++ b/addons/web_editor/static/src/js/iframe.js @@ -82,7 +82,7 @@ translator.Class.include({ //============================================================================== -odoo.define('web_editor.IframeRoot.instance', function (require) { +odoo.define('root.widget', function (require) { 'use strict'; require('web.dom_ready'); diff --git a/addons/web_tour/static/src/js/tour_service.js b/addons/web_tour/static/src/js/tour_service.js index fdfac78f6a9..d1d46625560 100644 --- a/addons/web_tour/static/src/js/tour_service.js +++ b/addons/web_tour/static/src/js/tour_service.js @@ -1,13 +1,11 @@ -odoo.define('web_tour.tour', function(require) { +odoo.define('web_tour.tour', function (require) { "use strict"; var ajax = require('web.ajax'); -var Class = require('web.Class'); var config = require('web.config'); var core = require('web.core'); -var mixins = require('web.mixins'); +var rootWidget = require('root.widget'); var rpc = require('web.rpc'); -var ServiceProviderMixin = require('web.ServiceProviderMixin'); var session = require('web.session'); var TourManager = require('web_tour.TourManager'); @@ -17,13 +15,6 @@ if (config.device.isMobile) { return $.Deferred().reject(); } -var CallService = Class.extend(mixins.EventDispatcherMixin, ServiceProviderMixin, { - init: function () { - mixins.EventDispatcherMixin.init.call(this); - ServiceProviderMixin.init.call(this); - }, -}); - /** * @namespace * @property {Object} active_tooltips @@ -48,7 +39,7 @@ return session.is_bound.then(function () { } return $.when.apply($, defs).then(function (consumed_tours) { consumed_tours = session.is_frontend ? consumed_tours : session.web_tours; - var tour_manager = new TourManager(new CallService(), consumed_tours); + var tour_manager = new TourManager(rootWidget, consumed_tours); // Use a MutationObserver to detect DOM changes var untracked_classnames = ["o_tooltip", "o_tooltip_content", "o_tooltip_overlay"]; @@ -83,8 +74,8 @@ return session.is_bound.then(function () { * Once the DOM is ready, we still have to wait all the modules are loaded before completing the tours * registration and starting listening for DOM mutations. */ - $.when(load_def).then(function () { - _.defer(function () { + $.when(load_def).then(function () { + _.defer(function () { tour_manager._register_all(observe); if (observe) { observer.observe(document.body, { diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js index fbe839365ae..336ea822902 100644 --- a/addons/website/static/src/js/content/website_root.js +++ b/addons/website/static/src/js/content/website_root.js @@ -1,4 +1,4 @@ -odoo.define('website.WebsiteRoot.instance', function (require) { +odoo.define('root.widget', function (require) { 'use strict'; require('web.dom_ready'); diff --git a/addons/website_slides/static/src/js/slides.js b/addons/website_slides/static/src/js/slides.js index 279c256c5ac..7d945e9eb28 100644 --- a/addons/website_slides/static/src/js/slides.js +++ b/addons/website_slides/static/src/js/slides.js @@ -7,7 +7,7 @@ var core = require('web.core'); var time = require('web.time'); var Widget = require('web.Widget'); var local_storage = require('web.local_storage'); -var websiteRootInstance = require('website.WebsiteRoot.instance'); +require('root.widget'); var _t = core._t; var page_widgets = {};