[IMP] web: FieldMany2One: improve focusout case

Before this commit, when the user entered some text in a many2one
and then focused out (e.g. clicked outside), a dialog was shown to
ask him if he wanted to create such a value, no matter what he
wrote in the input.

After this commit, if the search triggered by the text he entered
returned records, the first one is automatically selected when the
focus is lost. However, if it matches no record, the dialog still
opens. The text in the dialog has been slightly reworded as well.

Task 2372480
Closes #61455

closes odoo/odoo#62677

Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
This commit is contained in:
Aaron Bohy
2020-12-02 09:10:24 +00:00
parent 53f877bf38
commit 1d4d2a6831
3 changed files with 55 additions and 34 deletions
@@ -54,17 +54,7 @@ var M2ODialog = Dialog.extend({
this.trigger_up('quick_create', { value: this.value });
},
}, {
text: _t('Create and edit'),
classes: 'btn-primary',
close: true,
click: function () {
this.trigger_up('search_create_popup', {
view_type: 'form',
value: this.value,
});
},
}, {
text: _t('Cancel'),
text: _t('Discard'),
close: true,
}],
});
@@ -96,7 +86,6 @@ var FieldMany2One = AbstractField.extend({
'closed_unset': '_onDialogClosedUnset',
'field_changed': '_onFieldChanged',
'quick_create': '_onQuickCreate',
'search_create_popup': '_onSearchCreatePopup',
}),
events: _.extend({}, AbstractField.prototype.events, {
'click input': '_onInputClick',
@@ -146,6 +135,9 @@ var FieldMany2One = AbstractField.extend({
// Add default search method for M20 (name_search)
this._addAutocompleteSource(this._search, {placeholder: _t('Loading...'), order: 1});
// list of last autocomplete suggestions
this.suggestions = [];
// use a DropPrevious to properly handle related record quick creations,
// and store a createDef to be able to notify the environment that there
// is pending quick create operation
@@ -260,6 +252,7 @@ var FieldMany2One = AbstractField.extend({
}
this.$input.autocomplete({
source: function (req, resp) {
self.suggestions = [];
_.each(self._autocompleteSources, function (source) {
// Resets the results for this source
source.results = [];
@@ -274,7 +267,8 @@ var FieldMany2One = AbstractField.extend({
Promise.resolve(source.method.call(self, search)).then(function (results) {
source.results = results;
source.loading = false;
resp(self._concatenateAutocompleteResults());
self.suggestions = self._concatenateAutocompleteResults();
resp(self.suggestions);
});
}
});
@@ -771,7 +765,13 @@ var FieldMany2One = AbstractField.extend({
* @private
*/
_onInputFocusout: function () {
if (this.can_create && this.floating) {
if (!this.floating) {
return;
}
const firstValue = this.suggestions.find(s => s.id);
if (firstValue) {
this.reinitialize(firstValue.id);
} else if (this.can_create) {
new M2ODialog(this, this.string, this.$input.val()).open();
}
},
@@ -827,14 +827,6 @@ var FieldMany2One = AbstractField.extend({
_onQuickCreate: function (event) {
this._quickCreate(event.data.value);
},
/**
* @private
* @param {OdooEvent} event
*/
_onSearchCreatePopup: function (event) {
var data = event.data;
this._searchCreatePopup(data.view_type, false, this._createContext(data.value));
},
});
var Many2oneBarcode = FieldMany2One.extend({
+1 -1
View File
@@ -1652,7 +1652,7 @@
</t>
<t t-name="M2ODialog">
<div>
Do you want to create <strong t-esc="widget.value"/> as a new <t t-esc="widget.name"/>?
Create <strong t-esc="widget.value"/> as a new <t t-esc="widget.name"/>?
</div>
</t>
<t t-name="FieldMany2ManyCheckBoxes">
@@ -2265,7 +2265,7 @@ QUnit.module('fields', {}, function () {
await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'),
'new partner', ['keyup', 'blur']);
await testUtils.dom.click($('.modal .modal-footer .btn-primary').first());
assert.strictEqual($('.modal .modal-body').text().trim(), "Do you want to create new partner as a new Product?");
assert.strictEqual($('.modal .modal-body').text().trim(), "Create new partner as a new Product?");
form.destroy();
});
@@ -2365,19 +2365,23 @@ QUnit.module('fields', {}, function () {
},
});
// cancel the many2one creation with Cancel button
form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur');
// cancel the many2one creation with Discard button
form.$('.o_field_many2one input').focus().val('new product').trigger('input').trigger('keyup');
await testUtils.nextTick();
form.$('.o_field_many2one input').trigger('blur');
await testUtils.nextTick();
assert.strictEqual($('.modal').length, 1, "there should be one opened modal");
await testUtils.dom.click($('.modal .modal-footer .btn:contains(Cancel)'));
await testUtils.dom.click($('.modal .modal-footer .btn:contains(Discard)'));
assert.strictEqual($('.modal').length, 0, "the modal should be closed");
assert.strictEqual(form.$('.o_field_many2one input').val(), "",
'the many2one should not set a value as its creation has been cancelled (with Cancel button)');
// cancel the many2one creation with Close button
await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'),
'new product', ['keyup', 'blur']);
form.$('.o_field_many2one input').focus().val('new product').trigger('input').trigger('keyup');
await testUtils.nextTick();
form.$('.o_field_many2one input').trigger('blur');
await testUtils.nextTick();
assert.strictEqual($('.modal').length, 1, "there should be one opened modal");
await testUtils.dom.click($('.modal .modal-header button'));
assert.strictEqual(form.$('.o_field_many2one input').val(), "",
@@ -2389,21 +2393,24 @@ QUnit.module('fields', {}, function () {
await testUtils.fields.many2one.clickItem('product_id','o');
assert.strictEqual(form.$('.o_field_many2one input').val(), "xphone", "should have selected xphone");
form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur');
form.$('.o_field_many2one input').focus().val('new product').trigger('input').trigger('keyup');
await testUtils.nextTick();
form.$('.o_field_many2one input').trigger('blur');
await testUtils.nextTick();
assert.strictEqual($('.modal').length, 1, "there should be one opened modal");
await testUtils.dom.click($('.modal .modal-footer .btn:contains(Cancel)'));
await testUtils.dom.click($('.modal .modal-footer .btn:contains(Discard)'));
assert.strictEqual(form.$('.o_field_many2one input').val(), "xphone",
'should have restored the many2one with its previous selected value (xphone)');
// confirm the many2one creation
form.$('.o_field_many2one input').focus().val('new partner').trigger('keyup').trigger('blur');
form.$('.o_field_many2one input').focus().val('new product').trigger('input').trigger('keyup');
await testUtils.nextTick();
form.$('.o_field_many2one input').trigger('blur');
await testUtils.nextTick();
assert.strictEqual($('.modal').length, 1, "there should be one opened modal");
await testUtils.dom.click($('.modal .modal-footer .btn-primary:contains(Create and edit)'));
await testUtils.nextTick();
await testUtils.dom.click($('.modal .modal-footer .btn-primary:contains(Create)'));
assert.strictEqual($('.modal .o_form_view').length, 1,
'a new modal should be opened and contain a form view');
@@ -2412,6 +2419,28 @@ QUnit.module('fields', {}, function () {
form.destroy();
});
QUnit.test("select a many2one value by focusing out", async function (assert) {
assert.expect(3);
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `<form><field name="product_id"/></form>`,
});
form.$('.o_field_many2one input').focus().val('xph').trigger('input').trigger('keyup');
await testUtils.nextTick();
form.$('.o_field_many2one input').trigger('blur');
await testUtils.nextTick();
assert.containsNone(document.body, '.modal');
assert.strictEqual(form.$('.o_field_many2one input').val(), 'xphone');
assert.containsOnce(form, '.o_external_button');
form.destroy();
});
QUnit.test('no_create option on a many2one', async function (assert) {
assert.expect(1);