Skip to content

Commit

Permalink
[REF] web: Many2ManyTagsField: only edit color in Form
Browse files Browse the repository at this point in the history
One can edit the color of a tag in a Many2ManyTags field only in form view.
This commit refactors that field so that the standard field Component doesn't allow for it,
and conversely implements a specialization of the field that can, and should be
selected by the form view.

closes odoo#99499

Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
  • Loading branch information
kebeclibre committed Sep 6, 2022
1 parent 972b54f commit e940f14
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,27 +79,23 @@ export class Many2ManyTagsField extends Component {
get evalContext() {
return this.props.record.evalContext;
}
get canEditColor() {
return this.props.canEditColor && this.props.record.viewType !== "list";
}
get string() {
return this.props.record.activeFields[this.props.name].string;
}

get tags() {
return this.props.value.records.map((record) => ({
getTagProps(record) {
return {
id: record.id, // datapoint_X
resId: record.resId,
text: record.data.display_name,
colorIndex: record.data[this.props.colorField],
onClick: (ev) => this.onBadgeClick(ev, record),
onDelete: !this.props.readonly ? () => this.deleteTag(record.id) : undefined,
onKeydown: this.onTagKeydown.bind(this),
}));
};
}

get canOpenColorDropdown() {
return this.handlesColor() && this.canEditColor;
get tags() {
return this.props.value.records.map((record) => this.getTagProps(record));
}

get showM2OSelectionField() {
Expand All @@ -112,70 +108,13 @@ export class Many2ManyTagsField extends Component {
this.props.value.replaceWith(ids);
}

handlesColor() {
return this.props.colorField !== undefined && this.props.colorField !== null;
}

switchTagColor(colorIndex, tag) {
const tagRecord = this.props.value.records.find((record) => record.id === tag.id);
tagRecord.update({ [this.props.colorField]: colorIndex });
tagRecord.save();
this.closePopover();
}

onTagVisibilityChange(isHidden, tag) {
const tagRecord = this.props.value.records.find((record) => record.id === tag.id);
if (tagRecord.data[this.props.colorField] != 0) {
this.previousColorsMap[tagRecord.resId] = tagRecord.data[this.props.colorField];
}
tagRecord.update({
[this.props.colorField]: isHidden ? 0 : this.previousColorsMap[tagRecord.resId] || 1,
});
tagRecord.save();
this.closePopover();
}

closePopover() {
this.popoverCloseFn();
this.popoverCloseFn = null;
}

getDomain() {
return Domain.and([
this.domain,
Domain.not([["id", "in", this.props.value.currentIds]]),
]).toList(this.context);
}

onBadgeClick(ev, record) {
if (!this.canOpenColorDropdown) {
return;
}
const isClosed = !document.querySelector(".o_tag_popover");
if (isClosed) {
this.currentPopoverEl = null;
}
if (this.popoverCloseFn) {
this.closePopover();
}
if (isClosed || this.currentPopoverEl !== ev.currentTarget) {
this.currentPopoverEl = ev.currentTarget;
this.popoverCloseFn = this.popover.add(
ev.currentTarget,
this.constructor.components.Popover,
{
colors: this.constructor.RECORD_COLORS,
tag: {
id: record.id,
colorIndex: record.data[this.props.colorField],
},
switchTagColor: this.switchTagColor.bind(this),
onTagVisibilityChange: this.onTagVisibilityChange.bind(this),
}
);
}
}

focusTag(index) {
const autoCompleteParent = this.autoCompleteRef.el.parentElement;
const tags = autoCompleteParent.getElementsByClassName("badge");
Expand Down Expand Up @@ -279,14 +218,12 @@ Many2ManyTagsField.SEARCH_MORE_LIMIT = 320;

Many2ManyTagsField.template = "web.Many2ManyTagsField";
Many2ManyTagsField.components = {
Popover: Many2ManyTagsFieldColorListPopover,
TagsList,
Many2XAutocomplete,
};

Many2ManyTagsField.props = {
...standardFieldProps,
canEditColor: { type: Boolean, optional: true },
canCreate: { type: Boolean, optional: true },
canQuickCreate: { type: Boolean, optional: true },
canCreateEdit: { type: Boolean, optional: true },
Expand All @@ -298,7 +235,6 @@ Many2ManyTagsField.props = {
};
Many2ManyTagsField.defaultProps = {
canCreate: true,
canEditColor: true,
canQuickCreate: true,
canCreateEdit: true,
nameCreateField: "name",
Expand All @@ -320,7 +256,6 @@ Many2ManyTagsField.extractProps = ({ attrs, field }) => {
return {
colorField: attrs.options.color_field,
nameCreateField: attrs.options.create_name_field,
canEditColor: !attrs.options.no_edit_color,
relation: field.relation,
canQuickCreate: canCreate && !noQuickCreate,
canCreateEdit: canCreate && !noCreateEdit,
Expand All @@ -330,5 +265,91 @@ Many2ManyTagsField.extractProps = ({ attrs, field }) => {
};

registry.category("fields").add("many2many_tags", Many2ManyTagsField);
registry.category("fields").add("form.many2many_tags", Many2ManyTagsField);
registry.category("fields").add("list.many2many_tags", Many2ManyTagsField);

/**
* A specialization that allows to edit the color with the colorpicker.
* Used in form view.
*/
export class Many2ManyTagsFieldColorEditable extends Many2ManyTagsField {
getTagProps(record) {
const props = super.getTagProps(record);
props.onClick = (ev) => this.onBadgeClick(ev, record);
return props;
}

closePopover() {
this.popoverCloseFn();
this.popoverCloseFn = null;
}

onBadgeClick(ev, record) {
if (!this.props.canEditColor) {
return;
}
const isClosed = !document.querySelector(".o_tag_popover");
if (isClosed) {
this.currentPopoverEl = null;
}
if (this.popoverCloseFn) {
this.closePopover();
}
if (isClosed || this.currentPopoverEl !== ev.currentTarget) {
this.currentPopoverEl = ev.currentTarget;
this.popoverCloseFn = this.popover.add(
ev.currentTarget,
this.constructor.components.Popover,
{
colors: this.constructor.RECORD_COLORS,
tag: {
id: record.id,
colorIndex: record.data[this.props.colorField],
},
switchTagColor: this.switchTagColor.bind(this),
onTagVisibilityChange: this.onTagVisibilityChange.bind(this),
}
);
}
}

onTagVisibilityChange(isHidden, tag) {
const tagRecord = this.props.value.records.find((record) => record.id === tag.id);
if (tagRecord.data[this.props.colorField] != 0) {
this.previousColorsMap[tagRecord.resId] = tagRecord.data[this.props.colorField];
}
tagRecord.update({
[this.props.colorField]: isHidden ? 0 : this.previousColorsMap[tagRecord.resId] || 1,
});
tagRecord.save();
this.closePopover();
}

switchTagColor(colorIndex, tag) {
const tagRecord = this.props.value.records.find((record) => record.id === tag.id);
tagRecord.update({ [this.props.colorField]: colorIndex });
tagRecord.save();
this.closePopover();
}
}

Many2ManyTagsFieldColorEditable.components = {
...Many2ManyTagsField.components,
Popover: Many2ManyTagsFieldColorListPopover,
};
Many2ManyTagsFieldColorEditable.props = {
...Many2ManyTagsField.props,
canEditColor: { type: Boolean, optional: true },
};
Many2ManyTagsFieldColorEditable.defaultProps = {
...Many2ManyTagsField.defaultProps,
canEditColor: true,
};
Many2ManyTagsFieldColorEditable.extractProps = (params) => {
const props = Many2ManyTagsField.extractProps(params);
const attrs = params.attrs;
return {
...props,
canEditColor: !attrs.options.no_edit_color,
};
};

registry.category("fields").add("form.many2many_tags", Many2ManyTagsFieldColorEditable);
45 changes: 45 additions & 0 deletions addons/web/static/tests/views/fields/many2many_tags_field_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,53 @@ QUnit.module("Fields", (hooks) => {

// click on the tag: should do nothing and open the form view
click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
assert.verifySteps(["selectRecord"]);
await nextTick();

assert.containsNone(target, ".o_colorlist");

await click(target.querySelectorAll(".o_list_record_selector")[1]);
click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
assert.verifySteps(["selectRecord"]);
await nextTick();

assert.containsNone(target, ".o_colorlist");
});

QUnit.test("Many2ManyTagsField in tree view -- multi edit", async function (assert) {
serverData.models.partner.records[0].timmy = [12, 14];

await makeView({
type: "list",
resModel: "partner",
serverData,
arch: `
<tree multi_edit="1">
<field name="timmy" widget="many2many_tags" options="{'color_field': 'color'}"/>
<field name="foo"/>
</tree>`,
selectRecord: () => {
assert.step("selectRecord");
},
});

assert.containsN(target, ".o_field_many2many_tags .badge", 2, "there should be 2 tags");
assert.containsNone(target, ".badge.dropdown-toggle", "the tags should not be dropdowns");

// click on the tag: should do nothing and open the form view
click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
assert.verifySteps(["selectRecord"]);
await nextTick();

assert.containsNone(target, ".o_colorlist");

await click(target.querySelectorAll(".o_list_record_selector")[1]);
click(target.querySelector(".o_field_many2many_tags .badge :nth-child(1)"));
assert.verifySteps([]);
await nextTick();

assert.containsOnce(target, ".o_selected_row");
assert.containsNone(target, ".o_colorlist");
});

QUnit.test("Many2ManyTagsField view a domain", async function (assert) {
Expand Down

0 comments on commit e940f14

Please sign in to comment.