diff options
| author | Pierre Rousseau <pro@odoo.com> | 2019-12-03 09:58:52 +0000 |
|---|---|---|
| committer | Vincent Schippefilt <vsc@odoo.com> | 2019-12-09 12:53:24 +0000 |
| commit | 46cf65d68e9c13193dff4b563b9b0bbb2199dfc1 (patch) | |
| tree | 4338abceab39a837283a4075587b9c12d616ba67 /addons/web/static/src/js/views/pivot/pivot_controller.js | |
| parent | eef10097aefe04c3a89d5226727a45d540bf8f63 (diff) | |
[REF] web: rewrite pivot view renderer with OWL
Because the webclient and other higher level components such as the
actionManager are not yet rewritten in OWL, we cannot make a complete
migration of the controller and the model to a more OWL-ish code.
We have rewritten the renderer completely in OWL, it uses more
extensively a template and manages its own state and its sub-components.
We did not rewrite the controller, however we have made it inherit from
`web.ControllerAdapter` which is a thin compatibility layer used when a
odoo controller uses a OWL renderer.
In the tests, the only changes are that all the events triggered by the
test and handled by OWL must be triggered on DOM nodes directly and not
on JQuery elements.
closes odoo/odoo#39491
Related: odoo/enterprise#7135
Signed-off-by: VincentSchippefilt <VincentSchippefilt@users.noreply.github.com>
Co-authored-by: VincentSchippefilt <vsc@odoo.com>
Co-authored-by: pro-odoo <pro@odoo.com>
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; }); |
