[IMP] web: move properties field's "add a property" btn in the cog menu
Purpose: -------- When the property field is added into a form view, the "add a property" button is shown by default. However, most of the time the users won't add properties when opening a form view, making this button take up space for nothing. To alleviate this issue, commit [1] added an option that allows to hide this button, and added actions to allow adding properties from the cog menu instead. Now that properties are being added into several views, one would like this "add properties" button to be hidden by default, and show an entry in the cogmenu allowing add properties by default, without needing to create an action for every view using a property field. Therefore, a new static action "Add Properties" has been added in the form controller that is available if a properties field has been encountered during the parsing of the form view's arch. The "hideAddButton" has been changed into "showAddButton" and now allows to show the "Add a Property" by default. Task-3458627 [1]: https://github.com/odoo/odoo/commit/1f80810e39f811122a65e11cd8cac99aa39a7960 Part-of: odoo/odoo#137589
This commit is contained in:
committed by
Thibault Delavallée
parent
5801ec664d
commit
2d7a49dab1
@@ -32,7 +32,6 @@
|
||||
'data/crm_team_data.xml',
|
||||
'data/digest_data.xml',
|
||||
'data/ir_action_data.xml',
|
||||
'data/ir_actions_client_data.xml',
|
||||
'data/ir_cron_data.xml',
|
||||
'data/mail_message_subtype_data.xml',
|
||||
'data/crm_recurring_plan_data.xml',
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<record id="crm_lead_action_configure_properties_field" model="ir.actions.client">
|
||||
<field name="name">Add Properties</field>
|
||||
<field name="res_model">crm.lead</field>
|
||||
<field name="tag">action_configure_properties_field</field>
|
||||
<field name="binding_model_id" ref="crm.model_crm_lead"/>
|
||||
<field name="binding_view_types">form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -252,8 +252,7 @@
|
||||
</group>
|
||||
</group>
|
||||
<div class="d-flex">
|
||||
<field name="lead_properties" nolabel="1" columns="2"
|
||||
hideKanbanOption="1" hideAddButton="1"/>
|
||||
<field name="lead_properties" nolabel="1" columns="2" hideKanbanOption="1"/>
|
||||
</div>
|
||||
<notebook>
|
||||
<page string="Internal Notes" name="internal_notes">
|
||||
|
||||
@@ -13,14 +13,6 @@
|
||||
<field eval="False" name="doall" />
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_action_configure_properties_field" model="ir.actions.client">
|
||||
<field name="name">Add Properties</field>
|
||||
<field name="res_model">fleet.vehicle</field>
|
||||
<field name="tag">action_configure_properties_field</field>
|
||||
<field name="binding_model_id" ref="fleet.model_fleet_vehicle"/>
|
||||
<field name="binding_view_types">form</field>
|
||||
</record>
|
||||
|
||||
<record id="fleet_vehicle_state_new_request" model="fleet.vehicle.state">
|
||||
<field name="name">New Request</field>
|
||||
<field name="sequence">4</field>
|
||||
|
||||
@@ -112,8 +112,7 @@
|
||||
</group>
|
||||
</group>
|
||||
<div class="d-flex">
|
||||
<field name="vehicle_properties" nolabel="1" columns="2"
|
||||
hideKanbanOption="1" hideAddButton="1"/>
|
||||
<field name="vehicle_properties" nolabel="1" columns="2" hideKanbanOption="1"/>
|
||||
</div>
|
||||
<notebook>
|
||||
<page string="Tax Info">
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
'security/ir.model.access.csv',
|
||||
'security/ir.model.access.xml',
|
||||
'data/digest_data.xml',
|
||||
'data/ir_actions_client_data.xml',
|
||||
'report/project_task_burndown_chart_report_views.xml',
|
||||
'views/account_analytic_account_views.xml',
|
||||
'views/digest_digest_views.xml',
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<record id="project_task_action_configure_properties_field" model="ir.actions.client">
|
||||
<field name="name">Add Properties</field>
|
||||
<field name="res_model">project.task</field>
|
||||
<field name="tag">action_configure_properties_field</field>
|
||||
<field name="binding_model_id" ref="project.model_project_task"/>
|
||||
<field name="binding_view_types">form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -372,8 +372,7 @@
|
||||
</group>
|
||||
</group>
|
||||
<div class="d-flex">
|
||||
<field name="task_properties" nolabel="1" columns="2"
|
||||
hideKanbanOption="1" hideAddButton="1"/>
|
||||
<field name="task_properties" nolabel="1" columns="2" hideKanbanOption="1"/>
|
||||
</div>
|
||||
<notebook>
|
||||
<page name="description_page" string="Description">
|
||||
|
||||
@@ -8,7 +8,7 @@ import { PropertyDefinition } from "./property_definition";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { PropertyValue } from "./property_value";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import { usePopover } from "@web/core/popover/popover_hook";
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { reposition } from "@web/core/position_hook";
|
||||
@@ -30,7 +30,7 @@ export class PropertiesField extends Component {
|
||||
...standardFieldProps,
|
||||
context: { type: Object, optional: true },
|
||||
columns: { type: Number, optional: true },
|
||||
hideAddButton: { type: Boolean, optional: true },
|
||||
showAddButton: { type: Boolean, optional: true },
|
||||
hideKanbanOption: { type: Boolean, optional: true },
|
||||
};
|
||||
|
||||
@@ -56,10 +56,17 @@ export class PropertiesField extends Component {
|
||||
this.state = useState({
|
||||
canChangeDefinition: true,
|
||||
movedPropertyName: null,
|
||||
hideAddButton: this.props.hideAddButton,
|
||||
showAddButton: this.props.showAddButton,
|
||||
unfoldedSeparators: this._getUnfoldedSeparators(),
|
||||
});
|
||||
|
||||
// Properties can be added from the cogmenu of the form controller
|
||||
if (this.env.config?.viewType === "form") {
|
||||
useBus(this.env.model.bus, "PROPERTY_FIELD:ADD_PROPERTY_VALUE", () => {
|
||||
this.onPropertyCreate();
|
||||
});
|
||||
}
|
||||
|
||||
onWillStart(async () => {
|
||||
await this._checkDefinitionAccess();
|
||||
});
|
||||
@@ -609,7 +616,7 @@ export class PropertiesField extends Component {
|
||||
definition_changed: true,
|
||||
});
|
||||
this.openPropertyDefinition = newName;
|
||||
this.state.hideAddButton = false;
|
||||
this.state.showAddButton = true;
|
||||
this.props.record.update({ [this.props.name]: propertiesDefinitions });
|
||||
}
|
||||
|
||||
@@ -939,29 +946,10 @@ export const propertiesField = {
|
||||
return {
|
||||
context: dynamicInfo.context,
|
||||
columns: parseInt(attrs.columns || "1"),
|
||||
hideAddButton: archParseBoolean(attrs.hideAddButton),
|
||||
showAddButton: archParseBoolean(attrs.showAddButton),
|
||||
hideKanbanOption: archParseBoolean(attrs.hideKanbanOption),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("fields").add("properties", propertiesField);
|
||||
|
||||
/**
|
||||
* This action is meant to be called from a form client action
|
||||
* and will let the user create properties, typically used along
|
||||
* with the "hideAddButton" option of the properties field.
|
||||
*
|
||||
* @param {object} env
|
||||
*/
|
||||
async function actionAddProperty(env) {
|
||||
const addProperty = document.querySelector(".o_field_property_add button");
|
||||
if (addProperty) {
|
||||
addProperty.click();
|
||||
} else {
|
||||
const message = _t("You can not create a new property.");
|
||||
env.services.notification.add(message, { type: "danger" });
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("actions").add("action_configure_properties_field", actionAddProperty);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="web.PropertiesField">
|
||||
<div t-ref="properties" class="row"
|
||||
t-att-class="{'d-none': propertiesList.length === 0 and state.hideAddButton}"
|
||||
t-att-class="{'d-none': propertiesList.length === 0 and !state.showAddButton}"
|
||||
t-att-columns="renderedColumnsCount">
|
||||
<t t-set="unfoldedSeparators" t-value="state.unfoldedSeparators"/>
|
||||
<t t-set="_groupedPropertiesList" t-value="groupedPropertiesList"/>
|
||||
@@ -76,9 +76,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
t-if="propertiesListGroup_index === _groupedPropertiesList.length - 1"
|
||||
t-if="propertiesListGroup_index === _groupedPropertiesList.length - 1 and state.showAddButton"
|
||||
class="o_field_property_add"
|
||||
t-att-class="{'g-col-2': props.columns !== 1, 'd-none': state.hideAddButton}">
|
||||
t-att-class="{'g-col-2': props.columns !== 1}">
|
||||
<button
|
||||
t-if="!props.readonly and state.canChangeDefinition and definitionRecordId"
|
||||
class="btn btn-light text-muted text-break m-0"
|
||||
|
||||
@@ -27,6 +27,9 @@ export class FormArchParser {
|
||||
if (archParseBoolean(node.getAttribute("default_focus") || "")) {
|
||||
autofocusFieldId = fieldId;
|
||||
}
|
||||
if (fieldInfo.type === "properties") {
|
||||
activeActions.addPropertyFieldValue = true;
|
||||
}
|
||||
return false;
|
||||
} else if (node.tagName === "div" && node.classList.contains("oe_chatter")) {
|
||||
// remove this when chatter fields are declared as attributes on the root node
|
||||
|
||||
@@ -403,6 +403,13 @@ export class FormController extends Component {
|
||||
callback: () => this.deleteRecord(),
|
||||
skipSave: true,
|
||||
},
|
||||
addPropertyFieldValue: {
|
||||
isAvailable: () => activeActions.addPropertyFieldValue,
|
||||
sequence: 50,
|
||||
icon: "fa fa-cogs",
|
||||
description: _t("Add Properties"),
|
||||
callback: () => this.model.bus.trigger("PROPERTY_FIELD:ADD_PROPERTY_VALUE"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
patchWithCleanup,
|
||||
triggerEvent,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { toggleActionMenu } from "@web/../tests/search/helpers";
|
||||
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
|
||||
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
@@ -111,6 +112,7 @@ async function makePropertiesGroupView(properties) {
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC,
|
||||
actionMenus: {},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -352,13 +354,18 @@ QUnit.module("Fields", (hooks) => {
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC,
|
||||
actionMenus: {},
|
||||
});
|
||||
|
||||
const field = target.querySelector(".o_field_properties");
|
||||
assert.ok(field, "The field must be in the view");
|
||||
|
||||
const addButton = target.querySelector(".o_field_property_add button");
|
||||
assert.notOk(addButton, "The add button must not be in the view");
|
||||
await toggleActionMenu(target);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_cp_action_menus span:contains(Add Properties)",
|
||||
"Show Add Properties btn in cog menu",
|
||||
);
|
||||
|
||||
const editButton = field.querySelector(".o_field_property_open_popover");
|
||||
assert.notOk(editButton, "The edit definition button must not be in the view");
|
||||
@@ -393,13 +400,18 @@ QUnit.module("Fields", (hooks) => {
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC,
|
||||
actionMenus: {},
|
||||
});
|
||||
|
||||
const field = target.querySelector(".o_field_properties");
|
||||
assert.ok(field, "The field must be in the view");
|
||||
|
||||
const addButton = target.querySelector(".o_field_property_add button");
|
||||
assert.ok(addButton, "The add button must be in the view");
|
||||
await toggleActionMenu(target);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_cp_action_menus span:contains(Add Properties)",
|
||||
"The add button must be in the cog menu",
|
||||
);
|
||||
|
||||
const editButton = field.querySelectorAll(".o_field_property_open_popover");
|
||||
assert.ok(editButton, "The edit definition button must be in the view");
|
||||
@@ -491,16 +503,21 @@ QUnit.module("Fields", (hooks) => {
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC,
|
||||
actionMenus: {},
|
||||
});
|
||||
|
||||
const field = target.querySelector(".o_field_properties");
|
||||
assert.ok(field, "The field must be in the view");
|
||||
|
||||
const addButton = target.querySelector(".o_field_property_add button");
|
||||
assert.ok(addButton, "The add button must be in the view");
|
||||
await toggleActionMenu(target);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_cp_action_menus span:contains(Add Properties)",
|
||||
"The add button must be in the cog menu",
|
||||
);
|
||||
|
||||
// Create a new property
|
||||
await click(target, ".o_field_property_add button");
|
||||
await click(target, ".o_cp_action_menus span .fa-cogs");
|
||||
|
||||
await nextTick();
|
||||
|
||||
@@ -1805,6 +1822,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC,
|
||||
actionMenus: {},
|
||||
});
|
||||
|
||||
const field = target.querySelector(".o_field_properties");
|
||||
@@ -1813,7 +1831,8 @@ QUnit.module("Fields", (hooks) => {
|
||||
// create a new property
|
||||
// edit the default value and close the popover definition
|
||||
// because we just created the property, the default value should be propagated
|
||||
await click(target, ".o_field_property_add button");
|
||||
await toggleActionMenu(target);
|
||||
await click(target, ".o_cp_action_menus span .fa-cogs");
|
||||
await nextTick();
|
||||
await editInput(target, ".o_field_property_definition_value input", "First Default Value");
|
||||
await closePopover(target);
|
||||
@@ -1969,11 +1988,13 @@ QUnit.module("Fields", (hooks) => {
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC,
|
||||
actionMenus: {},
|
||||
});
|
||||
assert.notOk(target.querySelector(".o_test_properties_not_empty"));
|
||||
|
||||
// create the first property
|
||||
await click(target, ".o_field_property_add button");
|
||||
await toggleActionMenu(target);
|
||||
await click(target, ".o_cp_action_menus span .fa-cogs");
|
||||
assert.ok(target.querySelector(".o_test_properties_not_empty"));
|
||||
}
|
||||
);
|
||||
@@ -2041,7 +2062,8 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
|
||||
// create 3 new properties
|
||||
await click(target, ".o_field_property_add button");
|
||||
await toggleActionMenu(target);
|
||||
await click(target, ".o_cp_action_menus span .fa-cogs");
|
||||
await click(target, ".o_field_property_add button");
|
||||
await click(target, ".o_field_property_add button");
|
||||
await nextTick();
|
||||
@@ -2283,7 +2305,8 @@ QUnit.module("Fields", (hooks) => {
|
||||
assertFolded([false, false, false, true]);
|
||||
|
||||
// now, create a new property, it must unfold the last group
|
||||
await click(target, ".o_field_property_add button");
|
||||
await toggleActionMenu(target);
|
||||
await click(target, ".o_cp_action_menus span .fa-cogs");
|
||||
assert.deepEqual(getGroups(), [
|
||||
[
|
||||
["SEPARATOR 2", "property_2"],
|
||||
@@ -2454,4 +2477,49 @@ QUnit.module("Fields", (hooks) => {
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("properties: showAddButton option", async function (assert) {
|
||||
async function mockRPC(route, { method, model, kwargs }) {
|
||||
if (["check_access_rights", "check_access_rule"].includes(method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="company_id"/>
|
||||
<field name="properties" showAddButton="True"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
mockRPC,
|
||||
});
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_property_add button",
|
||||
"The add button must be in the view",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"properties: no add properties action in cogmenu if no properties field",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "res.users",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: '<form><field name="name"/></form>',
|
||||
actionMenus: {},
|
||||
});
|
||||
await toggleActionMenu(target);
|
||||
assert.containsNone(target, ".o_cp_action_menus span:contains(Add Properties)");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user