summaryrefslogtreecommitdiff
path: root/addons/web
diff options
context:
space:
mode:
authorJulien Mougenot <jum@odoo.com>2019-09-06 12:33:28 +0000
committerJulien Mougenot <jum@odoo.com>2019-09-18 08:24:28 +0000
commitd261d42d9be6bf18396b4458ecdeb0e0ab1a3a93 (patch)
tree2ff734cf8337da28718f64bee31e4f7f564d236c /addons/web
parentbb85f963b556ff033f74d785ed42747bed0bcc80 (diff)
[FIX] web: list: m2o doesn't trigger fieldChange when empty
Gives time to the user to enter an actual value in m2o in list views. Before this commit, emptying a many2one field (selecting all > BACKSPACE or DELETE) triggered instantly a field_change. This was an issue in multi edition as a field changed triggered instantly a save on selected records without giving time to the user to enter a new value. Now, many2ones only trigger a fieldChange when selecting a new valid value.
Diffstat (limited to 'addons/web')
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js73
-rw-r--r--addons/web/static/src/js/views/view_dialogs.js5
-rw-r--r--addons/web/static/tests/fields/relational_fields/field_many2one_tests.js75
3 files changed, 152 insertions, 1 deletions
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 5efaa2c8468..d91ad50051b 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -600,8 +600,10 @@ var FieldMany2One = AbstractField.extend({
no_create: !self.can_create,
on_selected: function (records) {
self.reinitialize(records[0]);
+ },
+ on_closed: function () {
self.activate();
- }
+ },
})).open();
},
/**
@@ -770,6 +772,22 @@ var ListFieldMany2One = FieldMany2One.extend({
'focusin input': '_onInputFocusin',
}),
+ /**
+ * Should never be allowed to be opened while in readonly mode in a list
+ *
+ * @override
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ // when we empty the input, we delay the setValue to prevent from
+ // triggering the 'fieldChanged' event twice when the user wants set
+ // another m2o value ; the following attribute is used to determine when
+ // we skipped the setValue, s.t. we can perform it later on if the user
+ // didn't select another value
+ this.mustSetValue = false;
+ this.m2oDialogFocused = false;
+ },
+
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@@ -785,6 +803,13 @@ var ListFieldMany2One = FieldMany2One.extend({
}
return this._super.apply(this, arguments);
},
+ /**
+ * @override
+ */
+ reinitialize: function () {
+ this.mustSetValue = false;
+ return this._super.apply(this, arguments);
+ },
//--------------------------------------------------------------------------
// Private
@@ -796,6 +821,52 @@ var ListFieldMany2One = FieldMany2One.extend({
_renderReadonly: function () {
this.$el.text(this.m2o_value);
},
+ /**
+ * @override
+ * @private
+ */
+ _searchCreatePopup: function () {
+ this.m2oDialogFocused = true;
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onInputFocusin: function () {
+ this.m2oDialogFocused = false;
+ },
+ /**
+ * In list views, we don't want to try to trigger a fieldChange when the field
+ * is being emptied. Instead, it will be triggered as the user leaves the field
+ * while it is empty.
+ *
+ * @override
+ * @private
+ */
+ _onInputFocusout: function () {
+ this._super.apply(this, arguments);
+ if (!this.m2oDialogFocused && this.$input.val() === "" && this.mustSetValue) {
+ this.reinitialize(false);
+ }
+ },
+ /**
+ * Prevents the triggering of an immediate _onFieldChanged when emptying the field.
+ *
+ * @override
+ * @private
+ */
+ _onInputKeyup: function () {
+ if (this.$input.val() !== "") {
+ this._super.apply(this, arguments);
+ } else {
+ this.mustSetValue = true;
+ }
+ },
});
var KanbanFieldMany2One = AbstractField.extend({
diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js
index 08150b96c86..b8e5eb3e7e8 100644
--- a/addons/web/static/src/js/views/view_dialogs.js
+++ b/addons/web/static/src/js/views/view_dialogs.js
@@ -328,6 +328,7 @@ var SelectCreateDialog = ViewDialog.extend({
this._super.apply(this, arguments);
_.defaults(this.options, { initial_view: 'search' });
this.on_selected = this.options.on_selected || (function () {});
+ this.on_closed = this.options.on_closed || (function () {});
this.initialIDs = this.options.initial_ids;
},
@@ -411,6 +412,10 @@ var SelectCreateDialog = ViewDialog.extend({
return fragment;
});
},
+ close: function () {
+ this._super.apply(this, arguments);
+ this.on_closed();
+ },
create_edit_record: function () {
var self = this;
var dialog = new FormViewDialog(this, _.extend({}, this.options, {
diff --git a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
index 419d7c5a4c7..bbff40709c7 100644
--- a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
+++ b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js
@@ -566,6 +566,81 @@ QUnit.module('fields', {}, function () {
form.destroy();
});
+ QUnit.test("many2one doesn't trigger field_change when being emptied", async function (assert) {
+ assert.expect(2);
+
+ const list = await createView({
+ arch: `
+ <tree editable="top">
+ <field name="trululu"/>
+ </tree>`,
+ data: this.data,
+ model: 'partner',
+ View: ListView,
+ });
+
+ // Select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ await testUtils.dom.click(list.$('.o_data_row:first() .o_data_cell:first()'));
+
+ const $input = list.$('.o_field_widget[name=trululu] input');
+
+ await testUtils.fields.editInput($input, "");
+ await testUtils.dom.triggerEvents($input, ['keyup']);
+
+ assert.containsNone(document.body, '.modal',
+ "No save should be triggered when removing value");
+
+ await testUtils.fields.many2one.clickOpenDropdown('trululu');
+ await testUtils.fields.many2one.clickHighlightedItem('trululu');
+
+ assert.containsOnce(document.body, '.modal',
+ "Saving should be triggered when selecting a value");
+ await testUtils.dom.click($('.modal .btn-primary'));
+
+ list.destroy();
+ });
+
+ QUnit.test("focus tracking on a many2one in a list", async function (assert) {
+ assert.expect(4);
+
+ const list = await createView({
+ arch: '<tree editable="top"><field name="trululu"/></tree>',
+ archs: {
+ 'partner,false,form': '<form string="Partners"><field name="foo"/></form>',
+ },
+ data: this.data,
+ model: 'partner',
+ View: ListView,
+ });
+
+ // Select two records
+ await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input'));
+ await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input'));
+
+ await testUtils.dom.click(list.$('.o_data_row:first() .o_data_cell:first()'));
+
+ const input = list.$('.o_data_row:first() .o_data_cell:first() input')[0];
+
+ assert.strictEqual(document.activeElement, input, "Input should be focused when activated");
+
+ await testUtils.fields.many2one.clickOpenDropdown('trululu');
+ await testUtils.fields.many2one.clickItem('trululu', 'Create');
+
+ // At this point, if the focus is correctly registered by the m2o, there
+ // should be only one modal (the "Create" one) and none for saving changes.
+ assert.containsOnce(document.body, '.modal', "There should be only one modal");
+
+ await testUtils.dom.click($('.modal .btn:not(.btn-primary)'));
+
+ assert.strictEqual(document.activeElement, input, "Input should be focused after dialog closes");
+ assert.strictEqual(input.value, "", "Input should be empty after discard");
+
+ list.destroy();
+ });
+
QUnit.test('many2one fields with option "no_open"', async function (assert) {
assert.expect(3);