From 10f43955d81ff3e7b5ab050656b312ec19f927c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Thu, 16 Aug 2018 12:39:26 +0200 Subject: [PATCH] [FIX] web: render JournalDashboardGraph when in DOM The lib 'nvd3' renders graph when they are attached to the DOM. However, widgets are rendered in fragments and appended to the DOM when ready (to prevent flickering). Before this commit, we used a `setTimeout(0)` and cross fingers that the widget was attached to the DOM when the `render` method was called. With this commit, the render method is called when it is attached to the DOM. Task-ID 1868252 Closes #26410 --- .../web/static/src/js/fields/basic_fields.js | 35 +++++++++++++ .../src/js/views/kanban/kanban_record.js | 12 +++++ .../src/js/views/kanban/kanban_renderer.js | 7 +++ .../static/tests/fields/basic_fields_tests.js | 52 +++++++++++++++++++ 4 files changed, 106 insertions(+) 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);