diff options
| author | Julien Mougenot <jum@odoo.com> | 2019-09-18 10:25:59 +0000 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2019-09-25 07:20:36 +0000 |
| commit | f328b9ca8ce9ef3a8aaa4ce7c513ba7c01929e62 (patch) | |
| tree | 1008549f4a98cf229dff6498ad0d36f71322a10a /addons | |
| parent | 6fea5b42d6ddabb83daf95b8d68021b05144cf42 (diff) | |
[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
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/web/static/src/js/views/list/list_confirm_dialog.js | 78 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list/list_controller.js | 69 | ||||
| -rw-r--r-- | addons/web/static/src/scss/modal.scss | 11 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 28 | ||||
| -rw-r--r-- | addons/web/static/tests/views/list_tests.js | 16 | ||||
| -rw-r--r-- | addons/web/views/webclient_templates.xml | 1 |
6 files changed, 158 insertions, 45 deletions
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 @@ </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" diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 540606d9770..1f83783dc33 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -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(); }); 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 @@ <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> |
