[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
This commit is contained in:
jpr-odoo
2018-02-09 06:59:56 +01:00
committed by Yannick Tivisse
parent dfdc06fe43
commit b7aa439826
6 changed files with 178 additions and 1 deletions
@@ -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 () {});
@@ -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;
},
@@ -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('<li>%s</li>', _.escape(fieldStr));
});
warnings.unshift('<ul>');
warnings.push('</ul>');
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);
});
+16
View File
@@ -1351,4 +1351,20 @@
</div>
</t>
<t t-name="AttachDocument">
<button class="btn btn-sm btn-default o_attachment_button">
<span class="o_attach_document"><t t-esc="widget.node.attrs.string"/></span>
<span class="hide">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileuploadID"/>
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<t t-set="multi_upload" t-value="true"/>
<input type="hidden" name="model" t-att-value="widget.res_model"/>
<input type="hidden" name="id" t-att-value="widget.res_id"/>
<input type="hidden" name="session_id" t-att-value="widget.getSession().session_id"/>
</t>
</span>
</button>
</t>
</templates>
@@ -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: '<form string="Partners">' +
'<header>' +
'<widget name="attach_document" string="Attach document"/>' +
'</header>' +
'</form>',
});
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);
+1
View File
@@ -240,6 +240,7 @@
<script type="text/javascript" src="/web/static/src/js/views/calendar/calendar_quick_create.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/calendar/calendar_renderer.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/calendar/calendar_view.js"></script>
<script type="text/javascript" src="/web/static/src/js/widgets/attach_document.js"></script>
<script type="text/javascript" src="/web/static/src/js/report/utils.js"/>
<script type="text/javascript" src="/web/static/src/js/report/client_action.js"/>