f473c02e76ed1c9551216edf286ab21c4a809701
[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         var self = this;
18         _.defaults(this.options, {
19             "quick_creatable": true,
20             "creatable": true,
21             "create_text": undefined,
22             "read_only_mode": false,
23             "confirm_on_delete": true,
24         });
25         this.fields_view = {};
26         this.fields_keys = [];
27         this.group_by = null;
28         this.group_by_field = {};
29         this.grouped_by_m2o = false;
30         this.many2manys = [];
31         this.state = {
32             groups : {},
33             records : {}
34         };
35         this.groups = [];
36         this.aggregates = {};
37         this.group_operators = ['avg', 'max', 'min', 'sum', 'count'];
38         this.qweb = new QWeb2.Engine();
39         this.qweb.debug = instance.session.debug;
40         this.qweb.default_dict = _.clone(QWeb.default_dict);
41         this.has_been_loaded = $.Deferred();
42         this.search_domain = this.search_context = this.search_group_by = null;
43         this.currently_dragging = {};
44         this.limit = options.limit || 40;
45         this.add_group_mutex = new $.Mutex();
46         if (!this.options.$buttons || !this.options.$buttons.length) {
47             this.options.$buttons = false;
48         }
49     },
50     view_loading: function(r) {
51         return this.load_kanban(r);
52     },
53     start: function() {
54         var self = this;
55         this._super.apply(this, arguments);
56         this.$el.on('click', '.oe_kanban_dummy_cell', function() {
57             if (self.$buttons) {
58                 self.$buttons.find('.oe_kanban_add_column').openerpBounce();
59             }
60         });
61     },
62     destroy: function() {
63         this._super.apply(this, arguments);
64         $('html').off('click.kanban');
65     },
66     load_kanban: function(data) {
67         this.fields_view = data;
68
69         // use default order if defined in xml description
70         var default_order = this.fields_view.arch.attrs.default_order,
71             unsorted = !this.dataset._sort.length;
72         if (unsorted && default_order) {
73             this.dataset.set_sort(default_order.split(','));
74         }
75         this.$el.addClass(this.fields_view.arch.attrs['class']);
76         this.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this}));
77         if (this.options.$buttons) {
78             this.$buttons.appendTo(this.options.$buttons);
79         } else {
80             this.$('.oe_kanban_buttons').replaceWith(this.$buttons);
81         }
82         this.$buttons
83             .on('click', 'button.oe_kanban_button_new', this.do_add_record)
84             .on('click', '.oe_kanban_add_column', this.do_add_group);
85         this.$groups = this.$el.find('.oe_kanban_groups tr');
86         this.fields_keys = _.keys(this.fields_view.fields);
87         this.add_qweb_template();
88         this.has_been_loaded.resolve();
89         this.trigger('kanban_view_loaded', data);
90         return $.when();
91     },
92     _is_quick_create_enabled: function() {
93         if (!this.options.quick_creatable || !this.is_action_enabled('create'))
94             return false;
95         if (this.fields_view.arch.attrs.quick_create !== undefined)
96             return JSON.parse(this.fields_view.arch.attrs.quick_create);
97         return !! this.group_by;
98     },
99     is_action_enabled: function(action) {
100         if (action === 'create' && !this.options.creatable)
101             return false;
102         return this._super(action);
103     },
104     /*  add_qweb_template
105     *   select the nodes into the xml and send to extract_aggregates the nodes with TagName="field"
106     */
107     add_qweb_template: function() {
108         for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) {
109             var child = this.fields_view.arch.children[i];
110             if (child.tag === "templates") {
111                 this.transform_qweb_template(child);
112                 this.qweb.add_template(instance.web.json_node_to_xml(child));
113                 break;
114             } else if (child.tag === 'field') {
115                 this.extract_aggregates(child);
116             }
117         }
118     },
119     /*  extract_aggregates
120     *   extract the agggregates from the nodes (TagName="field")
121     */
122     extract_aggregates: function(node) {
123         for (var j = 0, jj = this.group_operators.length; j < jj;  j++) {
124             if (node.attrs[this.group_operators[j]]) {
125                 this.aggregates[node.attrs.name] = node.attrs[this.group_operators[j]];
126                 break;
127             }
128         }
129     },
130     transform_qweb_template: function(node) {
131         var qweb_add_if = function(node, condition) {
132             if (node.attrs[QWeb.prefix + '-if']) {
133                 condition = _.str.sprintf("(%s) and (%s)", node.attrs[QWeb.prefix + '-if'], condition);
134             }
135             node.attrs[QWeb.prefix + '-if'] = condition;
136         };
137         // Process modifiers
138         if (node.tag && node.attrs.modifiers) {
139             var modifiers = JSON.parse(node.attrs.modifiers || '{}');
140             if (modifiers.invisible) {
141                 qweb_add_if(node, _.str.sprintf("!kanban_compute_domain(%s)", JSON.stringify(modifiers.invisible)));
142             }
143         }
144         switch (node.tag) {
145             case 'field':
146                 var ftype = this.fields_view.fields[node.attrs.name].type;
147                 ftype = node.attrs.widget ? node.attrs.widget : ftype;
148                 if (ftype === 'many2many') {
149                     if (_.indexOf(this.many2manys, node.attrs.name) < 0) {
150                         this.many2manys.push(node.attrs.name);
151                     }
152                     node.tag = 'div';
153                     node.attrs['class'] = (node.attrs['class'] || '') + ' oe_form_field oe_tags';
154                 } else if (instance.web_kanban.fields_registry.contains(ftype)) {
155                     // do nothing, the kanban record will handle it
156                 } else {
157                     node.tag = QWeb.prefix;
158                     node.attrs[QWeb.prefix + '-esc'] = 'record.' + node.attrs['name'] + '.value';
159                 }
160                 break;
161             case 'button':
162             case 'a':
163                 var type = node.attrs.type || '';
164                 if (_.indexOf('action,object,edit,open,delete,url'.split(','), type) !== -1) {
165                     _.each(node.attrs, function(v, k) {
166                         if (_.indexOf('icon,type,name,args,string,context,states,kanban_states'.split(','), k) != -1) {
167                             node.attrs['data-' + k] = v;
168                             delete(node.attrs[k]);
169                         }
170                     });
171                     if (node.attrs['data-string']) {
172                         node.attrs.title = node.attrs['data-string'];
173                     }
174                     if (node.attrs['data-icon']) {
175                         node.children = [{
176                             tag: 'img',
177                             attrs: {
178                                 src: instance.session.prefix + '/web/static/src/img/icons/' + node.attrs['data-icon'] + '.png',
179                                 width: '16',
180                                 height: '16'
181                             }
182                         }];
183                     }
184                     if (node.tag == 'a' && node.attrs['data-type'] != "url") {
185                         node.attrs.href = '#';
186                     } else {
187                         node.attrs.type = 'button';
188                     }
189                     node.attrs['class'] = (node.attrs['class'] || '') + ' oe_kanban_action oe_kanban_action_' + node.tag;
190                 }
191                 break;
192         }
193         if (node.children) {
194             for (var i = 0, ii = node.children.length; i < ii; i++) {
195                 this.transform_qweb_template(node.children[i]);
196             }
197         }
198     },
199     do_add_record: function() {
200         this.dataset.index = null;
201         this.do_switch_view('form');
202     },
203     do_add_group: function() {
204         var self = this;
205         self.do_action({
206             name: _t("Add column"),
207             res_model: self.group_by_field.relation,
208             views: [[false, 'form']],
209             type: 'ir.actions.act_window',
210             target: "new",
211             context: self.dataset.get_context(),
212             flags: {
213                 action_buttons: true,
214             }
215         });
216         var am = instance.webclient.action_manager;
217         var form = am.dialog_widget.views.form.controller;
218         form.on("on_button_cancel", am.dialog, am.dialog.close);
219         form.on('record_created', self, function(r) {
220             (new instance.web.DataSet(self, self.group_by_field.relation)).name_get([r]).done(function(new_record) {
221                 am.dialog.close();
222                 var domain = self.dataset.domain.slice(0);
223                 domain.push([self.group_by, '=', new_record[0][0]]);
224                 var dataset = new instance.web.DataSetSearch(self, self.dataset.model, self.dataset.get_context(), domain);
225                 var datagroup = {
226                     get: function(key) {
227                         return this[key];
228                     },
229                     value: new_record[0],
230                     length: 0,
231                     aggregates: {},
232                 };
233                 var new_group = new instance.web_kanban.KanbanGroup(self, [], datagroup, dataset);
234                 self.do_add_groups([new_group]).done(function() {
235                     $(window).scrollTo(self.groups.slice(-1)[0].$el, { axis: 'x' });
236                 });
237             });
238         });
239     },
240     do_search: function(domain, context, group_by) {
241         var self = this;
242         this.search_domain = domain;
243         this.search_context = context;
244         this.search_group_by = group_by;
245         return $.when(this.has_been_loaded).then(function() {
246             self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by;
247             self.group_by_field = self.fields_view.fields[self.group_by] || {};
248             self.grouped_by_m2o = (self.group_by_field.type === 'many2one');
249             self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o);
250             self.$el.toggleClass('oe_kanban_grouped_by_m2o', self.grouped_by_m2o);
251             var grouping_fields = self.group_by ? [self.group_by].concat(_.keys(self.aggregates)) : undefined;
252             if (!_.isEmpty(grouping_fields)) {
253                 // ensure group_by fields are read.
254                 self.fields_keys = _.unique(self.fields_keys.concat(grouping_fields));
255             }
256             var grouping = new instance.web.Model(self.dataset.model, context, domain).query(self.fields_keys).group_by(grouping_fields);
257             return self.alive($.when(grouping)).then(function(groups) {
258                 self.remove_no_result();
259                 if (groups) {
260                     return self.do_process_groups(groups);
261                 } else {
262                     return self.do_process_dataset();
263                 }
264             });
265         });
266     },
267     do_process_groups: function(groups) {
268         var self = this;
269
270         // Check in the arch the fields to fetch on the stage to get tooltips data.
271         // Fetching data is done in batch for all stages, to avoid doing multiple
272         // calls. The first naive implementation of group_by_tooltip made a call
273         // for each displayed stage and was quite limited.
274         // Data for the group tooltip (group_by_tooltip) and to display stage-related
275         // legends for kanban state management (states_legend) are fetched in
276         // one call.
277         var group_by_fields_to_read = [];
278         var recurse = function(node) {
279             if (node.tag === "field" && node.attrs && node.attrs.options) {
280                 var options = instance.web.py_eval(node.attrs.options);
281                 var states_fields_to_read = _.map(
282                     options && options.states_legend || {},
283                     function (value, key, list) { return value; });
284                 var tooltip_fields_to_read = _.map(
285                     options && options.group_by_tooltip || {},
286                     function (value, key, list) { return key; });
287                 group_by_fields_to_read = _.union(
288                     group_by_fields_to_read,
289                     states_fields_to_read,
290                     tooltip_fields_to_read);
291             }
292             _.each(node.children, function(child) {
293                 recurse(child);
294             });
295         };
296         recurse(this.fields_view.arch);
297         var group_ids = _.map(groups, function (elem) { return elem.attributes.value[0]});
298         if (this.grouped_by_m2o && group_ids.length && group_by_fields_to_read.length) {
299             var group_data = new instance.web.DataSet(
300                 this,
301                 this.group_by_field.relation).read_ids(group_ids, _.union(['display_name'], group_by_fields_to_read));
302         }
303         else { var group_data = $.Deferred().resolve({}); }
304
305         this.$el.find('table:first').show();
306         this.$el.removeClass('oe_kanban_ungrouped').addClass('oe_kanban_grouped');
307         return $.when(group_data).then(function (results) {
308             _.each(results, function (group_by_data) {
309                 var group = _.find(groups, function (elem) {return elem.attributes.value[0] == group_by_data.id});
310                 if (group) {
311                     group.values = group_by_data;
312                 }
313             });
314         }).done( function () {return self.add_group_mutex.exec(function() {
315             self.do_clear_groups();
316             self.dataset.ids = [];
317             if (!groups.length) {
318                 self.no_result();
319                 return $.when();
320             }
321             self.nb_records = 0;
322             var groups_array = [];
323             return $.when.apply(null, _.map(groups, function (group, index) {
324                 var def = $.when([]);
325                 var dataset = new instance.web.DataSetSearch(self, self.dataset.model,
326                     new instance.web.CompoundContext(self.dataset.get_context(), group.model.context()), group.model.domain());
327                 if (group.attributes.length >= 1) {
328                     def = dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit });
329                 }
330                 return def.then(function(records) {
331                         self.nb_records += records.length;
332                         self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
333                         groups_array[index] = new instance.web_kanban.KanbanGroup(self, records, group, dataset);
334                 });
335             })).then(function () {
336                 if(!self.nb_records) {
337                     self.no_result();
338                 }
339                 if (self.dataset.index >= self.nb_records){
340                     self.dataset.index = self.dataset.size() ? 0 : null;
341                 }
342                 return self.do_add_groups(groups_array).done(function() {
343                     self.trigger('kanban_groups_processed');
344                 });
345             });
346         })});
347     },
348     do_process_dataset: function() {
349         var self = this;
350         this.$el.find('table:first').show();
351         this.$el.removeClass('oe_kanban_grouped').addClass('oe_kanban_ungrouped');
352         var def = $.Deferred();
353         this.add_group_mutex.exec(function() {
354             self.do_clear_groups();
355             self.dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit }).done(function(records) {
356                 var kgroup = new instance.web_kanban.KanbanGroup(self, records, null, self.dataset);
357                 if (!_.isEmpty(self.dataset.ids) && (self.dataset.index === null || self.dataset.index >= self.dataset.ids.length)) {
358                     self.dataset.index = 0;
359                 } else if (_.isEmpty(self.dataset.ids)){
360                     self.dataset.index = null;
361                 }
362                 self.do_add_groups([kgroup]).done(function() {
363                     if (_.isEmpty(records)) {
364                         self.no_result();
365                     }
366                     self.trigger('kanban_dataset_processed');
367                     def.resolve();
368                 });
369             }).done(null, function() {
370                 def.reject();
371             });
372         });
373         return def;
374     },
375     do_reload: function() {
376         this.do_search(this.search_domain, this.search_context, this.search_group_by);
377     },
378     do_clear_groups: function() {
379         var groups = this.groups.slice(0);
380         this.groups = [];
381         _.each(groups, function(group) {
382             group.destroy();
383         });
384     },
385     do_add_groups: function(groups) {
386         var self = this;
387         var $parent = this.$el.parent();
388         this.$el.detach();
389         _.each(groups, function(group) {
390             self.groups[group.undefined_title ? 'unshift' : 'push'](group);
391         });
392         var $last_td = self.$el.find('.oe_kanban_groups_headers td:last');
393         var groups_started = _.map(this.groups, function(group) {
394             if (!group.is_started) {
395                 group.on("add_record", self, function () {
396                     self.remove_no_result();
397                 });
398                 return group.insertBefore($last_td);
399             }
400         });
401         return $.when.apply(null, groups_started).done(function () {
402             self.on_groups_started();
403             self.$el.appendTo($parent);
404             _.each(self.groups, function(group) {
405                 group.compute_cards_auto_height();
406             });
407         });
408     },
409     on_groups_started: function() {
410         var self = this;
411         if (this.group_by || this.fields_keys.indexOf("sequence") !== -1) {
412             // Kanban cards drag'n'drop
413             var prev_widget, is_folded, record, $columns;
414             if (this.group_by) {
415                 $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards, .oe_kanban_column .oe_kanban_folded_column_cards');
416             } else {
417                 $columns = this.$el.find('.oe_kanban_column_cards');
418             }
419             $columns.sortable({
420                 handle : '.oe_kanban_draghandle',
421                 start: function(event, ui) {
422                     self.currently_dragging.index = ui.item.parent().children('.oe_kanban_record').index(ui.item);
423                     self.currently_dragging.group = prev_widget = ui.item.parents('.oe_kanban_column:first').data('widget');
424                     ui.item.find('*').on('click.prevent', function(ev) {
425                         return false;
426                     });
427                     record = ui.item.data('widget');
428                     record.$el.bind('mouseup',function(ev,ui){
429                         if (is_folded) {
430                             record.$el.hide();
431                         }
432                         record.$el.unbind('mouseup');
433                     })
434                     ui.placeholder.height(ui.item.height());
435                 },
436                 over: function(event, ui) {
437                     var parent = $(event.target).parent();
438                     prev_widget.highlight(false);
439                     is_folded = parent.hasClass('oe_kanban_group_folded'); 
440                     if (is_folded) {
441                         var widget = parent.data('widget');
442                         widget.highlight(true);
443                         prev_widget = widget;
444                     }
445                  },
446                 revert: 150,
447                 stop: function(event, ui) {
448                     prev_widget.highlight(false);
449                     var old_index = self.currently_dragging.index;
450                     var new_index = ui.item.parent().children('.oe_kanban_record').index(ui.item);
451                     var old_group = self.currently_dragging.group;
452                     var new_group = ui.item.parents('.oe_kanban_column:first').data('widget');
453                     if (!(old_group.title === new_group.title && old_group.value === new_group.value && old_index == new_index)) {
454                         self.on_record_moved(record, old_group, old_index, new_group, new_index);
455                     }
456                     setTimeout(function() {
457                         // A bit hacky but could not find a better solution for Firefox (problem not present in chrome)
458                         // http://stackoverflow.com/questions/274843/preventing-javascript-click-event-with-scriptaculous-drag-and-drop
459                         ui.item.find('*').off('click.prevent');
460                     }, 0);
461                 },
462                 scroll: false
463             });
464             // Keep connectWith out of the sortable initialization for performance sake:
465             // http://www.planbox.com/blog/development/coding/jquery-ui-sortable-slow-to-bind.html
466             $columns.sortable({ connectWith: $columns });
467
468             // Kanban groups drag'n'drop
469             var start_index;
470             if (this.grouped_by_m2o) {
471                 this.$('.oe_kanban_groups_headers').sortable({
472                     items: '.oe_kanban_group_header',
473                     helper: 'clone',
474                     axis: 'x',
475                     opacity: 0.5,
476                     scroll: false,
477                     start: function(event, ui) {
478                         start_index = ui.item.index();
479                         self.$('.oe_kanban_record, .oe_kanban_quick_create').css({ visibility: 'hidden' });
480                     },
481                     stop: function(event, ui) {
482                         var stop_index = ui.item.index();
483                         if (start_index !== stop_index) {
484                             var $start_column = self.$('.oe_kanban_groups_records .oe_kanban_column').eq(start_index);
485                             var $stop_column = self.$('.oe_kanban_groups_records .oe_kanban_column').eq(stop_index);
486                             var method = (start_index > stop_index) ? 'insertBefore' : 'insertAfter';
487                             $start_column[method]($stop_column);
488                             var tmp_group = self.groups.splice(start_index, 1)[0];
489                             self.groups.splice(stop_index, 0, tmp_group);
490                             var new_sequence = _.pluck(self.groups, 'value');
491                             (new instance.web.DataSet(self, self.group_by_field.relation)).resequence(new_sequence).done(function(r) {
492                                 if (r === false) {
493                                     console.error("Kanban: could not resequence model '%s'. Probably no 'sequence' field.", self.group_by_field.relation);
494                                 }
495                             });
496                         }
497                         self.$('.oe_kanban_record, .oe_kanban_quick_create').css({ visibility: 'visible' });
498                     }
499                 });
500             }
501         } else {
502             this.$el.find('.oe_kanban_draghandle').removeClass('oe_kanban_draghandle');
503         }
504         this.postprocess_m2m_tags();
505     },
506     on_record_moved : function(record, old_group, old_index, new_group, new_index) {
507         var self = this;
508         record.$el.find('[title]').tooltip('destroy');
509         $(old_group.$el).add(new_group.$el).find('.oe_kanban_aggregates, .oe_kanban_group_length').hide();
510         if (old_group === new_group) {
511             new_group.records.splice(old_index, 1);
512             new_group.records.splice(new_index, 0, record);
513             new_group.do_save_sequences();
514         } else {
515             old_group.records.splice(old_index, 1);
516             new_group.records.splice(new_index, 0, record);
517             record.group = new_group;
518             var data = {};
519             data[this.group_by] = new_group.value;
520             this.dataset.write(record.id, data, {}).done(function() {
521                 record.do_reload();
522                 new_group.do_save_sequences();
523                 if (new_group.state.folded) {
524                     new_group.do_action_toggle_fold();
525                     record.prependTo(new_group.$records.find('.oe_kanban_column_cards'));
526                 }
527             }).fail(function(error, evt) {
528                 evt.preventDefault();
529                 alert(_t("An error has occured while moving the record to this group: ") + error.data.message);
530                 self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
531             });
532         }
533     },
534
535     do_show: function() {
536         if (this.options.$buttons) {
537             this.$buttons.show();
538         }
539         this.do_push_state({});
540         return this._super();
541     },
542     do_hide: function () {
543         if (this.$buttons) {
544             this.$buttons.hide();
545         }
546         return this._super();
547     },
548     open_record: function(id, editable) {
549         if (this.dataset.select_id(id)) {
550             this.do_switch_view('form', null, { mode: editable ? "edit" : undefined });
551         } else {
552             this.do_warn("Kanban: could not find id#" + id);
553         }
554     },
555     no_result: function() {
556         var self = this;
557         if (this.groups.group_by
558             || !this.options.action
559             || (!this.options.action.help && !this.options.action.get_empty_list_help)) {
560             return;
561         }
562         this.$el.css("position", "relative");
563         $(QWeb.render('KanbanView.nocontent', { content : this.options.action.get_empty_list_help || this.options.action.help})).insertBefore(this.$('table:first'));
564         this.$el.find('.oe_view_nocontent').click(function() {
565             self.$buttons.openerpBounce();
566         });
567     },
568     remove_no_result: function() {
569         this.$el.css("position", "");
570         this.$el.find('.oe_view_nocontent').remove();
571     },
572
573     /*
574     *  postprocessing of fields type many2many
575     *  make the rpc request for all ids/model and insert value inside .oe_tags fields
576     */
577     postprocess_m2m_tags: function() {
578         var self = this;
579         if (!this.many2manys.length) {
580             return;
581         }
582         var relations = {};
583         this.groups.forEach(function(group) {
584             group.records.forEach(function(record) {
585                 self.many2manys.forEach(function(name) {
586                     var field = record.record[name];
587                     var $el = record.$('.oe_form_field.oe_tags[name=' + name + ']').empty();
588                     if (!relations[field.relation]) {
589                         relations[field.relation] = { ids: [], elements: {}};
590                     }
591                     var rel = relations[field.relation];
592                     field.raw_value.forEach(function(id) {
593                         rel.ids.push(id);
594                         if (!rel.elements[id]) {
595                             rel.elements[id] = [];
596                         }
597                         rel.elements[id].push($el[0]);
598                     });
599                 });
600             });
601         });
602        _.each(relations, function(rel, rel_name) {
603             var dataset = new instance.web.DataSetSearch(self, rel_name, self.dataset.get_context());
604             dataset.name_get(_.uniq(rel.ids)).done(function(result) {
605                 result.forEach(function(nameget) {
606                     $(rel.elements[nameget[0]]).append('<span class="oe_tag">' + _.str.escapeHTML(nameget[1]) + '</span>');
607                 });
608             });
609         });
610     }
611 });
612
613
614 function get_class(name) {
615     return new instance.web.Registry({'tmp' : name}).get_object("tmp");
616 }
617
618 instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
619     template: 'KanbanView.group_header',
620     init: function (parent, records, group, dataset) {
621         var self = this;
622         this._super(parent);
623         this.$has_been_started = $.Deferred();
624         this.view = parent;
625         this.group = group;
626         this.dataset = dataset;
627         this.dataset_offset = 0;
628         this.aggregates = {};
629         this.value = this.title = this.values = null;
630         if (this.group) {
631             this.values = group.values;
632             this.value = group.get('value');
633             this.title = group.get('value');
634             if (this.value instanceof Array) {
635                 this.title = this.value[1];
636                 this.value = this.value[0];
637             }
638             var field = this.view.group_by_field;
639             if (!_.isEmpty(field)) {
640                 try {
641                     this.title = instance.web.format_value(group.get('value'), field, false);
642                 } catch(e) {}
643             }
644             _.each(this.view.aggregates, function(value, key) {
645                 self.aggregates[value] = instance.web.format_value(group.get('aggregates')[key], {type: 'float'});
646             });
647         }
648
649         if (this.title === false) {
650             this.title = _t('Undefined');
651             this.undefined_title = true;
652         }
653         var key = this.view.group_by + '-' + this.value;
654         if (!this.view.state.groups[key]) {
655             this.view.state.groups[key] = {
656                 folded: group ? group.get('folded') : false
657             };
658         }
659         this.state = this.view.state.groups[key];
660         this.$records = null;
661
662         this.records = [];
663         this.$has_been_started.done(function() {
664             self.do_add_records(records);
665         });
666     },
667     start: function() {
668         var self = this,
669             def = this._super();
670         if (! self.view.group_by) {
671             self.$el.addClass("oe_kanban_no_group");
672             self.quick = new (get_class(self.view.quick_create_class))(this, self.dataset, {}, false)
673                 .on('added', self, self.proxy('quick_created'));
674             self.quick.replace($(".oe_kanban_no_group_qc_placeholder"));
675         }
676         this.$records = $(QWeb.render('KanbanView.group_records_container', { widget : this}));
677         this.$records.insertBefore(this.view.$el.find('.oe_kanban_groups_records td:last'));
678
679         this.$el.on('click', '.oe_kanban_group_dropdown li a', function(ev) {
680             var fn = 'do_action_' + $(ev.target).data().action;
681             if (typeof(self[fn]) === 'function') {
682                 self[fn]($(ev.target));
683             }
684         });
685
686         this.$el.find('.oe_kanban_add').click(function () {
687             if (self.view.quick) {
688                 self.view.quick.trigger('close');
689             }
690             if (self.quick) {
691                 return false;
692             }
693             self.view.$el.find('.oe_view_nocontent').hide();
694             var ctx = {};
695             ctx['default_' + self.view.group_by] = self.value;
696             self.quick = new (get_class(self.view.quick_create_class))(this, self.dataset, ctx, true)
697                 .on('added', self, self.proxy('quick_created'))
698                 .on('close', self, function() {
699                     self.view.$el.find('.oe_view_nocontent').show();
700                     this.quick.destroy();
701                     delete self.view.quick;
702                     delete this.quick;
703                 });
704             self.quick.appendTo($(".oe_kanban_group_list_header", self.$records));
705             self.quick.focus();
706             self.view.quick = self.quick;
707         });
708         // Add bounce effect on image '+' of kanban header when click on empty space of kanban grouped column.
709         this.$records.on('click', '.oe_kanban_show_more', this.do_show_more);
710         if (this.state.folded) {
711             this.do_toggle_fold();
712         }
713         this.$el.data('widget', this);
714         this.$records.data('widget', this);
715         this.$has_been_started.resolve();
716         var add_btn = this.$el.find('.oe_kanban_add');
717         add_btn.tooltip({delay: { show: 500, hide:1000 }});
718         this.$records.find(".oe_kanban_column_cards").click(function (ev) {
719             if (ev.target == ev.currentTarget) {
720                 if (!self.state.folded) {
721                     add_btn.openerpBounce();
722                 }
723             }
724         });
725         this.is_started = true;
726         this.fetch_tooltip();
727         return def;
728     },
729     /* 
730      * Form the tooltip, based on optional group_by_tooltip on the grouping field.
731      * This function goes through the arch of the view, finding the declaration
732      * of the field used to group. If group_by_tooltip is defined, use the previously
733      * computed values of the group to form the tooltip. */
734     fetch_tooltip: function() {
735         var self = this;
736         if (! this.group)
737             return;        
738         var options = null;
739         var recurse = function(node) {
740             if (node.tag === "field" && node.attrs.name == self.view.group_by) {
741                 options = instance.web.py_eval(node.attrs.options || '{}');
742                 return;
743             }
744             _.each(node.children, function(child) {
745                 recurse(child);
746             });
747         };
748         recurse(this.view.fields_view.arch);
749         if (options && options.group_by_tooltip) {
750             this.tooltip = _.union(
751                 [this.title],
752                 _.map(
753                     options.group_by_tooltip,
754                     function (key, value, list) { return self.values[value] || ''; })
755             ).join('\n\n');
756             this.$(".oe_kanban_group_title_text").attr("title", this.tooltip || this.title || "");
757         }
758     },
759     compute_cards_auto_height: function() {
760         // oe_kanban_no_auto_height is an empty class used to disable this feature
761         if (!this.view.group_by) {
762             var min_height = 0;
763             var els = [];
764             _.each(this.records, function(r) {
765                 var $e = r.$el.children(':first:not(.oe_kanban_no_auto_height)').css('min-height', 0);
766                 if ($e.length) {
767                     els.push($e[0]);
768                     min_height = Math.max(min_height, $e.outerHeight());
769                 }
770             });
771             $(els).css('min-height', min_height);
772         }
773     },
774     destroy: function() {
775         this._super();
776         if (this.$records) {
777             this.$records.remove();
778         }
779     },
780     do_show_more: function(evt) {
781         var self = this;
782         var ids = self.view.dataset.ids.splice(0);
783         return this.dataset.read_slice(this.view.fields_keys.concat(['__last_update']), {
784             'limit': self.view.limit,
785             'offset': self.dataset_offset += self.view.limit
786         }).then(function(records) {
787             self.view.dataset.ids = ids.concat(self.dataset.ids);
788             self.do_add_records(records);
789             self.compute_cards_auto_height();
790             self.view.postprocess_m2m_tags();
791             return records;
792         });
793     },
794     do_add_records: function(records, prepend) {
795         var self = this;
796         var $list_header = this.$records.find('.oe_kanban_group_list_header');
797         var $show_more = this.$records.find('.oe_kanban_show_more');
798         var $cards = this.$records.find('.oe_kanban_column_cards');
799
800         _.each(records, function(record) {
801             var rec = new instance.web_kanban.KanbanRecord(self, record);
802             if (!prepend) {
803                 rec.appendTo($cards);
804                 self.records.push(rec);
805             } else {
806                 rec.prependTo($cards);
807                 self.records.unshift(rec);
808             }
809         });
810         if ($show_more.length) {
811             var size = this.dataset.size();
812             $show_more.toggle(this.records.length < size).find('.oe_kanban_remaining').text(size - this.records.length);
813         }
814     },
815     remove_record: function(id, remove_from_dataset) {
816         for (var i = 0; i < this.records.length; i++) {
817             if (this.records[i]['id'] === id) {
818                 this.records.splice(i, 1);
819                 i--;
820             }
821         }
822     },
823     do_toggle_fold: function(compute_width) {
824         this.$el.add(this.$records).toggleClass('oe_kanban_group_folded');
825         this.state.folded = this.$el.is('.oe_kanban_group_folded');
826         this.$("ul.oe_kanban_group_dropdown li a[data-action=toggle_fold]").text((this.state.folded) ? _t("Unfold") : _t("Fold"));
827     },
828     do_action_toggle_fold: function() {
829         this.do_toggle_fold();
830     },
831     do_action_edit: function() {
832         var self = this;
833         self.do_action({
834             res_id: this.value,
835             name: _t("Edit column"),
836             res_model: self.view.group_by_field.relation,
837             views: [[false, 'form']],
838             type: 'ir.actions.act_window',
839             target: "new",
840             flags: {
841                 action_buttons: true,
842             }
843         });
844         var am = instance.webclient.action_manager;
845         var form = am.dialog_widget.views.form.controller;
846         form.on("on_button_cancel", am.dialog, function() { return am.dialog.$dialog_box.modal('hide'); });
847         form.on('record_saved', self, function() {
848             am.dialog.$dialog_box.modal('hide');
849             self.view.do_reload();
850         });
851     },
852     do_action_delete: function() {
853         var self = this;
854         if (confirm(_t("Are you sure to remove this column ?"))) {
855             (new instance.web.DataSet(self, self.view.group_by_field.relation)).unlink([self.value]).done(function(r) {
856                 self.view.do_reload();
857             });
858         }
859     },
860     do_save_sequences: function() {
861         var self = this;
862         if (_.indexOf(this.view.fields_keys, 'sequence') > -1) {
863             var new_sequence = _.pluck(this.records, 'id');
864             self.view.dataset.resequence(new_sequence);
865         }
866     },
867     /**
868      * Handles a newly created record
869      *
870      * @param {id} id of the newly created record
871      */
872     quick_created: function (record) {
873         var id = record, self = this;
874         self.view.remove_no_result();
875         self.trigger("add_record");
876         this.dataset.read_ids([id], this.view.fields_keys)
877             .done(function (records) {
878                 self.view.dataset.ids.push(id);
879                 self.do_add_records(records, true);
880             });
881     },
882     highlight: function(show){
883         if(show){
884             this.$el.addClass('oe_kanban_column_higlight');
885             this.$records.addClass('oe_kanban_column_higlight');
886         }else{
887             this.$el.removeClass('oe_kanban_column_higlight');
888             this.$records.removeClass('oe_kanban_column_higlight');
889         }
890     }
891 });
892
893 instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
894     template: 'KanbanView.record',
895     init: function (parent, record) {
896         this._super(parent);
897         this.group = parent;
898         this.view = parent.view;
899         this.id = null;
900         this.set_record(record);
901         if (!this.view.state.records[this.id]) {
902             this.view.state.records[this.id] = {
903                 folded: false
904             };
905         }
906         this.state = this.view.state.records[this.id];
907         this.fields = {};
908     },
909     set_record: function(record) {
910         var self = this;
911         this.id = record.id;
912         this.values = {};
913         _.each(record, function(v, k) {
914             self.values[k] = {
915                 value: v
916             };
917         });
918         this.record = this.transform_record(record);
919     },
920     start: function() {
921         var self = this;
922         this._super();
923         this.init_content();
924     },
925     init_content: function() {
926         var self = this;
927         self.sub_widgets = [];
928         this.$("[data-field_id]").each(function() {
929             self.add_widget($(this));
930         });
931         this.$el.data('widget', this);
932         this.bind_events();
933     },
934     transform_record: function(record) {
935         var self = this,
936             new_record = {};
937         _.each(record, function(value, name) {
938             var r = _.clone(self.view.fields_view.fields[name] || {});
939             if ((r.type === 'date' || r.type === 'datetime') && value) {
940                 r.raw_value = instance.web.auto_str_to_date(value);
941             } else {
942                 r.raw_value = value;
943             }
944             r.value = instance.web.format_value(value, r);
945             new_record[name] = r;
946         });
947         return new_record;
948     },
949     renderElement: function() {
950         this.qweb_context = {
951             instance: instance,
952             record: this.record,
953             widget: this,
954             read_only_mode: this.view.options.read_only_mode,
955         };
956         for (var p in this) {
957             if (_.str.startsWith(p, 'kanban_')) {
958                 this.qweb_context[p] = _.bind(this[p], this);
959             }
960         }
961         var $el = instance.web.qweb.render(this.template, {
962             'widget': this,
963             'content': this.view.qweb.render('kanban-box', this.qweb_context)
964         });
965         this.replaceElement($el);
966         this.replace_fields();
967     },
968     replace_fields: function() {
969         var self = this;
970         this.$("field").each(function() {
971             var $field = $(this);
972             var $nfield = $("<span></span");
973             var id = _.uniqueId("kanbanfield");
974             self.fields[id] = $field;
975             $nfield.attr("data-field_id", id);
976             $field.replaceWith($nfield);
977         });
978     },
979     add_widget: function($node) {
980         var $orig = this.fields[$node.data("field_id")];
981         var field = this.record[$orig.attr("name")];
982         var type = field.type;
983         type = $orig.attr("widget") ? $orig.attr("widget") : type;
984         var obj = instance.web_kanban.fields_registry.get_object(type);
985         var widget = new obj(this, field, $orig);
986         this.sub_widgets.push(widget);
987         widget.replace($node);
988     },
989     bind_events: function() {
990         var self = this;
991         this.setup_color_picker();
992         this.$el.find('[title]').each(function(){
993             $(this).tooltip({
994                 delay: { show: 500, hide: 0},
995                 title: function() {
996                     var template = $(this).attr('tooltip');
997                     if (!self.view.qweb.has_template(template)) {
998                         return false;
999                     }
1000                     return self.view.qweb.render(template, self.qweb_context);
1001                 },
1002             });
1003         });
1004
1005         // If no draghandle is found, make the whole card as draghandle (provided one can edit)
1006         if (!this.$el.find('.oe_kanban_draghandle').length) {
1007             this.$el.children(':first')
1008                 .toggleClass('oe_kanban_draghandle', this.view.is_action_enabled('edit'));
1009         }
1010
1011         this.$el.find('.oe_kanban_action').click(function(ev) {
1012             ev.preventDefault();
1013             var $action = $(this),
1014                 type = $action.data('type') || 'button',
1015                 method = 'do_action_' + (type === 'action' ? 'object' : type);
1016             if ((type === 'edit' || type === 'delete') && ! self.view.is_action_enabled(type)) {
1017                 self.view.open_record(self.id, true);
1018             } else if (_.str.startsWith(type, 'switch_')) {
1019                 self.view.do_switch_view(type.substr(7));
1020             } else if (typeof self[method] === 'function') {
1021                 self[method]($action);
1022             } else {
1023                 self.do_warn("Kanban: no action for type : " + type);
1024             }
1025         });
1026
1027         if (this.$el.find('.oe_kanban_global_click,.oe_kanban_global_click_edit').length) {
1028             this.$el.on('click', function(ev) {
1029                 if (!ev.isTrigger && !$._data(ev.target, 'events')) {
1030                     var trigger = true;
1031                     var elem = ev.target;
1032                     var ischild = true;
1033                     var children = [];
1034                     while (elem) {
1035                         var events = $._data(elem, 'events');
1036                         if (elem == ev.currentTarget) {
1037                             ischild = false;
1038                         }
1039                         if (ischild) {
1040                             children.push(elem);
1041                             if (events && events.click) {
1042                                 // do not trigger global click if one child has a click event registered
1043                                 trigger = false;
1044                             }
1045                         }
1046                         if (trigger && events && events.click) {
1047                             _.each(events.click, function(click_event) {
1048                                 if (click_event.selector) {
1049                                     // For each parent of original target, check if a
1050                                     // delegated click is bound to any previously found children
1051                                     _.each(children, function(child) {
1052                                         if ($(child).is(click_event.selector)) {
1053                                             trigger = false;
1054                                         }
1055                                     });
1056                                 }
1057                             });
1058                         }
1059                         elem = elem.parentElement;
1060                     }
1061                     if (trigger) {
1062                         self.on_card_clicked(ev);
1063                     }
1064                 }
1065             });
1066         }
1067     },
1068     /* actions when user click on the block with a specific class
1069      *  open on normal view : oe_kanban_global_click
1070      *  open on form/edit view : oe_kanban_global_click_edit
1071      */
1072     on_card_clicked: function(ev) {
1073         if (this.$el.find('.oe_kanban_global_click').size() > 0 && this.$el.find('.oe_kanban_global_click').data('routing')) {
1074             instance.web.redirect(this.$el.find('.oe_kanban_global_click').data('routing') + "/" + this.id);
1075         }
1076         else if (this.$el.find('.oe_kanban_global_click_edit').size()>0)
1077             this.do_action_edit();
1078         else
1079             this.do_action_open();
1080     },
1081     setup_color_picker: function() {
1082         var self = this;
1083         var $el = this.$el.find('ul.oe_kanban_colorpicker');
1084         if ($el.length) {
1085             $el.html(QWeb.render('KanbanColorPicker', {
1086                 widget: this
1087             }));
1088             $el.on('click', 'a', function(ev) {
1089                 ev.preventDefault();
1090                 var color_field = $(this).parents('.oe_kanban_colorpicker').first().data('field') || 'color';
1091                 var data = {};
1092                 data[color_field] = $(this).data('color');
1093                 self.view.dataset.write(self.id, data, {}).done(function() {
1094                     self.record[color_field] = $(this).data('color');
1095                     self.do_reload();
1096                 });
1097             });
1098         }
1099     },
1100     do_action_delete: function($action) {
1101         var self = this;
1102         function do_it() {
1103             return $.when(self.view.dataset.unlink([self.id])).done(function() {
1104                 self.group.remove_record(self.id);
1105                 self.destroy();
1106             });
1107         }
1108         if (this.view.options.confirm_on_delete) {
1109             if (confirm(_t("Are you sure you want to delete this record ?"))) {
1110                 return do_it();
1111             }
1112         } else
1113             return do_it();
1114     },
1115     do_action_edit: function($action) {
1116         this.view.open_record(this.id, true);
1117     },
1118     do_action_open: function($action) {
1119         this.view.open_record(this.id);
1120     },
1121     do_action_object: function ($action) {
1122         var button_attrs = $action.data();
1123         this.view.do_execute_action(button_attrs, this.view.dataset, this.id, this.do_reload);
1124     },
1125     do_action_url: function($action) {
1126         return instance.web.redirect($action.attr("href"));
1127      },
1128     do_reload: function() {
1129         var self = this;
1130         this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).done(function(records) {
1131              _.each(self.sub_widgets, function(el) {
1132                  el.destroy();
1133              });
1134              self.sub_widgets = [];
1135             if (records.length) {
1136                 self.set_record(records[0]);
1137                 self.renderElement();
1138                 self.init_content();
1139                 self.group.compute_cards_auto_height();
1140                 self.view.postprocess_m2m_tags();
1141             } else {
1142                 self.destroy();
1143             }
1144         });
1145     },
1146     kanban_getcolor: function(variable) {
1147         var index = 0;
1148         switch (typeof(variable)) {
1149             case 'string':
1150                 for (var i=0, ii=variable.length; i<ii; i++) {
1151                     index += variable.charCodeAt(i);
1152                 }
1153                 break;
1154             case 'number':
1155                 index = Math.round(variable);
1156                 break;
1157             default:
1158                 return '';
1159         }
1160         var color = (index % this.view.number_of_color_schemes);
1161         return color;
1162     },
1163     kanban_color: function(variable) {
1164         var color = this.kanban_getcolor(variable);
1165         return color === '' ? '' : 'oe_kanban_color_' + color;
1166     },
1167     kanban_image: function(model, field, id, cache, options) {
1168         options = options || {};
1169         var url;
1170         if (this.record[field] && this.record[field].value && !instance.web.form.is_bin_size(this.record[field].value)) {
1171             url = 'data:image/png;base64,' + this.record[field].value;
1172         } else if (this.record[field] && ! this.record[field].value) {
1173             url = "/web/static/src/img/placeholder.png";
1174         } else {
1175             id = JSON.stringify(id);
1176             if (options.preview_image)
1177                 field = options.preview_image;
1178             url = this.session.url('/web/binary/image', {model: model, field: field, id: id});
1179             if (cache !== undefined) {
1180                 // Set the cache duration in seconds.
1181                 url += '&cache=' + parseInt(cache, 10);
1182             }
1183         }
1184         return url;
1185     },
1186     kanban_text_ellipsis: function(s, size) {
1187         size = size || 160;
1188         if (!s) {
1189             return '';
1190         } else if (s.length <= size) {
1191             return s;
1192         } else {
1193             return s.substr(0, size) + '...';
1194         }
1195     },
1196     kanban_compute_domain: function(domain) {
1197         return instance.web.form.compute_domain(domain, this.values);
1198     }
1199 });
1200
1201 /**
1202  * Quick creation view.
1203  *
1204  * Triggers a single event "added" with a single parameter "name", which is the
1205  * name entered by the user
1206  *
1207  * @class
1208  * @type {*}
1209  */
1210 instance.web_kanban.QuickCreate = instance.web.Widget.extend({
1211     template: 'KanbanView.quick_create',
1212
1213     /**
1214      * close_btn: If true, the widget will display a "Close" button able to trigger
1215      * a "close" event.
1216      */
1217     init: function(parent, dataset, context, buttons) {
1218         this._super(parent);
1219         this._dataset = dataset;
1220         this._buttons = buttons || false;
1221         this._context = context || {};
1222     },
1223     start: function () {
1224         var self = this;
1225         self.$input = this.$el.find('input');
1226         self.$input.keyup(function(event){
1227             if(event.keyCode == 13){
1228                 self.quick_add();
1229             }
1230         });
1231         $(".oe_kanban_quick_create").focusout(function (e) {
1232             var val = self.$el.find('input').val();
1233             if (/^\s*$/.test(val)) { self.trigger('close'); }
1234             e.stopImmediatePropagation();
1235         });
1236         $(".oe_kanban_quick_create_add", this.$el).click(function () {
1237             self.quick_add();
1238             self.focus();
1239         });
1240         $(".oe_kanban_quick_create_close", this.$el).click(function (ev) {
1241             ev.preventDefault();
1242             self.trigger('close');
1243         });
1244         self.$input.keyup(function(e) {
1245             if (e.keyCode == 27 && self._buttons) {
1246                 self.trigger('close');
1247             }
1248         });
1249     },
1250     focus: function() {
1251         this.$el.find('input').focus();
1252     },
1253     /**
1254      * Handles user event from nested quick creation view
1255      */
1256     quick_add: function () {
1257         var self = this;
1258         var val = this.$input.val();
1259         if (/^\s*$/.test(val)) { this.$el.remove(); return; }
1260         this._dataset.call(
1261             'name_create', [val, new instance.web.CompoundContext(
1262                     this._dataset.get_context(), this._context)])
1263             .then(function(record) {
1264                 self.$input.val("");
1265                 self.trigger('added', record[0]);
1266             }, function(error, event) {
1267                 event.preventDefault();
1268                 return self.slow_create();
1269             });
1270     },
1271     slow_create: function() {
1272         var self = this;
1273         var pop = new instance.web.form.SelectCreatePopup(this);
1274         pop.select_element(
1275             self._dataset.model,
1276             {
1277                 title: _t("Create: ") + (this.string || this.name),
1278                 initial_view: "form",
1279                 disable_multiple_selection: true
1280             },
1281             [],
1282             {"default_name": self.$input.val()}
1283         );
1284         pop.on("elements_selected", self, function(element_ids) {
1285             self.$input.val("");
1286             self.trigger('added', element_ids[0]);
1287         });
1288     }
1289 });
1290
1291 /**
1292  * Interface to be implemented by kanban fields.
1293  *
1294  */
1295 instance.web_kanban.FieldInterface = {
1296     /**
1297         Constructor.
1298         - parent: The widget's parent.
1299         - field: A dictionary giving details about the field, including the current field's value in the
1300             raw_value field.
1301         - $node: The field <field> tag as it appears in the view, encapsulated in a jQuery object.
1302     */
1303     init: function(parent, field, $node) {},
1304 };
1305
1306 /**
1307  * Abstract class for classes implementing FieldInterface.
1308  *
1309  * Properties:
1310  *     - value: useful property to hold the value of the field. By default, the constructor
1311  *     sets value property.
1312  *
1313  */
1314 instance.web_kanban.AbstractField = instance.web.Widget.extend(instance.web_kanban.FieldInterface, {
1315     /**
1316         Constructor that saves the field and $node parameters and sets the "value" property.
1317     */
1318     init: function(parent, field, $node) {
1319         this._super(parent);
1320         this.field = field;
1321         this.$node = $node;
1322         this.options = instance.web.py_eval(this.$node.attr("options") || '{}');
1323         this.set("value", field.raw_value);
1324     },
1325 });
1326
1327 instance.web_kanban.Priority = instance.web_kanban.AbstractField.extend({
1328     init: function(parent, field, $node) {
1329         this._super.apply(this, arguments);
1330         this.name = $node.attr('name')
1331         this.parent = parent;
1332     },
1333     prepare_priority: function() {
1334         var self = this;
1335         var selection = this.field.selection || [];
1336         var init_value = selection && selection[0][0] || 0;
1337         var data = _.map(selection.slice(1), function(element, index) {
1338             var value = {
1339                 'value': element[0],
1340                 'name': element[1],
1341                 'click_value': element[0],
1342             }
1343             if (index == 0 && self.get('value') == element[0]) {
1344                 value['click_value'] = init_value;
1345             }
1346             return value;
1347         });
1348         return data;
1349     },
1350     renderElement: function() {
1351         var self = this;
1352         this.record_id = self.parent.id;
1353         this.priorities = self.prepare_priority();
1354         this.$el = $(QWeb.render("Priority", {'widget': this}));
1355         this.$el.find('li').click(self.do_action.bind(self));
1356     },
1357     do_action: function(e) {
1358         var self = this;
1359         var li = $(e.target).closest( "li" );
1360         if (li.length) {
1361             var value = {};
1362             value[self.name] = String(li.data('value'));
1363             return self.parent.view.dataset._model.call('write', [[self.record_id], value, self.parent.view.dataset.get_context()]).done(self.reload_record.bind(self.parent));
1364         }
1365     },
1366     reload_record: function() {
1367         this.do_reload();
1368     },
1369 });
1370
1371 instance.web_kanban.KanbanSelection = instance.web_kanban.AbstractField.extend({
1372     init: function(parent, field, $node) {
1373         this._super.apply(this, arguments);
1374         this.name = $node.attr('name')
1375         this.parent = parent;
1376     },
1377     prepare_dropdown_selection: function() {
1378         var self = this;
1379         var data = [];
1380         _.map(this.field.selection || [], function(res) {
1381             var value = {
1382                 'name': res[0],
1383                 'tooltip': res[1],
1384                 'state_name': res[1],
1385             }
1386             var leg_opt = self.options && self.options.states_legend || null;
1387             if (leg_opt && leg_opt[res[0]] && self.parent.group.values && self.parent.group.values[leg_opt[res[0]]]) {
1388                 value['state_name'] = self.parent.group.values[leg_opt[res[0]]];
1389             }
1390             if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
1391             else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
1392             else { value['state_class'] = 'oe_kanban_status oe_kanban_status_red'; }
1393             data.push(value);
1394         });
1395         return data;
1396     },
1397     renderElement: function() {
1398         var self = this;
1399         this.record_id = self.parent.id;
1400         this.states = self.prepare_dropdown_selection();;
1401         this.$el = $(QWeb.render("KanbanSelection", {'widget': self}));
1402         this.$el.find('li').click(self.do_action.bind(self));
1403     },
1404     do_action: function(e) {
1405         var self = this;
1406         var li = $(e.target).closest( "li" );
1407         if (li.length) {
1408             var value = {};
1409             value[self.name] = String(li.data('value'));
1410             return self.parent.view.dataset._model.call('write', [[self.record_id], value, self.parent.view.dataset.get_context()]).done(self.reload_record.bind(self.parent));
1411         }
1412     },
1413     reload_record: function() {
1414         this.do_reload();
1415     },
1416 });
1417
1418 instance.web_kanban.fields_registry = new instance.web.Registry({});
1419 instance.web_kanban.fields_registry.add('priority','instance.web_kanban.Priority');
1420 instance.web_kanban.fields_registry.add('kanban_state_selection','instance.web_kanban.KanbanSelection');
1421 };
1422
1423 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: