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 @@
+