diff options
Diffstat (limited to 'addons/web/static/src/js/views/graph/graph_controller.js')
| -rw-r--r-- | addons/web/static/src/js/views/graph/graph_controller.js | 268 |
1 files changed, 177 insertions, 91 deletions
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 @@ -76,6 +90,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. * Set this.$buttons with the produced jQuery element @@ -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 }); } }, }); |
