Skip to content
Permalink
Browse files

[ADD] web: add edit tag feature on many2many_tags widget, also provid…

…e no_edit option to remove edit feature from many2many_tags widget

open a customer form from a meeting (to see the address, the phone, etc)
In case of mispelling, etc you need to go to the settings to change a m2m record. Most of the times menu items are hidden so its painful: product attributes, customer tags
the issue is worse in settings: in new settings there is a lot of m2m tag you cannot change from this screen.

with this commit clicking on m2m tag will give Edit option unless no_edit option is explicitly defined in xml view on field, edit form dialog is given to user to edit m2m tag

task-29227
PR-22957

Co-authored-by: Mohammed Shekha <msh@openerp.com>
Co-authored-by: Suraj Shukla <suh@odoo.com>
  • Loading branch information...
3 people committed Feb 9, 2018
1 parent b01ce6e commit fcb45b25abfc800a09e53a76a13f2c65d19977ef
@@ -1995,7 +1995,9 @@ var FieldMany2ManyTags = AbstractField.extend({
field_changed: '_onFieldChanged', field_changed: '_onFieldChanged',
}), }),
events: _.extend({}, AbstractField.prototype.events, { events: _.extend({}, AbstractField.prototype.events, {
'click .o_badge_text': '_onClickBadge',
'click .o_delete': '_onDeleteTag', 'click .o_delete': '_onDeleteTag',
'mousedown .o_edit_record': '_onEditRecord',
}), }),
fieldsToFetch: { fieldsToFetch: {
display_name: {type: 'char'}, display_name: {type: 'char'},
@@ -2011,8 +2013,11 @@ var FieldMany2ManyTags = AbstractField.extend({
this.className += ' o_input'; this.className += ' o_input';
} }


this.can_write = 'can_write' in this.attrs ? JSON.parse(this.attrs.can_write) : true;

this.colorField = this.nodeOptions.color_field; this.colorField = this.nodeOptions.color_field;
this.hasDropdown = false; this.hasDropdown = false;
this.no_edit = this.nodeOptions.no_edit || !this.can_write;
}, },


//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@@ -2069,6 +2074,44 @@ var FieldMany2ManyTags = AbstractField.extend({
}); });
} }
}, },
/**
* @private
* @param {integer} id
*/
_editRecord: function (id) {
var self = this;
var record = _.findWhere(this.value.data, { res_id: id });
var tag_id = record.id;
var context = this.record.getContext(this.recordParams);
this._rpc({
model: record.model,
method: 'get_formview_id',
context: context,
args: [[record.res_id]],
}).then(function (view_id) {
new dialogs.FormViewDialog(self, {
res_model: record.model,
res_id: record.res_id,
title: _t("Edit: ") + self.string,
context: context,
view_id: view_id,
readonly: false,
on_saved: function (record, changed) {
if (changed) {
self._setValue({operation: 'TRIGGER_ONCHANGE'}, {forceChange: true});
self.trigger_up('reload', {db_id: tag_id});
}
},
}).open();
});
},
_getRenderDropdownContext: function (tagID) {
return {
'tag_id': tagID,
'widget': this,
'has_options': this.mode === 'edit' && !this.no_edit
};
},
/** /**
* Get the QWeb rendering context used by the tag template; this computation * Get the QWeb rendering context used by the tag template; this computation
* is placed in a separate function for other tags to override it. * is placed in a separate function for other tags to override it.
@@ -2102,6 +2145,9 @@ var FieldMany2ManyTags = AbstractField.extend({
_renderEdit: function () { _renderEdit: function () {
var self = this; var self = this;
this._renderTags(); this._renderTags();
if (this.mode === 'edit' && !this.no_edit) {
this.$(".o_badge_text").addClass("dropdown-toggle o-no-caret").attr('data-toggle', 'dropdown');
}
if (this.many2one) { if (this.many2one) {
this.many2one.destroy(); this.many2one.destroy();
} }
@@ -2133,11 +2179,28 @@ var FieldMany2ManyTags = AbstractField.extend({
_renderTags: function () { _renderTags: function () {
this.$el.html(qweb.render(this.tag_template, this._getRenderTagsContext())); this.$el.html(qweb.render(this.tag_template, this._getRenderTagsContext()));
}, },
_renderTagDropdown: function (template, event) {
var tagID = $(event.currentTarget).parent().data('id');
this.$tag_dropdown = $(qweb.render(template, this._getRenderDropdownContext(tagID)));
$(event.currentTarget).after(this.$tag_dropdown);
this.$tag_dropdown.dropdown();
this.$tag_dropdown.attr("tabindex", 1).focus();
},


//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Handlers // Handlers
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------


/**
* @private
* @param {MouseEvent} ev
*/
_onClickBadge: function (ev) {
ev.preventDefault();
if (this.mode === 'edit' && !this.no_edit) {
this._renderTagDropdown('FieldMany2ManyTag.TagDropdown', ev);
}
},
/** /**
* @private * @private
* @param {MouseEvent} event * @param {MouseEvent} event
@@ -2147,6 +2210,14 @@ var FieldMany2ManyTags = AbstractField.extend({
event.stopPropagation(); event.stopPropagation();
this._removeTag($(event.target).parent().data('id')); this._removeTag($(event.target).parent().data('id'));
}, },
/**
* @private
* @param {MouseEvent} ev
*/
_onEditRecord: function (ev) {
ev.preventDefault();
this._editRecord($(ev.currentTarget).data('id'));
},
/** /**
* Controls the changes made in the internal m2o field. * Controls the changes made in the internal m2o field.
* *
@@ -2189,7 +2260,6 @@ var FieldMany2ManyTags = AbstractField.extend({


var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({ var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({
events: _.extend({}, FieldMany2ManyTags.prototype.events, { events: _.extend({}, FieldMany2ManyTags.prototype.events, {
'click .dropdown-toggle': '_onOpenColorPicker',
'mousedown .o_colorpicker a': '_onUpdateColor', 'mousedown .o_colorpicker a': '_onUpdateColor',
'mousedown .o_colorpicker .o_hide_in_kanban': '_onUpdateColor', 'mousedown .o_colorpicker .o_hide_in_kanban': '_onUpdateColor',
}), }),
@@ -2210,23 +2280,20 @@ var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({
* @private * @private
* @param {MouseEvent} ev * @param {MouseEvent} ev
*/ */
_onOpenColorPicker: function (ev) { _onClickBadge: function (ev) {
ev.preventDefault(); ev.preventDefault();
var tagID = $(ev.currentTarget).parent().data('id'); var tagID = $(ev.currentTarget).parent().data('id');
var tagColor = $(ev.currentTarget).parent().data('color');
var tag = _.findWhere(this.value.data, { res_id: tagID }); var tag = _.findWhere(this.value.data, { res_id: tagID });
if (tag && this.colorField in tag.data) { // if there is a color field on the related model var hasColorField = tag && this.colorField in tag.data;
this.$color_picker = $(qweb.render('FieldMany2ManyTag.colorpicker', {
'widget': this,
'tag_id': tagID,
}));


$(ev.currentTarget).after(this.$color_picker); if (hasColorField) {
this.$color_picker.dropdown(); var tagColor = $(ev.currentTarget).parent().data('color');
this.$color_picker.attr("tabindex", 1).focus(); this._renderTagDropdown('FieldMany2ManyTag.colorpicker', ev);
if (!tagColor) { if (!tagColor) {
this.$('.custom-checkbox input').prop('checked', true); this.$tag_dropdown.find('.o_checkbox input').prop('checked', true);
} }
} else {
this._super.apply(this, arguments);
} }
}, },
/** /**
@@ -2247,7 +2314,7 @@ var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({
var changes = {}; var changes = {};


if ($target.is('.o_hide_in_kanban')) { if ($target.is('.o_hide_in_kanban')) {
var $checkbox = $('.o_hide_in_kanban .custom-checkbox input'); var $checkbox = $('.o_hide_in_kanban input');
$checkbox.prop('checked', !$checkbox.prop('checked')); // toggle checkbox $checkbox.prop('checked', !$checkbox.prop('checked')); // toggle checkbox
this.prevColors = this.prevColors ? this.prevColors : {}; this.prevColors = this.prevColors ? this.prevColors : {};
if ($checkbox.is(':checked')) { if ($checkbox.is(':checked')) {
@@ -150,14 +150,31 @@
} }
} }


.tagcolor_dropdown_menu { .o_tag_dropdown_menu {
min-width: 150px; // down from 160px of .dropdown-menu min-width: 150px; // down from 160px of .dropdown-menu
margin-right: 0px; // cancel right margin of .dropdown-menu margin-right: 0px; // cancel right margin of .dropdown-menu
} .o_tag_dropdown_options > ul {

margin-bottom: 0px;
.o_colorpicker > ul { padding-left: 0px;
@include o-tag-colorpicker; list-style: none;
white-space: normal; > li > a {
color: $o-main-text-color;
display: block;
padding: 3px 20px;
font-weight: normal;
&:hover {
background-color: #E5E5E5;
}
}
}
.o_colorpicker > ul {
@include o-tag-colorpicker;
white-space: normal;
margin: 5px 0;
.o_hide_in_kanban .o_checkbox {
display: inline-block;
}
}
} }


@for $size from 1 through length($o-colors) { @for $size from 1 through length($o-colors) {
@@ -837,47 +837,54 @@
<t t-set="colornames" t-value="['No color', 'Red', 'Orange', 'Yellow', 'Light blue', 'Dark purple', 'Salmon pink', 'Medium blue', 'Dark blue', 'Fushia', 'Green', 'Purple']"/> <t t-set="colornames" t-value="['No color', 'Red', 'Orange', 'Yellow', 'Light blue', 'Dark purple', 'Salmon pink', 'Medium blue', 'Dark blue', 'Fushia', 'Green', 'Purple']"/>
<div t-attf-class="badge badge-pill #{hasDropdown ? 'dropdown' : ''} o_tag_color_#{color}" t-att-data-color="color" t-att-data-index="el_index" t-att-data-id="el.id" t-attf-title="Tag color: #{colornames[color]}"> <div t-attf-class="badge badge-pill #{hasDropdown ? 'dropdown' : ''} o_tag_color_#{color}" t-att-data-color="color" t-att-data-index="el_index" t-att-data-id="el.id" t-attf-title="Tag color: #{colornames[color]}">
<t t-set="_badge_text"> <t t-set="_badge_text">
<span class="o_badge_text" t-att-title="el.display_name"><span role="img" t-attf-aria-label="Tag color: #{colornames[color]}"/><t t-esc="el.display_name"/></span> <span><span role="img" t-attf-aria-label="Tag color: #{colornames[color]}"/><t t-esc="el.display_name"/></span>
</t> </t>
<t t-if="hasDropdown"> <t t-if="hasDropdown">
<a role="button" href="#" class="dropdown-toggle o-no-caret" data-toggle="dropdown" aria-expanded="false"> <a role="button" href="#" class="o_badge_text dropdown-toggle o-no-caret" data-toggle="dropdown" aria-expanded="false" t-att-title="el.display_name">
<t t-raw="_badge_text"/> <t t-raw="_badge_text"/>
</a> </a>
</t> </t>
<t t-else=""> <t t-else="">
<t t-raw="_badge_text"/> <a href="#" class="o_badge_text" t-att-title="el.display_name"><t t-raw="_badge_text"/></a>
</t> </t>
<a t-if="!readonly" href="#" class="fa fa-times o_delete" title="Delete" aria-label="Delete"/> <a t-if="!readonly" href="#" class="fa fa-times o_delete" title="Delete" aria-label="Delete"/>
</div> </div>
</t> </t>
</t> </t>
<t t-name="FieldMany2ManyTag.colorpicker"> <t t-name="FieldMany2ManyTag.TagDropdown">
<div class="o_colorpicker dropdown-menu tagcolor_dropdown_menu" role="menu"> <div class="o_tag_dropdown_menu dropdown-menu" role="menu">
<ul> <div t-if="has_options" class="o_tag_dropdown_options">
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_1" data-color="1" title="Red" aria-label="Red"/></li> <ul>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_2" data-color="2" title="Orange" aria-label="Orange"/></li> <li><a href="#" class="o_edit_record" t-att-data-id="tag_id">Edit</a></li>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_3" data-color="3" title="Yellow" aria-label="Yellow"/></li> </ul>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_4" data-color="4" title="Light blue" aria-label="Light blue"/></li> </div>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_5" data-color="5" title="Dark purple" aria-label="Dark purple"/></li>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_6" data-color="6" title="Salmon pink" aria-label="Salmon pink"/></li>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_7" data-color="7" title="Medium blue" aria-label="Medium blue"/></li>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_8" data-color="8" title="Dark blue" aria-label="Dark blue"/></li>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_9" data-color="9" title="Fushia" aria-label="Fushia"/></li>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_10" data-color="10" title="Green" aria-label="Green"/></li>
<li><a role="menuitem" href="#" t-att-data-id="tag_id" class="o_tag_color_11" data-color="11" title="Purple" aria-label="Purple"/></li>
<li> <!-- checkbox for tag color 0 -->
<div role="menuitem" class="o_hide_in_kanban"
t-att-data-id="tag_id"
t-att-data-color="0">
<div class="custom-control custom-checkbox">
<input type="checkbox" id="o_hide_in_kanban_checkbox" class="custom-control-input"/>
<label for="o_hide_in_kanban_checkbox" class="custom-control-label">Hide in Kanban</label>
</div>
</div>
</li>
</ul>
</div> </div>
</t> </t>
<t t-name="FieldMany2ManyTag.colorpicker" t-extend="FieldMany2ManyTag.TagDropdown">
<t t-jquery=".o_tag_dropdown_menu" t-operation="append">
<div t-if="has_options" class="divider"/>
<div class="o_colorpicker">
<ul>
<li t-foreach="11" t-as="color">
<a href="#"
t-att-data-id="tag_id"
t-att-data-color="color+1"
t-attf-class="o_tag_color_#{color+1}"/>
</li>
<li> <!-- checkbox for tag color 0 -->
<div class="o_hide_in_kanban"
t-att-data-id="tag_id"
t-att-data-color="0">
<div class="o_checkbox">
<input type="checkbox"/>
<span/>
</div> Hide in Kanban
</div>
</li>
</ul>
</div>
</t>
</t>
<t t-name="ProgressBar"> <t t-name="ProgressBar">
<div class="o_progressbar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"> <div class="o_progressbar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div t-if="widget.title" class="o_progressbar_title"><t t-esc="widget.title"/></div><div class="o_progress"> <div t-if="widget.title" class="o_progressbar_title"><t t-esc="widget.title"/></div><div class="o_progress">
@@ -754,15 +754,15 @@ QUnit.module('fields', {}, function () {


assert.containsOnce(form, '.o_data_row', assert.containsOnce(form, '.o_data_row',
"the record should have been added to the relation"); "the record should have been added to the relation");
assert.strictEqual(form.$('.o_data_row:first .o_badge_text').text(), 'leonardodonatello', assert.strictEqual(form.$('.o_data_row:first .o_badge_text span').text(), 'leonardodonatello',
"inner m2m should have been fetched and correctly displayed"); "inner m2m should have been fetched and correctly displayed");


await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
await testUtils.dom.click($('.modal .o_data_row:first')); await testUtils.dom.click($('.modal .o_data_row:first'));


assert.containsN(form, '.o_data_row', 2, assert.containsN(form, '.o_data_row', 2,
"the second record should have been added to the relation"); "the second record should have been added to the relation");
assert.strictEqual(form.$('.o_data_row:nth(1) .o_badge_text').text(), 'donatelloraphael', assert.strictEqual(form.$('.o_data_row:nth(1) .o_badge_text span').text(), 'donatelloraphael',
"inner m2m should have been fetched and correctly displayed"); "inner m2m should have been fetched and correctly displayed");


assert.verifySteps([ assert.verifySteps([
@@ -5013,14 +5013,14 @@ QUnit.module('fields', {}, function () {
'should display the line in editable mode'); 'should display the line in editable mode');
assert.strictEqual(form.$('.o_field_many2one input').val(), "xpad", assert.strictEqual(form.$('.o_field_many2one input').val(), "xpad",
'should display the product xpad'); 'should display the product xpad');
assert.strictEqual(form.$('.o_field_many2manytags.o_input .o_badge_text').text(), "first record", assert.strictEqual(form.$('.o_field_many2manytags.o_input .o_badge_text span').text(), "first record",
'should display the tag from the onchange'); 'should display the tag from the onchange');


await testUtils.dom.click(form.$('input.o_field_integer[name="int_field"]')); await testUtils.dom.click(form.$('input.o_field_integer[name="int_field"]'));


assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xpad", assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xpad",
'should display the product xpad'); 'should display the product xpad');
assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text').text(), "first record", assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text span').text(), "first record",
'should display the tag in readonly'); 'should display the tag in readonly');


// enable the many2many onchange and generate it // enable the many2many onchange and generate it
@@ -5052,15 +5052,15 @@ QUnit.module('fields', {}, function () {
'should display the line in editable mode'); 'should display the line in editable mode');
assert.strictEqual(form.$('.o_field_many2one input').val(), "xenomorphe", assert.strictEqual(form.$('.o_field_many2one input').val(), "xenomorphe",
'should display the product xenomorphe'); 'should display the product xenomorphe');
assert.strictEqual(form.$('.o_field_many2manytags.o_input .o_badge_text').text(), "second record", assert.strictEqual(form.$('.o_field_many2manytags.o_input .o_badge_text span').text(), "second record",
'should display the tag from the onchange'); 'should display the tag from the onchange');


// put list in readonly mode // put list in readonly mode
await testUtils.dom.click(form.$('input.o_field_integer[name="int_field"]')); await testUtils.dom.click(form.$('input.o_field_integer[name="int_field"]'));


assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone", assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone",
'should display the product xphone and xenomorphe'); 'should display the product xphone and xenomorphe');
assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text').text(), "second recordfirst record", assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text span').text(), "second recordfirst record",
'should display the tag in readonly (first record and second record)'); 'should display the tag in readonly (first record and second record)');


await testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '10'); await testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '10');
@@ -5298,7 +5298,7 @@ QUnit.module('fields', {}, function () {
"one2many list should contain 2 rows"); "one2many list should contain 2 rows");
assert.containsN(form, '.o_list_view .o_field_many2manytags[name="partner_ids"] .badge', 2, assert.containsN(form, '.o_list_view .o_field_many2manytags[name="partner_ids"] .badge', 2,
"m2mtags should contain two tags"); "m2mtags should contain two tags");
assert.strictEqual(form.$('.o_list_view .o_field_many2manytags[name="partner_ids"] .o_badge_text').text(), assert.strictEqual(form.$('.o_list_view .o_field_many2manytags[name="partner_ids"] .o_badge_text span').text(),
'aaafirst record', "tag names should have been correctly loaded"); 'aaafirst record', "tag names should have been correctly loaded");


form.destroy(); form.destroy();
@@ -6275,7 +6275,7 @@ QUnit.module('fields', {}, function () {
"timmy should be displayed in the form view"); "timmy should be displayed in the form view");
assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"] .badge').length, 1, assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"] .badge').length, 1,
"m2mtags should contain one tag"); "m2mtags should contain one tag");
assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"] .o_badge_text').text(), assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"] .o_badge_text span').text(),
'gold', "tag name should have been correctly loaded"); 'gold', "tag name should have been correctly loaded");


form.destroy(); form.destroy();

0 comments on commit fcb45b2

Please sign in to comment.
You can’t perform that action at this time.