Skip to content
Permalink
Browse files

[FIX] web: kanban: rendering of async widgets

This rev. fixes 3 issues with async (field) widgets in the kanban
view.

From rev. 5faec34, widget'$el doesn't exist before start. This
means that for async widgets, one can't interact with the widget's
$el right after calling appentTo. In KanbanRecord, this is exactly
what we did for both field widgets (<field name=.../>) and widgets
(<widget name=.../>).

Moreover, when the kanban view was updated (e.g. when the user
refined the search using the search view), and the rendering was
async (because of the presence of an async widget), the renderer
didn't wait at all for the widget to be ready before updating the
view. This caused flickering, mostly, but also a crash in
accounting with the JournalDashboardGraph widget (because
on_attach_callabck was called before the widget was ready). To
reproduce this particular issue, go to accounting, add a filter
that doesn't match any record and save it as favorite (default),
press F5 (no record is displayed), remove the filter.

opw-1925079
opw-1925479

Fixes #30087
Closes #31254

closes #31327
  • Loading branch information...
aab-odoo committed Feb 21, 2019
1 parent 6fc08a8 commit 7ffe3ce5c63f01fdb95342a8a821a731f4812fd6
@@ -300,6 +300,7 @@ var KanbanRecord = Widget.extend({
// field's widgets point of view
// that dict being shared between records, we don't modify it
// in place
var self = this;
var attrs = Object.create(null);
_.each(this.fieldsInfo[field_name], function (value, key) {
if (_.str.startsWith(key, 't-att-')) {
@@ -310,11 +311,12 @@ var KanbanRecord = Widget.extend({
});
var options = _.extend({}, this.options, {attrs: attrs});
var widget = new Widget(this, field_name, this.state, options);
var def = widget.replace($field);
var def = widget.replace($field).then(function () {
self._setFieldDisplay(widget.$el, field_name);
});
if (def.state() === 'pending') {
this.defs.push(def);
}
this._setFieldDisplay(widget.$el, field_name);
return widget;
},
_processWidgets: function () {
@@ -324,12 +326,13 @@ var KanbanRecord = Widget.extend({
var Widget = widgetRegistry.get($field.attr('name'));
var widget = new Widget(self, self.state);

var def = widget._widgetRenderAndInsert(function () {});
var def = widget._widgetRenderAndInsert(function () {}).then(function () {
widget.$el.addClass('o_widget');
$field.replaceWith(widget.$el);
});
if (def.state() === 'pending') {
self.defs.push(def);
}
widget.$el.addClass('o_widget');
$field.replaceWith(widget.$el);
});
},
/**
@@ -244,7 +244,6 @@ var KanbanRenderer = BasicRenderer.extend({
*/
updateState: function (state) {
this._setState(state);
this._toggleNoContentHelper();
return this._super.apply(this, arguments);
},

@@ -382,11 +381,8 @@ var KanbanRenderer = BasicRenderer.extend({
var self = this;
var oldWidgets = this.widgets;
this.widgets = [];
this.$el.empty();

var isGrouped = !!this.state.groupedBy.length;
this.$el.toggleClass('o_kanban_grouped', isGrouped);
this.$el.toggleClass('o_kanban_ungrouped', !isGrouped);
var fragment = document.createDocumentFragment();
// render the kanban view
this.defs = [];
@@ -395,15 +391,19 @@ var KanbanRenderer = BasicRenderer.extend({
} else {
this._renderUngrouped(fragment);
}
this.$el.append(fragment);
this._toggleNoContentHelper();
var defs = this.defs;
return this._super.apply(this, arguments).then(function () {
delete this.defs;
defs.push(this._super.apply(this, arguments));
return $.when.apply($, defs).then(function () {
_.invoke(oldWidgets, 'destroy');
self.$el.empty();
self.$el.toggleClass('o_kanban_grouped', isGrouped);
self.$el.toggleClass('o_kanban_ungrouped', !isGrouped);
self.$el.append(fragment);
self._toggleNoContentHelper();
if (self._isInDom) {
_.invoke(self.widgets, 'on_attach_callback');
}
return $.when.apply(null, defs);
});
},
/**
@@ -5133,6 +5133,116 @@ QUnit.module('Views', {
delete fieldRegistry.map.asyncWidget;
});

QUnit.test('asynchronous rendering of a field widget with display attr', function (assert) {
assert.expect(3);

var fooFieldDef = $.Deferred();
var FieldChar = fieldRegistry.get('char');
fieldRegistry.add('asyncwidget', FieldChar.extend({
willStart: function () {
return fooFieldDef;
},
start: function () {
this.$el.html('LOADED');
},
}));

var kanbanController;
testUtils.createAsyncView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><field name="foo" display="right" widget="asyncwidget"/></div>' +
'</t></templates></kanban>',
}).then(function (kanban) {
kanbanController = kanban;
});

assert.containsNone(document.body, '.o_kanban_record');

fooFieldDef.resolve();
assert.strictEqual(kanbanController.$('.o_kanban_record').text(),
"LOADEDLOADEDLOADEDLOADED");
assert.hasClass(kanbanController.$('.o_kanban_record:first .o_field_char'), 'float-right');

kanbanController.destroy();
delete fieldRegistry.map.asyncWidget;
});

QUnit.test('asynchronous rendering of a widget', function (assert) {
assert.expect(2);

var widgetDef = $.Deferred();
widgetRegistry.add('asyncwidget', Widget.extend({
willStart: function () {
return widgetDef;
},
start: function () {
this.$el.html('LOADED');
},
}));

var kanbanController;
testUtils.createAsyncView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><widget name="asyncwidget"/></div>' +
'</t></templates></kanban>',
}).then(function (kanban) {
kanbanController = kanban;
});

assert.containsNone(document.body, '.o_kanban_record');

widgetDef.resolve();
assert.strictEqual(kanbanController.$('.o_kanban_record .o_widget').text(),
"LOADEDLOADEDLOADEDLOADED");

kanbanController.destroy();
delete widgetRegistry.map.asyncWidget;
});

QUnit.test('update kanban with asynchronous field widget', function (assert) {
assert.expect(3);

var fooFieldDef = $.Deferred();
var FieldChar = fieldRegistry.get('char');
fieldRegistry.add('asyncwidget', FieldChar.extend({
willStart: function () {
return fooFieldDef;
},
start: function () {
this.$el.html('LOADED');
},
}));

var kanban = testUtils.createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
'<div><field name="foo" widget="asyncwidget"/></div>' +
'</t></templates></kanban>',
domain: [['id', '=', '0']], // no record matches this domain
});

assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)');

kanban.update({domain: []}); // this rendering will be async

assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)');

fooFieldDef.resolve();

assert.strictEqual(kanban.$('.o_kanban_record').text(),
"LOADEDLOADEDLOADEDLOADED");

kanban.destroy();
delete widgetRegistry.map.asyncWidget;
});
});

});

0 comments on commit 7ffe3ce

Please sign in to comment.
You can’t perform that action at this time.