[FIX] web: BasicRenderer: don't reset this.defs too soon

The 'defs' attribute of the BasicRenderer is used to push the
rendering deferred of asynchronous fields. It is set to an empty
array before the rendering, and for each asynchronous field, its
deferred is pushed in the array, so that the renderer can wait for
all its fields to be rendered (no flickering).

The function '_applyModifiers' is called during the whole view
rendering (for each field in the view), and when some fields
change, as the modifiers are then re-evaluated.

As it is called during the whole view rendering, it can't delete
the 'defs' attribute, otherwise it will crash if one of the
remaining fields in the view uses an asynchronous widget (trying
to push a deferred in an undefined variable).

This rev. moves the deletion of 'defs' to the function that is
called when all modifiers are re-evaluated and applied.
This commit is contained in:
Aaron Bohy
2017-04-20 13:12:50 +02:00
parent 6a5f18f970
commit e61454f148
2 changed files with 28 additions and 9 deletions
@@ -176,23 +176,17 @@ var BasicRenderer = AbstractRenderer.extend({
* @param {Object} modifiersData
* @param {Object} record
* @param {Object} [element] - do the update only on this element if given
* @returns {Deferred} resolved once finished
*/
_applyModifiers: function (modifiersData, record, element) {
var self = this;
var defs = [];
var modifiers = modifiersData.evaluatedModifiers[record.id] || {};
this.defs = defs; // Potentially filled by widget rerendering
if (element) {
_apply(element);
} else {
// Clone is necessary as the list might change during _.each
_.each(_.clone(modifiersData.elementsByRecord[record.id]), _apply);
}
delete this.defs;
return $.when.apply($, defs);
function _apply(element) {
// If the view is in edit mode and that a widget have to switch
@@ -540,10 +534,13 @@ var BasicRenderer = AbstractRenderer.extend({
_updateAllModifiers: function (record) {
var self = this;
var defs = _.flatten(_.map(this.allModifiersData, function (modifiersData) {
var defs = [];
this.defs = defs; // Potentially filled by widget rerendering
_.each(this.allModifiersData, function (modifiersData) {
self._computeModifiers(modifiersData, record);
return self._applyModifiers(modifiersData, record);
}));
self._applyModifiers(modifiersData, record);
});
delete this.defs;
return $.when.apply($, defs);
},
@@ -3112,5 +3112,27 @@ QUnit.module('Views', {
"the stat button should contain a span with the string attribute value");
form.destroy();
});
QUnit.test('renderer waits for asynchronous fields rendering', function (assert) {
assert.expect(1);
var done = assert.async();
testUtils.createAsyncView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="bar"/>' +
'<field name="foo" widget="ace"/>' +
'<field name="int_field"/>' +
'</form>',
res_id: 1,
}).then(function (form) {
assert.strictEqual(form.$('.ace_editor').length, 1,
"should have waited for ace to load its dependencies");
form.destroy();
done();
});
});
});
});