From b7aa439826df6b3e442b3f60bb593ff28efc8679 Mon Sep 17 00:00:00 2001 From: jpr-odoo Date: Wed, 1 Nov 2017 15:15:04 +0530 Subject: [PATCH] [ADD] web: Add a new widget to attach documents on a record Purpose ======= Add the possibility to easily attach a document on a record. That could be used for to attach receipts on expense for example. Specification ============= Add a new widget (AttachDocument) to allow attach document from mobile/web directly on record form After attaching document it will call action given on widget, that action will be a method name on given model --- .../src/js/views/basic/basic_renderer.js | 2 +- .../static/src/js/views/form/form_renderer.js | 3 + .../static/src/js/widgets/attach_document.js | 135 ++++++++++++++++++ addons/web/static/src/xml/base.xml | 16 +++ addons/web/static/tests/views/form_tests.js | 22 +++ addons/web/views/webclient_templates.xml | 1 + 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 addons/web/static/src/js/widgets/attach_document.js diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index d9c0f1a570b..f0d41f31f49 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -594,7 +594,7 @@ var BasicRenderer = AbstractRenderer.extend({ */ _renderWidget: function (record, node) { var Widget = widgetRegistry.get(node.attrs.name); - var widget = new Widget(this, record); + var widget = new Widget(this, record, node); // Prepare widget rendering and save the related deferred var def = widget._widgetRenderAndInsert(function () {}); diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index b289663fb19..b28d25c5b90 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -370,6 +370,9 @@ var FormRenderer = BasicRenderer.extend({ if (child.tag === 'button') { $buttons.append(self._renderHeaderButton(child)); } + if (child.tag === 'widget') { + $buttons.append(self._renderTagWidget(child)); + } }); return $buttons; }, diff --git a/addons/web/static/src/js/widgets/attach_document.js b/addons/web/static/src/js/widgets/attach_document.js new file mode 100644 index 00000000000..041b021d60b --- /dev/null +++ b/addons/web/static/src/js/widgets/attach_document.js @@ -0,0 +1,135 @@ +odoo.define('web.AttachDocument', function (require) { +"use static"; + +var core = require('web.core'); +var framework = require('web.framework'); +var widgetRegistry = require('web.widget_registry'); +var Widget = require('web.Widget'); + +var _t = core._t; + +var AttachDocument = Widget.extend({ + template: 'AttachDocument', + events: { + 'click span.o_attach_document': '_onClickAttachDocument', + 'change input.o_input_file': '_onFileChanged', + }, + /** + * @constructor + * @param {Widget} parent + * @param {Object} record + * @param {Object} nodeInfo + */ + init: function (parent, record, nodeInfo) { + this._super.apply(this, arguments); + this.res_id = record.res_id; + this.res_model = record.model; + this.state = record; + this.node = nodeInfo; + this.fileuploadID = _.uniqueId('o_fileupload'); + }, + /** + * @override + */ + start: function () { + $(window).on(this.fileuploadID, this._onFileLoaded.bind(this)); + return this._super.apply(this, arguments); + }, + /** + * @override + */ + destroy: function () { + $(window).off(this.fileuploadID); + this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // private + //-------------------------------------------------------------------------- + + /** + * Helper function to display a warning that some fields have an invalid + * value. This is used when a save operation cannot be completed. + * + * @private + * @param {string[]} invalidFields - list of field names + */ + _notifyInvalidFields: function (invalidFields) { + var fields = this.state.fields; + var warnings = invalidFields.map(function (fieldName) { + var fieldStr = fields[fieldName].string; + return _.str.sprintf('
  • %s
  • ', _.escape(fieldStr)); + }); + warnings.unshift(''); + this.do_warn(_t("The following fields are invalid:"), warnings.join('')); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Opens File Explorer dialog if all fields are valid and record is saved + * + * @private + * @param {Event} ev + */ + _onClickAttachDocument: function (ev) { + var fieldNames = this.getParent().canBeSaved(this.state.id); + if (fieldNames.length) { + return this._notifyInvalidFields(fieldNames); + } + // We want to save record on widget click and then open File Selection Explorer + // but due to this security restriction give warning to save record first. + // https://stackoverflow.com/questions/29728705/trigger-click-on-input-file-on-asynchronous-ajax-done/29873845#29873845 + if (!this.res_id) { + return this.do_warn(_t('Warning : You have to save first before attaching a file.')); + } + this.$('input.o_input_file').trigger('click'); + }, + /** + * Submits file + * + * @private + * @param {Event} ev + */ + _onFileChanged: function (ev) { + ev.stopPropagation(); + this.$('form.o_form_binary_form').trigger('submit'); + framework.blockUI(); + }, + /** + * Call action given as node attribute after file submission + * + * @private + */ + _onFileLoaded: function () { + var self = this, + def = $.Deferred(); + + if (self.node.attrs.action) { + // the first argument isn't a file but the jQuery.Event + var files = Array.prototype.slice.call(arguments, 1); + self._rpc({ + model: self.res_model, + method: self.node.attrs.action, + args: [self.res_id], + kwargs: { + 'attachment_ids': _.map(files, function (file) {return file.id;}), + } + }).then( function () { + def.resolve(); + }); + } else { + def.resolve(); + } + return $.when(def).then(function () { + self.trigger_up('reload'); + framework.unblockUI(); + }); + }, + +}); +widgetRegistry.add('attach_document', AttachDocument); +}); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 196346d4d9c..ce432fea823 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1351,4 +1351,20 @@ + + + + diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 6d8909ff2c3..37420e0f076 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -6227,6 +6227,28 @@ QUnit.module('Views', { delete widgetRegistry.map.test; }); + QUnit.test('support header button as widgets on form statusbar', function (assert) { + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
    ' + + '
    ' + + '' + + '
    ' + + '
    ', + }); + + assert.strictEqual(form.$('button.o_attachment_button').length, 1, + "should have 1 attach_document widget in the statusbar"); + assert.strictEqual(form.$('span.o_attach_document').text().trim(), 'Attach document', + "widget should have been instantiated"); + + form.destroy(); + }); + QUnit.test('bounce edit button in readonly mode', function (assert) { assert.expect(3); diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 2742871c790..35ded8170cf 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -240,6 +240,7 @@ +