[IMP] kanban, web: kanban tooltips and kanban states tooltips improvements.
authorThibault Delavallée <tde@openerp.com>
Fri, 31 Oct 2014 10:48:03 +0000 (11:48 +0100)
committerThibault Delavallée <tde@openerp.com>
Fri, 7 Nov 2014 10:57:52 +0000 (11:57 +0100)
Kanban and form view kanban selection widgets can now be dependent of the
current stage of the record. The displayed legend can be taken directly
from the stage instead of the field selection values.
- added states_legend option on kanban selection widget for kanban and form
view: used to change the selection displayed value by a value coming from the
record given by group_by (kanban) / states_legend_field (form view). This allows
customizing the kanban selection widget depending on stages.

Improved the tooltip of kanban groups customization by improving the first
implementation of group_by_tooltip parameter in kanban view declaration.
This is done by adding some options and improving the first (still not
used) implementation:
- added group_by_tooltip option on field used to group on kanban view. Its
values is a collection of fields on the record used to group. The tooltip of
the group header will be the text formed from those fields values, allowing
custom tooltips for different kanban columns.
- now all data of the kanban columns are fetched in one call instead of doing
one call for each column. The various fields to read are collected by
introspecting the view definition.

Future commits will aim at using those features various addons, notably
in recruitment, project and crm.

addons/web/static/src/js/view_form.js
addons/web_kanban/static/src/js/kanban.js

index 57569be..e364e38 100644 (file)
@@ -2337,29 +2337,58 @@ instance.web.form.KanbanSelection = instance.web.form.FieldChar.extend({
     init: function (field_manager, node) {
         this._super(field_manager, node);
     },
+    start: function () {
+        var self = this;
+        this.states = [];
+        this._super.apply(this, arguments);
+        // hook on form view content changed: recompute the states, because it may be related to the current stage
+        this.getParent().on('view_content_has_changed', self, function () {
+            self.render_value();
+        });
+    },
     prepare_dropdown_selection: function() {
         var self = this;
         var data = [];
-        var selection = self.field.selection || [];
-        _.map(selection, function(res) {
-            var value = {
-                'name': res[0],
-                'tooltip': res[1],
-                'state_name': res[1],
-            }
-            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);
+        var selection = this.field.selection || [];
+        var stage_id = _.isArray(this.view.datarecord.stage_id) ? this.view.datarecord.stage_id[0] : this.view.datarecord.stage_id;
+        var legend_field = this.options && this.options.states_legend_field || false;
+        var fields_to_read = _.map(
+            this.options && this.options.states_legend || {},
+            function (value, key, list) { return value; });
+        if (legend_field && fields_to_read && stage_id) {
+            var fetch_stage = new openerp.web.DataSet(
+                this,
+                self.view.fields[legend_field].field.relation).read_ids([stage_id],
+                fields_to_read);
+        }
+        else { var fetch_stage = $.Deferred().resolve(false); }
+        return $.when(fetch_stage).then(function (stage_data) {
+            _.map(selection, function(res) {
+                var value = {
+                    'name': res[0],
+                    'tooltip': res[1],
+                    'state_name': res[1],
+                }
+                if (stage_data && stage_data[0][self.options.states_legend[res[0]]]) {
+                    value['state_name'] = stage_data[0][self.options.states_legend[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;
         });
-        return data;
     },
     render_value: function() {
         var self = this;
         this.record_id = this.view.datarecord.id;
-        this.states = this.prepare_dropdown_selection();;
-        this.$el.html(QWeb.render("KanbanSelection", {'widget': self}));
-        this.$el.find('li').on('click', this.set_kanban_selection.bind(this));
+        var dd_fetched = this.prepare_dropdown_selection();;
+        return $.when(dd_fetched).then(function (states) {
+            self.states = states;
+            self.$el.html(QWeb.render("KanbanSelection", {'widget': self}));
+            self.$el.find('li').on('click', this.set_kanban_selection.bind(this));
+        })
     },
     /* setting the value: in view mode, perform an asynchronous call and reload
     the form view; in edit mode, use set_value to save the new value that will
index 1d2ba48..132f04e 100644 (file)
@@ -266,9 +266,52 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
     },
     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 = _.map(groups, function (elem) { return elem.attributes.value[0]});
+        if (group_ids && group_by_fields_to_read) {
+            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');
-        return 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) {
@@ -300,7 +343,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({
                     self.trigger('kanban_groups_processed');
                 });
             });
-        });
+        })});
     },
     do_process_dataset: function() {
         var self = this;
@@ -583,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) {
@@ -621,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)
@@ -678,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 || "").tooltip();
-            });
+        if (options && options.group_by_tooltip) {
+            this.tooltip = _.union(
+                [this.title],
+                _.map(
+                    options.group_by_tooltip,
+                    function (key, value, list) { return self.values[value] || ''; })
+            ).join('\n\n');
+            this.$(".oe_kanban_group_title_text").attr("title", this.tooltip || this.title || "");
         }
     },
     compute_cards_auto_height: function() {
@@ -1332,6 +1375,7 @@ instance.web_kanban.KanbanSelection = instance.web_kanban.AbstractField.extend({
         this.parent = parent;
     },
     prepare_dropdown_selection: function() {
+        var self = this;
         var data = [];
         _.map(this.field.selection || [], function(res) {
             var value = {
@@ -1339,6 +1383,10 @@ instance.web_kanban.KanbanSelection = instance.web_kanban.AbstractField.extend({
                 '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'; }