diff options
Diffstat (limited to 'addons/web/static/src/js')
| -rw-r--r-- | addons/web/static/src/js/report/client_action.js | 200 | ||||
| -rw-r--r-- | addons/web/static/src/js/report/qwebactionmanager.js | 140 | ||||
| -rw-r--r-- | addons/web/static/src/js/report/report.editor.js | 109 | ||||
| -rw-r--r-- | addons/web/static/src/js/report/subst.js | 14 | ||||
| -rw-r--r-- | addons/web/static/src/js/report/utils.js | 26 |
5 files changed, 489 insertions, 0 deletions
diff --git a/addons/web/static/src/js/report/client_action.js b/addons/web/static/src/js/report/client_action.js new file mode 100644 index 00000000000..60265c0a49d --- /dev/null +++ b/addons/web/static/src/js/report/client_action.js @@ -0,0 +1,200 @@ +odoo.define('report.client_action', function (require) { +'use strict'; + +var core = require('web.core'); +var ControlPanelMixin = require('web.ControlPanelMixin'); +var session = require('web.session'); +var Widget = require('web.Widget'); +var utils = require('report.utils'); + +var QWeb = core.qweb; + + +var AUTHORIZED_MESSAGES = [ + 'report.editor:save_ok', + 'report.editor:discard_ok', + 'report:do_action', +]; + +var ReportAction = Widget.extend(ControlPanelMixin, { + + template: 'report.client_action', + + init: function (parent, action, options) { + this._super.apply(this, arguments); + + options = options || {}; + + this.action_manager = parent; + this.title = options.display_name || options.name; + + this.edit_mode_available = false; + this.in_edit_mode = false; + this.report_url = options.report_url; + + // Extra info that will be useful to build a qweb-pdf action. + this.report_name = options.report_name; + this.report_file = options.report_file; + this.data = options.data || {}; + this.context = options.context || {}; + }, + + start: function () { + var self = this; + this.set('title', this.title); + this.iframe = this.$('iframe')[0]; + return $.when(this._super.apply(this, arguments), session.is_bound).then(function () { + var web_base_url = session['web.base.url']; + var trusted_host = utils.get_host_from_url(web_base_url); + var trusted_protocol = utils.get_protocol_from_url(web_base_url); + self.trusted_origin = utils.build_origin(trusted_protocol, trusted_host); + + self.$buttons = $(QWeb.render('report.client_action.ControlButtons', {})); + self.$buttons.on('click', '.o_report_edit', self.on_click_edit); + self.$buttons.on('click', '.o_report_print', self.on_click_print); + self.$buttons.on('click', '.o_report_save', self.on_click_save); + self.$buttons.on('click', '.o_report_discard', self.on_click_discard); + + self._update_control_panel(); + + // Load the report in the iframe. Note that we use a relative URL. + self.iframe.src = self.report_url; + + // Once the iframe is loaded, check if we can edit the report. + self.iframe.onload = function () { + self._on_iframe_loaded(); + }; + }); + }, + + do_show: function () { + this._update_control_panel(); + return this._super.apply(this, arguments); + }, + + on_attach_callback: function () { + // Register now the postMessage event handler. We only want to listen to ~trusted + // messages and we can only filter them by their origin, so we chose to ignore the + // messages that do not come from `web.base.url`. + $(window).on('message', this, this.on_message_received); + }, + + on_detach_callback: function () { + $(window).off('message', this.on_message_received); + }, + + _on_iframe_loaded: function () { + var editable = $(this.iframe).contents().find('html').data('editable'); + if (editable === 1) { + this.edit_mode_available = true; + this._update_control_panel(); + } + }, + + _update_control_panel: function () { + this.update_control_panel({ + breadcrumbs: this.action_manager.get_breadcrumbs(), + cp_content: { + $buttons: this.$buttons, + }, + }); + this._update_control_panel_buttons(); + }, + + /** + * Helper allowing to toggle groups of buttons in the control panel + * according to the `this.in_edit_mode` flag. + */ + _update_control_panel_buttons: function () { + this.$buttons.filter('div.o_report_edit_mode').toggle(this.in_edit_mode); + this.$buttons.filter('div.o_report_no_edit_mode').toggle(! this.in_edit_mode); + this.$buttons.filter('div.o_edit_mode_available').toggle(core.debug && this.edit_mode_available && ! this.in_edit_mode); + }, + + /** + * Event handler of the message post. We only handle them if they're from + * `web.base.url` host and protocol and if they're part of `AUTHORIZED_MESSAGES`. + */ + on_message_received: function (ev) { + // Check the origin of the received message. + var message_origin = utils.build_origin(ev.originalEvent.source.location.protocol, ev.originalEvent.source.location.host); + if (message_origin === this.trusted_origin) { + + // Check the syntax of the received message. + var message = ev.originalEvent.data; + if (_.isObject(message)) { + message = message.message; + } + if (! _.isString(message) || (_.isString(message) && ! _.contains(AUTHORIZED_MESSAGES, message))) { + return; + } + + switch(message) { + case 'report.editor:save_ok': + // Reload the iframe in order to disable the editor. + this.iframe.src = this.report_url; + this.in_edit_mode = false; + this._update_control_panel_buttons(); + break; + case 'report.editor:discard_ok': + // Reload the iframe in order to disable the editor. + this.iframe.src = this.report_url; + this.in_edit_mode = false; + this._update_control_panel_buttons(); + break; + case 'report:do_action': + return this.do_action(ev.originalEvent.data.action); + default: + } + } + }, + + /** + * Helper allowing to send a message to the `this.el` iframe's window and + * seting the `targetOrigin` as `this.trusted_origin` (which is the + * `web.base.url` ir.config_parameter key) - in other word, only when using + * this method we only send the message to a trusted domain. + */ + _post_message: function (message) { + this.iframe.contentWindow.postMessage(message, this.trusted_origin); + }, + + on_click_edit: function () { + // We reload the iframe with a special query string to enable the editor. + if (this.report_url.indexOf('?') === -1) { + this.iframe.src = this.report_url + '?enable_editor=1'; + } else { + this.iframe.src = this.report_url + '&enable_editor=1'; + } + this.in_edit_mode = true; + this._update_control_panel_buttons(); + }, + + on_click_discard: function () { + this._post_message('report.editor:ask_discard'); + }, + + on_click_save: function () { + this._post_message('report.editor:ask_save'); + }, + + on_click_print: function () { + var action = { + 'type': 'ir.actions.report', + 'report_type': 'qweb-pdf', + 'report_name': this.report_name, + 'report_file': this.report_file, + 'data': this.data, + 'context': this.context, + 'display_name': this.title, + }; + return this.do_action(action); + }, + +}); + +core.action_registry.add('report.client_action', ReportAction); + +return ReportAction; + +}); diff --git a/addons/web/static/src/js/report/qwebactionmanager.js b/addons/web/static/src/js/report/qwebactionmanager.js new file mode 100644 index 00000000000..3c8fee859e3 --- /dev/null +++ b/addons/web/static/src/js/report/qwebactionmanager.js @@ -0,0 +1,140 @@ +odoo.define('report.report', function (require) { +'use strict'; + +var ActionManager = require('web.ActionManager'); +var core = require('web.core'); +var crash_manager = require('web.crash_manager'); +var framework = require('web.framework'); + +var _t = core._t; +var _lt = core._lt; + + +var wkhtmltopdf_state; + +// Messages that will be shown to the user (if needed). +var WKHTMLTOPDF_MESSAGES = { + 'install': _lt('Unable to find Wkhtmltopdf on this \nsystem. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\nwkhtmltopdf.org</a>'), + 'workers': _lt('You need to start OpenERP with at least two \nworkers to print a pdf version of the reports.'), + 'upgrade': _lt('You should upgrade your version of\nWkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\nsupport for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \ntarget="_blank">wkhtmltopdf.org</a>'), + 'broken': _lt('Your installation of Wkhtmltopdf seems to be broken. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">wkhtmltopdf.org</a>') +}; + +var trigger_download = function (session, response, c, action, options) { + session.get_file({ + url: '/report/download', + data: {data: JSON.stringify(response)}, + complete: framework.unblockUI, + error: c.rpc_error.bind(c), + success: function () { + if (action && options && !action.dialog) { + options.on_close(); + } + }, + }); +}; + +/** + * This helper will generate an object containing the report's url (as value) + * for every qweb-type we support (as key). It's convenient because we may want + * to use another report's type at some point (for example, when `qweb-pdf` is + * not available). + */ +var make_report_url = function (action) { + var report_urls = { + 'qweb-html': '/report/html/' + action.report_name, + 'qweb-pdf': '/report/pdf/' + action.report_name, + }; + // We may have to build a query string with `action.data`. It's the place + // were report's using a wizard to customize the output traditionally put + // their options. + if (_.isUndefined(action.data) || _.isNull(action.data) || (_.isObject(action.data) && _.isEmpty(action.data))) { + if (action.context.active_ids) { + var active_ids_path = '/' + action.context.active_ids.join(','); + // Update the report's type - report's url mapping. + report_urls = _.mapObject(report_urls, function (value, key) { + return value += active_ids_path; + }); + } + } else { + var serialized_options_path = '?options=' + encodeURIComponent(JSON.stringify(action.data)); + serialized_options_path += '&context=' + encodeURIComponent(JSON.stringify(action.context)); + // Update the report's type - report's url mapping. + report_urls = _.mapObject(report_urls, function (value, key) { + return value += serialized_options_path; + }); + } + return report_urls; +}; + +ActionManager.include({ + ir_actions_report: function (action, options) { + var self = this; + action = _.clone(action); + + var report_urls = make_report_url(action); + + if (action.report_type === 'qweb-html') { + var client_action_options = _.extend({}, options, { + report_url: report_urls['qweb-html'], + report_name: action.report_name, + report_file: action.report_file, + data: action.data, + context: action.context, + name: action.name, + display_name: action.display_name, + }); + return this.do_action('report.client_action', client_action_options); + } else if (action.report_type === 'qweb-pdf') { + framework.blockUI(); + // Before doing anything, we check the state of wkhtmltopdf on the server. + (wkhtmltopdf_state = wkhtmltopdf_state || this._rpc({route: '/report/check_wkhtmltopdf'})).then(function (state) { + // Display a notification to the user according to wkhtmltopdf's state. + if (WKHTMLTOPDF_MESSAGES[state]) { + self.do_notify(_t('Report'), WKHTMLTOPDF_MESSAGES[state], true); + } + + if (state === 'upgrade' || state === 'ok') { + // Trigger the download of the PDF report. + var response; + var c = crash_manager; + + var treated_actions = []; + var current_action = action; + do { + report_urls = make_report_url(current_action); + response = [ + report_urls['qweb-pdf'], + action.report_type, //The 'root' report is considered the maine one, so we use its type for all the others. + ]; + trigger_download(self.getSession(), response, c, current_action, options); + + treated_actions.push(current_action); + current_action = current_action.next_report_to_generate; + } while (current_action && !_.contains(treated_actions, current_action)); + //Second part of the condition for security reasons (avoids infinite loop possibilty). + + return; + + } else { + // Open the report in the client action if generating the PDF is not possible. + var client_action_options = _.extend({}, options, { + report_url: report_urls['qweb-html'], + report_name: action.report_name, + report_file: action.report_file, + data: action.data, + context: action.context, + name: action.name, + display_name: action.display_name, + }); + framework.unblockUI(); + return self.do_action('report.client_action', client_action_options); + } + }); + } else { + return self._super(action, options); + } + } +}); + +}); diff --git a/addons/web/static/src/js/report/report.editor.js b/addons/web/static/src/js/report/report.editor.js new file mode 100644 index 00000000000..57942f9b79c --- /dev/null +++ b/addons/web/static/src/js/report/report.editor.js @@ -0,0 +1,109 @@ +odoo.define('report.editor', function (require) { +'use strict'; + +var ajax = require('web.ajax'); +var core = require('web.core'); +var utils = require('report.utils'); +var editor = require('web_editor.editor'); +var options = require('web_editor.snippets.options'); + +var web_base_url = $('html').attr('web-base-url'); +var trusted_host = utils.get_host_from_url(web_base_url); +var trusted_protocol = utils.get_protocol_from_url(web_base_url); +var trusted_origin = utils.build_origin(trusted_protocol, trusted_host); + +ajax.loadXML('/web/static/src/xml/base_common.xml', core.qweb); + +// Patch the editor's behavior when it is launched inside an iframe. +if (window.self !== window.top) { + $(document.body).addClass('o_in_iframe'); // in order to apply css rules + + // As `reload` is called after `save` and `cancel`, we nullify this function + // if the editor in order to be able to chain some deferred to `save` and + // `cancel`. + editor.reload = function () { + return $.when(); + }; + + // And now we chain some deferred to `save` and `cancel` in order to inform + // the report's client action that the actions are done. + editor.Class.include({ + save: function () { + return this._super.apply(this, arguments).then(function () { + window.parent.postMessage('report.editor:save_ok', trusted_origin); + }); + }, + cancel: function () { + return this._super.apply(this, arguments).then(function () { + window.parent.postMessage('report.editor:discard_ok', trusted_origin); + }); + }, + }); + + // postMessage logic. + var AUTHORIZED_MESSAGES = [ + 'report.editor:ask_discard', + 'report.editor:ask_save', + ]; + + window.addEventListener('message', function (ev) { + // Check the origin of the received message. + var message_origin = utils.build_origin(ev.source.location.protocol, ev.source.location.host); + if (message_origin === trusted_origin) { + // Check the syntax of the received message. + var message = ev.data; + if (! _.isString(message) || (_.isString(message) && ! _.contains(AUTHORIZED_MESSAGES, message))) { + return; + } + + switch(message) { + case 'report.editor:ask_save': + editor.editor_bar.save(); + break; + case 'report.editor:ask_discard': + editor.editor_bar.cancel(); + break; + default: + } + } + }, false); + + // Allow to send commands to the webclient when the editor is disabled. + if (window.location.search.indexOf('enable_editor') === -1) { + // `do_action` command + $('[res-id][res-model][view-type]') + .wrap('<a/>') + .attr('href', '#') + .on('click', function (ev) { + ev.preventDefault(); + var action = { + 'type': 'ir.actions.act_window', + 'view_type': $(this).attr('view-type'), + 'view_mode': $(this).attr('view-mode') || $(this).attr('view-type'), + 'res_id': Number($(this).attr('res-id')), + 'res_model': $(this).attr('res-model'), + 'views': [[$(this).attr('view-id') || false, $(this).attr('view-type')]], + }; + window.parent.postMessage({ + 'message': 'report:do_action', + 'action': action, + }, trusted_origin); + }); + } +} + +options.registry.many2one.include({ + select_record: function (li) { + var self = this; + this._super(li); + if (this.$target.data('oe-field') === "partner_id") { + var $img = $('.header .row img:first'); + var css = window.getComputedStyle($img[0]); + $img.css("max-height", css.height+'px'); + $img.attr("src", "/web/image/res.partner/"+self.ID+"/image"); + setTimeout(function () { $img.removeClass('o_dirty'); },0); + } + } +}); + +}); diff --git a/addons/web/static/src/js/report/subst.js b/addons/web/static/src/js/report/subst.js new file mode 100644 index 00000000000..076a5ff208a --- /dev/null +++ b/addons/web/static/src/js/report/subst.js @@ -0,0 +1,14 @@ +function subst() { + var vars = {}; + var x = document.location.search.substring(1).split('&'); + for (var i in x) { + var z = x[i].split('=', 2); + vars[z[0]] = unescape(z[1]); + } + var x=['frompage', 'topage', 'page', 'webpage', 'section', 'subsection', 'subsubsection']; + for (var i in x) { + var y = document.getElementsByClassName(x[i]); + for (var j=0; j<y.length; ++j) + y[j].textContent = vars[x[i]]; + } +} diff --git a/addons/web/static/src/js/report/utils.js b/addons/web/static/src/js/report/utils.js new file mode 100644 index 00000000000..64776efb4db --- /dev/null +++ b/addons/web/static/src/js/report/utils.js @@ -0,0 +1,26 @@ +odoo.define('report.utils', function (require) { +'use strict'; + +function get_protocol_from_url (url) { + var a = document.createElement('a'); + a.href = url; + return a.protocol; +} + +function get_host_from_url (url) { + var a = document.createElement('a'); + a.href = url; + return a.host; +} + +function build_origin (protocol, host) { + return protocol + '//' + host; +} + +return { + 'get_protocol_from_url': get_protocol_from_url, + 'get_host_from_url': get_host_from_url, + 'build_origin': build_origin, +}; + +}); |
