summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron Bohy <aab@odoo.com>2017-07-25 13:27:07 +0200
committerAaron Bohy <aab@odoo.com>2017-07-27 10:59:08 +0200
commit3d6420d4b3f062f12d7c18d7d9a976e2299b3444 (patch)
treebce34f977c8c068296f98247b4c0e9ab7069c5f3
parent98d3a08ebb38e5943310b5bf8e3847f0afbba02e (diff)
[FIX] web,*: display dashboard panel if no content
*board,crm Before this rev., the dashboard panel (e.g. of the sales team dashboard) wasn't displayed when there were no record to display, whereas it should. Only the nocontent helper was displayed. This was a bug introduced with the new view: the views' controller was in charge to display the renderer (if there were data to display) xor the nocontent helper. As the dashboard panel was rendered by the renderer, it couldn't be displayed alongside the nocontent helper. This rev. moves this logic to the renderer. So now, the controller always contains the renderer, which decides what to render (the data or the nocontent helper). This way, the dashboards can display their panel and the nocontent helper.
-rw-r--r--addons/board/static/src/js/dashboard.js1
-rw-r--r--addons/crm/static/tests/sales_team_dashboard_tests.js35
-rw-r--r--addons/web/static/src/js/views/abstract_controller.js27
-rw-r--r--addons/web/static/src/js/views/abstract_renderer.js2
-rw-r--r--addons/web/static/src/js/views/abstract_view.js2
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js11
-rw-r--r--addons/web/static/src/js/views/basic/basic_renderer.js24
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_controller.js24
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer.js106
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js5
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js15
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_controller.js15
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_renderer.js22
-rw-r--r--addons/web/static/tests/views/kanban_tests.js2
14 files changed, 173 insertions, 118 deletions
diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js
index 98d169c11e2..3c40524e12c 100644
--- a/addons/board/static/src/js/dashboard.js
+++ b/addons/board/static/src/js/dashboard.js
@@ -20,7 +20,6 @@ FormView.include({
*/
init: function (viewInfo) {
this._super.apply(this, arguments);
- this.rendererParams.noContentHelp = this.controllerParams.noContentHelp;
this.controllerParams.viewID = viewInfo.view_id;
},
});
diff --git a/addons/crm/static/tests/sales_team_dashboard_tests.js b/addons/crm/static/tests/sales_team_dashboard_tests.js
index d4da44e16a7..a4430d29a52 100644
--- a/addons/crm/static/tests/sales_team_dashboard_tests.js
+++ b/addons/crm/static/tests/sales_team_dashboard_tests.js
@@ -133,4 +133,39 @@ QUnit.test('dashboard: click on a button to execute an action', function (assert
kanban.destroy();
});
+QUnit.test('dashboard should be displayed even if there is no content', function (assert) {
+ assert.expect(2);
+
+ var dashboardData = this.dashboard_data;
+ var kanban = createView({
+ View: view_registry.get('sales_team_dashboard'),
+ model: 'partner',
+ data: this.data,
+ arch: '<kanban class="o_kanban_test">' +
+ '<templates><t t-name="kanban-box">' +
+ '<div><field name="foo"/></div>' +
+ '</t></templates>' +
+ '</kanban>',
+ domain: [['id', '=', 239]], // no record will match this domain
+ mockRPC: function (route, args) {
+ if (args.method === 'retrieve_sales_dashboard') {
+ return $.when(dashboardData);
+ }
+ return this._super(route, args);
+ },
+ viewOptions: {
+ action: {
+ help: '<p>A help message</p>',
+ },
+ },
+ });
+
+ assert.strictEqual(kanban.$('div.o_sales_dashboard').length, 1,
+ "should render the dashboard");
+ assert.strictEqual(kanban.$('.oe_view_nocontent:contains(A help message)').length, 1,
+ "should correctly render the nocontent helper");
+
+ kanban.destroy();
+});
+
});
diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js
index b84f6ec1951..3bdf8ab08ca 100644
--- a/addons/web/static/src/js/views/abstract_controller.js
+++ b/addons/web/static/src/js/views/abstract_controller.js
@@ -32,7 +32,6 @@ var AbstractController = Widget.extend({
* @param {string} params.modelName
* @param {any} [params.handle] a handle that will be given to the model (some id)
* @param {any} params.initialState the initialState
- * @param {string} [params.noContentHelp]
*/
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
@@ -42,7 +41,6 @@ var AbstractController = Widget.extend({
this.handle = params.handle;
this.activeActions = params.activeActions;
this.initialState = params.initialState;
- this.noContentHelp = params.noContentHelp;
},
/**
* Simply renders and updates the url.
@@ -224,31 +222,6 @@ var AbstractController = Widget.extend({
this.trigger_up('push_state', state || {});
},
/**
- * Hide or show the nocontent helper. For this, it also remove the renderer
- * from the dom, and reattach it when necessary.
- *
- * This method is a helper for controllers that want to display a help
- * message when no content is available. It is suggested to override
- * _update to call this method.
- *
- * @private
- * @param {boolean} hasNoContent
- */
- _toggleNoContentHelper: function (hasNoContent) {
- if (hasNoContent && this.noContentHelp) {
- this.renderer.$el.detach();
- var $msg = $('<div>')
- .addClass('oe_view_nocontent')
- .html(this.noContentHelp);
- this.$el.html($msg);
- } else {
- if (!document.contains(this.renderer.el)) {
- this.$('div.oe_view_nocontent').remove();
- this.$el.append(this.renderer.$el);
- }
- }
- },
- /**
* This method is called after each update or when the start method is
* completed.
*
diff --git a/addons/web/static/src/js/views/abstract_renderer.js b/addons/web/static/src/js/views/abstract_renderer.js
index 7d10f05eab8..d7eb5002f2d 100644
--- a/addons/web/static/src/js/views/abstract_renderer.js
+++ b/addons/web/static/src/js/views/abstract_renderer.js
@@ -15,11 +15,13 @@ return Widget.extend({
* @param {Widget} parent
* @param {any} state
* @param {Object} params
+ * @param {string} [params.noContentHelp]
*/
init: function (parent, state, params) {
this._super(parent);
this.state = state;
this.arch = params.arch;
+ this.noContentHelp = params.noContentHelp;
},
/**
* The rendering can be asynchronous (but it is not encouraged). The start
diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index e14e064ee33..74e1e1fce18 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -77,6 +77,7 @@ var AbstractView = Class.extend({
init: function (viewInfo, params) {
this.rendererParams = {
arch: viewInfo.arch,
+ noContentHelp: params.action && params.action.help,
};
this.controllerParams = {
@@ -87,7 +88,6 @@ var AbstractView = Class.extend({
delete: viewInfo.arch.attrs.delete ? JSON.parse(viewInfo.arch.attrs.delete) : true,
duplicate: viewInfo.arch.attrs.duplicate ? JSON.parse(viewInfo.arch.attrs.duplicate) : true,
},
- noContentHelp: params.action && params.action.help,
};
this.loadParams = {
diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js
index cc72816a85d..54c8ccfbf51 100644
--- a/addons/web/static/src/js/views/basic/basic_controller.js
+++ b/addons/web/static/src/js/views/basic/basic_controller.js
@@ -362,17 +362,6 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
};
},
/**
- * Used by list and kanban views to determine whether or not to display
- * the no content helper (if there is no data in the state to display)
- *
- * @private
- * @param {Object} state
- * @returns {boolean}
- */
- _hasContent: function (state) {
- return state.count !== 0;
- },
- /**
* Helper function to display a warning that some fields have an invalid
* value. This is used when a save operation cannot be completed.
*
diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js
index 8bbf9ae45a4..e040a5fa7f0 100644
--- a/addons/web/static/src/js/views/basic/basic_renderer.js
+++ b/addons/web/static/src/js/views/basic/basic_renderer.js
@@ -318,6 +318,16 @@ var BasicRenderer = AbstractRenderer.extend({
return _.findWhere(this.allModifiersData, {node: node});
},
/**
+ * Used by list and kanban renderers to determine whether or not to display
+ * the no content helper (if there is no data in the state to display)
+ *
+ * @private
+ * @returns {boolean}
+ */
+ _hasContent: function () {
+ return this.state.count !== 0;
+ },
+ /**
* Registers or updates the modifiers data associated to the given node.
* This method is quiet complex as it handles all the needs of the basic
* renderers:
@@ -487,6 +497,20 @@ var BasicRenderer = AbstractRenderer.extend({
return widget;
},
/**
+ * Renders the nocontent helper.
+ *
+ * This method is a helper for renderers that want to display a help
+ * message when no content is available.
+ *
+ * @private
+ */
+ _renderNoContentHelper: function () {
+ var $msg = $('<div>')
+ .addClass('oe_view_nocontent')
+ .html(this.noContentHelp);
+ this.$el.html($msg);
+ },
+ /**
* Actual rendering. Supposed to be overridden by concrete renderers.
* The basic responsabilities of _renderView are:
* - use the xml arch of the view to render a jQuery representation
diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js
index 2592e3d96ec..2cf6a94285e 100644
--- a/addons/web/static/src/js/views/kanban/kanban_controller.js
+++ b/addons/web/static/src/js/views/kanban/kanban_controller.js
@@ -81,19 +81,6 @@ var KanbanController = BasicController.extend({
return this.renderer.updateRecord(this.model.get(id));
},
/**
- * The nocontent helper should be displayed in kanban:
- * - ungrouped: if there is no records
- * - grouped: if there is no groups and no column quick create
- *
- * @override
- * @private
- */
- _hasContent: function (state) {
- return this._super.apply(this, arguments) ||
- this.createColumnEnabled ||
- (state.groupedBy.length && state.data.length);
- },
- /**
* The column quick create should be displayed in kanban iff grouped by an
* m2o field and group_create action enabled.
*
@@ -138,17 +125,6 @@ var KanbanController = BasicController.extend({
.toggleClass('btn-default', createMuted);
}
},
- /**
- * @override
- * @param {Object} state
- * @returns {Deferred}
- */
- _update: function (state) {
- var hasNoContent = !this._hasContent(state);
- this.$el.toggleClass('o_kanban_nocontent', hasNoContent);
- this._toggleNoContentHelper(hasNoContent);
- return this._super.apply(this, arguments);
- },
//--------------------------------------------------------------------------
// Handlers
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 ea2f0270017..0bbc7aa2698 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js
@@ -100,6 +100,8 @@ var KanbanRenderer = BasicRenderer.extend({
viewType: 'kanban',
});
this.columnOptions = _.extend({}, params.column_options, { qweb: this.qweb });
+
+ this._setState(state);
},
//--------------------------------------------------------------------------
@@ -167,12 +169,32 @@ var KanbanRenderer = BasicRenderer.extend({
record.update(recordState);
}
},
+ /**
+ * @override
+ */
+ updateState: function (state) {
+ this._setState(state);
+ return this._super.apply(this, arguments);
+ },
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
+ * The nocontent helper should be displayed in kanban:
+ * - ungrouped: if there is no records
+ * - grouped: if there is no groups and no column quick create
+ *
+ * @override
+ * @private
+ */
+ _hasContent: function () {
+ return this._super.apply(this, arguments) ||
+ this.createColumnEnabled ||
+ (this.state.groupedBy.length && this.state.data.length);
+ },
+ /**
* Renders empty invisible divs in a document fragment.
*
* @private
@@ -193,28 +215,6 @@ var KanbanRenderer = BasicRenderer.extend({
*/
_renderGrouped: function (fragment) {
var self = this;
- var groupByFieldAttrs = this.state.fields[this.state.groupedBy[0]];
- var groupByFieldInfo = this.state.fieldsInfo.kanban[this.state.groupedBy[0]];
- // Deactivate the drag'n'drop if the groupedBy field:
- // - is a date or datetime since we group by month or
- // - is readonly
- var draggable = true;
- if (groupByFieldAttrs) {
- if (groupByFieldAttrs.type === "date" || groupByFieldAttrs.type === "datetime") {
- draggable = false;
- } else if (groupByFieldAttrs.readonly !== undefined) {
- draggable = !(groupByFieldAttrs.readonly);
- }
- }
- var groupedByM2O = groupByFieldAttrs && (groupByFieldAttrs.type === 'many2one');
- var grouped_by_field = groupedByM2O && groupByFieldAttrs.relation;
- var groupByTooltip = groupByFieldInfo && groupByFieldInfo.options.group_by_tooltip;
- this.columnOptions = _.extend(this.columnOptions, {
- draggable: draggable,
- group_by_tooltip: groupByTooltip,
- grouped_by_m2o: groupedByM2O,
- relation: grouped_by_field,
- });
// Render columns
_.each(this.state.data, function (group) {
@@ -228,7 +228,7 @@ var KanbanRenderer = BasicRenderer.extend({
}
});
- if (groupedByM2O) {
+ if (this.groupedByM2O) {
// Enable column sorting
this.$el.sortable({
axis: 'x',
@@ -249,7 +249,7 @@ var KanbanRenderer = BasicRenderer.extend({
});
// Enable column quickcreate
- if (this.columnOptions.group_creatable) {
+ if (this.createColumnEnabled) {
this.quickCreate = new ColumnQuickCreate(this);
this.quickCreate.appendTo(fragment);
}
@@ -280,20 +280,62 @@ var KanbanRenderer = BasicRenderer.extend({
_renderView: function () {
var oldWidgets = this.widgets;
this.widgets = [];
- var isGrouped = !!this.state.groupedBy.length;
- this.$el.toggleClass('o_kanban_grouped', isGrouped);
- this.$el.toggleClass('o_kanban_ungrouped', !isGrouped);
this.$el.empty();
- var fragment = document.createDocumentFragment();
- if (isGrouped) {
- this._renderGrouped(fragment);
+ var displayNoContentHelper = !this._hasContent() && !!this.noContentHelp;
+ this.$el.toggleClass('o_kanban_nocontent', displayNoContentHelper);
+ if (displayNoContentHelper) {
+ // display the no content helper if there is no data to display
+ this._renderNoContentHelper();
} else {
- this._renderUngrouped(fragment);
+ 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
+ if (isGrouped) {
+ this._renderGrouped(fragment);
+ } else {
+ this._renderUngrouped(fragment);
+ }
+ this.$el.append(fragment);
}
- this.$el.append(fragment);
+
return this._super.apply(this, arguments).then(_.invoke.bind(_, oldWidgets, 'destroy'));
},
+ /**
+ * Sets the current state and updates some internal attributes accordingly.
+ *
+ * @private
+ * @param {Object} state
+ */
+ _setState: function (state) {
+ this.state = state;
+
+ var groupByFieldAttrs = state.fields[state.groupedBy[0]];
+ var groupByFieldInfo = state.fieldsInfo.kanban[state.groupedBy[0]];
+ // Deactivate the drag'n'drop if the groupedBy field:
+ // - is a date or datetime since we group by month or
+ // - is readonly
+ var draggable = true;
+ if (groupByFieldAttrs) {
+ if (groupByFieldAttrs.type === "date" || groupByFieldAttrs.type === "datetime") {
+ draggable = false;
+ } else if (groupByFieldAttrs.readonly !== undefined) {
+ draggable = !(groupByFieldAttrs.readonly);
+ }
+ }
+ this.groupedByM2O = groupByFieldAttrs && (groupByFieldAttrs.type === 'many2one');
+ var grouped_by_field = this.groupedByM2O && groupByFieldAttrs.relation;
+ var groupByTooltip = groupByFieldInfo && groupByFieldInfo.options.group_by_tooltip;
+ this.columnOptions = _.extend(this.columnOptions, {
+ draggable: draggable,
+ group_by_tooltip: groupByTooltip,
+ grouped_by_m2o: this.groupedByM2O,
+ relation: grouped_by_field,
+ });
+ this.createColumnEnabled = this.groupedByM2O && this.columnOptions.group_creatable;
+ },
});
return KanbanRenderer;
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index 2ec9cf3181e..3b74f140e4d 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -188,7 +188,6 @@ var ListController = BasicController.extend({
position: self.editable,
});
}).then(function (recordID) {
- self._toggleNoContentHelper(false);
var state = self.model.get(self.handle);
self.renderer.updateState(state, {});
self.renderer.editRecord(recordID);
@@ -283,12 +282,10 @@ var ListController = BasicController.extend({
},
/**
* @override
- * @param {Object} state
* @returns {Deferred}
*/
- _update: function (state) {
+ _update: function () {
this.selectedRecords = [];
- this._toggleNoContentHelper(!this._hasContent(state));
this._toggleSidebar();
return this._super.apply(this, arguments);
},
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index 802f2f03a09..295266db276 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -30,7 +30,6 @@ var FIELD_CLASSES = {
};
var ListRenderer = BasicRenderer.extend({
- className: 'table-responsive',
events: {
'click tbody tr': '_onRowClicked',
'click tbody .o_list_record_selector': '_onSelectRecord',
@@ -577,12 +576,24 @@ var ListRenderer = BasicRenderer.extend({
_renderView: function () {
var self = this;
+ this.$el
+ .removeClass('table-responsive')
+ .empty();
+
// destroy the previously instantiated pagers, if any
_.invoke(this.pagers, 'destroy');
this.pagers = [];
+ // display the no content helper if there is no data to display
+ if (!this._hasContent() && this.noContentHelp) {
+ this._renderNoContentHelper();
+ return this._super();
+ }
+
var $table = $('<table>').addClass('o_list_view table table-condensed table-striped');
- this.$el.empty().append($table);
+ this.$el
+ .addClass('table-responsive')
+ .append($table);
var is_grouped = !!this.state.groupedBy.length;
this._computeAggregates();
$table.toggleClass('o_list_view_grouped', is_grouped);
diff --git a/addons/web/static/src/js/views/pivot/pivot_controller.js b/addons/web/static/src/js/views/pivot/pivot_controller.js
index 78640a97124..731183e7efe 100644
--- a/addons/web/static/src/js/views/pivot/pivot_controller.js
+++ b/addons/web/static/src/js/views/pivot/pivot_controller.js
@@ -170,21 +170,8 @@ var PivotController = AbstractController.extend({
/**
* @private
*/
- _update: function (state) {
+ _update: function () {
this._updateButtons();
- var hasNoContent = !state.has_data || !state.measures.length;
-
- if (hasNoContent) {
- if (!this.$('.oe_view_nocontent').length) {
- this.renderer.$el.detach();
- this.$el.append(QWeb.render('PivotView.nodata'));
- }
- } else {
- if (!document.contains(this.renderer.el)) {
- this.$('div.oe_view_nocontent').remove();
- this.$el.append(this.renderer.$el);
- }
- }
return this._super.apply(this, arguments);
},
/**
diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js
index a37e65b104b..d279d59d4b1 100644
--- a/addons/web/static/src/js/views/pivot/pivot_renderer.js
+++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js
@@ -2,8 +2,11 @@ odoo.define('web.PivotRenderer', function (require) {
"use strict";
var AbstractRenderer = require('web.AbstractRenderer');
+var core = require('web.core');
var field_utils = require('web.field_utils');
+var QWeb = core.qweb;
+
var PivotRenderer = AbstractRenderer.extend({
tagName: 'table',
className: 'table-hover table-condensed table-bordered',
@@ -16,14 +19,31 @@ var PivotRenderer = AbstractRenderer.extend({
//--------------------------------------------------------------------------
/**
+ * Used to determine whether or not to display the no content helper.
+ *
+ * @private
+ * @returns {boolean}
+ */
+ _hasContent: function () {
+ return this.state.has_data && this.state.measures.length;
+ },
+ /**
* @override
* @private
* @returns {Deferred}
*/
_render: function () {
- if (!this.state.has_data) {
+ if (!this._hasContent()) {
+ // display the nocontent helper
+ this.replaceElement(QWeb.render('PivotView.nodata'));
return this._super.apply(this, arguments);
}
+
+ if (!this.$el.is('table')) {
+ // coming from the no content helper, so the root element has to be
+ // re-rendered before rendering and appending its content
+ this.renderElement();
+ }
var $fragment = $(document.createDocumentFragment());
var $table = $('<table>').appendTo($fragment);
var $thead = $('<thead>').appendTo($table);
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index 7aac83f26f8..712cce02067 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -953,7 +953,7 @@ QUnit.module('Views', {
},
});
- assert.ok(kanban.$el.hasClass('o_kanban_nocontent'),
+ assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_nocontent'),
"$el should have correct no content class");
assert.strictEqual(kanban.$('.oe_view_nocontent').length, 1,