summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/graph/graph_controller.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/views/graph/graph_controller.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
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.js356
1 files changed, 356 insertions, 0 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
new file mode 100644
index 00000000..6cb2b899
--- /dev/null
+++ b/addons/web/static/src/js/views/graph/graph_controller.js
@@ -0,0 +1,356 @@
+odoo.define('web.GraphController', function (require) {
+"use strict";
+
+/*---------------------------------------------------------
+ * Odoo Graph view
+ *---------------------------------------------------------*/
+
+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');
+const { _t } = require('web.core');
+
+class CarretDropdownMenu extends DropdownMenu {
+ /**
+ * @override
+ */
+ get displayCaret() {
+ return true;
+ }
+}
+
+var GraphController = AbstractController.extend({
+ custom_events: _.extend({}, AbstractController.prototype.custom_events, {
+ item_selected: '_onItemSelected',
+ open_view: '_onOpenView',
+ }),
+
+ /**
+ * @override
+ * @param {Widget} parent
+ * @param {GraphModel} model
+ * @param {GraphRenderer} renderer
+ * @param {Object} params
+ * @param {string[]} params.measures
+ * @param {boolean} params.isEmbedded
+ * @param {string[]} params.groupableFields,
+ */
+ init: function (parent, model, renderer, params) {
+ 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;
+ // views to use in the action triggered when the graph is clicked
+ this.views = params.views;
+ this.title = params.title;
+
+ // this parameter determines what is the list of fields
+ // that may be used within the groupby menu available when
+ // the view is embedded
+ this.groupableFields = params.groupableFields;
+ this.buttonDropdownPromises = [];
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ this.$el.addClass('o_graph_controller');
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * @todo check if this can be removed (mostly duplicate with
+ * AbstractController method)
+ */
+ destroy: function () {
+ if (this.$buttons) {
+ // remove jquery's tooltip() handlers
+ this.$buttons.find('button').off().tooltip('dispose');
+ }
+ this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Returns the current mode, measure 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
+ * @returns {Object}
+ */
+ getOwnedQueryParams: function () {
+ var state = this.model.get();
+ return {
+ context: {
+ graph_measure: state.measure,
+ graph_mode: state.mode,
+ graph_groupbys: state.groupBy,
+ }
+ };
+ },
+ /**
+ * @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
+ *
+ * @param {jQuery} [$node] a jQuery node where the rendered buttons should
+ * be inserted $node may be undefined, in which case the GraphView does
+ * nothing
+ */
+ renderButtons: function ($node) {
+ this.$buttons = $(qweb.render('GraphView.buttons'));
+ this.$buttons.find('button').tooltip();
+ this.$buttons.click(ev => this._onButtonClick(ev));
+
+ if (this.withButtons) {
+ 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: _t("Measures"),
+ items: this.measures,
+ });
+ this.buttonDropdownPromises = [this.measureMenu.mount(fragment)];
+ if ($node) {
+ if (this.isEmbedded) {
+ // Instantiate and append GroupBy menu
+ this.groupByMenu = new ComponentWrapper(this, CarretDropdownMenu, {
+ title: _t("Group By"),
+ icon: 'fa fa-bars',
+ items: this._getGroupBys(state.groupBy),
+ });
+ this.buttonDropdownPromises.push(this.groupByMenu.mount(fragment));
+ }
+ this.$buttons.appendTo($node);
+ }
+ }
+ },
+ /**
+ * Makes sure that the buttons in the control panel matches the current
+ * state (so, correct active buttons and stuff like that).
+ *
+ * @override
+ */
+ updateButtons: function () {
+ if (!this.$buttons) {
+ return;
+ }
+ var state = this.model.get();
+ this.$buttons.find('.o_graph_button').removeClass('active');
+ this.$buttons
+ .find('.o_graph_button[data-mode="' + state.mode + '"]')
+ .addClass('active');
+ this.$buttons
+ .find('.o_graph_button[data-mode="stack"]')
+ .data('stacked', state.stacked)
+ .toggleClass('active', state.stacked)
+ .toggleClass('o_hidden', state.mode !== 'bar');
+ this.$buttons
+ .find('.o_graph_button[data-order]')
+ .toggleClass('o_hidden', state.mode === 'pie' || !!Object.keys(state.timeRanges).length)
+ .filter('.o_graph_button[data-order="' + state.orderBy + '"]')
+ .toggleClass('active', !!state.orderBy);
+
+ if (this.withButtons) {
+ this._attachDropdownComponents();
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Attaches the different dropdown components to the buttons container.
+ *
+ * @returns {Promise}
+ */
+ async _attachDropdownComponents() {
+ await Promise.all(this.buttonDropdownPromises);
+ const actionsContainer = this.$buttons[0];
+ // Attach "measures" button
+ actionsContainer.appendChild(this.measureMenu.el);
+ this.measureMenu.el.classList.add('o_graph_measures_list');
+ if (this.isEmbedded) {
+ // Attach "groupby" button
+ actionsContainer.appendChild(this.groupByMenu.el);
+ this.groupByMenu.el.classList.add('o_group_by_menu');
+ }
+ // Update button classes accordingly to the current mode
+ 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;
+ }
+ }
+ },
+
+ /**
+ * 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;
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Do what need to be done when a button from the control panel is clicked.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onButtonClick: function (ev) {
+ var $target = $(ev.target);
+ if ($target.hasClass('o_graph_button')) {
+ if (_.contains(['bar','line', 'pie'], $target.data('mode'))) {
+ this.update({ mode: $target.data('mode') });
+ } else if ($target.data('mode') === 'stack') {
+ this.update({ stacked: !$target.data('stacked') });
+ } else if (['asc', 'desc'].includes($target.data('order'))) {
+ const order = $target.data('order');
+ const state = this.model.get();
+ this.update({ orderBy: state.orderBy === order ? false : order });
+ }
+ }
+ },
+
+ /**
+ * @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);
+ }
+ }
+ 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 });
+ }
+ },
+
+ /**
+ * @private
+ * @param {OdooEvent} ev
+ * @param {Array[]} ev.data.domain
+ */
+ _onOpenView(ev) {
+ ev.stopPropagation();
+ const state = this.model.get();
+ const context = Object.assign({}, state.context);
+ Object.keys(context).forEach(x => {
+ if (x === 'group_by' || x.startsWith('search_default_')) {
+ delete context[x];
+ }
+ });
+ this.do_action({
+ context: context,
+ domain: ev.data.domain,
+ name: this.title,
+ res_model: this.modelName,
+ target: 'current',
+ type: 'ir.actions.act_window',
+ view_mode: 'list',
+ views: this.views,
+ });
+ },
+});
+
+return GraphController;
+
+});