diff --git a/addons/web/static/src/js/views/list/list_confirm_dialog.js b/addons/web/static/src/js/views/list/list_confirm_dialog.js new file mode 100644 index 00000000000..8ed07a73ca0 --- /dev/null +++ b/addons/web/static/src/js/views/list/list_confirm_dialog.js @@ -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; + +}); diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js index 9921b52b52c..309550e86bb 100644 --- a/addons/web/static/src/js/views/list/list_controller.js +++ b/addons/web/static/src/js/views/list/list_controller.js @@ -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 { diff --git a/addons/web/static/src/scss/modal.scss b/addons/web/static/src/scss/modal.scss index e7243042d35..3463d9e20ac 100644 --- a/addons/web/static/src/scss/modal.scss +++ b/addons/web/static/src/scss/modal.scss @@ -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 { diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 5ea1a8e535c..bd4284b0b07 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -281,6 +281,34 @@ + + + + + Among the selected records, + are valid for this update. + + Are you sure you want to perform the following update on those records ? + + + + + + Field: + + + + Update to: + + + + + + + + + + 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(); }); diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index e31a9fbbf6b..91ffa58d088 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -351,6 +351,7 @@ +
+ + Among the selected records, + are valid for this update. + + Are you sure you want to perform the following update on those records ? +