diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 9bf432c97fc..71b2a14c434 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -2379,6 +2379,25 @@ var JournalDashboardGraph = AbstractField.extend({ } this._super.apply(this, arguments); }, + /** + * The widget view uses the nv(d3) lib to render the graph. This lib + * requires that the rendering is done directly into the DOM (so that it can + * correctly compute positions). However, the views are always rendered in + * fragments, and appended to the DOM once ready (to prevent them from + * flickering). We here use the on_attach_callback hook, called when the + * widget is attached to the DOM, to perform the rendering. This ensures + * that the rendering is always done in the DOM. + */ + on_attach_callback: function () { + this._isInDOM = true; + this._renderInDOM(); + }, + /** + * Called when the field is detached from the DOM. + */ + on_detach_callback: function () { + this._isInDOM = false; + }, //-------------------------------------------------------------------------- // Private @@ -2399,9 +2418,25 @@ var JournalDashboardGraph = AbstractField.extend({ } }, /** + * Render the widget only when it is in the DOM. This is because nvd3 + * renders graph only when it is in DOM, apparently to compute available + * height and width for instance. + * + * @override * @private */ _render: function () { + if (this._isInDOM) { + return this._renderInDOM(); + } + return $.when(); + }, + /** + * Render the widget. This function assumes that it is attached to the DOM. + * + * @private + */ + _renderInDOM: function () { // note: the rendering of this widget is aynchronous as nvd3 does a // setTimeout(0) before executing the callback given to addGraph var self = this; diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js index 627543b3422..9be296e3e31 100644 --- a/addons/web/static/src/js/views/kanban/kanban_record.js +++ b/addons/web/static/src/js/views/kanban/kanban_record.js @@ -65,6 +65,18 @@ var KanbanRecord = Widget.extend({ start: function () { return $.when(this._super.apply(this, arguments), this._render()); }, + /** + * Called each time the record is attached to the DOM. + */ + on_attach_callback: function () { + _.invoke(this.subWidgets, 'on_attach_callback'); + }, + /** + * Called each time the record is detached from the DOM. + */ + on_detach_callback: function () { + _.invoke(this.subWidgets, 'on_detach_callback'); + }, //-------------------------------------------------------------------------- // Public diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index 2e8a9787ac1..c51ac945de1 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -122,10 +122,17 @@ var KanbanRenderer = BasicRenderer.extend({ * Called each time the renderer is attached into the DOM. */ on_attach_callback: function () { + _.invoke(this.widgets, 'on_attach_callback'); if (this.quickCreate) { this.quickCreate.on_attach_callback(); } }, + /** + * Called each time the renderer is detached from the DOM. + */ + on_detach_callback: function () { + _.invoke(this.widgets, 'on_detach_callback'); + }, //-------------------------------------------------------------------------- // Public diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index 63e677f367d..68643f7b48d 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -14,6 +14,7 @@ var testUtils = require('web.test_utils'); var createView = testUtils.createView; var createAsyncView = testUtils.createAsyncView; var DebouncedField = basicFields.DebouncedField; +var JournalDashboardGraph = basicFields.JournalDashboardGraph; var _t = core._t; QUnit.module('fields', {}, function () { @@ -2009,6 +2010,57 @@ QUnit.module('basic_fields', { }, }); + QUnit.test('graph dashboard widget attach/detach callbacks', function (assert) { + // This widget is rendered with nvd3, and nvd3 renders the graphs when + // the svg is in the DOM. The intent of this test is to determine when + // the field widgets are in the DOM, so that we can tell nvd3 to render + // them. + var done = assert.async(); + assert.expect(6); + + testUtils.patch(JournalDashboardGraph, { + on_attach_callback: function () { + assert.step('on_attach_callback'); + }, + on_detach_callback: function () { + assert.step('on_detach_callback'); + }, + }); + + createAsyncView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '' + + '' + + '' + + '
' + + '' + + '
' + + '
' + + '
', + domain: [['id', 'in', [1, 2]]], + }).then(function (kanban) { + assert.verifySteps([ + 'on_attach_callback', + 'on_attach_callback' + ]); + + kanban.on_detach_callback(); + + assert.verifySteps([ + 'on_attach_callback', + 'on_attach_callback', + 'on_detach_callback', + 'on_detach_callback' + ]); + + kanban.destroy(); + testUtils.unpatch(JournalDashboardGraph); + done(); + }); + }); + QUnit.test('graph dashboard widget is rendered correctly', function (assert) { var done = assert.async(); assert.expect(4);