summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/graph/graph_model.js
diff options
context:
space:
mode:
authorGéry Debongnie <ged@odoo.com>2017-03-05 10:05:56 +0100
committerAaron Bohy <aab@odoo.com>2017-04-11 19:44:38 +0200
commit905e01921f3c3ef43ace6fc537d1d8c0d280002c (patch)
tree265c43ab21c5401a5d9f0f3d64d86f1ac417451c /addons/web/static/src/js/views/graph/graph_model.js
parent54d4c73af4ad05c7640d28c1892247c9538b8017 (diff)
[REF] web, *: redesign all JS views
This commit introduce a full redesign of all JS views. We started basically from scratch. The goal was to unify all the various views under a common framework, to make them testable, to make then usable in different conditions (in studio, or in the frontend), and to make our lives easier. Some important points are: - we introduced new coding guidelines (camelCase, 80 chars width, ...) - we have a brand new testing framework (still QUnit based) - kanban view moved to the web addon - calendar view (formerly web_calender) moved to web as well - the tree view was removed - all new code should be documented We hope that this code is the start of a new era for the Odoo web client, we want to have a high quality codebase, well documented, well tested, well designed. Work done by the framework team: mostly aab, ged, chm, dmo, qsm
Diffstat (limited to 'addons/web/static/src/js/views/graph/graph_model.js')
-rw-r--r--addons/web/static/src/js/views/graph/graph_model.js188
1 files changed, 188 insertions, 0 deletions
diff --git a/addons/web/static/src/js/views/graph/graph_model.js b/addons/web/static/src/js/views/graph/graph_model.js
new file mode 100644
index 00000000000..73578f5c3bc
--- /dev/null
+++ b/addons/web/static/src/js/views/graph/graph_model.js
@@ -0,0 +1,188 @@
+odoo.define('web.GraphModel', function (require) {
+"use strict";
+
+/**
+ * The graph model is responsible for fetching and processing data from the
+ * server. It basically just do a read_group and format/normalize data.
+ */
+
+var core = require('web.core');
+var AbstractModel = require('web.AbstractModel');
+
+var _t = core._t;
+
+return AbstractModel.extend({
+ /**
+ * @override
+ * @param {Widget} parent
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ this.chart = null;
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * We defend against outside modifications by extending the chart data. It
+ * may be overkill.
+ *
+ * @todo Adding the fields parameter looks wrong. If the renderer or the
+ * controller need the fields, they should get it via their init method.
+ *
+ * @returns {Object}
+ */
+ get: function () {
+ return _.extend({}, this.chart, {
+ fields: this.fields
+ });
+ },
+ /**
+ * Initial loading.
+ *
+ * @todo All the work to fall back on the graph_groupbys keys in the context
+ * should be done by the graphView I think.
+ *
+ * @param {Object} params
+ * @param {string[]} params.groupedBy
+ * @param {string} params.initialMode one of 'pie', 'bar', 'line
+ * @param {string} params.initialMeasure a valid field name
+ * @param {string[]} params.initialGroupBys a list of valid field names
+ * @param {Object} params.context
+ * @param {string[]} params.domain
+ * @returns {Deferred} The deferred does not return a handle, we don't need
+ * to keep track of various entities.
+ */
+ load: function (params) {
+ this.initialGroupBys = params.initialGroupBys;
+ this.fields = params.fields;
+ this.modelName = params.modelName;
+ if (!params.groupedBy.length) {
+ var groupedBy = params.context.graph_groupbys ||
+ (params.groupedBy.length && params.groupedBy) ||
+ this.initialGroupBys;
+ params.groupedBy = groupedBy.slice(0);
+ }
+ this.chart = {
+ data: [],
+ groupedBy: params.groupedBy,
+ measure: params.initialMeasure,
+ mode: params.initialMode,
+ domain: params.domain,
+ context: params.context,
+ };
+ return this._loadGraph();
+ },
+ /**
+ * Reload data. It is similar to the load function. Note that we ignore the
+ * handle parameter, we always expect our data to be in this.chart object.
+ *
+ * @todo This method takes 'groupBy' and load method takes 'groupedBy'. This
+ * is insane.
+ *
+ * @param {any} handle ignored!
+ * @param {Object} params
+ * @param {string[]} [params.domain]
+ * @param {string[]} [params.groupBy]
+ * @param {string} [params.mode] one of 'bar', 'pie', 'line'
+ * @param {string} [params.measure] a valid field name
+ * @returns {Deferred}
+ */
+ reload: function (handle, params) {
+ if ('domain' in params) {
+ this.chart.domain = params.domain;
+ }
+ if ('groupBy' in params) {
+ if (!params.groupBy.length) {
+ this.chart.groupedBy = this.initialGroupBys;
+ } else {
+ this.chart.groupedBy = params.groupBy;
+ }
+ }
+ if ('measure' in params) {
+ this.chart.measure = params.measure;
+ }
+ if ('mode' in params) {
+ this.chart.mode = params.mode;
+ return $.when();
+ }
+ return this._loadGraph();
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Fetch and process graph data. It is basically a read_group with correct
+ * fields. We have to do some light processing to separate date groups
+ * in the field list, because they can be defined with an aggregation
+ * function, such as my_date:week
+ *
+ * @returns {Deferred}
+ */
+ _loadGraph: function () {
+ var fields = _.map(this.chart.groupedBy, function (groupBy) {
+ return groupBy.split(':')[0];
+ });
+ if (this.chart.measure !== '__count__') {
+ fields = fields.concat(this.chart.measure);
+ }
+ return this.performModelRPC(this.modelName, 'read_group', [], {
+ context: this.chart.context,
+ domain: this.chart.domain,
+ fields: fields,
+ groupby: this.chart.groupedBy,
+ lazy: false,
+ }).then(this._processData.bind(this));
+ },
+ /**
+ * Since read_group is insane and returns its result on different keys
+ * depending of some input, we have to normalize the result.
+ * The final chart data is added to this.chart object.
+ *
+ * @todo This is not good for race conditions. The processing should get
+ * the object this.chart in argument, or an array or something. We want to
+ * avoid writing on a this.chart object modified by a subsequent read_group
+ *
+ * @param {any} raw_data result from the read_group
+ */
+ _processData: function (raw_data) {
+ var self = this;
+ var is_count = this.chart.measure === '__count__';
+ var data_pt, labels;
+
+ this.chart.data = [];
+ for (var i = 0; i < raw_data.length; i++) {
+ data_pt = raw_data[i];
+ labels = _.map(this.chart.groupedBy, function (field) {
+ return self._sanitizeValue(data_pt[field], field);
+ });
+ this.chart.data.push({
+ value: is_count ? data_pt.__count : data_pt[this.chart.measure],
+ labels: labels
+ });
+ }
+ },
+ /**
+ * Helper function (for _processData), turns various values in a usable
+ * string form, that we can display in the interface.
+ *
+ * @param {any} value some value received by the read_group rpc
+ * @param {string} field the name of the corresponding field
+ * @returns {string}
+ */
+ _sanitizeValue: function (value, field) {
+ if (value === false) return _t("Undefined");
+ if (value instanceof Array) return value[1];
+ if (field && this.fields[field] && (this.fields[field].type === 'selection')) {
+ var selected = _.where(this.fields[field].selection, {0: value})[0];
+ return selected ? selected[1] : value;
+ }
+ return value;
+ },
+});
+
+});