[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:
committed by
Yannick Tivisse
parent
dfdc06fe43
commit
b7aa439826
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user