[FIX] web_kanban: fixed issues when having no column to display in kanban
[odoo/odoo.git] / addons / web_kanban / static / src / js / kanban.js
index 934de72..762f06c 100644 (file)
@@ -43,7 +43,9 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
         this.currently_dragging = {};
         this.limit = options.limit || 40;
         this.add_group_mutex = new $.Mutex();
-        this.last_position = 'static';
+        if (!this.options.$buttons || !this.options.$buttons.length) {
+            this.options.$buttons = false;
+        }
     },
     view_loading: function(r) {
         return this.load_kanban(r);
@@ -63,12 +65,19 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
     },
     load_kanban: function(data) {
         this.fields_view = data;
+
+        // use default order if defined in xml description
+        var default_order = this.fields_view.arch.attrs.default_order,
+            unsorted = !this.dataset._sort.length;
+        if (unsorted && default_order) {
+            this.dataset.set_sort(default_order.split(','));
+        }
         this.$el.addClass(this.fields_view.arch.attrs['class']);
         this.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this}));
         if (this.options.$buttons) {
             this.$buttons.appendTo(this.options.$buttons);
         } else {
-            this.$el.find('.oe_kanban_buttons').replaceWith(this.$buttons);
+            this.$('.oe_kanban_buttons').replaceWith(this.$buttons);
         }
         this.$buttons
             .on('click', 'button.oe_kanban_button_new', this.do_add_record)
@@ -152,7 +161,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
             case 'button':
             case 'a':
                 var type = node.attrs.type || '';
-                if (_.indexOf('action,object,edit,open,delete'.split(','), type) !== -1) {
+                if (_.indexOf('action,object,edit,open,delete,url'.split(','), type) !== -1) {
                     _.each(node.attrs, function(v, k) {
                         if (_.indexOf('icon,type,name,args,string,context,states,kanban_states'.split(','), k) != -1) {
                             node.attrs['data-' + k] = v;
@@ -172,7 +181,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
                             }
                         }];
                     }
-                    if (node.tag == 'a') {
+                    if (node.tag == 'a' && node.attrs['data-type'] != "url") {
                         node.attrs.href = '#';
                     } else {
                         node.attrs.type = 'button';
@@ -245,30 +254,72 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
                 self.fields_keys = _.unique(self.fields_keys.concat(grouping_fields));
             }
             var grouping = new instance.web.Model(self.dataset.model, context, domain).query(self.fields_keys).group_by(grouping_fields);
-            return self.alive($.when(grouping)).done(function(groups) {
+            return self.alive($.when(grouping)).then(function(groups) {
                 self.remove_no_result();
                 if (groups) {
-                    self.do_process_groups(groups);
+                    return self.do_process_groups(groups);
                 } else {
-                    self.do_process_dataset();
+                    return self.do_process_dataset();
                 }
             });
         });
     },
     do_process_groups: function(groups) {
         var self = this;
+
+        // Check in the arch the fields to fetch on the stage to get tooltips data.
+        // Fetching data is done in batch for all stages, to avoid doing multiple
+        // calls. The first naive implementation of group_by_tooltip made a call
+        // for each displayed stage and was quite limited.
+        // Data for the group tooltip (group_by_tooltip) and to display stage-related
+        // legends for kanban state management (states_legend) are fetched in
+        // one call.
+        var group_by_fields_to_read = [];
+        var recurse = function(node) {
+            if (node.tag === "field" && node.attrs && node.attrs.options) {
+                var options = instance.web.py_eval(node.attrs.options);
+                var states_fields_to_read = _.map(
+                    options && options.states_legend || {},
+                    function (value, key, list) { return value; });
+                var tooltip_fields_to_read = _.map(
+                    options && options.group_by_tooltip || {},
+                    function (value, key, list) { return key; });
+                group_by_fields_to_read = _.union(
+                    group_by_fields_to_read,
+                    states_fields_to_read,
+                    tooltip_fields_to_read);
+            }
+            _.each(node.children, function(child) {
+                recurse(child);
+            });
+        };
+        recurse(this.fields_view.arch);
+        var group_ids = _.without(_.map(groups, function (elem) { return elem.attributes.value[0]}), undefined);
+        if (this.grouped_by_m2o && group_ids.length && group_by_fields_to_read.length) {
+            var group_data = new instance.web.DataSet(
+                this,
+                this.group_by_field.relation).read_ids(group_ids, _.union(['display_name'], group_by_fields_to_read));
+        }
+        else { var group_data = $.Deferred().resolve({}); }
+
         this.$el.find('table:first').show();
         this.$el.removeClass('oe_kanban_ungrouped').addClass('oe_kanban_grouped');
-        this.add_group_mutex.exec(function() {
+        return $.when(group_data).then(function (results) {
+            _.each(results, function (group_by_data) {
+                var group = _.find(groups, function (elem) {return elem.attributes.value[0] == group_by_data.id});
+                if (group) {
+                    group.values = group_by_data;
+                }
+            });
+        }).done( function () {return self.add_group_mutex.exec(function() {
             self.do_clear_groups();
             self.dataset.ids = [];
             if (!groups.length) {
                 self.no_result();
-                return false;
+                return $.when();
             }
             self.nb_records = 0;
-            var remaining = groups.length - 1,
-                groups_array = [];
+            var groups_array = [];
             return $.when.apply(null, _.map(groups, function (group, index) {
                 var def = $.when([]);
                 var dataset = new instance.web.DataSetSearch(self, self.dataset.model,
@@ -280,38 +331,46 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
                         self.nb_records += records.length;
                         self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
                         groups_array[index] = new instance.web_kanban.KanbanGroup(self, records, group, dataset);
-                        if (!remaining--) {
-                            self.dataset.index = self.dataset.size() ? 0 : null;
-                            return self.do_add_groups(groups_array);
-                        }
                 });
             })).then(function () {
                 if(!self.nb_records) {
                     self.no_result();
                 }
+                if (self.dataset.index >= self.nb_records){
+                    self.dataset.index = self.dataset.size() ? 0 : null;
+                }
+                return self.do_add_groups(groups_array).done(function() {
+                    self.trigger('kanban_groups_processed');
+                });
             });
-        });
+        })});
     },
     do_process_dataset: function() {
         var self = this;
         this.$el.find('table:first').show();
         this.$el.removeClass('oe_kanban_grouped').addClass('oe_kanban_ungrouped');
+        var def = $.Deferred();
         this.add_group_mutex.exec(function() {
-            var def = $.Deferred();
             self.do_clear_groups();
             self.dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit }).done(function(records) {
                 var kgroup = new instance.web_kanban.KanbanGroup(self, records, null, self.dataset);
+                if (!_.isEmpty(self.dataset.ids) && (self.dataset.index === null || self.dataset.index >= self.dataset.ids.length)) {
+                    self.dataset.index = 0;
+                } else if (_.isEmpty(self.dataset.ids)){
+                    self.dataset.index = null;
+                }
                 self.do_add_groups([kgroup]).done(function() {
                     if (_.isEmpty(records)) {
                         self.no_result();
                     }
+                    self.trigger('kanban_dataset_processed');
                     def.resolve();
                 });
             }).done(null, function() {
                 def.reject();
             });
-            return def;
         });
+        return def;
     },
     do_reload: function() {
         this.do_search(this.search_domain, this.search_context, this.search_group_by);
@@ -349,10 +408,14 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
     },
     on_groups_started: function() {
         var self = this;
-        if (this.group_by) {
+        if (this.group_by || this.fields_keys.indexOf("sequence") !== -1) {
             // Kanban cards drag'n'drop
-            var prev_widget, is_folded, record;
-            var $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards, .oe_kanban_column .oe_kanban_folded_column_cards');
+            var prev_widget, is_folded, record, $columns;
+            if (this.group_by) {
+                $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards, .oe_kanban_column .oe_kanban_folded_column_cards');
+            } else {
+                $columns = this.$el.find('.oe_kanban_column_cards');
+            }
             $columns.sortable({
                 handle : '.oe_kanban_draghandle',
                 start: function(event, ui) {
@@ -418,8 +481,8 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
                     stop: function(event, ui) {
                         var stop_index = ui.item.index();
                         if (start_index !== stop_index) {
-                            var $start_column = $('.oe_kanban_groups_records .oe_kanban_column').eq(start_index);
-                            var $stop_column = $('.oe_kanban_groups_records .oe_kanban_column').eq(stop_index);
+                            var $start_column = self.$('.oe_kanban_groups_records .oe_kanban_column').eq(start_index);
+                            var $stop_column = self.$('.oe_kanban_groups_records .oe_kanban_column').eq(stop_index);
                             var method = (start_index > stop_index) ? 'insertBefore' : 'insertAfter';
                             $start_column[method]($stop_column);
                             var tmp_group = self.groups.splice(start_index, 1)[0];
@@ -442,7 +505,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
     },
     on_record_moved : function(record, old_group, old_index, new_group, new_index) {
         var self = this;
-        $.fn.tipsy.clear();
+        record.$el.find('[title]').tooltip('destroy');
         $(old_group.$el).add(new_group.$el).find('.oe_kanban_aggregates, .oe_kanban_group_length').hide();
         if (old_group === new_group) {
             new_group.records.splice(old_index, 1);
@@ -470,7 +533,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
     },
 
     do_show: function() {
-        if (this.$buttons) {
+        if (this.options.$buttons) {
             this.$buttons.show();
         }
         this.do_push_state({});
@@ -496,16 +559,15 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
             || (!this.options.action.help && !this.options.action.get_empty_list_help)) {
             return;
         }
-        this.last_position = this.$el.find('table:first').css("position");
-        this.$el.find('table:first').css("position", "absolute");
-        $(QWeb.render('KanbanView.nocontent', { content : this.options.action.get_empty_list_help || this.options.action.help})).insertAfter(this.$('table:first'));
+        this.$el.css("position", "relative");
+        $(QWeb.render('KanbanView.nocontent', { content : this.options.action.get_empty_list_help || this.options.action.help})).insertBefore(this.$('table:first'));
         this.$el.find('.oe_view_nocontent').click(function() {
             self.$buttons.openerpBounce();
         });
     },
     remove_no_result: function() {
-        this.$el.find('table:first').css("position", this.last_position);
-        this.$el.find('.oe_view_nocontent').remove();        
+        this.$el.css("position", "");
+        this.$el.find('.oe_view_nocontent').remove();
     },
 
     /*
@@ -564,8 +626,9 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
         this.dataset = dataset;
         this.dataset_offset = 0;
         this.aggregates = {};
-        this.value = this.title = null;
+        this.value = this.title = this.values = null;
         if (this.group) {
+            this.values = group.values;
             this.value = group.get('value');
             this.title = group.get('value');
             if (this.value instanceof Array) {
@@ -602,7 +665,8 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
         });
     },
     start: function() {
-        var self = this;
+        var self = this,
+            def = this._super();
         if (! self.view.group_by) {
             self.$el.addClass("oe_kanban_no_group");
             self.quick = new (get_class(self.view.quick_create_class))(this, self.dataset, {}, false)
@@ -650,7 +714,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
         this.$records.data('widget', this);
         this.$has_been_started.resolve();
         var add_btn = this.$el.find('.oe_kanban_add');
-        add_btn.tipsy({delayIn: 500, delayOut: 1000});
+        add_btn.tooltip({delay: { show: 500, hide:1000 }});
         this.$records.find(".oe_kanban_column_cards").click(function (ev) {
             if (ev.target == ev.currentTarget) {
                 if (!self.state.folded) {
@@ -659,39 +723,37 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
             }
         });
         this.is_started = true;
-        var def_tooltip = this.fetch_tooltip();
-        return $.when(def_tooltip);
-    },
+        this.fetch_tooltip();
+        return def;
+    },
+    /* 
+     * Form the tooltip, based on optional group_by_tooltip on the grouping field.
+     * This function goes through the arch of the view, finding the declaration
+     * of the field used to group. If group_by_tooltip is defined, use the previously
+     * computed values of the group to form the tooltip. */
     fetch_tooltip: function() {
+        var self = this;
         if (! this.group)
-            return;
-        var field_name = this.view.group_by;
-        var field = this.view.group_by_field;
-        var field_desc = null;
+            return;        
+        var options = null;
         var recurse = function(node) {
-            if (node.tag === "field" && node.attrs.name === field_name) {
-                field_desc = node;
+            if (node.tag === "field" && node.attrs.name == self.view.group_by) {
+                options = instance.web.py_eval(node.attrs.options || '{}');
                 return;
             }
             _.each(node.children, function(child) {
-                if (field_desc === null)
-                    recurse(child);
+                recurse(child);
             });
         };
         recurse(this.view.fields_view.arch);
-        if (! field_desc)
-            return;
-        var options = instance.web.py_eval(field_desc.attrs.options || '{}')
-        if (! options.tooltip_on_group_by)
-            return;
-
-        var self = this;
-        if (this.value) {
-            return (new instance.web.Model(field.relation)).query([options.tooltip_on_group_by])
-                    .filter([["id", "=", this.value]]).first().then(function(res) {
-                self.tooltip = res[options.tooltip_on_group_by];
-                self.$(".oe_kanban_group_title_text").attr("title", self.tooltip || self.title || "").tipsy({html: true});
-            });
+        if (options && options.group_by_tooltip) {
+            this.tooltip = _.union(
+                [this.title],
+                _.map(
+                    options.group_by_tooltip,
+                    function (key, value, list) { return self.values && self.values[value] || ''; })
+            ).join('\n\n');
+            this.$(".oe_kanban_group_title_text").attr("title", this.tooltip || this.title || "");
         }
     },
     compute_cards_auto_height: function() {
@@ -781,9 +843,9 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
         });
         var am = instance.webclient.action_manager;
         var form = am.dialog_widget.views.form.controller;
-        form.on("on_button_cancel", am.dialog, am.dialog.close);
+        form.on("on_button_cancel", am.dialog, function() { return am.dialog.$dialog_box.modal('hide'); });
         form.on('record_saved', self, function() {
-            am.dialog.close();
+            am.dialog.$dialog_box.modal('hide');
             self.view.do_reload();
         });
     },
@@ -927,21 +989,17 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
     bind_events: function() {
         var self = this;
         this.setup_color_picker();
-        this.$el.find('[tooltip]').tipsy({
-            delayIn: 500,
-            delayOut: 0,
-            fade: true,
-            title: function() {
-                var template = $(this).attr('tooltip');
-                if (!self.view.qweb.has_template(template)) {
-                    return false;
-                }
-                return self.view.qweb.render(template, self.qweb_context);
-            },
-            gravity: 's',
-            html: true,
-            opacity: 0.8,
-            trigger: 'hover'
+        this.$el.find('[title]').each(function(){
+            $(this).tooltip({
+                delay: { show: 500, hide: 0},
+                title: function() {
+                    var template = $(this).attr('tooltip');
+                    if (!self.view.qweb.has_template(template)) {
+                        return false;
+                    }
+                    return self.view.qweb.render(template, self.qweb_context);
+                },
+            });
         });
 
         // If no draghandle is found, make the whole card as draghandle (provided one can edit)
@@ -1012,7 +1070,10 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
      *  open on form/edit view : oe_kanban_global_click_edit
      */
     on_card_clicked: function(ev) {
-        if(this.$el.find('.oe_kanban_global_click_edit').size()>0)
+        if (this.$el.find('.oe_kanban_global_click').size() > 0 && this.$el.find('.oe_kanban_global_click').data('routing')) {
+            instance.web.redirect(this.$el.find('.oe_kanban_global_click').data('routing') + "/" + this.id);
+        }
+        else if (this.$el.find('.oe_kanban_global_click_edit').size()>0)
             this.do_action_edit();
         else
             this.do_action_open();
@@ -1061,6 +1122,9 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
         var button_attrs = $action.data();
         this.view.do_execute_action(button_attrs, this.view.dataset, this.id, this.do_reload);
     },
+    do_action_url: function($action) {
+        return instance.web.redirect($action.attr("href"));
+     },
     do_reload: function() {
         var self = this;
         this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).done(function(records) {
@@ -1145,7 +1209,7 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
  */
 instance.web_kanban.QuickCreate = instance.web.Widget.extend({
     template: 'KanbanView.quick_create',
-    
+
     /**
      * close_btn: If true, the widget will display a "Close" button able to trigger
      * a "close" event.
@@ -1267,43 +1331,35 @@ instance.web_kanban.Priority = instance.web_kanban.AbstractField.extend({
         this.parent = parent;
     },
     prepare_priority: function() {
-        var data = [];
+        var self = this;
         var selection = this.field.selection || [];
-        _.map(selection, function(res) {  
-            value = {
-                'name': res[0],
-                'legend_name': res[1]
+        var init_value = selection && selection[0][0] || 0;
+        var data = _.map(selection.slice(1), function(element, index) {
+            var value = {
+                'value': element[0],
+                'name': element[1],
+                'click_value': element[0],
             }
-            if (res[0] == '0') {
-                value['legend'] = '<img src="/web/static/src/img/icons/star-off.png"/>';
-            } else {
-                value['legend'] = '<img src="/web/static/src/img/icons/star-on.png"/>';
+            if (index == 0 && self.get('value') == element[0]) {
+                value['click_value'] = init_value;
             }
-            data.push(value)
+            return value;
         });
         return data;
     },
     renderElement: function() {
         var self = this;
-        self.record_id = self.parent.id;
-        var data = {'widget': self }
-        data['legends'] = self.prepare_priority();
-        this.$el = $(QWeb.render("Priority", data));
-        this.$el.find('.oe_legend').click(self.do_action.bind(self));
+        this.record_id = self.parent.id;
+        this.priorities = self.prepare_priority();
+        this.$el = $(QWeb.render("Priority", {'widget': this}));
+        this.$el.find('li').click(self.do_action.bind(self));
     },
     do_action: function(e) {
         var self = this;
         var li = $(e.target).closest( "li" );
         if (li.length) {
             var value = {};
-            if (self.parent.val == li.data('value') && self.parent.check_star) {
-                value[self.name] = String(li.data('value') - 1);
-                self.parent.check_star = false
-            } else {
-                value[self.name] = String(li.data('value'));
-                self.parent.check_star = true;
-            }
-            self.parent.val = li.data('value')
+            value[self.name] = String(li.data('value'));
             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));
         }
     },
@@ -1312,27 +1368,38 @@ instance.web_kanban.Priority = instance.web_kanban.AbstractField.extend({
     },
 });
 
-instance.web_kanban.DropdownSelection = instance.web_kanban.AbstractField.extend({
+instance.web_kanban.KanbanSelection = instance.web_kanban.AbstractField.extend({
     init: function(parent, field, $node) {
         this._super.apply(this, arguments);
         this.name = $node.attr('name')
         this.parent = parent;
     },
     prepare_dropdown_selection: function() {
-        return [{ 'name': 'normal', 'state_name': ' In Progress', 'state_icon': 'status' },
-                { 'name': 'blocked', 'state_name': ' Blocked', 'state_icon': 'status error' },
-                { 'name': 'done', 'state_name': ' Ready', 'state_icon': 'status ok' }]
+        var self = this;
+        var data = [];
+        _.map(this.field.selection || [], function(res) {
+            var value = {
+                'name': res[0],
+                'tooltip': res[1],
+                'state_name': res[1],
+            }
+            var leg_opt = self.options && self.options.states_legend || null;
+            if (leg_opt && leg_opt[res[0]] && self.parent.group.values && self.parent.group.values[leg_opt[res[0]]]) {
+                value['state_name'] = self.parent.group.values[leg_opt[res[0]]];
+            }
+            if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
+            else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
+            else { value['state_class'] = 'oe_kanban_status oe_kanban_status_red'; }
+            data.push(value);
+        });
+        return data;
     },
     renderElement: function() {
         var self = this;
-        self.record_id = self.parent.id;
-        var data = {'widget': self }
-        data['states'] = self.prepare_dropdown_selection();
-        this.$el = $(QWeb.render("DropdownSelection", data));
-        this.$el.find('.oe_legend').click(self.do_action.bind(self));
-        this.$el.on('mouseenter mouseleave', function(e) {
-            self.$el.find('.caret').toggleClass('hidden', e.type == 'mouseleave');
-        });
+        this.record_id = self.parent.id;
+        this.states = self.prepare_dropdown_selection();;
+        this.$el = $(QWeb.render("KanbanSelection", {'widget': self}));
+        this.$el.find('li').click(self.do_action.bind(self));
     },
     do_action: function(e) {
         var self = this;
@@ -1350,7 +1417,7 @@ instance.web_kanban.DropdownSelection = instance.web_kanban.AbstractField.extend
 
 instance.web_kanban.fields_registry = new instance.web.Registry({});
 instance.web_kanban.fields_registry.add('priority','instance.web_kanban.Priority');
-instance.web_kanban.fields_registry.add('dropdown_selection','instance.web_kanban.DropdownSelection');
+instance.web_kanban.fields_registry.add('kanban_state_selection','instance.web_kanban.KanbanSelection');
 };
 
 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: