[IMP] web: list multi edition confirmation modal
Changed the information dipslayed by the confirmation modal in multi edition in list views. Before this commit, the modal simply showed a one-line message with the amount of valid records. Now, there are 3 elements: - If all of the fields cannot be changed, a first line warns the amount of valid records. - A second (or first if all fields can be updated) line ensuring that the user is aware of the occuring changes. - A table displaying the affected field and the updated value (readonly widget with the new value). Part of task 1967602
This commit is contained in:
committed by
Aaron Bohy
parent
6fea5b42d6
commit
f328b9ca8c
@@ -0,0 +1,78 @@
|
||||
odoo.define('web.ListConfirmDialog', function (require) {
|
||||
"use strict";
|
||||
|
||||
const core = require('web.core');
|
||||
const Dialog = require('web.Dialog');
|
||||
|
||||
const _t = core._t;
|
||||
const qweb = core.qweb;
|
||||
|
||||
/**
|
||||
* Multi edition confirmation modal for list views.
|
||||
*
|
||||
* Handles the display of the amount of changed records (+ valid ones) and
|
||||
* of the widget representing the new value.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
const ListConfirmDialog = Dialog.extend({
|
||||
/**
|
||||
* @constructor
|
||||
* @override
|
||||
* @param {Widget} parent
|
||||
* @param {Object} record edited record with updated value
|
||||
* @param {Object} changes changes registered by the list controller
|
||||
* @param {string} changes.fieldLabel label of the changed field
|
||||
* @param {string} changes.fieldName technical name of the changed field
|
||||
* @param {number} changes.nbRecords number of records (total)
|
||||
* @param {number} changes.nbValidRecords number of valid records
|
||||
* @param {Object} [options]
|
||||
*/
|
||||
init: function (parent, record, changes, options) {
|
||||
options = Object.assign({}, options, {
|
||||
$content: $(qweb.render('ListView.confirmModal', { changes })),
|
||||
buttons: options.buttons || [{
|
||||
text: _t("Ok"),
|
||||
classes: 'btn-primary',
|
||||
close: true,
|
||||
click: options.confirm_callback,
|
||||
}, {
|
||||
text: _t("Cancel"),
|
||||
close: true,
|
||||
click: options.cancel_callback,
|
||||
}],
|
||||
onForceClose: options.cancel_callback,
|
||||
size: options.size || 'medium',
|
||||
title: options.title || _t("Confirmation"),
|
||||
});
|
||||
|
||||
this._super(parent, options);
|
||||
|
||||
const Widget = record.fieldsInfo.list[changes.fieldName].Widget;
|
||||
this.fieldWidget = new Widget(this, changes.fieldName, record, {
|
||||
mode: 'readonly',
|
||||
viewType: 'list',
|
||||
noOpen: true,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
willStart: function () {
|
||||
return Promise.all([
|
||||
this.fieldWidget.appendTo(document.createDocumentFragment()),
|
||||
this._super.apply(this, arguments)
|
||||
]);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
this.$content.find('.o_changes_widget').replaceWith(this.fieldWidget.$el);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
return ListConfirmDialog;
|
||||
|
||||
});
|
||||
@@ -11,6 +11,7 @@ var core = require('web.core');
|
||||
var BasicController = require('web.BasicController');
|
||||
var DataExport = require('web.DataExport');
|
||||
var Dialog = require('web.Dialog');
|
||||
var ListConfirmDialog = require('web.ListConfirmDialog');
|
||||
var Sidebar = require('web.Sidebar');
|
||||
|
||||
var _t = core._t;
|
||||
@@ -396,62 +397,54 @@ var ListController = BasicController.extend({
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_saveMultipleRecords: function (recordId, node, changes) {
|
||||
var self = this;
|
||||
var fieldName = Object.keys(changes)[0];
|
||||
var value = Object.values(changes)[0];
|
||||
var recordIds = _.union([recordId], this.selectedRecords);
|
||||
var validRecordIds = recordIds.reduce(function (result, nextRecordId) {
|
||||
var record = self.model.get(nextRecordId);
|
||||
var modifiers = self.renderer._registerModifiers(node, record);
|
||||
var validRecordIds = recordIds.reduce((result, nextRecordId) => {
|
||||
var record = this.model.get(nextRecordId);
|
||||
var modifiers = this.renderer._registerModifiers(node, record);
|
||||
if (!modifiers.readonly && (!modifiers.required || value)) {
|
||||
result.push(nextRecordId);
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
const nbInvalid = recordIds.length - validRecordIds.length;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const rejectAndDiscard = () => {
|
||||
const discardAndReject = () => {
|
||||
this.model.discardChanges(recordId);
|
||||
return this._confirmSave(recordId).then(reject);
|
||||
this._confirmSave(recordId).then(() => {
|
||||
this.renderer.focusCell(recordId, node);
|
||||
reject();
|
||||
});
|
||||
};
|
||||
let dialog;
|
||||
if (validRecordIds.length > 0) {
|
||||
let message;
|
||||
if (nbInvalid === 0) {
|
||||
message = _.str.sprintf(
|
||||
_t("Do you want to set the value on the %d selected records?"),
|
||||
validRecordIds.length);
|
||||
} else {
|
||||
message = _.str.sprintf(
|
||||
_t("Do you want to set the value on the %d valid selected records? (%d invalid)"),
|
||||
validRecordIds.length, nbInvalid);
|
||||
}
|
||||
dialog = Dialog.confirm(this, message, {
|
||||
const dialogOptions = {
|
||||
confirm_callback: () => {
|
||||
return this.model.saveRecords(recordId, validRecordIds, fieldName)
|
||||
this.model.saveRecords(recordId, validRecordIds, fieldName)
|
||||
.then(async () => {
|
||||
this._updateButtons('readonly');
|
||||
const state = this.model.get(this.handle);
|
||||
await this.renderer.updateState(state, {keepWidths: true});
|
||||
await this.renderer.updateState(state, { keepWidths: true });
|
||||
await this.renderer.focusCell(recordId, node);
|
||||
resolve(Object.keys(changes));
|
||||
})
|
||||
.guardedCatch(rejectAndDiscard);
|
||||
.guardedCatch(discardAndReject);
|
||||
},
|
||||
cancel_callback: rejectAndDiscard,
|
||||
});
|
||||
cancel_callback: discardAndReject,
|
||||
};
|
||||
const record = this.model.get(recordId);
|
||||
const dialogChanges = {
|
||||
fieldLabel: node.attrs.string || record.fields[fieldName].string,
|
||||
fieldName: node.attrs.name,
|
||||
nbRecords: recordIds.length,
|
||||
nbValidRecords: validRecordIds.length,
|
||||
};
|
||||
new ListConfirmDialog(this, record, dialogChanges, dialogOptions)
|
||||
.open({ shouldFocusButtons: true });
|
||||
} else {
|
||||
dialog = Dialog.alert(this, _t("No valid record to save"), {
|
||||
confirm_callback: rejectAndDiscard,
|
||||
Dialog.alert(this, _t("No valid record to save"), {
|
||||
confirm_callback: discardAndReject,
|
||||
});
|
||||
}
|
||||
dialog.on('closed', this, async () => {
|
||||
// we need to wait for the dialog to be actually closed, but
|
||||
// the 'closed' event is triggered just before, and it prevents
|
||||
// from focussing the cell
|
||||
await Promise.resolve();
|
||||
this.renderer.focusCell(recordId, node);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -750,14 +743,14 @@ var ListController = BasicController.extend({
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_onSetDirty: function (ev) {
|
||||
var self = this;
|
||||
var recordId = ev.data.dataPointID;
|
||||
if (this.renderer.inMultipleRecordEdition(recordId)) {
|
||||
ev.stopPropagation();
|
||||
Dialog.alert(this, _t("No valid record to save"), {
|
||||
confirm_callback: function () {
|
||||
self.model.discardChanges(recordId);
|
||||
self._confirmSave(recordId);
|
||||
confirm_callback: async () => {
|
||||
this.model.discardChanges(recordId);
|
||||
await this._confirmSave(recordId);
|
||||
this.renderer.focusCell(recordId, ev.target.__node);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -27,6 +27,17 @@
|
||||
.o_view_nocontent {
|
||||
position: unset;
|
||||
}
|
||||
|
||||
.o_modal_changes td {
|
||||
&:first-child {
|
||||
padding-right: 10px;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
&:not(:first-child) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
|
||||
@@ -281,6 +281,34 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="ListView.confirmModal">
|
||||
<main role="alert">
|
||||
<p>
|
||||
<t t-if="changes.nbRecords != changes.nbValidRecords">
|
||||
Among the <t t-raw="changes.nbRecords"/> selected records,
|
||||
<t t-raw="changes.nbValidRecords"/> are valid for this update.<br/>
|
||||
</t>
|
||||
Are you sure you want to perform the following update on those <t t-raw="changes.nbValidRecords"/> records ?
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="o_modal_changes">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Field:</td>
|
||||
<td><t t-raw="changes.fieldLabel"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Update to:</td>
|
||||
<td>
|
||||
<div class="o_changes_widget"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</t>
|
||||
|
||||
<t t-name="FormView.buttons">
|
||||
<div class="o_form_buttons_view" role="toolbar" aria-label="Main actions">
|
||||
<button t-if="widget.is_action_enabled('edit')" type="button"
|
||||
|
||||
@@ -5430,7 +5430,7 @@ QUnit.module('Views', {
|
||||
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
|
||||
await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'), 666);
|
||||
await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_data_cell:eq(0)'));
|
||||
assert.ok($('.modal').text().includes('2 selected records'), "the number of records should be correctly displayed");
|
||||
assert.ok($('.modal').text().includes('those 2 records'), "the number of records should be correctly displayed");
|
||||
await testUtils.dom.click($('.modal .btn-primary'));
|
||||
assert.containsNone(list, '.o_data_cell input.o_field_widget', "no field should be editable anymore");
|
||||
assert.verifySteps(['write', 'read']);
|
||||
@@ -5660,7 +5660,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
QUnit.test('editable list view: multi edition with readonly modifiers', async function (assert) {
|
||||
assert.expect(5);
|
||||
assert.expect(4);
|
||||
|
||||
var list = await createView({
|
||||
View: ListView,
|
||||
@@ -5686,17 +5686,19 @@ QUnit.module('Views', {
|
||||
// edit a field
|
||||
await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)'));
|
||||
await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'), 666);
|
||||
assert.ok($('.modal').text().includes('2 valid'),
|
||||
"the number of records should be correctly displayed (only 2 not readonly)");
|
||||
assert.ok($('.modal').text().includes('2 invalid'),
|
||||
"should display the number of invalid records");
|
||||
|
||||
const modalText = $('.modal-body').text()
|
||||
.split(" ").filter(w => w.trim() !== '').join(" ")
|
||||
.split("\n").join('');
|
||||
assert.strictEqual(modalText,
|
||||
"Among the 4 selected records, 2 are valid for this update. Are you sure you want to " +
|
||||
"perform the following update on those 2 records ? Field: int_field Update to: 666");
|
||||
|
||||
await testUtils.dom.click($('.modal .btn-primary'));
|
||||
assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), "1yop666",
|
||||
"the first row should be updated");
|
||||
assert.strictEqual(list.$('.o_data_row:eq(1) .o_data_cell').text(), "2blip666",
|
||||
"the second row should be updated");
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
|
||||
@@ -351,6 +351,7 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/views/list/list_renderer.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/list/list_view.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/list/list_controller.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/list/list_confirm_dialog.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/pivot/pivot_model.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/pivot/pivot_controller.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/pivot/pivot_renderer.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user