[FIX] web: FieldMany2One: don't re-render if dirty
The 'main_flow_tour' noticed an error that we
had already corrected for the input fields.
See here: @a71f2237
In fact the same error can occur in the many2one fields
for the exact same reason.
It's why the 'main_flow_tour' crashed at the following step:
".ui-menu-item > a:contains('the_flow.customer')"
This commit is contained in:
@@ -95,6 +95,7 @@ var FieldMany2One = AbstractField.extend({
|
||||
template: 'FieldMany2One',
|
||||
custom_events: _.extend({}, AbstractField.prototype.custom_events, {
|
||||
'closed_unset': '_onDialogClosedUnset',
|
||||
'field_changed': '_onFieldChanged',
|
||||
'quick_create': '_onQuickCreate',
|
||||
'search_create_popup': '_onSearchCreatePopup',
|
||||
}),
|
||||
@@ -124,6 +125,12 @@ var FieldMany2One = AbstractField.extend({
|
||||
// 'recordParams' is a dict of params used when calling functions
|
||||
// 'getDomain' and 'getContext' on this.record
|
||||
this.recordParams = {fieldName: this.name, viewType: this.viewType};
|
||||
// We need to know if the widget is dirty (i.e. if the user has changed
|
||||
// the value, and those changes haven't been acknowledged yet by the
|
||||
// environment), to prevent erasing that new value on a reset (e.g.
|
||||
// coming by an onchange on another field)
|
||||
this._isDirty = false;
|
||||
this.lastChangeEvent = undefined;
|
||||
},
|
||||
start: function () {
|
||||
// booleean indicating that the content of the input isn't synchronized
|
||||
@@ -151,9 +158,31 @@ var FieldMany2One = AbstractField.extend({
|
||||
* TODO
|
||||
*/
|
||||
reinitialize: function (value) {
|
||||
this.isDirty = false;
|
||||
this.floating = false;
|
||||
this._setValue(value);
|
||||
},
|
||||
/**
|
||||
* Re-renders the widget if it isn't dirty. The widget is dirty if the user
|
||||
* changed the value, and that change hasn't been acknowledged yet by the
|
||||
* environment. For example, another field with an onchange has been updated
|
||||
* and this field is updated before the onchange returns. Two '_setValue'
|
||||
* are done (this is sequential), the first one returns and this widget is
|
||||
* reset. However, it has pending changes, so we don't re-render.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
reset: function (record, event) {
|
||||
this._reset(record, event);
|
||||
if (!event || event === this.lastChangeEvent) {
|
||||
this.isDirty = false;
|
||||
}
|
||||
if (this.isDirty) {
|
||||
return $.when();
|
||||
} else {
|
||||
return this._render();
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -237,6 +266,16 @@ var FieldMany2One = AbstractField.extend({
|
||||
_getDisplayName: function (value) {
|
||||
return value.split('\n')[0];
|
||||
},
|
||||
/**
|
||||
* Listens to events 'field_changed' to keep track of the last event that
|
||||
* has been trigerred. This allows to detect that all changes have been
|
||||
* acknowledged by the environment.
|
||||
*
|
||||
* @param {OdooEvent} event 'field_changed' event
|
||||
*/
|
||||
_onFieldChanged: function (event) {
|
||||
this.lastChangeEvent = event;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} name
|
||||
@@ -475,6 +514,7 @@ var FieldMany2One = AbstractField.extend({
|
||||
* @private
|
||||
*/
|
||||
_onDialogClosedUnset: function () {
|
||||
this._isDirty = false;
|
||||
this.floating = false;
|
||||
this._render();
|
||||
},
|
||||
@@ -543,6 +583,7 @@ var FieldMany2One = AbstractField.extend({
|
||||
// confirmation that the many2one is not properly set.
|
||||
return;
|
||||
}
|
||||
this.isDirty = true;
|
||||
if (this.$input.val() === "") {
|
||||
this.reinitialize(false);
|
||||
} else if (this._getDisplayName(this.m2o_value) !== this.$input.val()) {
|
||||
|
||||
@@ -6329,6 +6329,63 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('one2many field: change value before pending onchange returns', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(2);
|
||||
|
||||
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
|
||||
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
|
||||
|
||||
this.data.partner.onchanges = {
|
||||
int_field: function () {}
|
||||
};
|
||||
var def;
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<field name="p">' +
|
||||
'<tree editable="bottom">' +
|
||||
'<field name="int_field"/>' +
|
||||
'<field name="trululu"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</form>',
|
||||
mockRPC: function (route, args) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (args.method === 'onchange') {
|
||||
// delay the onchange RPC
|
||||
return $.when(def).then(_.constant(result));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
form.$('.o_field_x2many_list_row_add a').click();
|
||||
def = $.Deferred();
|
||||
form.$('.o_field_widget[name=int_field]')
|
||||
.val('44')
|
||||
.trigger('input');
|
||||
|
||||
var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
|
||||
// set trululu before onchange
|
||||
form.$('.o_field_many2one input').val('first').trigger('keydown').trigger('keyup');
|
||||
// complete the onchange
|
||||
def.resolve();
|
||||
assert.strictEqual(form.$('.o_field_many2one input').val(), 'first',
|
||||
'should have kept the new value');
|
||||
concurrency.delay(0).then(function () {
|
||||
// check name_search result
|
||||
assert.strictEqual($dropdown.find('li:not(.o_m2o_dropdown_option)').length, 1,
|
||||
'autocomplete should contains 1 suggestion');
|
||||
|
||||
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
|
||||
form.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('one2many with default value: edit line to make it invalid', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user