details: https://code.tryton.org/tryton/commit/2e35abbf0617
branch: 7.0
user: Cédric Krier <[email protected]>
date: Tue Aug 25 13:09:25 2026 +0200
description:
Ensure it is still the same record when executing the display callback
of a widget
When a widget display is asynchronous, we must ensure that it is still
the same
record that is being displayed when the callback is finally executed.
Otherwise
it may render the data of a different record in the HTML.
Closes #15041
(grafted from dbd215ffb07150c2f476922bd7c66dc896ed2665)
diffstat:
sao/src/view/form.js | 82 +++++++++++++++++++++++++++++++++++++--------------
1 files changed, 59 insertions(+), 23 deletions(-)
diffs (189 lines):
diff -r fbd3ddd36ceb -r 2e35abbf0617 sao/src/view/form.js
--- a/sao/src/view/form.js Fri Aug 21 10:04:05 2026 +0200
+++ b/sao/src/view/form.js Tue Aug 25 13:09:25 2026 +0200
@@ -2156,14 +2156,22 @@
Sao.common.selection_mixin.init_selection.call(this, key,
this.set_selection.bind(this));
},
- update_selection: function(record, field, callbak) {
+ update_selection: function(record, field, callback) {
+ let deferred = jQuery.Deferred();
Sao.common.selection_mixin.update_selection.call(this, record,
field, (selection, help) => {
this.set_selection(selection, help);
- if (callbak) {
- callbak(selection, help);
+ let prm;
+ if (callback) {
+ prm = callback(selection, help);
+ }
+ if (prm) {
+ prm.always(deferred.resolve);
+ } else {
+ deferred.resolve();
}
});
+ return deferred;
},
set_selection: function(selection, help) {
var select = this.select;
@@ -2177,9 +2185,13 @@
}
},
display_update_selection: function() {
- var record = this.record;
- var field = this.field;
- this.update_selection(record, field, (selection, help) => {
+ let record = this.record,
+ field = this.field;
+ return this.update_selection(record, field, (selection, help) => {
+ if (record !== this.record) {
+ return;
+ }
+
if (!field) {
this.select.val('');
return;
@@ -2195,7 +2207,7 @@
if (!found) {
prm = Sao.common.selection_mixin.get_inactive_selection
.call(this, value);
- prm.done(inactive => {
+ prm = prm.then(inactive => {
this.select.append(jQuery('<option/>', {
value: JSON.stringify(inactive[0]),
text: inactive[1],
@@ -2205,7 +2217,7 @@
} else {
prm = jQuery.when();
}
- prm.done(() => {
+ return prm.then(() => {
this.select.val(JSON.stringify(value));
var title = help[value] || null;
if (this.attributes.help && title) {
@@ -2217,7 +2229,7 @@
},
display: function() {
Sao.View.Form.Selection._super.display.call(this);
- this.display_update_selection();
+ return this.display_update_selection();
},
focus: function() {
this.select.focus();
@@ -2980,13 +2992,21 @@
this.set_selection.bind(this));
},
update_selection: function(record, field, callback) {
+ let deferred = jQuery.Deferred();
Sao.common.selection_mixin.update_selection.call(this, record,
field, (selection, help) => {
this.set_selection(selection, help);
+ let prm;
if (callback) {
- callback();
+ prm = callback(help);
+ }
+ if (prm) {
+ prm.always(deferred.resolve);
+ } else {
+ deferred.resolve();
}
});
+ return deferred;
},
set_selection: function(selection, help) {
var select = this.select;
@@ -3109,7 +3129,11 @@
}
},
display: function() {
- this.update_selection(this.record, this.field, () => {
+ let record = this.record;
+ return this.update_selection(this.record, this.field, () => {
+ if (record !== this.record) {
+ return;
+ }
Sao.View.Form.Reference._super.display.call(this);
});
},
@@ -3454,13 +3478,17 @@
display: function() {
Sao.View.Form.One2Many._super.display.call(this);
- let display = function() {
+ let record = this.record,
+ field = this.field;
+
+ let display = () => {
+ if (record !== this.record) {
+ return;
+ }
+
this._set_button_sensitive();
- var record = this.record;
- var field = this.field;
-
- if (!field) {
+ if (!field) {
this.screen.new_group();
this.screen.current_record = null;
this.screen.group.parent = null;
@@ -3500,7 +3528,7 @@
.css('max-height', this.attributes.height + 'px');
}
return this.screen.display();
- }.bind(this);
+ };
if (this.prm.state() == 'pending') {
return this.prm.then(() => display());
@@ -3985,9 +4013,13 @@
display: function() {
Sao.View.Form.Many2Many._super.display.call(this);
- let display = function() {
- var record = this.record;
- var field = this.field;
+ let record = this.record,
+ field = this.field;
+
+ let display = () => {
+ if (record !== this.record) {
+ return;
+ }
if (!field) {
this.screen.new_group();
@@ -4006,7 +4038,7 @@
.css('max-height', this.attributes.height + 'px');
}
return this.screen.display();
- }.bind(this);
+ };
if (this.prm.state() == 'pending') {
return this.prm.then(() => display());
@@ -5010,8 +5042,8 @@
display: function() {
Sao.View.Form.Dict._super.display.call(this);
- var record = this.record;
- var field = this.field;
+ let record = this.record,
+ field = this.field;
if (!field) {
return;
}
@@ -5037,6 +5069,10 @@
prm = jQuery.when();
}
prm.then(() => {
+ if (record !== this.record) {
+ return;
+ }
+
var i, len, key;
var keys = Object.keys(value)
.filter(function(key) {