[MERGE] merge few bugfixes
[odoo/odoo.git] / addons / web_kanban / static / src / js / kanban.js
1 openerp.web_kanban = function (instance) {
2
3 var _t = instance.web._t,
4    _lt = instance.web._lt;
5 var QWeb = instance.web.qweb;
6 instance.web.views.add('kanban', 'instance.web_kanban.KanbanView');
7
8 instance.web_kanban.KanbanView = instance.web.View.extend({
9     template: "KanbanView",
10     display_name: _lt('Kanban'),
11     default_nr_columns: 1,
12     view_type: "kanban",
13     quick_create_class: "instance.web_kanban.QuickCreate",
14     number_of_color_schemes: 10,
15     init: function (parent, dataset, view_id, options) {
16         this._super(parent, dataset, view_id, options);
17         _.defaults(this.options, {
18             "quick_creatable": true,
19             "creatable": true,
20             "create_text": undefined,
21             "read_only_mode": false,
22             "confirm_on_delete": true,
23         });
24         this.fields_view = {};
25         this.fields_keys = [];
26         this.group_by = null;
27         this.state = {
28             groups : {},
29             records : {}
30         };
31         this.groups = [];
32         this.form_dialog = new instance.web.form.FormDialog(this, {}, this.options.action_views_ids.form, dataset).start();
33         this.form_dialog.on_form_dialog_saved.add_last(this.do_reload);
34         this.aggregates = {};
35         this.group_operators = ['avg', 'max', 'min', 'sum', 'count'];
36         this.qweb = new QWeb2.Engine();
37         this.qweb.debug = instance.connection.debug;
38         this.qweb.default_dict = _.clone(QWeb.default_dict);
39         this.has_been_loaded = $.Deferred();
40         this.search_domain = this.search_context = this.search_group_by = null;
41         this.currently_dragging = {};
42         this.limit = options.limit || 80;
43         this.add_group_mutex = new $.Mutex();
44     },
45     destroy: function() {
46         this._super.apply(this, arguments);
47         $('html').off('click.kanban');
48     },
49     on_loaded: function(data) {
50         this.fields_view = data;
51         this.$element.addClass(this.fields_view.arch.attrs['class']);
52         this.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this}));
53         if (this.options.$buttons) {
54             this.$buttons.appendTo(this.options.$buttons);
55         } else {
56             this.$element.find('.oe_kanban_buttons').replaceWith(this.$buttons);
57         }
58         this.$buttons
59             .on('click','button.oe_kanban_button_new', this.do_add_record);
60         this.$groups = this.$element.find('.oe_kanban_groups tr');
61         this.fields_keys = _.keys(this.fields_view.fields);
62         this.add_qweb_template();
63         this.has_been_loaded.resolve();
64         return $.when();
65     },
66     _is_quick_create_enabled: function() {
67         if (! this.options.quick_creatable)
68             return false;
69         if (this.fields_view.arch.attrs.quick_create !== undefined)
70             return JSON.parse(this.fields_view.arch.attrs.quick_create);
71         return !! this.group_by;
72     },
73     _is_create_enabled: function() {
74         if (! this.options.creatable)
75             return false;
76         if (this.fields_view.arch.attrs.create !== undefined)
77             return JSON.parse(this.fields_view.arch.attrs.create);
78         return true;
79     },
80     add_qweb_template: function() {
81         for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) {
82             var child = this.fields_view.arch.children[i];
83             if (child.tag === "templates") {
84                 this.transform_qweb_template(child);
85                 this.qweb.add_template(instance.web.json_node_to_xml(child));
86                 break;
87             } else if (child.tag === 'field') {
88                 this.extract_aggregates(child);
89             }
90         }
91     },
92     extract_aggregates: function(node) {
93         for (var j = 0, jj = this.group_operators.length; j < jj;  j++) {
94             if (node.attrs[this.group_operators[j]]) {
95                 this.aggregates[node.attrs.name] = node.attrs[this.group_operators[j]];
96                 break;
97             }
98         }
99     },
100     transform_qweb_template: function(node) {
101         var qweb_add_if = function(node, condition) {
102             if (node.attrs[QWeb.prefix + '-if']) {
103                 condition = _.str.sprintf("(%s) and (%s)", node.attrs[QWeb.prefix + '-if'], condition);
104             }
105             node.attrs[QWeb.prefix + '-if'] = condition;
106         };
107         // Process modifiers
108         if (node.tag && node.attrs.modifiers) {
109             var modifiers = JSON.parse(node.attrs.modifiers || '{}');
110             if (modifiers.invisible) {
111                 qweb_add_if(node, _.str.sprintf("!kanban_compute_domain(%s)", JSON.stringify(modifiers.invisible)));
112             }
113         }
114         switch (node.tag) {
115             case 'field':
116                 node.tag = QWeb.prefix;
117                 node.attrs[QWeb.prefix + '-esc'] = 'record.' + node.attrs['name'] + '.value';
118                 this.extract_aggregates(node);
119                 break;
120             case 'button':
121             case 'a':
122                 var type = node.attrs.type || '';
123                 if (_.indexOf('action,object,edit,delete'.split(','), type) !== -1) {
124                     _.each(node.attrs, function(v, k) {
125                         if (_.indexOf('icon,type,name,args,string,context,states,kanban_states'.split(','), k) != -1) {
126                             node.attrs['data-' + k] = v;
127                             delete(node.attrs[k]);
128                         }
129                     });
130                     if (node.attrs['data-string']) {
131                         node.attrs.title = node.attrs['data-string'];
132                     }
133                     if (node.attrs['data-icon']) {
134                         node.children = [{
135                             tag: 'img',
136                             attrs: {
137                                 src: instance.connection.prefix + '/web/static/src/img/icons/' + node.attrs['data-icon'] + '.png',
138                                 width: '16',
139                                 height: '16'
140                             }
141                         }];
142                     }
143                     if (node.tag == 'a') {
144                         node.attrs.href = '#';
145                     } else {
146                         node.attrs.type = 'button';
147                     }
148                     node.attrs['class'] = (node.attrs['class'] || '') + ' oe_kanban_action oe_kanban_action_' + node.tag;
149                 }
150                 break;
151         }
152         if (node.children) {
153             for (var i = 0, ii = node.children.length; i < ii; i++) {
154                 this.transform_qweb_template(node.children[i]);
155             }
156         }
157     },
158     do_add_record: function() {
159         this.dataset.index = null;
160         this.do_switch_view('form');
161     },
162     do_search: function(domain, context, group_by) {
163         var self = this;
164         this.$element.find('.oe_view_nocontent').remove();
165         this.search_domain = domain;
166         this.search_context = context;
167         this.search_group_by = group_by;
168         $.when(this.has_been_loaded).then(function() {
169             self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by;
170             self.datagroup = new instance.web.DataGroup(self, self.dataset.model, domain, context, self.group_by ? [self.group_by] : []);
171             self.datagroup.list(self.fields_keys, self.do_process_groups, self.do_process_dataset);
172         });
173     },
174     do_process_groups: function(groups) {
175         var self = this;
176         this.add_group_mutex.exec(function() {
177             self.do_clear_groups();
178             self.dataset.ids = [];
179             var remaining = groups.length - 1,
180                 groups_array = [];
181             return $.when.apply(null, _.map(groups, function (group, index) {
182                 var dataset = new instance.web.DataSetSearch(self, self.dataset.model, group.context, group.domain);
183                 return dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit })
184                     .pipe(function(records) {
185                         self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
186                         groups_array[index] = new instance.web_kanban.KanbanGroup(self, records, group, dataset);
187                         if (!remaining--) {
188                             self.dataset.index = self.dataset.size() ? 0 : null;
189                             return self.do_add_groups(groups_array);
190                         }
191                 });
192             }));
193         });
194     },
195     do_process_dataset: function(dataset) {
196         var self = this;
197         this.add_group_mutex.exec(function() {
198             var def = $.Deferred();
199             self.do_clear_groups();
200             self.dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit }).then(function(records) {
201                 if (_.isEmpty(records)) {
202                     self.no_result();
203                     def.reject();
204                 } else {
205                     var kgroup = new instance.web_kanban.KanbanGroup(self, records, null, self.dataset);
206                     self.do_add_groups([kgroup]).then(function() {
207                         def.resolve();
208                     });
209                 }
210             }).then(null, function() {
211                 def.reject();
212             });
213             return def;
214         });
215     },
216     do_reload: function() {
217         this.do_search(this.search_domain, this.search_context, this.search_group_by);
218     },
219     do_clear_groups: function() {
220         _.each(this.groups, function(group) {
221             group.destroy();
222         });
223         this.groups = [];
224         this.$element.find('.oe_kanban_groups_headers, .oe_kanban_groups_records').empty();
225     },
226     do_add_groups: function(groups) {
227         var self = this;
228         _.each(groups, function(group) {
229             self.groups[group.undefined_title ? 'unshift' : 'push'](group);
230         });
231         var groups_started = _.map(this.groups, function(group) {
232             return group.appendTo(self.$element.find('.oe_kanban_groups_headers'));
233         });
234         return $.when.apply(null, groups_started).then(function () {
235             self.on_groups_started();
236         });
237     },
238     on_groups_started: function() {
239         var self = this;
240         this.compute_groups_width();
241         if (this.group_by) {
242             this.$element.find('.oe_kanban_column').sortable({
243                 connectWith: '.oe_kanban_column',
244                 handle : '.oe_kanban_draghandle',
245                 start: function(event, ui) {
246                     self.currently_dragging.index = ui.item.index();
247                     self.currently_dragging.group = ui.item.parents('.oe_kanban_column:first').data('widget');
248                 },
249                 stop: function(event, ui) {
250                     var record = ui.item.data('widget'),
251                         old_index = self.currently_dragging.index,
252                         new_index = ui.item.index(),
253                         old_group = self.currently_dragging.group,
254                         new_group = ui.item.parents('.oe_kanban_column:first').data('widget');
255                     if (!(old_group.title === new_group.title && old_group.value === new_group.value && old_index == new_index)) {
256                         self.on_record_moved(record, old_group, old_index, new_group, new_index);
257                     }
258                 },
259                 scroll: false
260             });
261         } else {
262             this.$element.find('.oe_kanban_draghandle').removeClass('oe_kanban_draghandle');
263         }
264     },
265     on_record_moved : function(record, old_group, old_index, new_group, new_index) {
266         var self = this;
267         $.fn.tipsy.clear();
268         $(old_group.$element).add(new_group.$element).find('.oe_kanban_aggregates, .oe_kanban_group_length').hide();
269         if (old_group === new_group) {
270             new_group.records.splice(old_index, 1);
271             new_group.records.splice(new_index, 0, record);
272             new_group.do_save_sequences();
273         } else {
274             old_group.records.splice(old_index, 1);
275             new_group.records.splice(new_index, 0, record);
276             record.group = new_group;
277             var data = {};
278             data[this.group_by] = new_group.value;
279             this.dataset.write(record.id, data, {}, function() {
280                 record.do_reload();
281                 new_group.do_save_sequences();
282             }).fail(function(error, evt) {
283                 evt.preventDefault();
284                 alert("An error has occured while moving the record to this group.");
285                 self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
286             });
287         }
288     },
289     compute_groups_width: function() {
290         var unfolded = 0;
291         _.each(this.groups, function(group) {
292             unfolded += group.state.folded ? 0 : 1;
293             group.$element.css('width', '');
294         });
295         _.each(this.groups, function(group) {
296             if (!group.state.folded) {
297                 group.$element.css('width', Math.round(100/unfolded) + '%');
298             }
299         });
300     },
301
302     do_show: function() {
303         if (this.$buttons) {
304             this.$buttons.show();
305         }
306         this.do_push_state({});
307         return this._super();
308     },
309     do_hide: function () {
310         if (this.$buttons) {
311             this.$buttons.hide();
312         }
313         return this._super();
314     },
315     open_record: function(id, editable) {
316         if (this.dataset.select_id(id)) {
317             this.do_switch_view('form', null);
318         } else {
319             this.do_warn("Kanban: could not find id#" + id);
320         }
321     },
322     no_result: function() {
323         if (this.groups.group_by
324             || !this.options.action
325             || !this.options.action.help) {
326             return;
327         }
328         this.$element.find('.oe_view_nocontent').remove();
329         this.$element.prepend(
330             $('<div class="oe_view_nocontent">')
331                 .append($('<img>', { src: '/web/static/src/img/view_empty_arrow.png' }))
332                 .append($('<div>').html(this.options.action.help))
333         );
334     }
335 });
336
337 function get_class(name) {
338     return new instance.web.Registry({'tmp' : name}).get_object("tmp");
339 }
340
341 instance.web_kanban.KanbanGroup = instance.web.OldWidget.extend({
342     template: 'KanbanView.group_header',
343     init: function (parent, records, group, dataset) {
344         var self = this;
345         this._super(parent);
346         this.$has_been_started = $.Deferred();
347         this.view = parent;
348         this.group = group;
349         this.dataset = dataset;
350         this.dataset_offset = 0;
351         this.aggregates = {};
352         this.value = this.title = null;
353         if (this.group) {
354             this.value = group.value;
355             this.title = group.value;
356             if (this.value instanceof Array) {
357                 this.title = this.value[1];
358                 this.value = this.value[0];
359             }
360             var field = this.view.fields_view.fields[this.view.group_by];
361             if (field) {
362                 try {
363                     this.title = instance.web.format_value(group.value, field, false);
364                 } catch(e) {}
365             }
366             _.each(this.view.aggregates, function(value, key) {
367                 self.aggregates[value] = group.aggregates[key];
368             });
369         }
370
371         if (this.title === false) {
372             this.title = _t('Undefined');
373             this.undefined_title = true;
374         }
375         var key = this.view.group_by + '-' + this.value;
376         if (!this.view.state.groups[key]) {
377             this.view.state.groups[key] = {
378                 folded: false
379             };
380         }
381         this.state = this.view.state.groups[key];
382         this.$records = null;
383
384         this.records = [];
385         this.$has_been_started.then(function() {
386             self.do_add_records(records);
387         });
388     },
389     start: function() {
390         var self = this,
391             def = this._super();
392         if (! self.view.group_by) {
393             self.$element.addClass("oe_kanban_no_group");
394             self.quick = new (get_class(self.view.quick_create_class))(this, self.dataset, {}, false)
395                 .on('added', self, self.proxy('quick_created'));
396             self.quick.replace($(".oe_kanban_no_group_qc_placeholder"));
397         }
398         this.$records = $(QWeb.render('KanbanView.group_records_container', { widget : this}));
399         this.$records.appendTo(this.view.$element.find('.oe_kanban_groups_records'));
400         this.$element.find(".oe_kanban_fold_icon").click(function() {
401             self.do_toggle_fold();
402             self.view.compute_groups_width();
403             return false;
404         });
405         this.$element.find('.oe_kanban_add').click(function () {
406             if (self.quick) { return; }
407             var ctx = {};
408             ctx['default_' + self.view.group_by] = self.value;
409             self.quick = new (get_class(self.view.quick_create_class))(this, self.dataset, ctx, true)
410                 .on('added', self, self.proxy('quick_created'))
411                 .on('close', self, function() {
412                     this.quick.destroy();
413                     delete this.quick;
414                 });
415             self.quick.appendTo($(".oe_kanban_group_list_header", self.$records));
416             self.quick.focus();
417         });
418         this.$records.find('.oe_kanban_show_more').click(this.do_show_more);
419         if (this.state.folded) {
420             this.do_toggle_fold();
421         }
422         this.$element.data('widget', this);
423         this.$records.data('widget', this);
424         this.$has_been_started.resolve();
425         this.compute_cards_auto_height();
426         return def;
427     },
428     compute_cards_auto_height: function() {
429         // oe_kanban_auto_height is an empty class used by the kanban view in order
430         // to normalize height amongst kanban cards. (by group)
431         var self = this;
432         var min_height = 0;
433         var els = [];
434         _.each(this.records, function(r) {
435             var $e = r.$element.find('.oe_kanban_auto_height').first().css('min-height', 0);
436             if ($e.length) {
437                 els.push($e[0]);
438                 min_height = Math.max(min_height, $e.outerHeight());
439             }
440         });
441         $(els).css('min-height', min_height);
442     },
443     destroy: function() {
444         this._super();
445         if (this.$records) {
446             this.$records.remove();
447         }
448     },
449     do_show_more: function(evt) {
450         var self = this;
451         this.dataset.read_slice(this.view.fields_keys.concat(['__last_update']), {
452             'limit': self.view.limit,
453             'offset': self.dataset_offset += self.view.limit
454         }).then(this.do_add_records);
455     },
456     do_add_records: function(records, prepend) {
457         var self = this;
458         _.each(records, function(record) {
459             var rec = new instance.web_kanban.KanbanRecord(self, record);
460             if (!prepend) {
461                 rec.insertBefore(self.$records.find('.oe_kanban_show_more'));
462                 self.records.push(rec);
463             } else {
464                 rec.insertAfter($(".oe_kanban_group_list_header", self.$records));
465                 self.records.unshift(rec);
466             }
467         });
468         this.$records.find('.oe_kanban_show_more').toggle(this.records.length < this.dataset.size())
469             .find('.oe_kanban_remaining').text(this.dataset.size() - this.records.length);
470     },
471     remove_record: function(id, remove_from_dataset) {
472         for (var i = 0; i < this.records.length; i++) {
473             if (this.records[i]['id'] === id) {
474                 this.records.splice(i, 1);
475                 i--;
476             }
477         }
478     },
479     do_toggle_fold: function(compute_width) {
480         this.$element.add(this.$records).toggleClass('oe_kanban_group_folded');
481         this.state.folded = this.$element.is('.oe_kanban_group_folded');
482     },
483     do_save_sequences: function() {
484         var self = this;
485         if (_.indexOf(this.view.fields_keys, 'sequence') > -1) {
486             _.each(this.records, function(record, index) {
487                 self.view.dataset.write(record.id, { sequence : index });
488             });
489         }
490     },
491     /**
492      * Handles a non-erroneous response from name_create
493      *
494      * @param {(Id, String)} record name_get format for the newly created record
495      */
496     quick_created: function (record) {
497         var id = record, self = this;
498         this.dataset.read_ids([id], this.view.fields_keys)
499             .then(function (records) {
500                 self.view.dataset.ids.push(id);
501                 self.do_add_records(records, true);
502             });
503     }
504 });
505
506 instance.web_kanban.KanbanRecord = instance.web.OldWidget.extend({
507     template: 'KanbanView.record',
508     init: function (parent, record) {
509         this._super(parent);
510         this.group = parent;
511         this.view = parent.view;
512         this.id = null;
513         this.set_record(record);
514         if (!this.view.state.records[this.id]) {
515             this.view.state.records[this.id] = {
516                 folded: false
517             };
518         }
519         this.state = this.view.state.records[this.id];
520     },
521     set_record: function(record) {
522         var self = this;
523         this.id = record.id;
524         this.values = {};
525         _.each(record, function(v, k) {
526             self.values[k] = {
527                 value: v
528             };
529         });
530         this.record = this.transform_record(record);
531     },
532     start: function() {
533         this._super();
534         this.$element.data('widget', this);
535         this.bind_events();
536     },
537     transform_record: function(record) {
538         var self = this,
539             new_record = {};
540         _.each(record, function(value, name) {
541             var r = _.clone(self.view.fields_view.fields[name] || {});
542             if ((r.type === 'date' || r.type === 'datetime') && value) {
543                 r.raw_value = instance.web.auto_str_to_date(value);
544             } else {
545                 r.raw_value = value;
546             }
547             r.value = instance.web.format_value(value, r);
548             new_record[name] = r;
549         });
550         return new_record;
551     },
552     render: function() {
553         this.qweb_context = {
554             record: this.record,
555             widget: this,
556             read_only_mode: this.view.options.read_only_mode,
557         };
558         for (var p in this) {
559             if (_.str.startsWith(p, 'kanban_')) {
560                 this.qweb_context[p] = _.bind(this[p], this);
561             }
562         }
563         return this._super({
564             'content': this.view.qweb.render('kanban-box', this.qweb_context)
565         });
566     },
567     bind_events: function() {
568         var self = this;
569         this.setup_color_picker();
570         var $show_on_click = self.$element.find('.oe_kanban_box_show_onclick');
571         $show_on_click.toggle(this.state.folded);
572         this.$element.find('.oe_kanban_box_show_onclick_trigger').click(function() {
573             $show_on_click.toggle();
574             self.state.folded = !self.state.folded;
575         });
576
577         this.$element.find('[tooltip]').tipsy({
578             delayIn: 500,
579             delayOut: 0,
580             fade: true,
581             title: function() {
582                 var template = $(this).attr('tooltip');
583                 if (!self.view.qweb.has_template(template)) {
584                     return false;
585                 }
586                 return self.view.qweb.render(template, self.qweb_context);
587             },
588             gravity: 's',
589             html: true,
590             opacity: 0.8,
591             trigger: 'hover'
592         });
593
594         // If no draghandle is found, make the whole card as draghandle
595         if (!this.$element.find('.oe_kanban_draghandle').length) {
596             this.$element.children(':first').addClass('oe_kanban_draghandle');
597         }
598
599         this.$element.find('.oe_kanban_action').click(function() {
600             var $action = $(this),
601                 type = $action.data('type') || 'button',
602                 method = 'do_action_' + (type === 'action' ? 'object' : type);
603             if (_.str.startsWith(type, 'switch_')) {
604                 self.view.do_switch_view(type.substr(7));
605             } else if (typeof self[method] === 'function') {
606                 self[method]($action);
607             } else {
608                 self.do_warn("Kanban: no action for type : " + type);
609             }
610         });
611
612         if (this.$element.find('.oe_kanban_global_click').length) {
613             this.$element.on('click', function(ev) {
614                 if (!ev.isTrigger && !$(ev.target).data('events')) {
615                     var trigger = true;
616                     var elem = ev.target;
617                     var ischild = true;
618                     var children = [];
619                     while (elem) {
620                         var events = $(elem).data('events');
621                         if (elem == ev.currentTarget) {
622                             ischild = false;
623                         }
624                         if (ischild) {
625                             children.push(elem);
626                             if (events && events.click) {
627                                 // do not trigger global click if one child has a click event registered
628                                 trigger = false;
629                             }
630                         }
631                         if (trigger && events && events.click) {
632                             _.each(events.click, function(click_event) {
633                                 if (click_event.selector) {
634                                     // For each parent of original target, check if a
635                                     // delegated click is bound to any previously found children
636                                     _.each(children, function(child) {
637                                         if ($(child).is(click_event.selector)) {
638                                             trigger = false;
639                                         }
640                                     });
641                                 }
642                             });
643                         }
644                         elem = elem.parentElement;
645                     }
646                     if (trigger) {
647                         self.on_card_clicked(ev);
648                     }
649                 }
650             });
651         }
652     },
653     on_card_clicked: function(ev) {
654         this.view.open_record(this.id);
655     },
656     setup_color_picker: function() {
657         var self = this;
658         var $el = this.$element.find('ul.oe_kanban_colorpicker');
659         if ($el.length) {
660             $el.html(QWeb.render('KanbanColorPicker', {
661                 widget: this
662             }));
663             $el.on('click', 'a', function(ev) {
664                 ev.preventDefault();
665                 var color_field = $(this).parents('.oe_kanban_colorpicker').first().data('field') || 'color';
666                 var data = {};
667                 data[color_field] = $(this).data('color');
668                 self.view.dataset.write(self.id, data, {}, function() {
669                     self.record[color_field] = $(this).data('color');
670                     self.do_reload();
671                 });
672             });
673         }
674     },
675     do_action_delete: function($action) {
676         var self = this;
677         function do_it() {
678             return $.when(self.view.dataset.unlink([self.id])).then(function() {
679                 self.group.remove_record(self.id);
680                 self.destroy();
681             });
682         }
683         if (this.view.options.confirm_on_delete) {
684             if (confirm(_t("Are you sure you want to delete this record ?"))) {
685                 return do_it();
686             }
687         } else
688             return do_it();
689     },
690     do_action_edit: function($action) {
691         var self = this;
692         if ($action.attr('target') === 'dialog') {
693             this.view.form_dialog.select_id(this.id).then(function() {
694                 self.view.form_dialog.open();
695             });
696         } else {
697             this.view.open_record(this.id, true);
698         }
699     },
700     do_action_object: function ($action) {
701         var button_attrs = $action.data();
702         this.view.do_execute_action(button_attrs, this.view.dataset, this.id, this.do_reload);
703     },
704     do_reload: function() {
705         var self = this;
706         this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).then(function(records) {
707             if (records.length) {
708                 self.set_record(records[0]);
709                 var $render = $(self.render());
710                 self.$element.replaceWith($render);
711                 self.$element = $render;
712                 self.$element.data('widget', self);
713                 self.bind_events();
714                 self.group.compute_cards_auto_height();
715             } else {
716                 self.destroy();
717             }
718         });
719     },
720     kanban_getcolor: function(variable) {
721         var index = 0;
722         switch (typeof(variable)) {
723             case 'string':
724                 for (var i=0, ii=variable.length; i<ii; i++) {
725                     index += variable.charCodeAt(i);
726                 }
727                 break;
728             case 'number':
729                 index = Math.round(variable);
730                 break;
731             default:
732                 return '';
733         }
734         var color = (index % this.view.number_of_color_schemes);
735         return color;
736     },
737     kanban_color: function(variable) {
738         var color = this.kanban_getcolor(variable);
739         return color === '' ? '' : 'oe_kanban_color_' + color;
740     },
741     kanban_gravatar: function(email, size) {
742         size = size || 22;
743         email = _.str.trim(email || '').toLowerCase();
744         var default_ = _.str.isBlank(email) ? 'mm' : 'identicon';
745         var email_md5 = $.md5(email);
746         return 'http://www.gravatar.com/avatar/' + email_md5 + '.png?s=' + size + '&d=' + default_;
747     },
748     kanban_image: function(model, field, id, cache) {
749         id = id || '';
750         var url = instance.connection.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + id;
751         if (cache !== undefined) {
752             // Set the cache duration in seconds.
753             url += '&cache=' + parseInt(cache, 10);
754         }
755         return url;
756     },
757     kanban_text_ellipsis: function(s, size) {
758         size = size || 160;
759         if (!s) {
760             return '';
761         } else if (s.length <= size) {
762             return s;
763         } else {
764             return s.substr(0, size) + '...';
765         }
766     },
767     kanban_compute_domain: function(domain) {
768         return instance.web.form.compute_domain(domain, this.values);
769     }
770 });
771
772 /**
773  * Quick creation view.
774  *
775  * Triggers a single event "added" with a single parameter "name", which is the
776  * name entered by the user
777  *
778  * @class
779  * @type {*}
780  */
781 instance.web_kanban.QuickCreate = instance.web.Widget.extend({
782     template: 'KanbanView.quick_create',
783     
784     /**
785      * close_btn: If true, the widget will display a "Close" button able to trigger
786      * a "close" event.
787      */
788     init: function(parent, dataset, context, buttons) {
789         this._super(parent);
790         this._dataset = dataset;
791         this._buttons = buttons || false;
792         this._context = context || {};
793     },
794     start: function () {
795         var self = this;
796         self.$input = this.$element.find('input');
797         self.$input.keyup(function(event){
798             if(event.keyCode == 13){
799                 self.quick_add();
800             }
801         });
802         $(".oe_kanban_quick_create_add", this.$element).click(function () {
803             self.quick_add();
804         });
805         $(".oe_kanban_quick_create_close", this.$element).click(function () {
806             self.trigger('close');
807         });
808         self.$input.keyup(function(e) {
809             if (e.keyCode == 27 && self._buttons) {
810                 self.trigger('close');
811             }
812         });
813     },
814     focus: function() {
815         this.$element.find('input').focus();
816     },
817     /**
818      * Handles user event from nested quick creation view
819      */
820     quick_add: function () {
821         var self = this;
822         this._dataset.call(
823             'name_create', [self.$input.val(), new instance.web.CompoundContext(
824                     this._dataset.get_context(), this._context)])
825             .pipe(function(record) {
826                 self.$input.val("");
827                 self.trigger('added', record[0]);
828             }, function(error, event) {
829                 event.preventDefault();
830                 return self.slow_create();
831             });
832     },
833     slow_create: function() {
834         var self = this;
835         var pop = new instance.web.form.SelectCreatePopup(this);
836         pop.select_element(
837             self._dataset.model,
838             {
839                 title: _t("Create: ") + (this.string || this.name),
840                 initial_view: "form",
841                 disable_multiple_selection: true
842             },
843             [],
844             {"default_name": self.$input.val()}
845         );
846         pop.on_select_elements.add(function(element_ids) {
847             self.$input.val("");
848             self.trigger('added', element_ids[0]);
849         });
850     }
851 });
852 };
853
854 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: