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