From 18e5416636b19b0953b1e6e26bfaeab1ebecce4c Mon Sep 17 00:00:00 2001 From: Julien Mougenot Date: Wed, 11 Mar 2020 09:38:28 +0000 Subject: [REF] web: Adapt views/actions to new control panel Some web actions/controllers/viewsl have been adapted to properly instantiate the newly refactored control panel and update it accordingly. We needed to adapt the starting mechanism of actions and controllers to ensure that the control panel instantiation did not delay the loading of any widget. Part of task 2196029 Co-authored-by: Mathieu Duckerts-Antoine --- .../static/src/js/views/graph/graph_controller.js | 268 ++++++++++++++------- 1 file changed, 177 insertions(+), 91 deletions(-) (limited to 'addons/web/static/src/js/views/graph/graph_controller.js') diff --git a/addons/web/static/src/js/views/graph/graph_controller.js b/addons/web/static/src/js/views/graph/graph_controller.js index 1c580a25bd6..f47bfaeb5bb 100644 --- a/addons/web/static/src/js/views/graph/graph_controller.js +++ b/addons/web/static/src/js/views/graph/graph_controller.js @@ -1,16 +1,30 @@ odoo.define('web.GraphController', function (require) { "use strict"; + /*--------------------------------------------------------- * Odoo Graph view *---------------------------------------------------------*/ -var core = require('web.core'); -var AbstractController = require('web.AbstractController'); -var GroupByMenuMixin = require('web.GroupByMenuMixin'); +const AbstractController = require('web.AbstractController'); +const { ComponentWrapper } = require('web.OwlCompatibility'); +const DropdownMenu = require('web.DropdownMenu'); +const { DEFAULT_INTERVAL, INTERVAL_OPTIONS } = require('web.searchUtils'); +const { qweb } = require('web.core'); + +class CarretDropdownMenu extends DropdownMenu { + /** + * @override + */ + get displayCaret() { + return true; + } +} -var qweb = core.qweb; +var GraphController = AbstractController.extend({ + custom_events: _.extend({}, AbstractController.prototype.custom_events, { + item_selected: '_onItemSelected', + }), -var GraphController = AbstractController.extend(GroupByMenuMixin,{ /** * @override * @param {Widget} parent @@ -22,12 +36,12 @@ var GraphController = AbstractController.extend(GroupByMenuMixin,{ * @param {string[]} params.groupableFields, */ init: function (parent, model, renderer, params) { - GroupByMenuMixin.init.call(this); this._super.apply(this, arguments); this.measures = params.measures; // this parameter condition the appearance of a 'Group By' // button in the control panel owned by the graph view. this.isEmbedded = params.isEmbedded; + this.withButtons = params.withButtons; // this parameter determines what is the list of fields // that may be used within the groupby menu available when @@ -75,6 +89,18 @@ var GraphController = AbstractController.extend(GroupByMenuMixin,{ } }; }, + /** + * @override + */ + reload: async function () { + const promises = [this._super(...arguments)]; + if (this.withButtons) { + const state = this.model.get(); + this.measures.forEach(m => m.isActive = m.fieldName === state.measure); + promises.push(this.measureMenu.update({ items: this.measures })); + } + return Promise.all(promises); + }, /** * Render the buttons according to the GraphView.buttons and * add listeners on it. @@ -85,86 +111,62 @@ var GraphController = AbstractController.extend(GroupByMenuMixin,{ * nothing */ renderButtons: function ($node) { - if ($node) { - var context = { - measures: _.sortBy(_.pairs(_.omit(this.measures, '__count__')), function (x) { return x[1].string.toLowerCase(); }), - }; - this.$buttons = $(qweb.render('GraphView.buttons', context)); - this.$measureList = this.$buttons.find('.o_graph_measures_list'); - this.$buttons.find('button').tooltip(); - this.$buttons.click(this._onButtonClick.bind(this)); - this._updateButtons(); - this.$buttons.appendTo($node); - if (this.isEmbedded) { - this._addGroupByMenu($node, this.groupableFields).then(function(){ - var groupByButton = $node.find('.o_dropdown_toggler_btn'); - groupByButton.removeClass("o_dropdown_toggler_btn btn btn-secondary dropdown-toggle"); - groupByButton.addClass("btn dropdown-toggle btn-outline-secondary"); - }); - } + this.$buttons = $(qweb.render('GraphView.buttons')); + this.$buttons.find('button').tooltip(); + this.$buttons.click(ev => this._onButtonClick(ev)); + if (this.withButtons) { + const actionsContainer = this.$buttons[0]; + const promises = []; + const state = this.model.get(); + const fragment = document.createDocumentFragment(); + // Instantiate and append MeasureMenu + this.measures.forEach(m => m.isActive = m.fieldName === state.measure); + this.measureMenu = new ComponentWrapper(this, CarretDropdownMenu, { + title: "Measures", + items: this.measures, + }); + promises.push(this.measureMenu.mount(fragment).then(() => { + actionsContainer.appendChild(this.measureMenu.el); + this.measureMenu.el.classList.add('o_graph_measures_list'); + })); + if ($node) { + if (this.isEmbedded) { + // Instantiate and append GroupBy menu + this.groupByMenu = new ComponentWrapper(this, CarretDropdownMenu, { + title: "Group By", + icon: 'fa fa-bars', + items: this._getGroupBys(state.groupBy), + }); + promises.push(this.groupByMenu.mount(fragment).then(() => { + actionsContainer.appendChild(this.groupByMenu.el); + this.groupByMenu.el.classList.add('o_group_by_menu'); + })); + } + this.$buttons.appendTo($node); + } + Promise.all(promises).then(() => { + // Similar behaviour for all buttons + const buttons = actionsContainer.querySelectorAll('.o_dropdown_toggler_btn'); + for (const button of buttons) { + button.classList.remove('o_dropdown_toggler_btn', 'btn-secondary'); + if (this.isEmbedded) { + button.classList.add('btn-outline-secondary'); + } else { + button.classList.add('btn-primary'); + button.tabIndex = 0; + } + } + }); } }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /* - * override - * - * @private - * @param {string[]} groupBy - */ - _setGroupby: function (groupBy) { - this.update({groupBy: groupBy}); - }, - /** - * @todo remove this and directly calls update. Update should be overridden - * and modified to call _updateButtons - * - * @private - * - * @param {'pie'|'line'|'bar'} mode - */ - _setMode: function (mode) { - this.update({mode: mode}); - this._updateButtons(); - }, /** - * @todo same as _setMode + * Makes sure that the buttons in the control panel matches the current + * state (so, correct active buttons and stuff like that). * - * @param {string} measure should be a valid (and aggregatable) field name - */ - _setMeasure: function (measure) { - var self = this; - this.update({measure: measure}).then(function () { - self._updateButtons(); - }); - }, - /** - * @private - * - * @param {boolean} stacked - */ - _toggleStackMode: function (stacked) { - this.update({stacked: stacked}); - this._updateButtons(); - }, - /** - * override - * - * @private - */ - _update: function () { - this._updateButtons(); - return this._super.apply(this, arguments); - }, - /** - * makes sure that the buttons in the control panel matches the current - * state (so, correct active buttons and stuff like that) + * @override */ - _updateButtons: function () { + updateButtons: function () { if (!this.$buttons) { return; } @@ -178,9 +180,60 @@ var GraphController = AbstractController.extend(GroupByMenuMixin,{ .data('stacked', state.stacked) .toggleClass('active', state.stacked) .toggleClass('o_hidden', state.mode !== 'bar'); - _.each(this.$measureList.find('.dropdown-item'), function (item) { - var $item = $(item); - $item.toggleClass('selected', $item.data('field') === state.measure); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Returns the items used by the Group By menu in embedded mode. + * + * @private + * @param {string[]} activeGroupBys + * @returns {Object[]} + */ + _getGroupBys(activeGroupBys) { + const normalizedGroupBys = this._normalizeActiveGroupBys(activeGroupBys); + const groupBys = Object.keys(this.groupableFields).map(fieldName => { + const field = this.groupableFields[fieldName]; + const groupByActivity = normalizedGroupBys.filter(gb => gb.fieldName === fieldName); + const groupBy = { + id: fieldName, + isActive: Boolean(groupByActivity.length), + description: field.string, + itemType: 'groupBy', + }; + if (['date', 'datetime'].includes(field.type)) { + groupBy.hasOptions = true; + const activeOptionIds = groupByActivity.map(gb => gb.interval); + groupBy.options = Object.values(INTERVAL_OPTIONS).map(o => { + return Object.assign({}, o, { isActive: activeOptionIds.includes(o.id) }); + }); + } + return groupBy; + }).sort((gb1, gb2) => { + return gb1.description.localeCompare(gb2.description); + }); + return groupBys; + }, + + /** + * This method puts the active groupBys in a convenient form. + * + * @private + * @param {string[]} activeGroupBys + * @returns {Object[]} normalizedGroupBys + */ + _normalizeActiveGroupBys(activeGroupBys) { + return activeGroupBys.map(groupBy => { + const fieldName = groupBy.split(':')[0]; + const field = this.groupableFields[fieldName]; + const normalizedGroupBy = { fieldName }; + if (['date', 'datetime'].includes(field.type)) { + normalizedGroupBy.interval = groupBy.split(':')[1] || DEFAULT_INTERVAL; + } + return normalizedGroupBy; }); }, @@ -196,18 +249,51 @@ var GraphController = AbstractController.extend(GroupByMenuMixin,{ */ _onButtonClick: function (ev) { var $target = $(ev.target); - var field; if ($target.hasClass('o_graph_button')) { if (_.contains(['bar','line', 'pie'], $target.data('mode'))) { - this._setMode($target.data('mode')); + this.update({ mode: $target.data('mode') }); } else if ($target.data('mode') === 'stack') { - this._toggleStackMode(!$target.data('stacked')); + this.update({ stacked: !$target.data('stacked') }); + } + } + }, + + /** + * @private + * @param {OdooEvent} ev + */ + _onItemSelected(ev) { + const item = ev.data.item; + if (this.isEmbedded && item.itemType === 'groupBy') { + const fieldName = item.id; + const optionId = ev.data.option && ev.data.option.id; + const activeGroupBys = this.model.get().groupBy; + if (optionId) { + const normalizedGroupBys = this._normalizeActiveGroupBys(activeGroupBys); + const index = normalizedGroupBys.findIndex(ngb => + ngb.fieldName === fieldName && ngb.interval === optionId); + if (index === -1) { + activeGroupBys.push(fieldName + ':' + optionId); + } else { + activeGroupBys.splice(index, 1); + } + } else { + const groupByFieldNames = activeGroupBys.map(gb => gb.split(':')[0]); + const indexOfGroupby = groupByFieldNames.indexOf(fieldName); + if (indexOfGroupby === -1) { + activeGroupBys.push(fieldName); + } else { + activeGroupBys.splice(indexOfGroupby, 1); + } } - } else if ($target.parents('.o_graph_measures_list').length) { - ev.preventDefault(); - ev.stopPropagation(); - field = $target.data('field'); - this._setMeasure(field); + this.update({ groupBy: activeGroupBys }); + this.groupByMenu.update({ + items: this._getGroupBys(activeGroupBys), + }); + } else if (item.itemType === 'measure') { + this.update({ measure: item.fieldName }); + this.measures.forEach(m => m.isActive = m.fieldName === item.fieldName); + this.measureMenu.update({ items: this.measures }); } }, }); -- cgit v1.2.3