diff options
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.js | 614 |
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; }); |
