[FIX] web_kanban, web: KanbanSelection widget refactoring

- Change the widget behavior from asynchronous to asynchronous, by adding 3 related fields
  on the project.task model, which are the custom labels defined for the stage
- Instead of making a rpc call, we are taking directly the labels on the parent for the kanban view,
  or on the view datarecord for the form view
- There are now 2 templates for the kanban view or the form view
- The development has been backported in the community version
- The options attribute has been removed on the project_view.xml file for the kanban_state field
This commit is contained in:
Yannick Tivisse
2015-09-22 11:33:58 +02:00
parent 8c8fee29ea
commit e5181b05f7
6 changed files with 112 additions and 96 deletions
+3 -1
View File
@@ -453,6 +453,9 @@ class task(osv.osv):
'attachment_ids': fields.one2many('ir.attachment', 'res_id', domain=lambda self: [('res_model', '=', self._name)], auto_join=True, string='Attachments'),
# In the domain of displayed_image_id, we couln't use attachment_ids because a one2many is represented as a list of commands so we used res_model & res_id
'displayed_image_id': fields.many2one('ir.attachment', domain="[('res_model', '=', 'project.task'), ('res_id', '=', id), ('mimetype', 'ilike', 'image')]", string='Displayed Image'),
'legend_blocked': fields.related("stage_id", "legend_blocked", type="char", string='Kanban Blocked Explanation'),
'legend_done': fields.related("stage_id", "legend_done", type="char", string='Kanban Valid Explanation'),
'legend_normal': fields.related("stage_id", "legend_normal", type="char", string='Kanban Ongoing Explanation'),
}
_defaults = {
'stage_id': _get_default_stage_id,
@@ -637,7 +640,6 @@ class task(osv.osv):
# user_id change: update date_assign
if vals.get('user_id'):
vals['date_assign'] = fields.datetime.now()
# context: no_log, because subtype already handle this
create_context = dict(context, mail_create_nolog=True)
task_id = super(task, self).create(cr, uid, vals, context=create_context)
+8 -7
View File
@@ -254,11 +254,7 @@
options='{"terminology": "archive"}'/>
</button>
</div>
<field name="kanban_state" widget="kanban_state_selection"
options='{
"states_legend_field": "stage_id",
"states_legend": {"normal": "legend_normal", "blocked": "legend_blocked", "done": "legend_done"}
}'/>
<field name="kanban_state" widget="kanban_state_selection"/>
<div class="oe_title">
<h1 class="o_row">
<field name="priority" widget="priority"/>
@@ -274,6 +270,9 @@
<field name="planned_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"
on_change="onchange_planned(planned_hours)"/>
<field name="legend_blocked" invisible="1"/>
<field name="legend_normal" invisible="1"/>
<field name="legend_done" invisible="1"/>
</group>
<group>
<field name="date_deadline"/>
@@ -329,6 +328,9 @@
<field name="attachment_ids"/>
<field name="displayed_image_id"/>
<field name="active"/>
<field name="legend_blocked"/>
<field name="legend_normal"/>
<field name="legend_done"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
@@ -365,8 +367,7 @@
<div class="oe_kanban_bottom_right">
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
<div class="pull-left" groups="base.group_user">
<field name="kanban_state" widget="kanban_state_selection"
options='{"states_legend": {"normal": "legend_normal", "blocked": "legend_blocked", "done": "legend_done"}}'/>
<field name="kanban_state" widget="kanban_state_selection"/>
</div>
</div>
</div>
+42 -37
View File
@@ -165,6 +165,7 @@ var FieldChar = common.AbstractField.extend(common.ReinitializeFieldMixin, {
});
var KanbanSelection = FieldChar.extend({
template: "FormSelection",
init: function (field_manager, node) {
this._super(field_manager, node);
},
@@ -180,54 +181,58 @@ var KanbanSelection = FieldChar.extend({
prepare_dropdown_selection: function() {
var self = this;
var _data = [];
var fetch_stage;
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) {
fetch_stage = new data.DataSet(
this,
self.view.fields[legend_field].field.relation).read_ids([stage_id],
fields_to_read);
}
else { 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]]];
value['tooltip'] = 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;
var current_stage_id = self.view.datarecord.stage_id[0];
var stage_data = {
id: current_stage_id,
legend_normal: self.view.datarecord.legend_normal,
legend_blocked : self.view.datarecord.legend_blocked,
legend_done: self.view.datarecord.legend_done,
};
_.map(self.field.selection || [], function(selection_item) {
var value = {
'name': selection_item[0],
'tooltip': selection_item[1],
};
if (selection_item[0] === 'normal') {
value.state_name = stage_data.legend_normal ? stage_data.legend_normal : selection_item[1];
} else if (selection_item[0] === 'done') {
value.state_class = 'oe_kanban_status_green';
value.state_name = stage_data.legend_done ? stage_data.legend_done : selection_item[1];
} else {
value.state_class = 'oe_kanban_status_red';
value.state_name = stage_data.legend_blocked ? stage_data.legend_blocked : selection_item[1];
}
_data.push(value);
});
return _data;
},
render_value: function() {
this._super();
this.states = this.prepare_dropdown_selection();
var self = this;
var dd_fetched = this.prepare_dropdown_selection();
return $.when(dd_fetched).then(function (states) {
self.states = states;
self.$el.addClass('oe_right oe_inline');
self.$el.html(QWeb.render("KanbanSelection", {'widget': self}));
self.$el.find('li').on('click', self.set_kanban_selection.bind(self));
// Adapt "FormSelection"
var current_state = _.find(this.states, function(state) {
return state.name === self.get('value');
});
this.$('.oe_kanban_status')
.removeClass('oe_kanban_status_red oe_kanban_status_green')
.addClass(current_state.state_class);
// Render "FormSelection.Items" and move it into "FormSelection"
var $items = $(QWeb.render('FormSelection.items', {
states: _.without(this.states, current_state)
}));
var $dropdown = this.$el.find('.dropdown-menu');
$dropdown.children().remove(); // remove old items
$items.appendTo($dropdown);
this.$el.find('a').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
be written when saving the record. */
set_kanban_selection: function (ev) {
var self = this;
ev.preventDefault();
var li = $(ev.target).closest('li');
if (li.length) {
var value = String(li.data('value'));
+11 -18
View File
@@ -679,28 +679,21 @@
</t>
</span>
</t>
<t t-name="KanbanSelection">
<div class="btn-group kanban_state">
<t t-foreach="widget.states" t-as="rec">
<a t-if="widget.get('value') === rec.name">
<a class="dropdown-toggle" data-toggle="dropdown">
<span t-att-class="rec.state_class" t-if="widget.get('value') === rec.name" t-att-title="rec.tooltip"/>
<span class="sr-only">Toggle Dropdown</span>
</a>
</a>
</t>
<t t-name="FormSelection">
<div class="btn-group o_kanban_selection">
<a href="#" data-toggle="dropdown"><span class="oe_kanban_status"/></a>
<ul class="dropdown-menu state" role="menu">
<t t-foreach="widget.states" t-as="rec">
<t t-if="widget.get('value') !== rec.name">
<li t-att-data-value="rec.name" ><a href="#">
<span t-att-class="rec.state_class" t-att-title="rec.tooltip"/>
<t t-raw="rec.state_name" /></a>
</li>
</t>
</t>
</ul>
</div>
</t>
<t t-name="FormSelection.items">
<li t-foreach="states" t-as="rec" t-att-data-value="rec.name">
<a href="#">
<span t-att-class="'oe_kanban_status ' + (rec.state_class &amp;&amp; rec.state_class || '')"/>
<t t-raw="rec.state_name"/>
</a>
</li>
</t>
<div t-name="Priority" class="o_priority">
<a t-if="!widget.readonly" t-foreach="widget.values.slice(1)" t-as="v" href="#" t-att-data-value="v[0]" t-att-title="v[1]" class="o_priority_star fa"/>
<span t-if="widget.readonly" t-foreach="widget.values.slice(1)" t-as="v" t-att-data-value="v[0]" t-att-title="v[1]" class="o_priority_star fa"/>
@@ -9,7 +9,6 @@ var pyeval = require('web.pyeval');
var Registry = require('web.Registry');
var session = require('web.session');
var Widget = require('web.Widget');
var QWeb = core.qweb;
/**
* Interface to be implemented by kanban fields.
@@ -103,53 +102,52 @@ var KanbanSelection = AbstractField.extend({
this.name = $node.attr('name');
this.parent = parent;
},
// Instead of making several rpc calls for each kanban card, take the values from the related fields on the tasks
prepare_dropdown_selection: function() {
var self = this;
var data = [];
_.map(this.field.selection || [], function(res) {
var _data = [];
var stage_id = self.parent.values.stage_id.value[0];
var stage_data = {
id: stage_id,
legend_normal: self.parent.values.legend_normal.value,
legend_blocked : self.parent.values.legend_blocked.value,
legend_done: self.parent.values.legend_done.value,
};
_.map(self.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) {
var key = leg_opt[value.name];
var legend = self.parent.group_info && self.parent.group_info[key];
if (legend) {
value.state_name = legend;
value.tooltip = legend;
}
if (res[0] === 'normal') {
value.state_name = stage_data.legend_normal ? stage_data.legend_normal : res[1];
} else if (res[0] === 'done') {
value.state_class = 'oe_kanban_status_green';
value.state_name = stage_data.legend_done ? stage_data.legend_done : res[1];
} else {
value.state_class = 'oe_kanban_status_red';
value.state_name = stage_data.legend_blocked ? stage_data.legend_blocked : 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);
_data.push(value);
});
return data;
return _data;
},
renderElement: function() {
var self = this;
this.states = this.prepare_dropdown_selection();
var state;
for(var i = 0 ; i < this.states.length ; i++) {
if(this.states[i].name === this.get('value')) {
state = this.states[i];
}
}
self.states = this.prepare_dropdown_selection();
this.$el = $(QWeb.render("KanbanSelection", {'widget': this}));
this.$('a').first().find('span').removeClass().addClass(state.state_class);
this.$('ul li').show().filter(function() {
return ($(this).data('value') === state.name);
}).hide();
this.$('a').click(function (ev) {
ev.preventDefault();
var current_state = _.find(this.states, function(state) {
return state.name === self.get('value');
});
this.$('li').click(this.set_kanban_selection.bind(this));
self.$el = $(QWeb.render("KanbanSelection", {
current_state_class: current_state.state_class,
states: _.without(this.states, current_state)
}));
self.$('a').click(function (ev) {
ev.preventDefault();
});
self.$('a').click(self.set_kanban_selection.bind(self));
},
set_kanban_selection: function(e) {
e.preventDefault();
@@ -84,4 +84,21 @@
<img t-att-src="'/web/image/' + widget.field.raw_value[0] + '?unique=1'"/>
</div>
</t>
<t t-name="KanbanSelection">
<div class="btn-group o_kanban_selection">
<a href="#" data-toggle="dropdown">
<span t-att-class="'oe_kanban_status ' + (current_state_class &amp;&amp; current_state_class || '')"/>
</a>
<ul class="dropdown-menu state" role="menu">
<li t-foreach="states" t-as="rec" t-att-data-value="rec.name">
<a href="#">
<span t-att-class="'oe_kanban_status ' + (rec.state_class &amp;&amp; rec.state_class || '')"/>
<t t-raw="rec.state_name"/>
</a>
</li>
</ul>
</div>
</t>
</template>