[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.
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user