summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/graph/graph_controller.js
diff options
context:
space:
mode:
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.js268
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 });
}
},
});