[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.
This commit is contained in:
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
@@ -223,31 +221,6 @@ var AbstractController = Widget.extend({
|
||||
_pushState: function (state) {
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -361,17 +361,6 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
model: this.modelName,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -317,6 +317,16 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
_getModifiersData: function (node) {
|
||||
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
|
||||
@@ -486,6 +496,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:
|
||||
|
||||
@@ -80,19 +80,6 @@ var KanbanController = BasicController.extend({
|
||||
_confirmSave: function (id) {
|
||||
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
|
||||
|
||||
@@ -100,6 +100,8 @@ var KanbanRenderer = BasicRenderer.extend({
|
||||
viewType: 'kanban',
|
||||
});
|
||||
this.columnOptions = _.extend({}, params.column_options, { qweb: this.qweb });
|
||||
|
||||
this._setState(state);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -167,11 +169,31 @@ 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.
|
||||
*
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -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',
|
||||
@@ -15,15 +18,32 @@ var PivotRenderer = AbstractRenderer.extend({
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user