summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/pivot/pivot_controller.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js/views/pivot/pivot_controller.js')
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_controller.js614
1 files changed, 285 insertions, 329 deletions
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 71427baff1b..606367d4d3a 100644
--- a/addons/web/static/src/js/views/pivot/pivot_controller.js
+++ b/addons/web/static/src/js/views/pivot/pivot_controller.js
@@ -1,355 +1,311 @@
odoo.define('web.PivotController', function (require) {
-"use strict";
-/**
- * Odoo Pivot Table Controller
- *
- * This class is the Controller for the pivot table view. It has to coordinate
- * the actions coming from the search view (through the update method), from
- * the renderer, from the model, and from the control panel.
- *
- * It can display action buttons in the control panel, to select a different
- * measure, or to perform some other actions such as download/expand/flip the
- * view.
- */
-
-var AbstractController = require('web.AbstractController');
-var core = require('web.core');
-var framework = require('web.framework');
-var session = require('web.session');
-
-var _t = core._t;
-var QWeb = core.qweb;
-
-var PivotController = AbstractController.extend({
- contentTemplate: 'PivotView',
- events: {
- 'click .o_pivot_field_menu a': '_onGroupByMenuSelection',
- 'click .o_pivot_header_cell_closed': '_onClosedHeaderClick',
- },
- custom_events: _.extend({}, AbstractController.prototype.custom_events, {
- close_group: '_onCloseGroup',
- open_view: '_onOpenView',
- sort_rows: '_onSortRows',
- }),
+ "use strict";
/**
- * @override
- * @param {Object} params
- * @param {Object} params.groupableFields a map from field names to field
- * props
+ * Odoo Pivot Table Controller
+ *
+ * This class is the Controller for the pivot table view. It has to coordinate
+ * the actions coming from the search view (through the update method), from
+ * the renderer, from the model, and from the control panel.
+ *
+ * It can display action buttons in the control panel, to select a different
+ * measure, or to perform some other actions such as download/expand/flip the
+ * view.
*/
- init: function (parent, model, renderer, params) {
- this._super.apply(this, arguments);
- this.measures = params.measures;
- this.groupableFields = params.groupableFields;
- this.title = params.title;
- // views to use in the action triggered when a data cell is clicked
- this.views = params.views;
- this.groupSelected = null;
- },
- /**
- * @override
- */
- start: function () {
- this.$groupBySelection = this.$('.o_field_selection');
- core.bus.on('click', this, function () {
- this.$groupBySelection.empty();
- });
- return this._super();
- },
- /**
- * @override
- */
- destroy: function () {
- if (this.$buttons) {
- // remove jquery's tooltip() handlers
- this.$buttons.find('button').off();
- }
- return this._super.apply(this, arguments);
- },
+ const ControllerAdapter = require('web.ControllerAdapter');
+ const core = require('web.core');
+ const framework = require('web.framework');
+ const session = require('web.session');
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
+ const _t = core._t;
+ const QWeb = core.qweb;
- /**
- * Returns the current measures and groupbys, so we can restore the view
- * when we save the current state in the search view, or when we add it to
- * the dashboard.
- *
- * @override method from AbstractController
- * @returns {Object}
- */
- getOwnedQueryParams: function () {
- var state = this.model.get({raw: true});
- return {
- context: {
- pivot_measures: state.measures,
- pivot_column_groupby: state.colGroupBys,
- pivot_row_groupby: state.rowGroupBys,
+ const PivotController = ControllerAdapter.extend({
+ events: Object.assign({}, ControllerAdapter.prototype.events, {
+ closed_header_click: '_onClosedHeaderClicked',
+ open_view: '_onOpenView',
+ opened_header_click: '_onOpenedHeaderClicked',
+ sort_rows: '_onSortRows',
+ groupby_menu_selection: '_onGroupByMenuSelection',
+ }),
+ /**
+ * @override
+ * @param parent
+ * @param model
+ * @param renderer
+ * @param {Object} params
+ * @param {Object} params.groupableFields a map from field names to field
+ * props
+ */
+ init: function (parent, model, renderer, params) {
+ this._super(...arguments);
+
+ this.measures = params.measures;
+ this.title = params.title;
+ // views to use in the action triggered when a data cell is clicked
+ this.views = params.views;
+ this.groupSelected = null;
+ },
+ /**
+ * @override
+ */
+ destroy: function () {
+ if (this.$buttons) {
+ // remove jquery's tooltip() handlers
+ this.$buttons.find('button').off();
}
- };
- },
- /**
- * Render the buttons according to the PivotView.buttons template and
- * add listeners on it.
- * Set this.$buttons with the produced jQuery element
- *
- * @param {jQuery} [$node] a jQuery node where the rendered buttons should
- * be inserted. $node may be undefined, in which case the PivotView
- * does nothing
- */
- renderButtons: function ($node) {
- if ($node) {
- var context = {
- measures: _.sortBy(_.pairs(_.omit(this.measures, '__count')), function (x) {
- return x[1].string.toLowerCase();
- }),
+ return this._super(...arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Returns the current measures and groupbys, so we can restore the view
+ * when we save the current state in the search view, or when we add it to
+ * the dashboard.
+ *
+ * @override method from AbstractController
+ * @returns {Object}
+ */
+ getOwnedQueryParams: function () {
+ const state = this.model.get({ raw: true });
+ return {
+ context: {
+ pivot_measures: state.measures,
+ pivot_column_groupby: state.colGroupBys,
+ pivot_row_groupby: state.rowGroupBys,
+ }
};
- this.$buttons = $(QWeb.render('PivotView.buttons', context));
- this.$buttons.click(this._onButtonClick.bind(this));
- this.$buttons.find('button').tooltip();
+ },
+ /**
+ * Render the buttons according to the PivotView.buttons template and
+ * add listeners on it.
+ * Set this.$buttons with the produced jQuery element
+ *
+ * @param {jQuery} [$node] a jQuery node where the rendered buttons should
+ * be inserted. $node may be undefined, in which case the PivotView
+ * does nothing
+ */
+ renderButtons: function ($node) {
+ if ($node) {
+ const context = {
+ measures: Object.entries(this.measures)
+ .filter(x => x[0] !== '__count')
+ .sort((a, b) => a[1].string.toLowerCase() > b[1].string.toLowerCase() ? 1 : -1),
+ };
+ this.$buttons = $(QWeb.render('PivotView.buttons', context));
+ this.$buttons.click(this._onButtonClick.bind(this));
+ this.$buttons.find('button').tooltip();
- this.$buttons.appendTo($node);
- this._updateButtons();
- }
- },
+ this.$buttons.appendTo($node);
+ this._updateButtons();
+ }
+ },
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
- /**
- * Export the current pivot table data in a xls file. For this, we have to
- * serialize the current state, then call the server /web/pivot/export_xls.
- * Force a reload before exporting to ensure to export up-to-date data.
- *
- * @private
- */
- _downloadTable: function () {
- var self = this;
- if (self.model.getTableWidth() > 256) {
- this.call('crash_manager', 'show_message', _t("For Excel compatibility, data cannot be exported if there are more than 256 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures."));
- framework.unblockUI();
- return;
- }
- var table = self.model.exportData();
- table.title = self.title;
- session.get_file({
- url: '/web/pivot/export_xls',
- data: {data: JSON.stringify(table)},
- complete: framework.unblockUI,
- error: (error) => this.call('crash_manager', 'rpc_error', error),
- });
- },
- /**
- * Render the field selection menu, to select a groupable field. We disable
- * already selected groupbys.
- *
- * @private
- * @param {number} top top coordinate where we have to render the menu
- * @param {number} left left coordinate for the menu
- */
- _renderGroupBySelection: function (top, left) {
- var state = this.model.get({raw: true});
- var groupedFields = state.rowGroupBys
- .concat(state.colGroupBys)
- .map(function (f) {
- return f.split(':')[0];
+ /**
+ * Export the current pivot table data in a xls file. For this, we have to
+ * serialize the current state, then call the server /web/pivot/export_xls.
+ * Force a reload before exporting to ensure to export up-to-date data.
+ *
+ * @private
+ */
+ _downloadTable: function () {
+ if (this.model.getTableWidth() > 256) {
+ this.call('crash_manager', 'show_message', _t("For Excel compatibility, data cannot be exported if there are more than 256 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures."));
+ framework.unblockUI();
+ return;
+ }
+ const table = this.model.exportData();
+ table.title = this.title;
+ session.get_file({
+ url: '/web/pivot/export_xls',
+ data: { data: JSON.stringify(table) },
+ complete: framework.unblockUI,
+ error: (error) => this.call('crash_manager', 'rpc_error', error),
});
+ },
+ /**
+ * @override
+ * @private
+ */
+ _update: function () {
+ this._updateButtons();
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * @private
+ */
+ _updateButtons: function () {
+ if (!this.$buttons) {
+ return;
+ }
+ const state = this.model.get({ raw: true });
+ Object.entries(this.measures).forEach(elt => {
+ const name = elt[0];
+ const isSelected = state.measures.includes(name);
+ this.$buttons.find('.dropdown-item[data-field="' + name + '"]')
+ .toggleClass('selected', isSelected);
- var fields = _.chain(this.groupableFields)
- .pairs()
- .sortBy(function (f) {
- return f[1].string;
- })
- .map(function (f) {
- return [f[0], f[1], _.contains(groupedFields, f[0])];
- })
- .value();
+ });
+ const noDataDisplayed = !state.hasData || !state.measures.length;
+ this.$buttons.find('.o_pivot_flip_button').prop('disabled', noDataDisplayed);
+ this.$buttons.find('.o_pivot_expand_button').prop('disabled', noDataDisplayed);
+ this.$buttons.find('.o_pivot_download').prop('disabled', noDataDisplayed);
+ },
- this.$groupBySelection.html(QWeb.render('PivotView.GroupBySelection', {
- fields: fields
- }));
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
- var cssProps = {top: top};
- var isRTL = _t.database.parameters.direction === 'rtl';
- cssProps[isRTL ? 'right' : 'left'] = isRTL ? this.$el.width() - left : left;
- this.$groupBySelection.find('.dropdown-menu').first().css(cssProps).addClass('show');
- },
- /**
- * @override
- * @private
- */
- _startRenderer: function () {
- return this.renderer.appendTo(this.$('.o_pivot'));
- },
- /**
- * @override
- * @private
- */
- _update: function () {
- this._updateButtons();
- return this._super.apply(this, arguments);
- },
- /**
- * @private
- */
- _updateButtons: function () {
- if (!this.$buttons) {
- return;
- }
- var self = this;
- var state = this.model.get({raw: true});
- _.each(this.measures, function (measure, name) {
- var isSelected = _.contains(state.measures, name);
- self.$buttons.find('.dropdown-item[data-field="' + name + '"]')
- .toggleClass('selected', isSelected);
- });
- var noDataDisplayed = !state.hasData || !state.measures.length;
- this.$buttons.find('.o_pivot_flip_button').prop('disabled', noDataDisplayed);
- this.$buttons.find('.o_pivot_expand_button').prop('disabled', noDataDisplayed);
- this.$buttons.find('.o_pivot_download').prop('disabled', noDataDisplayed);
- },
+ /**
+ * This handler is called when the user clicked on a button in the control
+ * panel. We then have to react properly: it can either be a change in the
+ * current measures, or a request to flip/expand/download data.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onButtonClick: async function (ev) {
+ const $target = $(ev.target);
+ if ($target.hasClass('o_pivot_flip_button')) {
+ this.model.flip();
+ this.update({}, { reload: false });
+ }
+ if ($target.hasClass('o_pivot_expand_button')) {
+ await this.model.expandAll();
+ this.update({}, { reload: false });
+ }
+ if ($target.parents('.o_pivot_measures_list').length) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ const field = $target.data('field');
+ await this.model.toggleMeasure(field);
+ this.update({}, { reload: false });
+ }
+ if ($target.hasClass('o_pivot_download')) {
+ this._downloadTable();
+ }
+ },
+ /**
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onCloseGroup: function (ev) {
+ this.model.closeGroup(ev.data.groupId, ev.data.type);
+ this.update({}, { reload: false });
+ },
+ /**
+ * @param {CustomEvent} ev
+ * @private
+ * */
+ _onOpenedHeaderClicked: function (ev) {
+ this.model.closeGroup(ev.detail.cell.groupId, ev.detail.type);
+ this.update({}, { reload: false });
+ },
+ /**
+ * @param {CustomEvent} ev
+ * @private
+ * */
+ _onClosedHeaderClicked: async function (ev) {
+ const cell = ev.detail.cell;
+ const groupId = cell.groupId;
+ const type = ev.detail.type;
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
+ const group = {
+ rowValues: groupId[0],
+ colValues: groupId[1],
+ type: type
+ };
- /**
- * This handler is called when the user clicked on a button in the control
- * panel. We then have to react properly: it can either be a change in the
- * current measures, or a request to flip/expand/download data.
- *
- * @private
- * @param {MouseEvent} ev
- */
- _onButtonClick: function (ev) {
- var $target = $(ev.target);
- if ($target.hasClass('o_pivot_flip_button')) {
- this.model.flip();
- this.update({}, {reload: false});
- }
- if ($target.hasClass('o_pivot_expand_button')) {
- this.model.expandAll().then(this.update.bind(this, {}, {reload: false}));
- }
- if ($target.parents('.o_pivot_measures_list').length) {
- ev.preventDefault();
+ const state = this.model.get({ raw: true });
+ const groupValues = type === 'row' ? groupId[0] : groupId[1];
+ const groupBys = type === 'row' ?
+ state.rowGroupBys :
+ state.colGroupBys;
+ this.selectedGroup = group;
+ if (groupValues.length < groupBys.length) {
+ const groupBy = groupBys[groupValues.length];
+ await this.model.expandGroup(this.selectedGroup, groupBy);
+ this.update({}, { reload: false });
+ }
+ },
+ /**
+ * This handler is called when the user selects a groupby in the dropdown menu.
+ *
+ * @private
+ * @param {CustomEvent} ev
+ */
+ _onGroupByMenuSelection: async function (ev) {
ev.stopPropagation();
- var field = $target.data('field');
- this.model.toggleMeasure(field).then(this.update.bind(this, {}, {reload: false}));
- }
- if ($target.hasClass('o_pivot_download')) {
- this._downloadTable();
- }
- },
- /**
- *
- * @private
- * @param {OdooEvent} ev
- */
- _onCloseGroup: function (ev) {
- this.model.closeGroup(ev.data.groupId, ev.data.type);
- this.update({}, {reload: false});
- },
- /**
- * When we click on a closed row (col) header, we either want to open the
- * dropdown menu to select a new field to add to rowGroupBys (resp. colGroupBys),
- * or we want to open the clicked header, if rowGroupBys (resp. colGroupBys)
- * has length strictly greater than header
- *
- * @private
- * @param {MouseEvent} ev
- */
- _onClosedHeaderClick: function (ev) {
- ev.preventDefault();
- ev.stopPropagation();
-
- var $target = $(ev.target);
- var groupId = $target.data('groupId');
- var type = $target.data('type');
- var group = {
- rowValues: groupId[0],
- colValues: groupId[1],
- type: type
- };
-
- var state = this.model.get({raw: true});
+ let groupBy = ev.detail.field.name;
+ const interval = ev.detail.interval;
+ if (interval) {
+ groupBy = groupBy + ':' + interval;
+ }
+ this.model.addGroupBy(groupBy, this.selectedGroup.type);
+ await this.model.expandGroup(this.selectedGroup, groupBy);
+ this.update({}, { reload: false });
+ },
+ /**
+ * @private
+ * @param {CustomEvent} ev
+ */
+ _onOpenView: function (ev) {
+ ev.stopPropagation();
+ const cell = ev.detail;
+ if (cell.value === undefined || this.initialState.disableLinking) {
+ return;
+ }
- var groupValues = type === 'row' ? groupId[0] : groupId[1];
- var groupBys = type === 'row' ?
- state.rowGroupBys :
- state.colGroupBys;
+ const context = Object.assign({}, this.model.data.context);
+ Object.keys(context).forEach(x => {
+ if (x === 'group_by' || x.startsWith('search_default_')) {
+ delete context[x];
+ }
+ });
- this.selectedGroup = group;
- if (groupValues.length < groupBys.length) {
- var groupBy = groupBys[groupValues.length];
- this.model
- .expandGroup(this.selectedGroup, groupBy)
- .then(this.update.bind(this, {}, {reload: false}));
- } else {
- var position = $target.position();
- var top = position.top + $target.height();
- var left = ev.clientX;
- this._renderGroupBySelection(top, left);
- }
- },
- /**
- * This handler is called when the user selects a groupby in the dropdown menu.
- *
- * @private
- * @param {MouseEvent} ev
- */
- _onGroupByMenuSelection: function (ev) {
- ev.preventDefault();
- var $target = $(ev.target);
- if ($target.hasClass('disabled')) {
- ev.stopPropagation();
- return;
- }
+ const group = {
+ rowValues: cell.groupId[0],
+ colValues: cell.groupId[1],
+ originIndex: cell.originIndexes[0]
+ };
- var groupBy = $target.data('field');
- var interval = $target.data('interval');
- if (interval) {
- groupBy = groupBy + ':' + interval;
- }
- this.model.addGroupBy(groupBy, this.selectedGroup.type);
- this.model
- .expandGroup(this.selectedGroup, groupBy)
- .then(this.update.bind(this, {}, {reload: false}));
- },
- /**
- * @private
- * @param {OdooEvent} ev
- */
- _onOpenView: function (ev) {
- ev.stopPropagation();
- var context = ev.data.context;
- var group = ev.data.group;
- var domain = this.model._getGroupDomain(group);
+ const domain = this.model._getGroupDomain(group);
- this.do_action({
- type: 'ir.actions.act_window',
- name: this.title,
- res_model: this.modelName,
- views: this.views,
- view_mode: 'list',
- target: 'current',
- context: context,
- domain: domain,
- });
- },
- /**
- * @private
- * @param {OdooEvent} ev
- */
- _onSortRows: function (ev) {
- this.model.sortRows(ev.data.sortedColumn);
- this.update({}, {reload: false});
- },
-});
+ this.do_action({
+ type: 'ir.actions.act_window',
+ name: this.title,
+ res_model: this.modelName,
+ views: this.views,
+ view_mode: 'list',
+ target: 'current',
+ context: context,
+ domain: domain,
+ });
+ },
+ /**
+ * @private
+ * @param {CustomEvent} ev
+ */
+ _onSortRows: function (ev) {
+ this.model.sortRows({
+ groupId: ev.detail.groupId,
+ measure: ev.detail.measure,
+ order: (ev.detail.order || 'desc') === 'asc' ? 'desc' : 'asc',
+ originIndexes: ev.detail.originIndexes,
+ });
+ this.update({}, { reload: false });
+ },
+ });
-return PivotController;
+ return PivotController;
});