[MERGE] mail,web,*: JS mail and services tracebacks
* account,bus, web_editor, website, website_slides, web_tour This merge fixes most issues where the webclient crashed with discuss installed in mobile. These issues occur due to the fact that the chat_manager, now a service, was loaded after the webclient is instantiated. The main idea of the fix is to let the webclient register services that are loaded after its instantiation. Despite appearances, the desktop mode had also this issue, but most of the bugs were hidden thanks to the availability of tours that are loaded after the module mail.
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -63,13 +63,14 @@ var PartnerInviteDialog = Dialog.extend({
|
||||
return $('<span>').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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Object>} 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;
|
||||
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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: '<form>' +
|
||||
'<sheet></sheet>' +
|
||||
'<div class="oe_chatter">' +
|
||||
@@ -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: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="foo"/>' +
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
odoo.define('root.widget', function (require) {
|
||||
"use strict";
|
||||
|
||||
var webClient = require('web.web_client');
|
||||
|
||||
return webClient;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ return {
|
||||
crash_registry: new Registry(),
|
||||
search_filters_registry: new Registry(),
|
||||
search_widgets_registry: new Registry(),
|
||||
serviceRegistry: new Registry(),
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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', {
|
||||
|
||||
@@ -21,7 +21,6 @@ QUnit.module('Views', {
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
},
|
||||
}, function () {
|
||||
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/chrome/control_panel.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/chrome/abstract_web_client.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/chrome/web_client.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/chrome/root_widget.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/_deprecated/data.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/context.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/misc.js"></script>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
odoo.define('website.WebsiteRoot.instance', function (require) {
|
||||
odoo.define('root.widget', function (require) {
|
||||
'use strict';
|
||||
|
||||
require('web.dom_ready');
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
Reference in New Issue
Block a user