4d74ad71121f26e03d662fa16027a712ade6cad4
[odoo/odoo.git] / addons / web_graph / static / src / js / graph.js
1 /*---------------------------------------------------------
2  * OpenERP web_graph
3  *---------------------------------------------------------*/
4
5 /* jshint undef: false  */
6
7
8 openerp.web_graph = function (instance) {
9 'use strict';
10
11 var _lt = instance.web._lt;
12 var _t = instance.web._t;
13 var QWeb = instance.web.qweb;
14
15 instance.web.views.add('graph', 'instance.web_graph.GraphView');
16
17  /**
18   * GraphView view.  It mostly contains a widget (PivotTable), some data, and 
19   * calls to charts function.
20   */
21 instance.web_graph.GraphView = instance.web.View.extend({
22     template: 'GraphView',
23     display_name: _lt('Graph'),
24     view_type: 'graph',
25
26     events: {
27         'click .graph_mode_selection li' : function (event) {
28             event.preventDefault();
29             var mode = event.target.attributes['data-mode'].nodeValue;
30             this.switch_mode(mode);
31         },
32
33         'click .web_graph_click' : function (event) {
34             event.preventDefault();
35             if (event.target.attributes['data-row-id'] !== undefined) {
36                 this.handle_header_event({type:'row', event:event});
37
38             }
39             if (event.target.attributes['data-col-id'] !== undefined) {
40                 this.handle_header_event({type:'col', event:event});
41             }
42         },
43
44         'click a.field-selection' : function (event) {
45             var id,
46                 field_id = event.target.attributes['data-field-id'].nodeValue;
47             event.preventDefault();
48             this.dropdown.remove();
49             if (event.target.attributes['data-row-id'] !== undefined) {
50                 id = event.target.attributes['data-row-id'].nodeValue;
51                 this.pivot_table.expand_row(id, field_id)
52                     .then(this.proxy('draw_table'));
53             } 
54             if (event.target.attributes['data-col-id'] !== undefined) {
55                 id = event.target.attributes['data-col-id'].nodeValue;
56                 this.pivot_table.expand_col(id, field_id)
57                     .then(this.proxy('draw_table'));
58             } 
59         },
60
61         'click label.graph_swap_axis' : function (event) {
62             this.pivot_table.swap_axis();
63             this.draw_table();
64         },
65
66         'click label.graph_clear_all' : function (event) {
67             this.pivot_table.clear_all();
68             this.draw_table();
69         },
70     },
71
72     view_loading: function (fields_view_get) {
73         var self = this,
74             model = new instance.web.Model(fields_view_get.model, {group_by_no_leaf: true}),
75             domain = [],
76             measure = null,
77             fields,
78             important_fields = [],
79             col_groupby = [], 
80             row_groupby = [];
81
82         this.pivot_table = null;
83
84         // get the default groupbys and measure defined in the field view
85         _.each(fields_view_get.arch.children, function (field) {
86             if ('name' in field.attrs) {
87                 if ('operator' in field.attrs) {
88                     measure = field.attrs.name;
89                 } else {
90                     row_groupby.push(field.attrs.name);
91                 }
92             }
93         });
94
95         // get the most important fields (of the model) by looking at the
96         // groupby filters defined in the search view
97         var load_view = instance.web.fields_view_get({
98             model: model,
99             view_type: 'search',
100         });
101
102         var important_fields_def = $.when(load_view).then(function (search_view) {
103             var groups = _.select(search_view.arch.children, function (c) {
104                 return (c.tag == 'group') && (c.attrs.string != 'Display');
105             });
106             _.each(groups, function(g) {
107                 _.each(g.children, function (g) {
108                     if (g.attrs.context) {
109                         var field_id = py.eval(g.attrs.context).group_by;
110                         important_fields.push(field_id);
111                     }
112                 });
113             });
114         });
115
116         // get the fields descriptions from the model
117         var field_descr_def = model.call('fields_get', [])
118             .then(function (fs) { fields = fs; });
119
120         return $.when(important_fields_def, field_descr_def)
121             .then(function () {
122                 self.data = {
123                     model: model,
124                     domain: domain,
125                     fields: fields,
126                     important_fields: important_fields,
127                     measure: measure,
128                     measure_label: fields[measure].string,
129                     col_groupby: [],
130                     row_groupby: row_groupby,
131                     groups: [],
132                     total: null,
133                 };
134             });
135     },
136
137     start: function () {
138         this.table = $('<table></table>');
139         this.svg = $('<div><svg></svg></div>');
140         this.$el.filter('.graph_main_content').append(this.table);
141         this.$el.filter('.graph_main_content').append(this.svg);
142         return this.load_view();
143     },
144
145     do_search: function (domain, context, group_by) {
146         var self = this;
147         this.data.domain = new instance.web.CompoundDomain(domain);
148
149         if (!this.pivot_table) {
150             self.pivot_table = new PivotTable(self.data);
151             self.pivot_table.start().then(self.proxy('draw_table'));
152         } else {
153             this.pivot_table.update_values.done(function () {
154                 self.draw_table();
155             });
156         }
157     },
158
159     do_show: function () {
160         this.do_push_state({});
161         return this._super();
162     },
163
164     switch_mode: function (mode) {
165         var self = this;
166         this.mode = mode;
167         if (mode === 'pivot') {
168             this.table.css('display', 'block');
169             this.svg.css('display','none');
170         } else {
171             this.table.css('display', 'none');
172             this.svg.remove();
173             this.svg = $('<div><svg></svg></div>');
174             this.$el.filter('.graph_main_content').append(this.svg);
175             Charts[mode](this.pivot_table);
176
177         }
178     },
179
180     handle_header_event: function (options) {
181         var pivot = this.pivot_table,
182             id = options.event.target.attributes['data-' + options.type + '-id'].nodeValue,
183             header = pivot['get_' + options.type](id);
184
185         if (header.is_expanded) {
186             pivot['fold_' + options.type](header);
187             this.draw_table();
188         } else {
189             if (header.path.length < pivot[options.type + '_groupby'].length) {
190                 // expand the corresponding header
191                 var field = pivot[options.type + '_groupby'][header.path.length];
192                 pivot['expand_' + options.type](id, field)
193                     .then(this.proxy('draw_table'));
194             } else {
195                 // display dropdown to query field to expand
196                 this.display_dropdown({id:header.id, 
197                                        type: options.type,
198                                        target: $(event.target), 
199                                        x: event.pageX, 
200                                        y: event.pageY});
201             }
202         }
203     },
204
205     display_dropdown: function (options) {
206         var self = this,
207             pivot = this.pivot_table,
208             already_grouped = pivot.row_groupby.concat(pivot.col_groupby),
209             possible_groups = _.difference(self.data.important_fields, already_grouped),
210             dropdown_options = {
211                 fields: _.map(possible_groups, function (field) {
212                     return {id: field, value: self.data.fields[field].string};
213             })};
214         dropdown_options[options.type + '_id'] = options.id;
215
216         this.dropdown = $(QWeb.render('field_selection', dropdown_options));
217         options.target.after(this.dropdown);
218         this.dropdown.css({position:'absolute',
219                            left:options.x,
220                            top:options.y});
221         $('.field-selection').next('.dropdown-menu').toggle();
222     },
223
224     draw_table: function () {
225         this.table.empty();
226         this.draw_top_headers();
227         _.each(this.pivot_table.rows, this.proxy('draw_row'));
228     },
229
230     make_border_cell: function (colspan, rowspan) {
231         return $('<td></td>').addClass('graph_border')
232                              .attr('colspan', colspan)
233                              .attr('rowspan', rowspan);
234     },
235
236     make_header_title: function (header) {
237         return $('<span> </span>')
238             .addClass('web_graph_click')
239             .attr('href', '#')
240             .addClass((header.is_expanded) ? 'icon-minus-sign' : 'icon-plus-sign')
241             .append(header.name);
242     },
243
244     draw_top_headers: function () {
245         var self = this,
246             pivot = this.pivot_table,
247             height = _.max(_.map(pivot.cols, function(g) {return g.path.length;})),
248             header_cells = [[this.make_border_cell(1, height)]];
249
250         function set_dim (cols) {
251             _.each(cols.children, set_dim);
252             if (cols.children.length === 0) {
253                 cols.height = height - cols.path.length + 1;
254                 cols.width = 1;
255             } else {
256                 cols.height = 1;
257                 cols.width = _.reduce(cols.children, function (sum,c) { return sum + c.width;}, 0);
258             }
259         }
260
261         function make_col_header (col) {
262             var cell = self.make_border_cell(col.width, col.height);
263             return cell.append(self.make_header_title(col).attr('data-col-id', col.id));
264         }
265
266         function make_cells (queue, level) {
267             var col = queue[0];
268             queue = _.rest(queue).concat(col.children);
269             if (col.path.length == level) {
270                 _.last(header_cells).push(make_col_header(col));
271             } else {
272                 level +=1;
273                 header_cells.push([make_col_header(col)]);
274             }
275             if (queue.length !== 0) {
276                 make_cells(queue, level);
277             }
278         }
279
280         set_dim(pivot.cols[0]);  // add width and height info to columns headers
281         if (pivot.cols[0].children.length === 0) {
282             make_cells(pivot.cols, 0);
283         } else {
284             make_cells(pivot.cols[0].children, 1);
285         }
286
287         _.each(header_cells, function (cells) {
288             self.table.append($("<tr></tr>").append(cells));
289         });
290     },
291
292     draw_row: function (row) {
293         var pivot = this.pivot_table,
294             html_row = $('<tr></tr>'),
295             row_header = this.make_border_cell(1,1)
296                 .append(this.make_header_title(row).attr('data-row-id', row.id))
297                 .addClass('graph_border');
298
299         for (var i in _.range(row.path.length)) {
300             row_header.prepend($('<span/>', {class:'web_graph_indent'}));
301         }
302
303         html_row.append(row_header);
304
305         _.each(pivot.cols, function (col) {
306             if (col.children.length === 0) {
307                 var cell = $('<td></td>').append(pivot.get_value(row.id, col.id));
308                 html_row.append(cell);
309             }
310         });
311         this.table.append(html_row);
312     }
313 });
314
315 };