QUnit.test('thread window\'s input can still be focused when the UI is blocked', function (assert) { assert.expect(2); var parent = this.createParent({ data: this.data, services: this.services, }); var $dom = $('#qunit-fixture'); // get channel instance to link to thread window var channel = parent.call('mail_service', 'getChannel', 1); channel.detach(); var $input = $('<input/>', {type: 'text'}).appendTo($dom); $input.focus().click(); assert.strictEqual(document.activeElement, $input[0], "fake input should be focused"); framework.blockUI(); // cannot force focus here otherwise the test // makes no sense, this test is just about // making sure that the code which forces the // focus on click is not removed $('.o_thread_window .o_composer_text_field').click(); assert.strictEqual(document.activeElement, $('.o_thread_window .o_composer_text_field')[0], "thread window's input should now be focused"); framework.unblockUI(); parent.destroy(); });
return $.when(view_controller.do_show(view_options)).done(function () { // Prepare the ControlPanel content and update it var cp_status = { active_view_selector: '.o_cp_switch_' + self.active_view.type, breadcrumbs: self.action_manager && self.action_manager.get_breadcrumbs(), cp_content: _.extend({}, self.searchview_elements, view_control_elements), hidden: self.flags.headless, searchview: self.searchview, search_view_hidden: view_controller.searchable === false || view_controller.searchview_hidden, }; self.update_control_panel(cp_status); // Detach the old view and store it if (old_view && old_view !== self.active_view) { // Store the scroll position if (self.action_manager && self.action_manager.webclient) { old_view.controller.set_scrollTop(self.action_manager.webclient.get_scrollTop()); } // Do not detach ui-autocomplete elements to let jquery-ui garbage-collect them var $to_detach = self.$el.contents().not('.ui-autocomplete'); old_view.$fragment = framework.detach([{widget: old_view.controller}], {$to_detach: $to_detach}); } // If the user switches from a multi-record to a mono-record view, // the action manager should be scrolled to the top. if (old_view && old_view.controller.multi_record === true && view_controller.multi_record === false) { view_controller.set_scrollTop(0); } // Append the view fragment to self.$el framework.append(self.$el, view_fragment, { in_DOM: self.is_in_DOM, callbacks: [{widget: view_controller}], }); });
(wkhtmltopdf_state = wkhtmltopdf_state || session.rpc('/report/check_wkhtmltopdf')).then(function (presence) { // Fallback on html if wkhtmltopdf is not installed or if eCore is started with one worker if (presence === 'install') { self.do_notify(_t('Report'), _t('Unable to find Wkhtmltopdf on this \ system. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\ wkhtmltopdf.org</a>'), true); report_url = report_url.substring(12); window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024'); framework.unblockUI(); return; } else if (presence === 'workers') { self.do_notify(_t('Report'), _t('You need to start eCore with at least two \ workers to print a pdf version of the reports.'), true); report_url = report_url.substring(12); window.open('/report/html/' + report_url, '_blank', 'height=768,width=1024'); framework.unblockUI(); return; } else if (presence === 'upgrade') { self.do_notify(_t('Report'), _t('You should upgrade your version of\ Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\ support for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \ target="_blank">wkhtmltopdf.org</a>'), true); } return trigger_download(self.session, response, c, action, options); });
this.clear_uncommitted_changes().then(function() { // Save the current scroll position of the action_manager self.action_manager.set_scrollTop(self.get_scrollTop()); // Detach the web_client contents and its navbar var $to_detach = self.$el.contents() .not('.o_loading') .not('.o_chat_window') .not('.ui-autocomplete'); self.$web_client_content = framework.detach([{widget: self.action_manager}], {$to_detach: $to_detach}); framework.detach([{widget: self.menu}]); // Attach the app_switcher and its navbar framework.prepend(self.$el, [self.app_switcher_navbar.$el, self.app_switcher.$el], { in_DOM: true, callbacks: [{ widget: self.app_switcher_navbar, callback_args: { display_back_button: (self.action_manager.get_inner_action() !== null), } }], }); // Save and clear the url self.url = $.bbq.getState(); self._ignore_hashchange = true; $.bbq.removeState(); });
QUnit.test('chat window\'s input can still be focused when the UI is blocked', function (assert) { assert.expect(2); function createParent(params) { var widget = new Widget(); testUtils.addMockEnvironment(widget, params); return widget; } var parent = createParent({ data: {}, services: this.services, }); var $dom = $('#qunit-fixture'); var chatWindow = new ChatWindow(parent, 1, "user", false, [], {}); chatWindow.appendTo($dom); var $input = $('<input/>', {type: 'text'}).appendTo($dom); $input.focus().click(); assert.strictEqual(document.activeElement, $input[0], "fake input should be focused"); framework.blockUI(); chatWindow.$input.click(); // cannot force focus here otherwise the test // makes no sense, this test is just about // making sure that the code which forces the // focus on click is not removed assert.strictEqual(document.activeElement, chatWindow.$input[0], "chat window's input should now be focused"); framework.unblockUI(); parent.destroy(); });
_downloadTable: function () { var table = this.model.exportData(); if(table.measure_row.length + 1 > 256) { crash_manager.show_message(_t("For Excel compatibility, data cannot be exported if there are more than 256 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures.")); framework.unblockUI(); return; } framework.blockUI(); table.title = this.title; session.get_file({ url: '/web/pivot/export_xls', data: {data: JSON.stringify(table)}, complete: framework.unblockUI, error: crash_manager.rpc_error.bind(crash_manager) }); },
on_click_export_data: function() { var self = this; var exported_fields = this.$el.find('#fields_list option').map(function () { // DOM property is textContent, but IE8 only knows innerText return {name: self.records[this.value] || this.value, label: this.textContent || this.innerText}; }).get(); if (_.isEmpty(exported_fields)) { alert(_t("Please select fields to export...")); return; } exported_fields.unshift({name: 'id', label: 'External ID'}); var export_format = this.$el.find("#export_format").val(); framework.blockUI(); this.session.get_file({ url: '/web/export/' + export_format, data: {data: JSON.stringify({ model: this.dataset.model, fields: exported_fields, ids: this.ids_to_export, domain: this.domain, context: pyeval.eval('contexts', [this.dataset._model.context()]), import_compat: !!this.$el.find("#import_compat").val(), })}, complete: framework.unblockUI, error: crash_manager.rpc_error.bind(crash_manager), }); },
start: function() { if (!this.session.session_is_valid()) { framework.redirect('/web/login?redir=' + encodeURIComponent(window.location)); } else { this.show_import(); } },
return $.when(view_controller.do_show(view_options)).done(function () { // Prepare the ControlPanel content and update it var cp_status = { active_view_selector: '.oe-cp-switch-' + self.active_view.type, breadcrumbs: self.action_manager && self.action_manager.get_breadcrumbs(), cp_content: _.extend({}, self.control_elements, view_control_elements), hidden: self.flags.headless, searchview: self.searchview, search_view_hidden: view_controller.searchable === false || view_controller.searchview_hidden, }; self.update_control_panel(cp_status); if (old_view) { // Detach the old view but not ui-autocomplete elements to let // jquery-ui garbage-collect them old_view.$container.contents().not('.ui-autocomplete').detach(); // Hide old view (at first rendering, there is no view to hide) if (self.active_view !== old_view) { if (old_view.controller) old_view.controller.do_hide(); if (old_view.$container) old_view.$container.hide(); } } // Append the view fragment to its $container framework.append(self.active_view.$container, view_fragment, self.is_in_DOM); });
_downloadReport: function (url) { var self = this; framework.blockUI(); return new Promise(function (resolve, reject) { var type = 'qweb-' + url.split('/')[2]; var blocked = !session.get_file({ url: '/report/download', data: { data: JSON.stringify([url, type]), }, success: resolve, error: function () { crash_manager.rpc_error.apply(crash_manager, arguments); reject(); }, complete: framework.unblockUI, }); if (blocked) { // AAB: this check should be done in get_file service directly, // should not be the concern of the caller (and that way, get_file // could return a promise) var message = _t('A popup window with your report was blocked. You ' + 'may need to change your browser settings to allow ' + 'popup windows for this page.'); self.do_warn(_t('Warning'), message, true); } }); },
this.rpc("/web/database/create", {'fields': fields}).done(function(result) { if (result) { framework.redirect('/web'); } else { alert("Failed to create database"); } });
export_data: function() { var self = this; var exported_fields = this.$('.o_fields_list option').map(function () { return { name: (self.records[this.value] || this).value, label: this.textContent || this.innerText // DOM property is textContent, but IE8 only knows innerText }; }).get(); if (_.isEmpty(exported_fields)) { Dialog.alert(this, _t("Please select fields to export...")); return; } exported_fields.unshift({name: 'id', label: 'External ID'}); var export_format = this.$export_format_inputs.filter(':checked').val(); framework.blockUI(); this.getSession().get_file({ url: '/web/export/' + export_format, data: {data: JSON.stringify({ model: this.record.model, fields: exported_fields, ids: this.ids_to_export, domain: this.domain, context: pyeval.eval('contexts', [this.record.getContext()]), import_compat: !!this.$import_compat_radios.filter(':checked').val(), })}, complete: framework.unblockUI, error: crash_manager.rpc_error.bind(crash_manager), }); },
on_rpc_event : function(increment) { var self = this; if (!this.count && increment === 1) { // Block UI after 3s this.long_running_timer = setTimeout(function () { self.blocked_ui = true; framework.blockUI(); }, 3000); } this.count += increment; if (this.count > 0) { if (session.debug) { this.$el.text(_.str.sprintf( _t("Loading (%d)"), this.count)); } else { this.$el.text(_t("Loading")); } this.$el.show(); this.getParent().$el.addClass('oe_wait'); } else { this.count = 0; clearTimeout(this.long_running_timer); // Don't unblock if blocked by somebody else if (self.blocked_ui) { this.blocked_ui = false; framework.unblockUI(); } this.$el.fadeOut(); this.getParent().$el.removeClass('oe_wait'); } }
scrollTo: function (ev) { var offset = {top: ev.data.offset, left: ev.data.offset_left || 0}; var xs_device = config.device.size_class <= config.device.SIZES.XS; if (!offset.top) { offset = framework.getPosition(document.querySelector(ev.data.selector)); if (!xs_device) { // Substract the position of the action_manager as it is the scrolling part offset.top -= framework.getPosition(this.action_manager.el).top; } } if (xs_device) { this.el.scrollTop = offset.top; } else { this.action_manager.el.scrollTop = offset.top; } this.action_manager.el.scrollLeft = offset.left; },
.then(function(result) { if (self.messages_separator_position === 'top') { self.messages_separator_position = undefined; // reset value to re-compute separator position } self.thread.render(result, self.get_thread_rendering_options(result)); offset += framework.getPosition(document.querySelector(oldest_msg_selector)).top; self.thread.scroll_to({offset: offset}); });
ir_actions_act_url: function (action) { if (action.target === 'self') { framework.redirect(action.url); } else { window.open(action.url, '_blank'); } return $.when(); },
on_card_clicked: function(ev) { if (this.$el.find('.oe_kanban_global_click').size() > 0 && this.$el.find('.oe_kanban_global_click').data('routing')) { framework.redirect(this.$el.find('.oe_kanban_global_click').data('routing') + "/" + this.id); } else if (this.$el.find('.oe_kanban_global_click_edit').size()>0) this.do_action_edit(); else this.do_action_open(); },
ir_actions_act_url: function (action) { if (action.target === 'self') { framework.redirect(action.url); return $.Deferred(); // The action is finished only when the redirection is done } else { window.open(action.url, '_blank'); } return $.when(); },
on_card_clicked: function() { if (this.$el.hasClass('oe_kanban_global_click_edit') && this.$el.data('routing')) { framework.redirect(this.$el.data('routing') + "/" + this.id); } else if (this.$el.hasClass('oe_kanban_global_click_edit')) { this.trigger_up('kanban_record_edit', {id: this.id}); } else { this.trigger_up('kanban_record_open', {id: this.id}); } },
message_load_history: function() { var self = this; var oldest_msg_selector = '.o_mail_thread_message[data-message-id="' + this.get('messages')[0].id + '"]'; var offset = -framework.getPosition(document.querySelector(oldest_msg_selector)).top; mail_thread.MailThreadMixin.message_load_history.call(this).then(function() { offset += framework.getPosition(document.querySelector(oldest_msg_selector)).top; self.$messages.scrollTop(offset); }); },
return $.when(action.restore(index)).done(function() { if (action !== old_action) { // Clear the action stack (this also removes the current action from the DOM) self.clear_action_stack(to_destroy); // Append the fragment of the action to restore to self.$el framework.append(self.$el, action.get_fragment(), true); self.inner_action.set_is_in_DOM(true); } });
load_more_messages: function () { var self = this; var top_msg_id = this.$('.o_thread_message').first().data('messageId'); var top_msg_selector = '.o_thread_message[data-message-id="' + top_msg_id + '"]'; var offset = -framework.getPosition(document.querySelector(top_msg_selector)).top; this.fetch_and_render_thread(this.msg_ids, {force_fetch: true}).then(function(){ offset += framework.getPosition(document.querySelector(top_msg_selector)).top; self.thread.scroll_to({offset: offset}); }); },
on_attachment_changed: function(e) { var $e = $(e.target); if ($e.val() !== '') { this.$('form.o_form_binary_form').submit(); $e.parent().find('input[type=file]').prop('disabled', true); $e.parent().find('button').prop('disabled', true).find('img, span').toggle(); this.$('.o_sidebar_add_attachment a').text(_t('Uploading...')); framework.blockUI(); } },
ir_actions_act_url: function (action) { var url = action.url; if (session.debug && url && url.length && url[0] === '/') { url = $.param.querystring(url, 'debug'); } if (action.target === 'self') { framework.redirect(url); } else { window.open(url, '_blank'); } return $.when(); },
return $.when(action.restore(index)).done(function() { // Attach the DOM of the action and restore the scroll position only if necessary if (action !== old_action) { // Clear the action stack (this also removes the current action from the DOM) self.clear_action_stack(to_destroy); // Append the fragment of the action to restore to self.$el framework.append(self.$el, action.get_fragment(), { in_DOM: self.is_in_DOM, callbacks: [{widget: action.widget}], }); } });
ir_actions_report_xml: function(action, options) { var self = this; framework.blockUI(); action = _.clone(action); var eval_contexts = ([session.user_context] || []).concat([action.context]); action.context = pyeval.eval('contexts',eval_contexts); // iOS devices doesn't allow iframe use the way we do it, // opening a new window seems the best way to workaround if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) { var params = { action: JSON.stringify(action), token: new Date().getTime() }; var url = self.session.url('/web/report', params); framework.unblockUI(); $('<a href="'+url+'" target="_blank"></a>')[0].click(); return; } var c = crash_manager; return $.Deferred(function (d) { self.session.get_file({ url: '/web/report', data: {action: JSON.stringify(action)}, complete: framework.unblockUI, success: function(){ if (!self.dialog) { options.on_close(); } self.dialog_stop(); d.resolve(); }, error: function () { c.rpc_error.apply(c, arguments); d.reject(); } }); }); },
P.call('get_param', ['database.uuid']).then(function(dbuuid) { var state = { 'd': session.db, 'u': window.location.protocol + '//' + window.location.host, }; var params = { response_type: 'token', client_id: dbuuid || '', state: JSON.stringify(state), scope: 'userinfo', }; framework.redirect('https://accounts.odoo.com/oauth2/auth?'+$.param(params)); }).fail(function(result, ev){
ir_actions_act_url: function (action) { var url = action.url; if (session.debug && url && url.length && url[0] === '/') { url = $.param.querystring(url, {debug: session.debug}); } if (action.target === 'self') { framework.redirect(url); return $.Deferred(); // The action is finished only when the redirection is done } else { window.open(url, '_blank'); } return $.when(); },
load_more_messages: function () { var self = this; var oldest_msg_id = this.$('.o_thread_message').first().data('messageId'); var oldest_msg_selector = '.o_thread_message[data-message-id="' + oldest_msg_id + '"]'; var offset = -framework.getPosition(document.querySelector(oldest_msg_selector)).top; return chat_manager .get_messages({channel_id: this.channel.id, domain: this.domain, load_more: true}) .then(function(result) { if (self.messages_separator_position === 'top') { self.messages_separator_position = undefined; // reset value to re-compute separator position } self.thread.render(result, self.get_thread_rendering_options(result)); offset += framework.getPosition(document.querySelector(oldest_msg_selector)).top; self.thread.scroll_to({offset: offset}); }); },
_executeURLAction: function (action, options) { var url = action.url; if (config.debug && url && url.length && url[0] === '/') { url = $.param.querystring(url, {debug: config.debug}); } if (action.target === 'self') { framework.redirect(url); return $.Deferred(); } else { window.open(url, '_blank'); } options.on_close(); return $.when(); },