[IMP] factorise View.do_show do_hide
[odoo/odoo.git] / addons / web_kanban / static / src / js / kanban.js
1 openerp.web_kanban = function (openerp) {
2
3 var _t = openerp.web._t;
4 var QWeb = openerp.web.qweb;
5 openerp.web.views.add('kanban', 'openerp.web_kanban.KanbanView');
6
7 openerp.web_kanban.KanbanView = openerp.web.View.extend({
8     template: "KanbanView",
9     default_nr_columns: 3,
10     init: function (parent, dataset, view_id, options) {
11         this._super(parent);
12         this.set_default_options(options);
13         this.dataset = dataset;
14         this.view_id = view_id;
15         this.fields_view = {};
16         this.fields_keys = [];
17         this.group_by = null;
18         this.state = {
19             groups : {},
20             records : {}
21         };
22         this.groups = [];
23         this.form_dialog = new openerp.web.FormDialog(this, {}, this.options.action_views_ids.form, dataset).start();
24         this.form_dialog.on_form_dialog_saved.add_last(this.do_reload);
25         this.aggregates = {};
26         this.group_operators = ['avg', 'max', 'min', 'sum', 'count'];
27         this.qweb = new QWeb2.Engine();
28         this.qweb.debug = openerp.connection.debug;
29         this.qweb.default_dict = _.clone(QWeb.default_dict);
30         this.has_been_loaded = $.Deferred();
31         this.search_domain = this.search_context = this.search_group_by = null;
32         this.currently_dragging = {};
33     },
34     start: function() {
35         this._super();
36         this.$element.find('button.oe_kanban_button_new').click(this.do_add_record);
37         this.$groups = this.$element.find('.oe_kanban_groups tr');
38         var context = new openerp.web.CompoundContext(this.dataset.get_context());
39         return this.rpc('/web/view/load', {
40                 'model': this.dataset.model,
41                 'view_id': this.view_id,
42                 'view_type': 'kanban',
43                 context: context
44             }, this.on_loaded);
45     },
46     on_loaded: function(data) {
47         this.fields_view = data;
48         this.fields_keys = _.keys(this.fields_view.fields);
49         this.add_qweb_template();
50         this.has_been_loaded.resolve();
51     },
52     add_qweb_template: function() {
53         for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) {
54             var child = this.fields_view.arch.children[i];
55             if (child.tag === "templates") {
56                 this.transform_qweb_template(child);
57                 this.qweb.add_template(openerp.web.json_node_to_xml(child));
58                 break;
59             } else if (child.tag === 'field') {
60                 this.extract_aggregates(child);
61             }
62         }
63     },
64     extract_aggregates: function(node) {
65         for (var j = 0, jj = this.group_operators.length; j < jj;  j++) {
66             if (node.attrs[this.group_operators[j]]) {
67                 this.aggregates[node.attrs.name] = node.attrs[this.group_operators[j]];
68                 break;
69             }
70         }
71     },
72     transform_qweb_template: function(node) {
73         var qweb_prefix = QWeb.prefix;
74         switch (node.tag) {
75             case 'field':
76                 node.tag = qweb_prefix;
77                 node.attrs[qweb_prefix + '-esc'] = 'record.' + node.attrs['name'] + '.value';
78                 this.extract_aggregates(node);
79                 break
80             case 'button':
81             case 'a':
82                 var type = node.attrs.type || '';
83                 if (_.indexOf('action,object,edit,delete,color'.split(','), type) !== -1) {
84                     _.each(node.attrs, function(v, k) {
85                         if (_.indexOf('icon,type,name,args,string,context,states,kanban_states'.split(','), k) != -1) {
86                             node.attrs['data-' + k] = v;
87                             delete(node.attrs[k]);
88                         }
89                     });
90                     if (node.attrs['data-states']) {
91                         var states = _.map(node.attrs['data-states'].split(','), function(state) {
92                             return "record.state.raw_value == '" + _.str.trim(state) + "'";
93                         });
94                         node.attrs[qweb_prefix + '-if'] = states.join(' or ');
95                     }
96                     if (node.attrs['data-kanban_states']) {
97                         var states = _.map(node.attrs['data-kanban_states'].split(','), function(state) {
98                             return "record.kanban_state.raw_value == '" + _.str.trim(state) + "'";
99                         });
100                         node.attrs[qweb_prefix + '-if'] = states.join(' or ');
101                     }
102                     if (node.attrs['data-string']) {
103                         node.attrs.title = node.attrs['data-string'];
104                     }
105                     if (node.attrs['data-icon']) {
106                         node.children = [{
107                             tag: 'img',
108                             attrs: {
109                                 src: openerp.connection.server + '/web/static/src/img/icons/' + node.attrs['data-icon'] + '.png',
110                                 width: '16',
111                                 height: '16'
112                             }
113                         }];
114                     }
115                     if (node.tag == 'a') {
116                         node.attrs.href = '#';
117                     } else {
118                         node.attrs.type = 'button';
119                     }
120                     node.attrs['class'] = (node.attrs['class'] || '') + ' oe_kanban_action oe_kanban_action_' + node.tag;
121                 }
122                 break;
123         }
124         if (node.children) {
125             for (var i = 0, ii = node.children.length; i < ii; i++) {
126                 this.transform_qweb_template(node.children[i]);
127             }
128         }
129     },
130     do_add_record: function() {
131         this.dataset.index = null;
132         this.do_switch_view('form');
133     },
134     do_search: function(domain, context, group_by) {
135         var self = this;
136         this.search_domain = domain;
137         this.search_context = context;
138         this.search_group_by = group_by;
139         $.when(this.has_been_loaded).then(function() {
140             self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by;
141             self.datagroup = new openerp.web.DataGroup(self, self.dataset.model, domain, context, self.group_by ? [self.group_by] : []);
142             self.datagroup.list(self.fields_keys, self.do_process_groups, self.do_process_dataset);
143         });
144     },
145     do_process_groups: function(groups) {
146         this.do_clear_groups();
147         this.dataset.ids = [];
148         var self = this,
149             remaining = groups.length - 1,
150             groups_array = [];
151         _.each(groups, function (group, index) {
152             var group_name = group.value,
153                 group_value = group.value,
154                 group_aggregates = {};
155             if (group.value instanceof Array) {
156                 group_name = group.value[1];
157                 group_value = group.value[0];
158             }
159             _.each(self.aggregates, function(value, key) {
160                 group_aggregates[value] = group.aggregates[key];
161             });
162             var dataset = new openerp.web.DataSetSearch(self, self.dataset.model, group.context, group.domain);
163             dataset.read_slice(self.fields_keys, {'domain': group.domain, 'context': group.context}, function(records) {
164                 self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
165                 groups_array[index] = new openerp.web_kanban.KanbanGroup(self, records, group_value, group_name, group_aggregates);
166                 if (!remaining--) {
167                     self.dataset.index = self.dataset.ids.length ? 0 : null;
168                     self.do_add_groups(groups_array);
169                 }
170             });
171         });
172     },
173     do_process_dataset: function(dataset) {
174         var self = this;
175         this.do_clear_groups();
176         this.dataset.read_slice(this.fields_keys, {}, function(records) {
177             var groups = [];
178             while (records.length) {
179                 for (var i = 0; i < self.default_nr_columns; i++) {
180                     if (!groups[i]) {
181                         groups[i] = [];
182                     }
183                     groups[i].push(records.shift());
184                 }
185             }
186             for (var i = 0; i < groups.length; i++) {
187                 groups[i] = new openerp.web_kanban.KanbanGroup(self, _.compact(groups[i]));
188             }
189             self.do_add_groups(groups);
190         });
191     },
192     do_reload: function() {
193         this.do_search(this.search_domain, this.search_context, this.search_group_by);
194     },
195     do_clear_groups: function() {
196         _.each(this.groups, function(group) {
197             group.stop();
198         });
199         this.groups = [];
200         this.$element.find('.oe_kanban_groups_headers, .oe_kanban_groups_records').empty();
201     },
202     do_add_groups: function(groups) {
203         var self = this;
204         _.each(groups, function(group) {
205             self.groups[group.undefined_title ? 'unshift' : 'push'](group);
206         });
207         _.each(this.groups, function(group) {
208             group.appendTo(self.$element.find('.oe_kanban_groups_headers'));
209         });
210         this.on_groups_started();
211     },
212     on_groups_started: function() {
213         var self = this;
214         this.compute_groups_width();
215         if (this.group_by) {
216             this.$element.find('.oe_kanban_column').sortable({
217                 connectWith: '.oe_kanban_column',
218                 handle : '.oe_kanban_draghandle',
219                 start: function(event, ui) {
220                     self.currently_dragging.index = ui.item.index();
221                     self.currently_dragging.group = ui.item.parents('.oe_kanban_column:first').data('widget');
222                 },
223                 stop: function(event, ui) {
224                     var record = ui.item.data('widget'),
225                         old_index = self.currently_dragging.index,
226                         new_index = ui.item.index(),
227                         old_group = self.currently_dragging.group,
228                         new_group = ui.item.parents('.oe_kanban_column:first').data('widget');
229                     if (!(old_group.title === new_group.title && old_group.value === new_group.value && old_index == new_index)) {
230                         self.on_record_moved(record, old_group, old_index, new_group, new_index);
231                     }
232                 },
233                 scroll: false
234             });
235         } else {
236             this.$element.find('.oe_kanban_draghandle').removeClass('oe_kanban_draghandle');
237         }
238     },
239     on_record_moved : function(record, old_group, old_index, new_group, new_index) {
240         var self = this;
241         if (old_group === new_group) {
242             new_group.records.splice(old_index, 1);
243             new_group.records.splice(new_index, 0, record);
244             new_group.do_save_sequences();
245         } else {
246             old_group.records.splice(old_index, 1);
247             new_group.records.splice(new_index, 0, record);
248             record.group = new_group;
249             var data = {};
250             data[this.group_by] = new_group.value;
251             this.dataset.write(record.id, data, {}, function() {
252                 record.do_reload();
253                 new_group.do_save_sequences();
254             }).fail(function(error, evt) {
255                 evt.preventDefault();
256                 alert("An error has occured while moving the record to this group.");
257                 self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
258             });
259         }
260     },
261     compute_groups_width: function() {
262         var unfolded = 0;
263         _.each(this.groups, function(group) {
264             unfolded += group.state.folded ? 0 : 1;
265             group.$element.css('width', '');
266         });
267         _.each(this.groups, function(group) {
268             if (!group.state.folded) {
269                 group.$element.css('width', Math.round(100/unfolded) + '%');
270             }
271         });
272     }
273 });
274
275 openerp.web_kanban.KanbanGroup = openerp.web.Widget.extend({
276     template: 'KanbanView.group_header',
277     init: function (parent, records, value, title, aggregates) {
278         var self = this;
279         this._super(parent);
280         this.view = parent;
281         this.value = value;
282         this.title = title;
283         if (title === false) {
284             this.title = _t('Undefined');
285             this.undefined_title = true;
286         }
287         this.aggregates = aggregates || {};
288         var key = this.view.group_by + '-' + value;
289         if (!this.view.state.groups[key]) {
290             this.view.state.groups[key] = {
291                 folded: false
292             }
293         }
294         this.state = this.view.state.groups[key];
295         this.$records = null;
296         this.records = _.map(records, function(record) {
297             return new openerp.web_kanban.KanbanRecord(self, record);
298         });
299     },
300     start: function() {
301         var self = this,
302             def = this._super();
303         this.$records = $(QWeb.render('KanbanView.group_records_container', { widget : this}));
304         this.$records.appendTo(this.view.$element.find('.oe_kanban_groups_records'));
305         _.each(this.records, function(record) {
306             record.appendTo(self.$records);
307         });
308         this.$element.find(".oe_kanban_fold_icon").click(function() {
309             self.do_toggle_fold();
310             self.view.compute_groups_width();
311             return false;
312         });
313         if (this.state.folded) {
314             this.do_toggle_fold();
315         }
316         this.$element.data('widget', this);
317         this.$records.data('widget', this);
318         return def;
319     },
320     stop: function() {
321         this._super();
322         if (this.$records) {
323             this.$records.remove();
324         }
325     },
326     remove_record: function(id, remove_from_dataset) {
327         for (var i = 0, ii = this.records.length; i < ii; i++) {
328             if (this.records[i]['id'] === id) {
329                 this.records.splice(i, 1);
330             }
331         }
332     },
333     do_toggle_fold: function(compute_width) {
334         this.$element.toggleClass('oe_kanban_group_folded');
335         this.$records.find('.oe_kanban_record').toggle();
336         this.state.folded = this.$element.is('.oe_kanban_group_folded');
337     },
338     do_save_sequences: function() {
339         var self = this;
340         if (_.indexOf(this.view.fields_keys, 'sequence') > -1) {
341             _.each(this.records, function(record, index) {
342                 self.view.dataset.write(record.id, { sequence : index });
343             });
344         }
345     }
346 });
347
348 openerp.web_kanban.KanbanRecord = openerp.web.Widget.extend({
349     template: 'KanbanView.record',
350     init: function (parent, record) {
351         this._super(parent);
352         this.group = parent;
353         this.view = parent.view;
354         this.id = null;
355         this.set_record(record);
356         if (!this.view.state.records[this.id]) {
357             this.view.state.records[this.id] = {
358                 folded: false
359             };
360         }
361         this.state = this.view.state.records[this.id];
362     },
363     set_record: function(record) {
364         this.id = record.id;
365         this.record = this.transform_record(record);
366     },
367     start: function() {
368         this._super();
369         this.$element.data('widget', this);
370         this.bind_events();
371     },
372     transform_record: function(record) {
373         var self = this,
374             new_record = {};
375         _.each(record, function(value, name) {
376             var r = _.clone(self.view.fields_view.fields[name] || {});
377             if ((r.type === 'date' || r.type === 'datetime') && value) {
378                 r.raw_value = openerp.web.auto_str_to_date(value);
379             } else {
380                 r.raw_value = value;
381             }
382             r.value = openerp.web.format_value(value, r);
383             new_record[name] = r;
384         });
385         return new_record;
386     },
387     render: function() {
388         this.qweb_context = {
389             record: this.record,
390             widget: this
391         }
392         for (var p in this) {
393             if (_.str.startsWith(p, 'kanban_')) {
394                 this.qweb_context[p] = _.bind(this[p], this);
395             }
396         }
397         return this._super({
398             'content': this.view.qweb.render('kanban-box', this.qweb_context)
399         });
400     },
401     bind_events: function() {
402         var self = this,
403             $show_on_click = self.$element.find('.oe_kanban_box_show_onclick');
404         $show_on_click.toggle(this.state.folded);
405         this.$element.find('.oe_kanban_box_show_onclick_trigger').click(function() {
406             $show_on_click.toggle();
407             self.state.folded = !self.state.folded;
408         });
409
410         this.$element.find('[tooltip]').tipTip({
411             maxWidth: 500,
412             defaultPosition: 'top',
413             content: function() {
414                 var template = $(this).attr('tooltip');
415                 if (!self.view.qweb.has_template(template)) {
416                     return false;
417                 }
418                 return self.view.qweb.render(template, self.qweb_context);
419             }
420         });
421
422         this.$element.find('.oe_kanban_action').click(function() {
423             var $action = $(this),
424                 type = $action.data('type') || 'button',
425                 method = 'do_action_' + (type === 'action' ? 'object' : type);
426             if (_.str.startsWith(type, 'switch_')) {
427                 self.view.do_switch_view(type.substr(7));
428             } else if (typeof self[method] === 'function') {
429                 self[method]($action);
430             } else {
431                 self.do_warn("Kanban: no action for type : " + type);
432             }
433             return false;
434         });
435     },
436     do_action_delete: function($action) {
437         var self = this;
438         if (confirm(_t("Are you sure you want to delete this record ?"))) {
439             return $.when(this.view.dataset.unlink([this.id])).then(function() {
440                 self.group.remove_record(self.id)
441                 self.stop();
442             });
443         }
444     },
445     do_action_edit: function($action) {
446         var self = this;
447         if ($action.attr('target') === 'dialog') {
448             this.view.form_dialog.select_id(this.id).then(function() {
449                 self.view.form_dialog.open();
450             });
451         } else {
452             if (self.view.dataset.select_id(this.id)) {
453                 this.view.do_switch_view('form');
454             } else {
455                 this.do_warn("Kanban: could not find id#" + id);
456             }
457         }
458     },
459     do_action_color: function($action) {
460         var self = this,
461             colors = '#FFFFFF,#CCCCCC,#FFC7C7,#FFF1C7,#E3FFC7,#C7FFD5,#C7FFFF,#C7D5FF,#E3C7FF,#FFC7F1'.split(','),
462             $cpicker = $(QWeb.render('KanbanColorPicker', { colors : colors, columns: 2 }));
463         $action.after($cpicker);
464         $cpicker.mouseenter(function() {
465             clearTimeout($cpicker.data('timeoutId'));
466         }).mouseleave(function(evt) {
467             var timeoutId = setTimeout(function() { $cpicker.remove() }, 500);
468             $cpicker.data('timeoutId', timeoutId);
469         });
470         $cpicker.find('a').click(function() {
471             var data = {};
472             data[$action.data('name')] = $(this).data('color');
473             self.view.dataset.write(self.id, data, {}, function() {
474                 self.record[$action.data('name')] = $(this).data('color');
475                 self.do_reload();
476             });
477             $cpicker.remove();
478             return false;
479         });
480     },
481     do_action_object: function ($action) {
482         var button_attrs = $action.data();
483         this.view.do_execute_action(button_attrs, this.view.dataset, this.id, this.do_reload);
484     },
485     do_reload: function() {
486         var self = this;
487         this.view.dataset.read_ids([this.id], this.view.fields_keys, function(records) {
488             if (records.length) {
489                 self.set_record(records[0]);
490                 self.do_render();
491             } else {
492                 self.stop();
493             }
494         });
495     },
496     do_render: function() {
497         this.$element.html(this.render());
498         this.bind_events();
499     },
500     kanban_color: function(variable) {
501         var number_of_color_schemes = 10,
502             index = 0;
503         switch (typeof(variable)) {
504             case 'string':
505                 for (var i=0, ii=variable.length; i<ii; i++) {
506                     index += variable.charCodeAt(i);
507                 }
508                 break;
509             case 'number':
510                 index = Math.round(variable);
511                 break;
512             default:
513                 return '';
514         }
515         var color = (index % number_of_color_schemes);
516         return 'oe_kanban_color_' + color;
517     },
518     kanban_gravatar: function(email, size) {
519         size = size || 22;
520         email = _.str.trim(email || '').toLowerCase();
521         var default_ = _.str.isBlank(email) ? 'mm' : 'identicon';
522         var email_md5 = $.md5(email);
523         return 'http://www.gravatar.com/avatar/' + email_md5 + '.png?s=' + size + '&d=' + default_;
524     },
525     kanban_image: function(model, field, id) {
526         id = id || '';
527         return openerp.connection.server + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + id;
528     },
529     kanban_text_ellipsis: function(s, size) {
530         size = size || 160;
531         if (!s) {
532             return '';
533         } else if (s.length <= size) {
534             return s;
535         } else {
536             return s.substr(0, size) + '...';
537         }
538     }
539 });
540 };
541
542 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: