[MERGE] forward port of branch 7.0 up to be7c894
[odoo/odoo.git] / addons / web_kanban / static / src / js / kanban.js
index 7db23b3..b352fb2 100644 (file)
@@ -43,6 +43,7 @@ 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';
     },
     view_loading: function(r) {
         return this.load_kanban(r);
@@ -133,10 +134,16 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
         }
         switch (node.tag) {
             case 'field':
-                if (this.fields_view.fields[node.attrs.name].type === 'many2many') {
-                    this.many2manys.push(node.attrs.name);
+                var ftype = this.fields_view.fields[node.attrs.name].type;
+                ftype = node.attrs.widget ? node.attrs.widget : ftype;
+                if (ftype === 'many2many') {
+                    if (_.indexOf(this.many2manys, node.attrs.name) < 0) {
+                        this.many2manys.push(node.attrs.name);
+                    }
                     node.tag = 'div';
                     node.attrs['class'] = (node.attrs['class'] || '') + ' oe_form_field oe_tags';
+                } else if (instance.web_kanban.fields_registry.contains(ftype)) {
+                    // do nothing, the kanban record will handle it
                 } else {
                     node.tag = QWeb.prefix;
                     node.attrs[QWeb.prefix + '-esc'] = 'record.' + node.attrs['name'] + '.value';
@@ -226,14 +233,19 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
         this.search_domain = domain;
         this.search_context = context;
         this.search_group_by = group_by;
-        $.when(this.has_been_loaded).done(function() {
+        return $.when(this.has_been_loaded).then(function() {
             self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by;
             self.group_by_field = self.fields_view.fields[self.group_by] || {};
             self.grouped_by_m2o = (self.group_by_field.type === 'many2one');
             self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o);
             self.$el.toggleClass('oe_kanban_grouped_by_m2o', self.grouped_by_m2o);
-            var grouping = new instance.web.Model(self.dataset.model, context, domain).query().group_by(self.group_by);
-            $.when(grouping).done(function(groups) {
+            var grouping_fields = self.group_by ? [self.group_by].concat(_.keys(self.aggregates)) : undefined;
+            if (!_.isEmpty(grouping_fields)) {
+                // ensure group_by fields are read.
+                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) {
                 self.remove_no_result();
                 if (groups) {
                     self.do_process_groups(groups);
@@ -255,25 +267,27 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
                 return false;
             }
             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,
                     new instance.web.CompoundContext(self.dataset.get_context(), group.model.context()), group.model.domain());
-                return dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit })
-                    .then(function (records) {
+                if (group.attributes.length >= 1) {
+                    def = dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit });
+                }
+                return def.then(function(records) {
                         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);
             });
         });
     },
@@ -286,6 +300,11 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
             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();
@@ -336,20 +355,38 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
         var self = this;
         if (this.group_by) {
             // Kanban cards drag'n'drop
-            var $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards');
+            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');
             $columns.sortable({
                 handle : '.oe_kanban_draghandle',
                 start: function(event, ui) {
                     self.currently_dragging.index = ui.item.parent().children('.oe_kanban_record').index(ui.item);
-                    self.currently_dragging.group = ui.item.parents('.oe_kanban_column:first').data('widget');
+                    self.currently_dragging.group = prev_widget = ui.item.parents('.oe_kanban_column:first').data('widget');
                     ui.item.find('*').on('click.prevent', function(ev) {
                         return false;
                     });
+                    record = ui.item.data('widget');
+                    record.$el.bind('mouseup',function(ev,ui){
+                        if (is_folded) {
+                            record.$el.hide();
+                        }
+                        record.$el.unbind('mouseup');
+                    })
                     ui.placeholder.height(ui.item.height());
                 },
+                over: function(event, ui) {
+                    var parent = $(event.target).parent();
+                    prev_widget.highlight(false);
+                    is_folded = parent.hasClass('oe_kanban_group_folded'); 
+                    if (is_folded) {
+                        var widget = parent.data('widget');
+                        widget.highlight(true);
+                        prev_widget = widget;
+                    }
+                 },
                 revert: 150,
                 stop: function(event, ui) {
-                    var record = ui.item.data('widget');
+                    prev_widget.highlight(false);
                     var old_index = self.currently_dragging.index;
                     var new_index = ui.item.parent().children('.oe_kanban_record').index(ui.item);
                     var old_group = self.currently_dragging.group;
@@ -385,8 +422,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];
@@ -424,9 +461,13 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
             this.dataset.write(record.id, data, {}).done(function() {
                 record.do_reload();
                 new_group.do_save_sequences();
+                if (new_group.state.folded) {
+                    new_group.do_action_toggle_fold();
+                    record.prependTo(new_group.$records.find('.oe_kanban_column_cards'));
+                }
             }).fail(function(error, evt) {
                 evt.preventDefault();
-                alert(_t("An error has occured while moving the record to this group: ") + data.message);
+                alert(_t("An error has occured while moving the record to this group: ") + error.data.message);
                 self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
             });
         }
@@ -456,18 +497,19 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
         var self = this;
         if (this.groups.group_by
             || !this.options.action
-            || (!this.options.action.help)) {
+            || (!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.dynamic_help || this.options.action.help})).insertAfter(this.$('table:first'));
+        $(QWeb.render('KanbanView.nocontent', { content : this.options.action.get_empty_list_help || this.options.action.help})).insertAfter(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", false);
-        this.$el.find('.oe_view_nocontent').remove();
+        this.$el.find('table:first').css("position", this.last_position);
+        this.$el.find('.oe_view_nocontent').remove();        
     },
 
     /*
@@ -541,7 +583,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
                 } catch(e) {}
             }
             _.each(this.view.aggregates, function(value, key) {
-                self.aggregates[value] = group.get('aggregates')[key];
+                self.aggregates[value] = instance.web.format_value(group.get('aggregates')[key], {type: 'float'});
             });
         }
 
@@ -564,8 +606,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
         });
     },
     start: function() {
-        var self = this,
-            def = this._super();
+        var self = this;
         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)
@@ -622,7 +663,40 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
             }
         });
         this.is_started = true;
-        return def;
+        var def_tooltip = this.fetch_tooltip();
+        return $.when(def_tooltip);
+    },
+    fetch_tooltip: function() {
+        if (! this.group)
+            return;
+        var field_name = this.view.group_by;
+        var field = this.view.group_by_field;
+        var field_desc = null;
+        var recurse = function(node) {
+            if (node.tag === "field" && node.attrs.name === field_name) {
+                field_desc = node;
+                return;
+            }
+            _.each(node.children, function(child) {
+                if (field_desc === null)
+                    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});
+            });
+        }
     },
     compute_cards_auto_height: function() {
         // oe_kanban_no_auto_height is an empty class used to disable this feature
@@ -655,6 +729,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
             self.view.dataset.ids = ids.concat(self.dataset.ids);
             self.do_add_records(records);
             self.compute_cards_auto_height();
+            self.view.postprocess_m2m_tags();
             return records;
         });
     },
@@ -745,6 +820,15 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({
                 self.view.dataset.ids.push(id);
                 self.do_add_records(records, true);
             });
+    },
+    highlight: function(show){
+        if(show){
+            this.$el.addClass('oe_kanban_column_higlight');
+            this.$records.addClass('oe_kanban_column_higlight');
+        }else{
+            this.$el.removeClass('oe_kanban_column_higlight');
+            this.$records.removeClass('oe_kanban_column_higlight');
+        }
     }
 });
 
@@ -762,6 +846,7 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
             };
         }
         this.state = this.view.state.records[this.id];
+        this.fields = {};
     },
     set_record: function(record) {
         var self = this;
@@ -775,7 +860,16 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
         this.record = this.transform_record(record);
     },
     start: function() {
+        var self = this;
         this._super();
+        this.init_content();
+    },
+    init_content: function() {
+        var self = this;
+        self.sub_widgets = [];
+        this.$("[data-field_id]").each(function() {
+            self.add_widget($(this));
+        });
         this.$el.data('widget', this);
         this.bind_events();
     },
@@ -811,6 +905,28 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
             'content': this.view.qweb.render('kanban-box', this.qweb_context)
         });
         this.replaceElement($el);
+        this.replace_fields();
+    },
+    replace_fields: function() {
+        var self = this;
+        this.$("field").each(function() {
+            var $field = $(this);
+            var $nfield = $("<span></span");
+            var id = _.uniqueId("kanbanfield");
+            self.fields[id] = $field;
+            $nfield.attr("data-field_id", id);
+            $field.replaceWith($nfield);
+        });
+    },
+    add_widget: function($node) {
+        var $orig = this.fields[$node.data("field_id")];
+        var field = this.record[$orig.attr("name")];
+        var type = field.type;
+        type = $orig.attr("widget") ? $orig.attr("widget") : type;
+        var obj = instance.web_kanban.fields_registry.get_object(type);
+        var widget = new obj(this, field, $orig);
+        this.sub_widgets.push(widget);
+        widget.replace($node);
     },
     bind_events: function() {
         var self = this;
@@ -952,11 +1068,14 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
     do_reload: function() {
         var self = this;
         this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).done(function(records) {
+             _.each(self.sub_widgets, function(el) {
+                 el.destroy();
+             });
+             self.sub_widgets = [];
             if (records.length) {
                 self.set_record(records[0]);
                 self.renderElement();
-                self.$el.data('widget', self);
-                self.bind_events();
+                self.init_content();
                 self.group.compute_cards_auto_height();
                 self.view.postprocess_m2m_tags();
             } else {
@@ -985,13 +1104,6 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({
         var color = this.kanban_getcolor(variable);
         return color === '' ? '' : 'oe_kanban_color_' + color;
     },
-    kanban_gravatar: function(email, size) {
-        size = size || 22;
-        email = _.str.trim(email || '').toLowerCase();
-        var default_ = _.str.isBlank(email) ? 'mm' : 'identicon';
-        var email_md5 = $.md5(email);
-        return 'http://www.gravatar.com/avatar/' + email_md5 + '.png?s=' + size + '&d=' + default_;
-    },
     kanban_image: function(model, field, id, cache, options) {
         options = options || {};
         var url;
@@ -1037,7 +1149,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.
@@ -1056,6 +1168,11 @@ instance.web_kanban.QuickCreate = instance.web.Widget.extend({
                 self.quick_add();
             }
         });
+        $(".oe_kanban_quick_create").focusout(function (e) {
+            var val = self.$el.find('input').val();
+            if (/^\s*$/.test(val)) { self.trigger('close'); }
+            e.stopImmediatePropagation();
+        });
         $(".oe_kanban_quick_create_add", this.$el).click(function () {
             self.quick_add();
             self.focus();
@@ -1079,7 +1196,7 @@ instance.web_kanban.QuickCreate = instance.web.Widget.extend({
     quick_add: function () {
         var self = this;
         var val = this.$input.val();
-        if (/^\s*$/.test(val)) { return; }
+        if (/^\s*$/.test(val)) { this.$el.remove(); return; }
         this._dataset.call(
             'name_create', [val, new instance.web.CompoundContext(
                     this._dataset.get_context(), this._context)])
@@ -1110,6 +1227,44 @@ instance.web_kanban.QuickCreate = instance.web.Widget.extend({
         });
     }
 });
+
+/**
+ * Interface to be implemented by kanban fields.
+ *
+ */
+instance.web_kanban.FieldInterface = {
+    /**
+        Constructor.
+        - parent: The widget's parent.
+        - field: A dictionary giving details about the field, including the current field's value in the
+            raw_value field.
+        - $node: The field <field> tag as it appears in the view, encapsulated in a jQuery object.
+    */
+    init: function(parent, field, $node) {},
+};
+
+/**
+ * Abstract class for classes implementing FieldInterface.
+ *
+ * Properties:
+ *     - value: useful property to hold the value of the field. By default, the constructor
+ *     sets value property.
+ *
+ */
+instance.web_kanban.AbstractField = instance.web.Widget.extend(instance.web_kanban.FieldInterface, {
+    /**
+        Constructor that saves the field and $node parameters and sets the "value" property.
+    */
+    init: function(parent, field, $node) {
+        this._super(parent);
+        this.field = field;
+        this.$node = $node;
+        this.options = instance.web.py_eval(this.$node.attr("options") || '{}');
+        this.set("value", field.raw_value);
+    },
+});
+
+instance.web_kanban.fields_registry = new instance.web.Registry({});
 };
 
 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: