[REF] *: extractProps receive dynamicInfo
Before this commit, the use of multiple <field> with the same name in a view was not well supported. Why was this? Some Field components need to know information related to the <field> such as context, domain, required and readonly. The solution used before this commit to access this information is to use the getFieldContext, getFieldDomain, isReadonly, isRequired functions of the model. Unfortunately, these only take into account the last occurrence of the <field> because the model is not aware that the same field is present several times on the view. The information must therefore not come from the model. For example, it was not possible to have the same field twice with 2 different domains. It will use the domain of the last field for both. Solution: We will add the object "dynamicInfo" to the fieldInfo passed to the Fields extractProps function. This object will contain a getter to get the value of required, readonly, domain and context for the current <field>. If a Field needs one of its information, it will just have to get it from extractProps. Part of Task: 3179751 closes odoo/odoo#115197 Related: odoo/enterprise#38151 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -39,15 +39,14 @@ export const many2ManyAttendee = {
|
|||||||
|
|
||||||
registry.category("fields").add("many2manyattendee", many2ManyAttendee);
|
registry.category("fields").add("many2manyattendee", many2ManyAttendee);
|
||||||
|
|
||||||
export function preloadMany2ManyAttendee(orm, record, fieldName) {
|
export function preloadMany2ManyAttendee(orm, record, fieldName, { context }) {
|
||||||
const context = record.getFieldContext(fieldName);
|
|
||||||
return orm.call(
|
return orm.call(
|
||||||
"res.partner",
|
"res.partner",
|
||||||
"get_attendee_detail",
|
"get_attendee_detail",
|
||||||
[record.data[fieldName].records.map(rec => rec.resId), [record.resId || false]],
|
[record.data[fieldName].records.map((rec) => rec.resId), [record.resId || false]],
|
||||||
{
|
{
|
||||||
context,
|
context,
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-5
@@ -36,11 +36,12 @@ FilterableSelectionField.props = {
|
|||||||
export const filterableSelectionField = {
|
export const filterableSelectionField = {
|
||||||
...selectionField,
|
...selectionField,
|
||||||
component: FilterableSelectionField,
|
component: FilterableSelectionField,
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ options }) {
|
||||||
...selectionField.extractProps(fieldInfo),
|
const props = selectionField.extractProps(...arguments);
|
||||||
whitelisted_values: fieldInfo.options.whitelisted_values,
|
props.whitelisted_values = options.whitelisted_values;
|
||||||
blacklisted_values: fieldInfo.options.blacklisted_values,
|
props.blacklisted_values = options.blacklisted_values;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("filterable_selection", filterableSelectionField);
|
registry.category("fields").add("filterable_selection", filterableSelectionField);
|
||||||
|
|||||||
@@ -57,17 +57,18 @@ export function useAssignUserCommand() {
|
|||||||
|
|
||||||
const provide = async (env, options) => {
|
const provide = async (env, options) => {
|
||||||
const value = options.searchValue.trim();
|
const value = options.searchValue.trim();
|
||||||
let domain = component.props.record.getFieldDomain(component.props.name);
|
let domain =
|
||||||
const context = component.props.record.getFieldContext(component.props.name);
|
component.props.domain || component.props.record.getFieldDomain(component.props.name);
|
||||||
|
const context = component.props.context;
|
||||||
if (type === "many2many") {
|
if (type === "many2many") {
|
||||||
const selectedUserIds = getCurrentIds();
|
const selectedUserIds = getCurrentIds();
|
||||||
if (selectedUserIds.length) {
|
if (selectedUserIds.length) {
|
||||||
domain = Domain.and([domain, [["id", "not in", selectedUserIds]]]);
|
domain = Domain.and([domain, [["id", "not in", selectedUserIds]]]).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const searchResult = await orm.call(component.relation, "name_search", [], {
|
const searchResult = await orm.call(component.relation, "name_search", [], {
|
||||||
name: value,
|
name: value,
|
||||||
args: domain.toList(),
|
args: domain,
|
||||||
operator: "ilike",
|
operator: "ilike",
|
||||||
limit: 80,
|
limit: 80,
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ export class FieldMany2ManyTagsEmailTagsList extends TagsList {}
|
|||||||
FieldMany2ManyTagsEmailTagsList.template = "FieldMany2ManyTagsEmailTagsList";
|
FieldMany2ManyTagsEmailTagsList.template = "FieldMany2ManyTagsEmailTagsList";
|
||||||
|
|
||||||
export class FieldMany2ManyTagsEmail extends Many2ManyTagsField {
|
export class FieldMany2ManyTagsEmail extends Many2ManyTagsField {
|
||||||
|
static props = {
|
||||||
|
...Many2ManyTagsField.props,
|
||||||
|
context: { type: Object, optional: true },
|
||||||
|
};
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
super.setup();
|
super.setup();
|
||||||
|
|
||||||
@@ -54,7 +59,7 @@ export class FieldMany2ManyTagsEmail extends Many2ManyTagsField {
|
|||||||
dialogDefs.push(
|
dialogDefs.push(
|
||||||
this.openMany2xRecord({
|
this.openMany2xRecord({
|
||||||
resId: record.resId,
|
resId: record.resId,
|
||||||
context: props.record.getFieldContext(this.props.name),
|
context: props.context,
|
||||||
title: sprintf(_t("Edit: %s"), record.data.display_name),
|
title: sprintf(_t("Edit: %s"), record.data.display_name),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -101,6 +106,11 @@ FieldMany2ManyTagsEmail.components = {
|
|||||||
export const fieldMany2ManyTagsEmail = {
|
export const fieldMany2ManyTagsEmail = {
|
||||||
...many2ManyTagsField,
|
...many2ManyTagsField,
|
||||||
component: FieldMany2ManyTagsEmail,
|
component: FieldMany2ManyTagsEmail,
|
||||||
|
extractProps(fieldInfo, dynamicInfo) {
|
||||||
|
const props = many2ManyTagsField.extractProps(...arguments);
|
||||||
|
props.context = dynamicInfo.context;
|
||||||
|
return props;
|
||||||
|
},
|
||||||
relatedFields: (fieldInfo) => {
|
relatedFields: (fieldInfo) => {
|
||||||
return [...many2ManyTagsField.relatedFields(fieldInfo), { name: "email", type: "char" }];
|
return [...many2ManyTagsField.relatedFields(fieldInfo), { name: "email", type: "char" }];
|
||||||
},
|
},
|
||||||
|
|||||||
+14
-8
@@ -29,6 +29,8 @@ export class Many2OneAvatarUserField extends Many2OneAvatarField {
|
|||||||
static template = "mail.Many2OneAvatarUserField";
|
static template = "mail.Many2OneAvatarUserField";
|
||||||
static props = {
|
static props = {
|
||||||
...Many2OneAvatarField.props,
|
...Many2OneAvatarField.props,
|
||||||
|
context: { type: Object, optional: true },
|
||||||
|
domain: { type: Array, optional: true },
|
||||||
withCommand: { type: Boolean, optional: true },
|
withCommand: { type: Boolean, optional: true },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -38,10 +40,13 @@ export const many2OneAvatarUserField = {
|
|||||||
...many2OneAvatarField,
|
...many2OneAvatarField,
|
||||||
component: Many2OneAvatarUserField,
|
component: Many2OneAvatarUserField,
|
||||||
additionalClasses: ["o_field_many2one_avatar"],
|
additionalClasses: ["o_field_many2one_avatar"],
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps(fieldInfo, dynamicInfo) {
|
||||||
...many2OneAvatarField.extractProps(fieldInfo),
|
const props = many2OneAvatarField.extractProps(...arguments);
|
||||||
withCommand: fieldInfo.viewType === "form",
|
props.context = dynamicInfo.context;
|
||||||
}),
|
props.domain = dynamicInfo.domain;
|
||||||
|
props.withCommand = fieldInfo.viewType === "form";
|
||||||
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("many2one_avatar_user", many2OneAvatarUserField);
|
registry.category("fields").add("many2one_avatar_user", many2OneAvatarUserField);
|
||||||
@@ -77,10 +82,11 @@ export const kanbanMany2OneAvatarUserField = {
|
|||||||
...kanbanMany2OneAvatarField,
|
...kanbanMany2OneAvatarField,
|
||||||
component: KanbanMany2OneAvatarUserField,
|
component: KanbanMany2OneAvatarUserField,
|
||||||
additionalClasses: [...kanbanMany2OneAvatarField.additionalClasses, "o_field_many2one_avatar"],
|
additionalClasses: [...kanbanMany2OneAvatarField.additionalClasses, "o_field_many2one_avatar"],
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ options }) {
|
||||||
...kanbanMany2OneAvatarField.extractProps(fieldInfo),
|
const props = kanbanMany2OneAvatarField.extractProps(...arguments);
|
||||||
displayAvatarName: fieldInfo.options.display_avatar_name || false,
|
props.displayAvatarName = options.display_avatar_name || false;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("kanban.many2one_avatar_user", kanbanMany2OneAvatarUserField);
|
registry.category("fields").add("kanban.many2one_avatar_user", kanbanMany2OneAvatarUserField);
|
||||||
|
|||||||
@@ -153,11 +153,12 @@ FieldMany2OneMailingFilter.defaultProps = {
|
|||||||
export const fieldMany2OneMailingFilter = {
|
export const fieldMany2OneMailingFilter = {
|
||||||
...many2OneField,
|
...many2OneField,
|
||||||
component: FieldMany2OneMailingFilter,
|
component: FieldMany2OneMailingFilter,
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ options }) {
|
||||||
...many2OneField.extractProps(fieldInfo),
|
const props = many2OneField.extractProps(...arguments);
|
||||||
domain_field: fieldInfo.options.domain_field,
|
props.domain_field = options.domain_field;
|
||||||
model_field: fieldInfo.options.model_field,
|
props.model_field = options.model_field;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("mailing_filter", fieldMany2OneMailingFilter);
|
registry.category("fields").add("mailing_filter", fieldMany2OneMailingFilter);
|
||||||
|
|||||||
@@ -609,12 +609,13 @@ export const massMailingHtmlField = {
|
|||||||
...htmlField,
|
...htmlField,
|
||||||
component: MassMailingHtmlField,
|
component: MassMailingHtmlField,
|
||||||
displayName: _lt("Email"),
|
displayName: _lt("Email"),
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ attrs, options }) {
|
||||||
...htmlField.extractProps(fieldInfo),
|
const props = htmlField.extractProps(...arguments);
|
||||||
filterTemplates: fieldInfo.options.filterTemplates,
|
props.filterTemplates = options.filterTemplates;
|
||||||
inlineField: fieldInfo.options['inline-field'],
|
props.inlineField = options['inline-field'];
|
||||||
iframeHtmlClass: fieldInfo.attrs.iframeHtmlClass,
|
props.iframeHtmlClass = attrs.iframeHtmlClass;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("mass_mailing_html", massMailingHtmlField);
|
registry.category("fields").add("mass_mailing_html", massMailingHtmlField);
|
||||||
|
|||||||
+6
-6
@@ -169,12 +169,12 @@ export const projectTaskStateSelection = {
|
|||||||
...stateSelectionField,
|
...stateSelectionField,
|
||||||
component: ProjectTaskStateSelection,
|
component: ProjectTaskStateSelection,
|
||||||
fieldDependencies: [{ name: "project_id", type: "many2one" }],
|
fieldDependencies: [{ name: "project_id", type: "many2one" }],
|
||||||
extractProps: ({ options, viewType }) => ({
|
extractProps({ options, viewType }) {
|
||||||
...stateSelectionField.extractProps({ options }),
|
const props = stateSelectionField.extractProps(...arguments);
|
||||||
isToggleMode: Boolean(options.is_toggle_mode),
|
props.isToggleMode = Boolean(options.is_toggle_mode);
|
||||||
viewType,
|
props.viewType = viewType;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
registry.category("fields").add("project_task_state_selection", projectTaskStateSelection);
|
registry.category("fields").add("project_task_state_selection", projectTaskStateSelection);
|
||||||
|
|||||||
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
<templates>
|
<templates>
|
||||||
<t t-name="project.ProjectTaskStateSelection" t-inherit="web.StateSelectionField" t-inherit-mode="primary" owl="1">
|
<t t-name="project.ProjectTaskStateSelection" t-inherit="web.StateSelectionField" t-inherit-mode="primary" owl="1">
|
||||||
<!-- Readonly button -->
|
<!-- Readonly button -->
|
||||||
<xpath expr="//t[@t-if='isReadonly']/button/span[1]" position="attributes">
|
<xpath expr="//t[@t-if='props.readonly']/button/span[1]" position="attributes">
|
||||||
<attribute name="t-attf-class">{{ stateIcon(currentValue) }} {{ statusColor(currentValue) }}</attribute>
|
<attribute name="t-attf-class">{{ stateIcon(currentValue) }} {{ statusColor(currentValue) }}</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
<!-- Waiting state button -->
|
<!-- Waiting state button -->
|
||||||
<xpath expr="//t[@t-if='isReadonly']" position="after">
|
<xpath expr="//t[@t-if='props.readonly']" position="after">
|
||||||
<t t-elif="currentValue == '04_waiting_normal' and ['kanban', 'list'].includes(this.props.viewType)">
|
<t t-elif="currentValue == '04_waiting_normal' and ['kanban', 'list'].includes(this.props.viewType)">
|
||||||
<button class="d-flex align-items-center btn fw-normal p-0 justify-content-center " title="This task is blocked by another unfinished task">
|
<button class="d-flex align-items-center btn fw-normal p-0 justify-content-center " title="This task is blocked by another unfinished task">
|
||||||
<i class="fa fa-lg fa-hourglass-o"></i>
|
<i class="fa fa-lg fa-hourglass-o"></i>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</t>
|
</t>
|
||||||
</xpath>
|
</xpath>
|
||||||
<!-- The toggle mark as done button -->
|
<!-- The toggle mark as done button -->
|
||||||
<xpath expr="//t[@t-if='isReadonly']" position="after">
|
<xpath expr="//t[@t-if='props.readonly']" position="after">
|
||||||
<t t-elif="isToggleMode and currentValue == '01_in_progress'">
|
<t t-elif="isToggleMode and currentValue == '01_in_progress'">
|
||||||
<button t-if="['kanban', 'list'].includes(this.props.viewType) or this.env.isSmall" class="d-flex align-items-center btn fw-normal p-0" t-on-click.stop="toggleState">
|
<button t-if="['kanban', 'list'].includes(this.props.viewType) or this.env.isSmall" class="d-flex align-items-center btn fw-normal p-0" t-on-click.stop="toggleState">
|
||||||
<i t-attf-class="{{ stateIcon(currentValue) }} {{ statusColor(currentValue) }}"></i>
|
<i t-attf-class="{{ stateIcon(currentValue) }} {{ statusColor(currentValue) }}"></i>
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ export class PrioritySwitchField extends PriorityField {
|
|||||||
export const prioritySwitchField = {
|
export const prioritySwitchField = {
|
||||||
...priorityField,
|
...priorityField,
|
||||||
component: PrioritySwitchField,
|
component: PrioritySwitchField,
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ viewType }) {
|
||||||
...priorityField.extractProps(fieldInfo),
|
const props = priorityField.extractProps(...arguments);
|
||||||
withCommand: fieldInfo.viewType === "form",
|
props.withCommand = viewType === "form";
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("priority_switch", prioritySwitchField);
|
registry.category("fields").add("priority_switch", prioritySwitchField);
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
import { registry } from '@web/core/registry';
|
import { registry } from "@web/core/registry";
|
||||||
import { Many2OneField, many2OneField } from '@web/views/fields/many2one/many2one_field';
|
import { Many2OneField, many2OneField } from "@web/views/fields/many2one/many2one_field";
|
||||||
|
|
||||||
export class SaleOrderLineProductField extends Many2OneField {
|
export class SaleOrderLineProductField extends Many2OneField {
|
||||||
|
static props = {
|
||||||
|
...Many2OneField.props,
|
||||||
|
readonlyField: { type: Boolean, optional: true },
|
||||||
|
};
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
super.setup();
|
super.setup();
|
||||||
@@ -14,7 +18,10 @@ export class SaleOrderLineProductField extends Many2OneField {
|
|||||||
// NB: quick creation doesn't go through here, but through quickCreate
|
// NB: quick creation doesn't go through here, but through quickCreate
|
||||||
// below
|
// below
|
||||||
if (value) {
|
if (value) {
|
||||||
if (Array.isArray(value[0]) && this.props.record.data[this.props.name] != value[0]) {
|
if (
|
||||||
|
Array.isArray(value[0]) &&
|
||||||
|
this.props.record.data[this.props.name] != value[0]
|
||||||
|
) {
|
||||||
// product (existing)
|
// product (existing)
|
||||||
newValue = true;
|
newValue = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -24,7 +31,7 @@ export class SaleOrderLineProductField extends Many2OneField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
if (this.relation === 'product.template') {
|
if (this.relation === "product.template") {
|
||||||
this._onProductTemplateUpdate();
|
this._onProductTemplateUpdate();
|
||||||
} else {
|
} else {
|
||||||
this._onProductUpdate();
|
this._onProductUpdate();
|
||||||
@@ -36,9 +43,9 @@ export class SaleOrderLineProductField extends Many2OneField {
|
|||||||
// HACK to make quick creation also open
|
// HACK to make quick creation also open
|
||||||
// configurators if needed
|
// configurators if needed
|
||||||
this.quickCreate = async (name, params = {}) => {
|
this.quickCreate = async (name, params = {}) => {
|
||||||
await this.props.record.update({ [this.props.name]: [false, name]});
|
await this.props.record.update({ [this.props.name]: [false, name] });
|
||||||
|
|
||||||
if (this.relation === 'product.template') {
|
if (this.relation === "product.template") {
|
||||||
this._onProductTemplateUpdate();
|
this._onProductTemplateUpdate();
|
||||||
} else {
|
} else {
|
||||||
this._onProductUpdate();
|
this._onProductUpdate();
|
||||||
@@ -51,11 +58,10 @@ export class SaleOrderLineProductField extends Many2OneField {
|
|||||||
// product form should be accessible if the widget field is readonly
|
// product form should be accessible if the widget field is readonly
|
||||||
// or if the line cannot be edited (e.g. locked SO)
|
// or if the line cannot be edited (e.g. locked SO)
|
||||||
return (
|
return (
|
||||||
this.props.record.isReadonly(this.props.name)
|
this.props.readonlyField ||
|
||||||
|| this.props.record.model.root.isReadonly
|
(this.props.record.model.root.activeFields.order_line &&
|
||||||
&& this.props.record.model.root.activeFields.order_line
|
this.props.record.model.root._isReadonly("order_line"))
|
||||||
&& this.props.record.model.root.isReadonly('order_line')
|
);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
get hasExternalButton() {
|
get hasExternalButton() {
|
||||||
// Keep external button, even if field is specified as 'no_open' so that the user is not
|
// Keep external button, even if field is specified as 'no_open' so that the user is not
|
||||||
@@ -66,19 +72,23 @@ export class SaleOrderLineProductField extends Many2OneField {
|
|||||||
get hasConfigurationButton() {
|
get hasConfigurationButton() {
|
||||||
return this.isConfigurableLine || this.isConfigurableTemplate;
|
return this.isConfigurableLine || this.isConfigurableTemplate;
|
||||||
}
|
}
|
||||||
get isConfigurableLine() { return false; }
|
get isConfigurableLine() {
|
||||||
get isConfigurableTemplate() { return false; }
|
return false;
|
||||||
|
}
|
||||||
|
get isConfigurableTemplate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
get configurationButtonHelp() {
|
get configurationButtonHelp() {
|
||||||
return this.env._t("Edit Configuration");
|
return this.env._t("Edit Configuration");
|
||||||
}
|
}
|
||||||
|
|
||||||
get configurationButtonIcon() {
|
get configurationButtonIcon() {
|
||||||
return 'btn btn-secondary fa ' + this.configurationButtonFAIcon();
|
return "btn btn-secondary fa " + this.configurationButtonFAIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
configurationButtonFAIcon() {
|
configurationButtonFAIcon() {
|
||||||
return 'fa-pencil';
|
return "fa-pencil";
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(ev) {
|
onClick(ev) {
|
||||||
@@ -86,14 +96,13 @@ export class SaleOrderLineProductField extends Many2OneField {
|
|||||||
if (this.props.readonly) {
|
if (this.props.readonly) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.openAction();
|
this.openAction();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
super.onClick(ev);
|
super.onClick(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onProductTemplateUpdate() { }
|
async _onProductTemplateUpdate() {}
|
||||||
async _onProductUpdate() { } // event_booth_sale, event_sale, sale_renting
|
async _onProductUpdate() {} // event_booth_sale, event_sale, sale_renting
|
||||||
|
|
||||||
onEditConfiguration() {
|
onEditConfiguration() {
|
||||||
if (this.isConfigurableLine) {
|
if (this.isConfigurableLine) {
|
||||||
@@ -102,9 +111,8 @@ export class SaleOrderLineProductField extends Many2OneField {
|
|||||||
this._editProductConfiguration();
|
this._editProductConfiguration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_editLineConfiguration() { } // event_booth_sale, event_sale, sale_renting
|
_editLineConfiguration() {} // event_booth_sale, event_sale, sale_renting
|
||||||
_editProductConfiguration() { } // sale_product_configurator, sale_product_matrix
|
_editProductConfiguration() {} // sale_product_configurator, sale_product_matrix
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SaleOrderLineProductField.template = "sale.SaleProductField";
|
SaleOrderLineProductField.template = "sale.SaleProductField";
|
||||||
@@ -112,6 +120,11 @@ SaleOrderLineProductField.template = "sale.SaleProductField";
|
|||||||
export const saleOrderLineProductField = {
|
export const saleOrderLineProductField = {
|
||||||
...many2OneField,
|
...many2OneField,
|
||||||
component: SaleOrderLineProductField,
|
component: SaleOrderLineProductField,
|
||||||
|
extractProps(fieldInfo, dynamicInfo) {
|
||||||
|
const props = many2OneField.extractProps(...arguments);
|
||||||
|
props.readonlyField = dynamicInfo.readonly;
|
||||||
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("sol_product_many2one", saleOrderLineProductField);
|
registry.category("fields").add("sol_product_many2one", saleOrderLineProductField);
|
||||||
|
|||||||
@@ -20,11 +20,10 @@ patch(PhoneField, "sms.PhoneField", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const patchDescr = {
|
const patchDescr = {
|
||||||
extractProps(fieldInfo) {
|
extractProps({ options }) {
|
||||||
return {
|
const props = this._super(...arguments);
|
||||||
...this._super(fieldInfo),
|
props.enableButton = options.enable_sms;
|
||||||
enableButton: fieldInfo.options.enable_sms,
|
return props;
|
||||||
};
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ var concurrency = require('web.concurrency');
|
|||||||
var Context = require('web.Context');
|
var Context = require('web.Context');
|
||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var Domain = require('web.Domain');
|
var Domain = require('web.Domain');
|
||||||
const pyUtils = require('web.py_utils');
|
|
||||||
var session = require('web.session');
|
var session = require('web.session');
|
||||||
var utils = require('web.utils');
|
var utils = require('web.utils');
|
||||||
var viewUtils = require('web.viewUtils');
|
var viewUtils = require('web.viewUtils');
|
||||||
@@ -1211,6 +1210,7 @@ var BasicModel = AbstractModel.extend({
|
|||||||
resolve(changedFields);
|
resolve(changedFields);
|
||||||
}
|
}
|
||||||
}).guardedCatch(reject);
|
}).guardedCatch(reject);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
resolve(changedFields);
|
resolve(changedFields);
|
||||||
}
|
}
|
||||||
@@ -3761,7 +3761,6 @@ var BasicModel = AbstractModel.extend({
|
|||||||
current_company_id,
|
current_company_id,
|
||||||
id: evalContext.id || false,
|
id: evalContext.id || false,
|
||||||
},
|
},
|
||||||
pyUtils.context(),
|
|
||||||
session.user_context,
|
session.user_context,
|
||||||
element.context,
|
element.context,
|
||||||
evalContext,
|
evalContext,
|
||||||
@@ -3798,7 +3797,6 @@ var BasicModel = AbstractModel.extend({
|
|||||||
current_company_id,
|
current_company_id,
|
||||||
id: evalContext.id || false,
|
id: evalContext.id || false,
|
||||||
},
|
},
|
||||||
pyUtils.context(),
|
|
||||||
session.user_context,
|
session.user_context,
|
||||||
element.context,
|
element.context,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import { deserializeDate, deserializeDateTime } from "@web/core/l10n/dates";
|
|||||||
import { KeepLast } from "@web/core/utils/concurrency";
|
import { KeepLast } from "@web/core/utils/concurrency";
|
||||||
import { escape } from "@web/core/utils/strings";
|
import { escape } from "@web/core/utils/strings";
|
||||||
import { mapDoActionOptionAPI } from "@web/legacy/backend_utils";
|
import { mapDoActionOptionAPI } from "@web/legacy/backend_utils";
|
||||||
|
import {
|
||||||
|
mapActiveFieldsToFieldsInfo,
|
||||||
|
mapViews,
|
||||||
|
mapWowlValueToLegacy,
|
||||||
|
} from "@web/views/legacy_utils";
|
||||||
import { Model } from "@web/views/model";
|
import { Model } from "@web/views/model";
|
||||||
import { evalDomain } from "@web/views/utils";
|
import { evalDomain } from "@web/views/utils";
|
||||||
import {
|
|
||||||
mapWowlValueToLegacy,
|
|
||||||
mapViews,
|
|
||||||
mapActiveFieldsToFieldsInfo,
|
|
||||||
} from "@web/views/legacy_utils";
|
|
||||||
import BasicModel from "web.BasicModel";
|
import BasicModel from "web.BasicModel";
|
||||||
import Context from "web.Context";
|
import Context from "web.Context";
|
||||||
|
|
||||||
@@ -70,6 +70,11 @@ class DataPoint {
|
|||||||
return this.model.__bm__._getLazyEvalContext(datapoint);
|
return this.model.__bm__._getLazyEvalContext(datapoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEvalContext(forDomain) {
|
||||||
|
const datapoint = this.model.__bm__.localData[this.__bm_handle__];
|
||||||
|
return this.model.__bm__._getEvalContext(datapoint, forDomain, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
get fieldNames() {
|
get fieldNames() {
|
||||||
return Object.keys(this.activeFields);
|
return Object.keys(this.activeFields);
|
||||||
}
|
}
|
||||||
@@ -207,7 +212,7 @@ export class Record extends DataPoint {
|
|||||||
|
|
||||||
const isSet = activeField && activeField.field && activeField.field.isSet;
|
const isSet = activeField && activeField.field && activeField.field.isSet;
|
||||||
|
|
||||||
if (this.isRequired(fieldName) && isSet && !isSet(this.data[fieldName])) {
|
if (this._isRequired(fieldName) && isSet && !isSet(this.data[fieldName])) {
|
||||||
this.setInvalidField(fieldName);
|
this.setInvalidField(fieldName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -230,7 +235,7 @@ export class Record extends DataPoint {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!isSet && this.isRequired(fieldName) && !this.data[fieldName]) {
|
if (!isSet && this._isRequired(fieldName) && !this.data[fieldName]) {
|
||||||
this._setInvalidField(fieldName);
|
this._setInvalidField(fieldName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,7 +283,7 @@ export class Record extends DataPoint {
|
|||||||
* @param {string} fieldName
|
* @param {string} fieldName
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isReadonly(fieldName) {
|
_isReadonly(fieldName) {
|
||||||
const { readonly } = this.activeFields[fieldName].modifiers || {};
|
const { readonly } = this.activeFields[fieldName].modifiers || {};
|
||||||
return evalDomain(readonly, this.evalContext);
|
return evalDomain(readonly, this.evalContext);
|
||||||
}
|
}
|
||||||
@@ -288,7 +293,7 @@ export class Record extends DataPoint {
|
|||||||
* @param {string} fieldName
|
* @param {string} fieldName
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isRequired(fieldName) {
|
_isRequired(fieldName) {
|
||||||
const { required } = this.activeFields[fieldName].modifiers || {};
|
const { required } = this.activeFields[fieldName].modifiers || {};
|
||||||
return evalDomain(required, this.evalContext);
|
return evalDomain(required, this.evalContext);
|
||||||
}
|
}
|
||||||
@@ -446,21 +451,18 @@ export class Record extends DataPoint {
|
|||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFieldContext(fieldName) {
|
getFieldDomain(fieldName) {
|
||||||
|
const { domain } = this.fields[fieldName];
|
||||||
|
return domain ? new Domain(domain).toList(this.getEvalContext(true)) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFieldContext(fieldName) {
|
||||||
return this.model.__bm__.localData[this.__bm_handle__].getContext({
|
return this.model.__bm__.localData[this.__bm_handle__].getContext({
|
||||||
fieldName,
|
fieldName,
|
||||||
viewType: this.__viewType,
|
viewType: this.__viewType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getFieldDomain(fieldName) {
|
|
||||||
const domain = this.model.__bm__.localData[this.__bm_handle__].getDomain({
|
|
||||||
fieldName,
|
|
||||||
viewType: this.__viewType,
|
|
||||||
});
|
|
||||||
return new Domain(JSON.parse(JSON.stringify(domain))); // legacy pyjs inserts weird structures
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(changes) {
|
async update(changes) {
|
||||||
if (this.batchingUpdateProm) {
|
if (this.batchingUpdateProm) {
|
||||||
// Assign changes in the current batch
|
// Assign changes in the current batch
|
||||||
|
|||||||
@@ -82,10 +82,8 @@ export const badgeSelectionField = {
|
|||||||
|
|
||||||
registry.category("fields").add("selection_badge", badgeSelectionField);
|
registry.category("fields").add("selection_badge", badgeSelectionField);
|
||||||
|
|
||||||
export function preloadSelection(orm, record, fieldName) {
|
export function preloadSelection(orm, record, fieldName, { domain }) {
|
||||||
const field = record.fields[fieldName];
|
const field = record.fields[fieldName];
|
||||||
const context = record.evalContext;
|
|
||||||
const domain = record.getFieldDomain(fieldName).toList(context);
|
|
||||||
return orm.call(field.relation, "name_search", ["", domain]);
|
return orm.call(field.relation, "name_search", ["", domain]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,6 @@ export class BooleanField extends Component {
|
|||||||
...standardFieldProps,
|
...standardFieldProps,
|
||||||
};
|
};
|
||||||
|
|
||||||
get isReadonly() {
|
|
||||||
return (
|
|
||||||
!this.props.record.isInEdition ||
|
|
||||||
this.props.record.isReadonly(this.props.name) ||
|
|
||||||
this.props.readonly
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} newValue
|
* @param {boolean} newValue
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<templates xml:space="preserve">
|
<templates xml:space="preserve">
|
||||||
|
|
||||||
<t t-name="web.BooleanField" owl="1">
|
<t t-name="web.BooleanField" owl="1">
|
||||||
<CheckBox id="props.id" value="props.record.data[props.name] or false" className="'d-inline-block'" disabled="isReadonly" onChange.bind="onChange" />
|
<CheckBox id="props.id" value="props.record.data[props.name] or false" className="'d-inline-block'" disabled="props.readonly" onChange.bind="onChange" />
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
</templates>
|
</templates>
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
import { registry } from "@web/core/registry";
|
|
||||||
import { _lt } from "@web/core/l10n/translation";
|
import { _lt } from "@web/core/l10n/translation";
|
||||||
|
import { registry } from "@web/core/registry";
|
||||||
import { booleanField, BooleanField } from "../boolean/boolean_field";
|
import { booleanField, BooleanField } from "../boolean/boolean_field";
|
||||||
|
|
||||||
export class BooleanToggleField extends BooleanField {
|
export class BooleanToggleField extends BooleanField {
|
||||||
static template = "web.BooleanToggleField";
|
static template = "web.BooleanToggleField";
|
||||||
|
|
||||||
get isReadonly() {
|
|
||||||
return this.props.record.isReadonly(this.props.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onChange(newValue) {
|
async onChange(newValue) {
|
||||||
await this.props.record.update({ [this.props.name]: newValue });
|
await this.props.record.update({ [this.props.name]: newValue });
|
||||||
return this.props.record.save();
|
return this.props.record.save();
|
||||||
@@ -21,6 +17,11 @@ export const booleanToggleField = {
|
|||||||
...booleanField,
|
...booleanField,
|
||||||
component: BooleanToggleField,
|
component: BooleanToggleField,
|
||||||
displayName: _lt("Toggle"),
|
displayName: _lt("Toggle"),
|
||||||
|
extractProps(fieldInfo, dynamicInfo) {
|
||||||
|
return {
|
||||||
|
readonly: dynamicInfo.readonly,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("boolean_toggle", booleanToggleField);
|
registry.category("fields").add("boolean_toggle", booleanToggleField);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export class ListBooleanToggleField extends BooleanToggleField {
|
|||||||
static template = "web.ListBooleanToggleField";
|
static template = "web.ListBooleanToggleField";
|
||||||
|
|
||||||
async onClick() {
|
async onClick() {
|
||||||
if (!this.props.readonly) {
|
if (!this.props.readonly && this.props.record.isInEdition) {
|
||||||
await this.props.record.update({
|
await this.props.record.update({
|
||||||
[this.props.name]: !this.props.record.data[this.props.name],
|
[this.props.name]: !this.props.record.data[this.props.name],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { Component, onWillUpdateProps, useState } from "@odoo/owl";
|
||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
import { standardFieldProps } from "../standard_field_props";
|
import { standardFieldProps } from "../standard_field_props";
|
||||||
|
|
||||||
import { Component, useState, onWillUpdateProps } from "@odoo/owl";
|
|
||||||
|
|
||||||
export class ColorField extends Component {
|
export class ColorField extends Component {
|
||||||
static template = "web.ColorField";
|
static template = "web.ColorField";
|
||||||
static props = {
|
static props = {
|
||||||
@@ -20,15 +19,16 @@ export class ColorField extends Component {
|
|||||||
this.state.color = nextProps.record.data[nextProps.name] || "";
|
this.state.color = nextProps.record.data[nextProps.name] || "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get isReadonly() {
|
|
||||||
return this.props.record.isReadonly(this.props.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const colorField = {
|
export const colorField = {
|
||||||
component: ColorField,
|
component: ColorField,
|
||||||
supportedTypes: ["char"],
|
supportedTypes: ["char"],
|
||||||
|
extractProps(fieldInfo, dynamicInfo) {
|
||||||
|
return {
|
||||||
|
readonly: dynamicInfo.readonly,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("color", colorField);
|
registry.category("fields").add("color", colorField);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<templates xml:space="preserve">
|
<templates xml:space="preserve">
|
||||||
|
|
||||||
<t t-name="web.ColorField" owl="1">
|
<t t-name="web.ColorField" owl="1">
|
||||||
<div class="o_field_color d-flex" t-att-class="{ 'o_field_cursor_disabled': isReadonly }" t-attf-style="background: #{state.color or 'url(/web/static/img/transparent.png)'}">
|
<div class="o_field_color d-flex" t-att-class="{ 'o_field_cursor_disabled': props.readonly }" t-attf-style="background: #{state.color or 'url(/web/static/img/transparent.png)'}">
|
||||||
<input t-on-click.stop="" class="w-100 h-100 opacity-0" type="color" t-att-value="state.color" t-att-disabled="isReadonly" t-on-input="(ev) => this.state.color = ev.target.value" t-on-change="(ev) => this.props.record.update({ [this.props.name]: ev.target.value })" />
|
<input t-on-click.stop="" class="w-100 h-100 opacity-0" type="color" t-att-value="state.color" t-att-disabled="props.readonly" t-on-input="(ev) => this.state.color = ev.target.value" t-on-change="(ev) => this.props.record.update({ [this.props.name]: ev.target.value })" />
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export class DomainField extends Component {
|
|||||||
};
|
};
|
||||||
static props = {
|
static props = {
|
||||||
...standardFieldProps,
|
...standardFieldProps,
|
||||||
|
context: { type: Object, optional: true },
|
||||||
editInDialog: { type: Boolean, optional: true },
|
editInDialog: { type: Boolean, optional: true },
|
||||||
resModel: { type: String, optional: true },
|
resModel: { type: String, optional: true },
|
||||||
};
|
};
|
||||||
@@ -61,7 +62,7 @@ export class DomainField extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getContext(p) {
|
getContext(p) {
|
||||||
return p.record.getFieldContext(p.name);
|
return p.context;
|
||||||
}
|
}
|
||||||
getResModel(p) {
|
getResModel(p) {
|
||||||
let resModel = p.resModel;
|
let resModel = p.resModel;
|
||||||
@@ -149,10 +150,13 @@ export const domainField = {
|
|||||||
displayName: _lt("Domain"),
|
displayName: _lt("Domain"),
|
||||||
supportedTypes: ["char"],
|
supportedTypes: ["char"],
|
||||||
isEmpty: () => false,
|
isEmpty: () => false,
|
||||||
extractProps: ({ options }) => ({
|
extractProps({ options }, dynamicInfo) {
|
||||||
editInDialog: options.in_dialog,
|
return {
|
||||||
resModel: options.model,
|
editInDialog: options.in_dialog,
|
||||||
}),
|
resModel: options.model,
|
||||||
|
context: dynamicInfo.context,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("domain", domainField);
|
registry.category("fields").add("domain", domainField);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { makeContext } from "@web/core/context";
|
||||||
|
import { Domain } from "@web/core/domain";
|
||||||
import { evaluateExpr } from "@web/core/py_js/py";
|
import { evaluateExpr } from "@web/core/py_js/py";
|
||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
import {
|
import {
|
||||||
@@ -122,13 +124,53 @@ export class Field extends Component {
|
|||||||
const modifiers = fieldInfo.modifiers || {};
|
const modifiers = fieldInfo.modifiers || {};
|
||||||
readonlyFromModifiers = evalDomain(modifiers.readonly, evalContext);
|
readonlyFromModifiers = evalDomain(modifiers.readonly, evalContext);
|
||||||
|
|
||||||
if (this.props.attrs) {
|
if (this.field.extractProps) {
|
||||||
fieldInfo = {
|
if (this.props.attrs) {
|
||||||
...fieldInfo,
|
fieldInfo = {
|
||||||
attrs: { ...fieldInfo.attrs, ...this.props.attrs },
|
...fieldInfo,
|
||||||
|
attrs: { ...fieldInfo.attrs, ...this.props.attrs },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const dynamicInfo = {
|
||||||
|
get context() {
|
||||||
|
const evalContext = record.getEvalContext
|
||||||
|
? record.getEvalContext(false)
|
||||||
|
: record.evalContext;
|
||||||
|
|
||||||
|
const context = {};
|
||||||
|
for (const key in record.context) {
|
||||||
|
if (!key.startsWith("default_")) {
|
||||||
|
context[key] = record.context[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
...makeContext([fieldInfo.context], evalContext),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
get domain() {
|
||||||
|
const evalContext = record.getEvalContext
|
||||||
|
? record.getEvalContext(true)
|
||||||
|
: record.evalContext;
|
||||||
|
|
||||||
|
return fieldInfo.domain
|
||||||
|
? new Domain(evaluateExpr(fieldInfo.domain, evalContext)).toList()
|
||||||
|
: undefined;
|
||||||
|
},
|
||||||
|
readonly: readonlyFromModifiers,
|
||||||
|
get required() {
|
||||||
|
return (
|
||||||
|
fieldInfo.modifiers &&
|
||||||
|
fieldInfo.modifiers.required &&
|
||||||
|
evalDomain(fieldInfo.modifiers.required, record.evalContext)
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
propsFromNode = this.field.extractProps(fieldInfo, dynamicInfo);
|
||||||
}
|
}
|
||||||
propsFromNode = this.field.extractProps ? this.field.extractProps(fieldInfo) : {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = { ...this.props };
|
const props = { ...this.props };
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ export class FloatFactorField extends FloatField {
|
|||||||
export const floatFactorField = {
|
export const floatFactorField = {
|
||||||
...floatField,
|
...floatField,
|
||||||
component: FloatFactorField,
|
component: FloatFactorField,
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ options }) {
|
||||||
...floatField.extractProps(fieldInfo),
|
const props = floatField.extractProps(...arguments);
|
||||||
factor: fieldInfo.options.factor,
|
props.factor = options.factor;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("float_factor", floatFactorField);
|
registry.category("fields").add("float_factor", floatFactorField);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class FontSelectionField extends Component {
|
|||||||
static props = {
|
static props = {
|
||||||
...standardFieldProps,
|
...standardFieldProps,
|
||||||
placeholder: { type: String, optional: true },
|
placeholder: { type: String, optional: true },
|
||||||
|
required: { type: Boolean, optional: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
@@ -19,9 +20,6 @@ export class FontSelectionField extends Component {
|
|||||||
(option) => option[0] !== false && option[1] !== ""
|
(option) => option[0] !== false && option[1] !== ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
get isRequired() {
|
|
||||||
return this.props.record.isRequired(this.props.name);
|
|
||||||
}
|
|
||||||
get string() {
|
get string() {
|
||||||
return formatSelection(this.props.record.data[this.props.name], {
|
return formatSelection(this.props.record.data[this.props.name], {
|
||||||
selection: this.options,
|
selection: this.options,
|
||||||
@@ -45,9 +43,12 @@ export const fontSelectionField = {
|
|||||||
component: FontSelectionField,
|
component: FontSelectionField,
|
||||||
displayName: _lt("Font Selection"),
|
displayName: _lt("Font Selection"),
|
||||||
supportedTypes: ["selection"],
|
supportedTypes: ["selection"],
|
||||||
extractProps: ({ attrs }) => ({
|
extractProps({ attrs }, dynamicInfo) {
|
||||||
placeholder: attrs.placeholder,
|
return {
|
||||||
}),
|
placeholder: attrs.placeholder,
|
||||||
|
required: dynamicInfo.required,
|
||||||
|
};
|
||||||
|
},
|
||||||
legacySpecialData: "_fetchSpecialRelation",
|
legacySpecialData: "_fetchSpecialRelation",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
t-att-selected="false === value"
|
t-att-selected="false === value"
|
||||||
t-att-value="stringify(false)"
|
t-att-value="stringify(false)"
|
||||||
t-esc="this.props.placeholder || ''"
|
t-esc="this.props.placeholder || ''"
|
||||||
t-attf-style="{{ isRequired ? 'display:none' : '' }}"
|
t-attf-style="{{ props.required ? 'display:none' : '' }}"
|
||||||
/>
|
/>
|
||||||
<t t-foreach="options" t-as="option" t-key="option[0]">
|
<t t-foreach="options" t-as="option" t-key="option[0]">
|
||||||
<option
|
<option
|
||||||
|
|||||||
+1
-3
@@ -49,10 +49,8 @@ export const many2ManyCheckboxesField = {
|
|||||||
|
|
||||||
registry.category("fields").add("many2many_checkboxes", many2ManyCheckboxesField);
|
registry.category("fields").add("many2many_checkboxes", many2ManyCheckboxesField);
|
||||||
|
|
||||||
export function preloadMany2ManyCheckboxes(orm, record, fieldName) {
|
export function preloadMany2ManyCheckboxes(orm, record, fieldName, { domain }) {
|
||||||
const field = record.fields[fieldName];
|
const field = record.fields[fieldName];
|
||||||
const context = record.evalContext;
|
|
||||||
const domain = record.getFieldDomain(fieldName).toList(context);
|
|
||||||
return orm.call(field.relation, "name_search", ["", domain]);
|
return orm.call(field.relation, "name_search", ["", domain]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ export class Many2ManyTagsField extends Component {
|
|||||||
canCreateEdit: { type: Boolean, optional: true },
|
canCreateEdit: { type: Boolean, optional: true },
|
||||||
colorField: { type: String, optional: true },
|
colorField: { type: String, optional: true },
|
||||||
createDomain: { type: [Array, Boolean], optional: true },
|
createDomain: { type: [Array, Boolean], optional: true },
|
||||||
|
domain: { type: Array, optional: true },
|
||||||
|
context: { type: Object, optional: true },
|
||||||
placeholder: { type: String, optional: true },
|
placeholder: { type: String, optional: true },
|
||||||
nameCreateField: { type: String, optional: true },
|
nameCreateField: { type: String, optional: true },
|
||||||
string: { type: String, optional: true },
|
string: { type: String, optional: true },
|
||||||
@@ -48,6 +50,7 @@ export class Many2ManyTagsField extends Component {
|
|||||||
canQuickCreate: true,
|
canQuickCreate: true,
|
||||||
canCreateEdit: true,
|
canCreateEdit: true,
|
||||||
nameCreateField: "name",
|
nameCreateField: "name",
|
||||||
|
context: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
static RECORD_COLORS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
static RECORD_COLORS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||||
@@ -96,7 +99,7 @@ export class Many2ManyTagsField extends Component {
|
|||||||
if (this.props.canQuickCreate) {
|
if (this.props.canQuickCreate) {
|
||||||
this.quickCreate = async (name) => {
|
this.quickCreate = async (name) => {
|
||||||
const created = await this.orm.call(this.relation, "name_create", [name], {
|
const created = await this.orm.call(this.relation, "name_create", [name], {
|
||||||
context: this.context,
|
context: this.props.context,
|
||||||
});
|
});
|
||||||
return saveRecord([created[0]]);
|
return saveRecord([created[0]]);
|
||||||
};
|
};
|
||||||
@@ -106,12 +109,6 @@ export class Many2ManyTagsField extends Component {
|
|||||||
get relation() {
|
get relation() {
|
||||||
return this.props.record.fields[this.props.name].relation;
|
return this.props.record.fields[this.props.name].relation;
|
||||||
}
|
}
|
||||||
get domain() {
|
|
||||||
return this.props.record.getFieldDomain(this.props.name);
|
|
||||||
}
|
|
||||||
get context() {
|
|
||||||
return this.props.record.getFieldContext(this.props.name);
|
|
||||||
}
|
|
||||||
get evalContext() {
|
get evalContext() {
|
||||||
return this.props.record.evalContext;
|
return this.props.record.evalContext;
|
||||||
}
|
}
|
||||||
@@ -152,9 +149,9 @@ export class Many2ManyTagsField extends Component {
|
|||||||
|
|
||||||
getDomain() {
|
getDomain() {
|
||||||
return Domain.and([
|
return Domain.and([
|
||||||
this.domain,
|
this.props.domain || this.props.record.getFieldDomain(this.props.name),
|
||||||
Domain.not([["id", "in", this.props.record.data[this.props.name].currentIds]]),
|
Domain.not([["id", "in", this.props.record.data[this.props.name].currentIds]]),
|
||||||
]).toList(this.context);
|
]).toList(this.props.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
focusTag(index) {
|
focusTag(index) {
|
||||||
@@ -267,7 +264,7 @@ export const many2ManyTagsField = {
|
|||||||
}
|
}
|
||||||
return relatedFields;
|
return relatedFields;
|
||||||
},
|
},
|
||||||
extractProps: ({ attrs, options, string }) => {
|
extractProps({ attrs, options, string }, dynamicInfo) {
|
||||||
const noCreate = Boolean(options.no_create);
|
const noCreate = Boolean(options.no_create);
|
||||||
const canCreate = attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !noCreate;
|
const canCreate = attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !noCreate;
|
||||||
const noQuickCreate = Boolean(options.no_quick_create);
|
const noQuickCreate = Boolean(options.no_quick_create);
|
||||||
@@ -279,6 +276,8 @@ export const many2ManyTagsField = {
|
|||||||
canQuickCreate: canCreate && !noQuickCreate,
|
canQuickCreate: canCreate && !noQuickCreate,
|
||||||
canCreateEdit: canCreate && !noCreateEdit,
|
canCreateEdit: canCreate && !noCreateEdit,
|
||||||
createDomain: options.create,
|
createDomain: options.create,
|
||||||
|
context: dynamicInfo.context,
|
||||||
|
domain: dynamicInfo.domain,
|
||||||
placeholder: attrs.placeholder,
|
placeholder: attrs.placeholder,
|
||||||
string,
|
string,
|
||||||
};
|
};
|
||||||
@@ -374,10 +373,11 @@ export class Many2ManyTagsFieldColorEditable extends Many2ManyTagsField {
|
|||||||
export const many2ManyTagsFieldColorEditable = {
|
export const many2ManyTagsFieldColorEditable = {
|
||||||
...many2ManyTagsField,
|
...many2ManyTagsField,
|
||||||
component: Many2ManyTagsFieldColorEditable,
|
component: Many2ManyTagsFieldColorEditable,
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ options }) {
|
||||||
...many2ManyTagsField.extractProps(fieldInfo),
|
const props = many2ManyTagsField.extractProps(...arguments);
|
||||||
canEditColor: !fieldInfo.options.no_edit_color && !!fieldInfo.options.color_field,
|
props.canEditColor = !options.no_edit_color && !!options.color_field;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("form.many2many_tags", many2ManyTagsFieldColorEditable);
|
registry.category("fields").add("form.many2many_tags", many2ManyTagsFieldColorEditable);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
activeActions="activeActions"
|
activeActions="activeActions"
|
||||||
update="update"
|
update="update"
|
||||||
quickCreate="activeActions.create ? quickCreate : null"
|
quickCreate="activeActions.create ? quickCreate : null"
|
||||||
context="context"
|
context="props.context"
|
||||||
getDomain.bind="getDomain"
|
getDomain.bind="getDomain"
|
||||||
isToMany="true"
|
isToMany="true"
|
||||||
nameCreateField="props.nameCreateField"
|
nameCreateField="props.nameCreateField"
|
||||||
|
|||||||
+13
-10
@@ -32,10 +32,12 @@ export class Many2ManyTagsAvatarField extends Many2ManyTagsField {
|
|||||||
export const many2ManyTagsAvatarField = {
|
export const many2ManyTagsAvatarField = {
|
||||||
...many2ManyTagsField,
|
...many2ManyTagsField,
|
||||||
component: Many2ManyTagsAvatarField,
|
component: Many2ManyTagsAvatarField,
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ viewType }, dynamicInfo) {
|
||||||
...many2ManyTagsField.extractProps(fieldInfo),
|
const props = many2ManyTagsField.extractProps(...arguments);
|
||||||
withCommand: fieldInfo.viewType === "form",
|
props.withCommand = viewType === "form";
|
||||||
}),
|
props.domain = dynamicInfo.domain;
|
||||||
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("many2many_tags_avatar", many2ManyTagsAvatarField);
|
registry.category("fields").add("many2many_tags_avatar", many2ManyTagsAvatarField);
|
||||||
@@ -139,19 +141,15 @@ export class KanbanMany2ManyTagsAvatarFieldTagsList extends TagsList {
|
|||||||
export class KanbanMany2ManyTagsAvatarField extends Many2ManyTagsAvatarField {
|
export class KanbanMany2ManyTagsAvatarField extends Many2ManyTagsAvatarField {
|
||||||
static template = "web.KanbanMany2ManyTagsAvatarField";
|
static template = "web.KanbanMany2ManyTagsAvatarField";
|
||||||
static components = {
|
static components = {
|
||||||
...Many2ManyTagsAvatarField.component,
|
...Many2ManyTagsAvatarField.components,
|
||||||
TagsList: KanbanMany2ManyTagsAvatarFieldTagsList,
|
TagsList: KanbanMany2ManyTagsAvatarFieldTagsList,
|
||||||
};
|
};
|
||||||
itemsVisible = 2;
|
itemsVisible = 2;
|
||||||
|
|
||||||
get isFieldReadonly() {
|
|
||||||
return this.props.record.isReadonly(this.props.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
get popoverProps() {
|
get popoverProps() {
|
||||||
return {
|
return {
|
||||||
...this.props,
|
...this.props,
|
||||||
readonly: this.isFieldReadonly,
|
readonly: this.props.readonly,
|
||||||
deleteTag: this.deleteTag.bind(this),
|
deleteTag: this.deleteTag.bind(this),
|
||||||
updateTag: this.updateTag.bind(this),
|
updateTag: this.updateTag.bind(this),
|
||||||
};
|
};
|
||||||
@@ -178,6 +176,11 @@ export class KanbanMany2ManyTagsAvatarField extends Many2ManyTagsAvatarField {
|
|||||||
export const kanbanMany2ManyTagsAvatarField = {
|
export const kanbanMany2ManyTagsAvatarField = {
|
||||||
...many2ManyTagsAvatarField,
|
...many2ManyTagsAvatarField,
|
||||||
component: KanbanMany2ManyTagsAvatarField,
|
component: KanbanMany2ManyTagsAvatarField,
|
||||||
|
extractProps(fieldInfo, dynamicInfo) {
|
||||||
|
const props = many2ManyTagsAvatarField.extractProps(...arguments);
|
||||||
|
props.readonly = dynamicInfo.readonly;
|
||||||
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("kanban.many2many_tags_avatar", kanbanMany2ManyTagsAvatarField);
|
registry.category("fields").add("kanban.many2many_tags_avatar", kanbanMany2ManyTagsAvatarField);
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@
|
|||||||
owl="1">
|
owl="1">
|
||||||
<xpath expr="//TagsList" position="attributes">
|
<xpath expr="//TagsList" position="attributes">
|
||||||
<attribute name="popoverProps">popoverProps</attribute>
|
<attribute name="popoverProps">popoverProps</attribute>
|
||||||
<attribute name="readonly">isFieldReadonly</attribute>
|
<attribute name="readonly">props.readonly</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
</t>
|
</t>
|
||||||
<t t-name="web.Many2ManyTagsAvatarFieldPopover" t-inherit="web.Many2ManyTagsAvatarField" t-inherit-mode="primary" owl="1">
|
<t t-name="web.Many2ManyTagsAvatarFieldPopover" t-inherit="web.Many2ManyTagsAvatarField" t-inherit-mode="primary" owl="1">
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ export class Many2OneField extends Component {
|
|||||||
canWrite: { type: Boolean, optional: true },
|
canWrite: { type: Boolean, optional: true },
|
||||||
canQuickCreate: { type: Boolean, optional: true },
|
canQuickCreate: { type: Boolean, optional: true },
|
||||||
canCreateEdit: { type: Boolean, optional: true },
|
canCreateEdit: { type: Boolean, optional: true },
|
||||||
|
context: { type: Object, optional: true },
|
||||||
|
domain: { type: Array, optional: true },
|
||||||
nameCreateField: { type: String, optional: true },
|
nameCreateField: { type: String, optional: true },
|
||||||
searchLimit: { type: Number, optional: true },
|
searchLimit: { type: Number, optional: true },
|
||||||
relation: { type: String, optional: true },
|
relation: { type: String, optional: true },
|
||||||
@@ -70,6 +72,7 @@ export class Many2OneField extends Component {
|
|||||||
searchLimit: 7,
|
searchLimit: 7,
|
||||||
string: "",
|
string: "",
|
||||||
canScanBarcode: false,
|
canScanBarcode: false,
|
||||||
|
context: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
static SEARCH_MORE_LIMIT = 320;
|
static SEARCH_MORE_LIMIT = 320;
|
||||||
@@ -98,7 +101,7 @@ export class Many2OneField extends Component {
|
|||||||
onRecordSaved: async (record) => {
|
onRecordSaved: async (record) => {
|
||||||
const resId = this.value[0];
|
const resId = this.value[0];
|
||||||
const fields = ["display_name"];
|
const fields = ["display_name"];
|
||||||
const context = this.props.record.getFieldContext(this.props.name);
|
const { context } = this.props;
|
||||||
const records = await this.orm.read(this.relation, [resId], fields, { context });
|
const records = await this.orm.read(this.relation, [resId], fields, { context });
|
||||||
await this.updateRecord(m2oTupleFromData(records[0]));
|
await this.updateRecord(m2oTupleFromData(records[0]));
|
||||||
if (this.props.record.model.root.id !== this.props.record.id) {
|
if (this.props.record.model.root.id !== this.props.record.id) {
|
||||||
@@ -151,15 +154,12 @@ export class Many2OneField extends Component {
|
|||||||
get string() {
|
get string() {
|
||||||
return this.props.string || this.props.record.fields[this.props.name].string || "";
|
return this.props.string || this.props.record.fields[this.props.name].string || "";
|
||||||
}
|
}
|
||||||
get context() {
|
|
||||||
return this.props.record.getFieldContext(this.props.name);
|
|
||||||
}
|
|
||||||
get domain() {
|
|
||||||
return this.props.record.getFieldDomain(this.props.name);
|
|
||||||
}
|
|
||||||
get hasExternalButton() {
|
get hasExternalButton() {
|
||||||
return this.props.canOpen && !!this.value && !this.state.isFloating;
|
return this.props.canOpen && !!this.value && !this.state.isFloating;
|
||||||
}
|
}
|
||||||
|
get context() {
|
||||||
|
return this.props.context;
|
||||||
|
}
|
||||||
get displayName() {
|
get displayName() {
|
||||||
return this.value ? this.value[1].split("\n")[0] : "";
|
return this.value ? this.value[1].split("\n")[0] : "";
|
||||||
}
|
}
|
||||||
@@ -203,7 +203,7 @@ export class Many2OneField extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
getDomain() {
|
getDomain() {
|
||||||
return this.domain.toList(this.context);
|
return this.props.domain || this.props.record.getFieldDomain(this.props.name);
|
||||||
}
|
}
|
||||||
async openAction() {
|
async openAction() {
|
||||||
const action = await this.orm.call(this.relation, "get_formview_action", [[this.resId]], {
|
const action = await this.orm.call(this.relation, "get_formview_action", [[this.resId]], {
|
||||||
@@ -299,7 +299,7 @@ export const many2OneField = {
|
|||||||
component: Many2OneField,
|
component: Many2OneField,
|
||||||
displayName: _lt("Many2one"),
|
displayName: _lt("Many2one"),
|
||||||
supportedTypes: ["many2one"],
|
supportedTypes: ["many2one"],
|
||||||
extractProps: ({ attrs, options, string }) => {
|
extractProps({ attrs, options, string }, dynamicInfo) {
|
||||||
const canCreate =
|
const canCreate =
|
||||||
attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !options.no_create;
|
attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !options.no_create;
|
||||||
return {
|
return {
|
||||||
@@ -309,6 +309,8 @@ export const many2OneField = {
|
|||||||
canWrite: attrs.can_write && Boolean(JSON.parse(attrs.can_write)),
|
canWrite: attrs.can_write && Boolean(JSON.parse(attrs.can_write)),
|
||||||
canQuickCreate: canCreate && !options.no_quick_create,
|
canQuickCreate: canCreate && !options.no_quick_create,
|
||||||
canCreateEdit: canCreate && !options.no_create_edit,
|
canCreateEdit: canCreate && !options.no_create_edit,
|
||||||
|
context: dynamicInfo.context,
|
||||||
|
domain: dynamicInfo.domain,
|
||||||
nameCreateField: options.create_name_field,
|
nameCreateField: options.create_name_field,
|
||||||
canScanBarcode: !!options.can_scan_barcode,
|
canScanBarcode: !!options.can_scan_barcode,
|
||||||
string,
|
string,
|
||||||
|
|||||||
@@ -70,17 +70,8 @@ export class KanbanMany2OneAvatarField extends Many2OneAvatarField {
|
|||||||
readonly: false,
|
readonly: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
get isFieldReadonly() {
|
|
||||||
return this.props.record.isReadonly(this.props.name);
|
|
||||||
}
|
|
||||||
get many2OneProps() {
|
|
||||||
return {
|
|
||||||
...super.many2OneProps,
|
|
||||||
readonly: this.isFieldReadonly,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
openPopover(ev) {
|
openPopover(ev) {
|
||||||
if (this.isFieldReadonly) {
|
if (this.props.readonly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.closePopoverFn) {
|
if (this.closePopoverFn) {
|
||||||
@@ -102,10 +93,10 @@ export class KanbanMany2OneAvatarField extends Many2OneAvatarField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get canDisplayDelete() {
|
get canDisplayDelete() {
|
||||||
return !this.isFieldReadonly && this.props.record.data[this.props.name] && !isMobileOS();
|
return !this.props.readonly && this.props.record.data[this.props.name] && !isMobileOS();
|
||||||
}
|
}
|
||||||
async remove(ev) {
|
async remove(ev) {
|
||||||
if (this.isFieldReadonly) {
|
if (this.props.readonly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.props.record.update({ [this.props.name]: false });
|
await this.props.record.update({ [this.props.name]: false });
|
||||||
@@ -117,6 +108,11 @@ export const kanbanMany2OneAvatarField = {
|
|||||||
...many2OneField,
|
...many2OneField,
|
||||||
component: KanbanMany2OneAvatarField,
|
component: KanbanMany2OneAvatarField,
|
||||||
additionalClasses: ["o_field_many2one_avatar_kanban"],
|
additionalClasses: ["o_field_many2one_avatar_kanban"],
|
||||||
|
extractProps(fieldInfo, dynamicInfo) {
|
||||||
|
const props = many2OneField.extractProps(...arguments);
|
||||||
|
props.readonly = dynamicInfo.readonly;
|
||||||
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
registry.category("fields").add("many2one_avatar", many2OneAvatarField);
|
registry.category("fields").add("many2one_avatar", many2OneAvatarField);
|
||||||
registry.category("fields").add("kanban.many2one_avatar", kanbanMany2OneAvatarField);
|
registry.category("fields").add("kanban.many2one_avatar", kanbanMany2OneAvatarField);
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ export const many2OneBarcodeField = {
|
|||||||
...many2OneField,
|
...many2OneField,
|
||||||
component: Many2OneBarcodeField,
|
component: Many2OneBarcodeField,
|
||||||
displayName: _lt("Many2OneBarcode"),
|
displayName: _lt("Many2OneBarcode"),
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps() {
|
||||||
...many2OneField.extractProps(fieldInfo),
|
const props = many2OneField.extractProps(...arguments);
|
||||||
canScanBarcode: true,
|
props.canScanBarcode = true;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("many2one_barcode", many2OneBarcodeField);
|
registry.category("fields").add("many2one_barcode", many2OneBarcodeField);
|
||||||
|
|||||||
@@ -62,9 +62,6 @@ export class PriorityField extends Component {
|
|||||||
? this.state.index
|
? this.state.index
|
||||||
: this.options.findIndex((o) => o[0] === this.props.record.data[this.props.name]);
|
: this.options.findIndex((o) => o[0] === this.props.record.data[this.props.name]);
|
||||||
}
|
}
|
||||||
get isReadonly() {
|
|
||||||
return this.props.record.isReadonly(this.props.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTooltip(value) {
|
getTooltip(value) {
|
||||||
return this.tooltipLabel && this.tooltipLabel !== value
|
return this.tooltipLabel && this.tooltipLabel !== value
|
||||||
@@ -93,9 +90,12 @@ export const priorityField = {
|
|||||||
component: PriorityField,
|
component: PriorityField,
|
||||||
displayName: _lt("Priority"),
|
displayName: _lt("Priority"),
|
||||||
supportedTypes: ["selection"],
|
supportedTypes: ["selection"],
|
||||||
extractProps: ({ viewType }) => ({
|
extractProps({ viewType }, dynamicInfo) {
|
||||||
withCommand: viewType === "form",
|
return {
|
||||||
}),
|
withCommand: viewType === "form",
|
||||||
|
readonly: dynamicInfo.readonly,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("priority", priorityField);
|
registry.category("fields").add("priority", priorityField);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="o_priority" role="radiogroup" name="priority" aria-label="Priority">
|
<div class="o_priority" role="radiogroup" name="priority" aria-label="Priority">
|
||||||
<t t-foreach="options" t-as="value" t-key="value">
|
<t t-foreach="options" t-as="value" t-key="value">
|
||||||
<t t-if="!value_first">
|
<t t-if="!value_first">
|
||||||
<t t-if="isReadonly">
|
<t t-if="props.readonly">
|
||||||
<span
|
<span
|
||||||
class="o_priority_star fa"
|
class="o_priority_star fa"
|
||||||
role="radio"
|
role="radio"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
id="generateUniqueDomID()"
|
id="generateUniqueDomID()"
|
||||||
canChangeDefinition="state.canChangeDefinition"
|
canChangeDefinition="state.canChangeDefinition"
|
||||||
comodel="propertyConfiguration.comodel || ''"
|
comodel="propertyConfiguration.comodel || ''"
|
||||||
context="context"
|
context="props.context"
|
||||||
domain="propertyConfiguration.domain || '[]'"
|
domain="propertyConfiguration.domain || '[]'"
|
||||||
readonly="props.readonly"
|
readonly="props.readonly"
|
||||||
selection="propertyConfiguration.selection"
|
selection="propertyConfiguration.selection"
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export class PropertiesField extends Component {
|
|||||||
};
|
};
|
||||||
static props = {
|
static props = {
|
||||||
...standardFieldProps,
|
...standardFieldProps,
|
||||||
|
context: { type: Object, optional: true },
|
||||||
columns: { type: Number, optional: true },
|
columns: { type: Number, optional: true },
|
||||||
hideKanbanOption: { type: Boolean, optional: true },
|
hideKanbanOption: { type: Boolean, optional: true },
|
||||||
};
|
};
|
||||||
@@ -73,15 +74,6 @@ export class PropertiesField extends Component {
|
|||||||
* Public methods / Getters
|
* Public methods / Getters
|
||||||
* -------------------------------------------------------- */
|
* -------------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the current context
|
|
||||||
*
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
get context() {
|
|
||||||
return this.props.record.getFieldContext(this.props.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current properties value.
|
* Return the current properties value.
|
||||||
*
|
*
|
||||||
@@ -476,7 +468,7 @@ export class PropertiesField extends Component {
|
|||||||
propertyDefinition: this.propertiesList.find(
|
propertyDefinition: this.propertiesList.find(
|
||||||
(property) => property.name === currentName(propertyName)
|
(property) => property.name === currentName(propertyName)
|
||||||
),
|
),
|
||||||
context: this.context,
|
context: this.props.context,
|
||||||
onChange: this.onPropertyDefinitionChange.bind(this),
|
onChange: this.onPropertyDefinitionChange.bind(this),
|
||||||
onDelete: () => this.onPropertyDelete(currentName(propertyName)),
|
onDelete: () => this.onPropertyDelete(currentName(propertyName)),
|
||||||
onPropertyMove: (direction) =>
|
onPropertyMove: (direction) =>
|
||||||
@@ -522,10 +514,13 @@ export const propertiesField = {
|
|||||||
component: PropertiesField,
|
component: PropertiesField,
|
||||||
displayName: _lt("Properties"),
|
displayName: _lt("Properties"),
|
||||||
supportedTypes: ["properties"],
|
supportedTypes: ["properties"],
|
||||||
extractProps: ({ attrs }) => ({
|
extractProps({ attrs }, dynamicInfo) {
|
||||||
columns: parseInt(attrs.columns || "1"),
|
return {
|
||||||
hideKanbanOption: archParseBoolean(attrs.hideKanbanOption),
|
context: dynamicInfo.context,
|
||||||
}),
|
columns: parseInt(attrs.columns || "1"),
|
||||||
|
hideKanbanOption: archParseBoolean(attrs.hideKanbanOption),
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("properties", propertiesField);
|
registry.category("fields").add("properties", propertiesField);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
id="domId"
|
id="domId"
|
||||||
canChangeDefinition="state.canChangeDefinition"
|
canChangeDefinition="state.canChangeDefinition"
|
||||||
comodel="propertyConfiguration.comodel || ''"
|
comodel="propertyConfiguration.comodel || ''"
|
||||||
context="context"
|
context="props.context"
|
||||||
domain="propertyConfiguration.domain || '[]'"
|
domain="propertyConfiguration.domain || '[]'"
|
||||||
readonly="props.readonly"
|
readonly="props.readonly"
|
||||||
selection="propertyConfiguration.selection"
|
selection="propertyConfiguration.selection"
|
||||||
|
|||||||
@@ -81,10 +81,8 @@ export const radioField = {
|
|||||||
|
|
||||||
registry.category("fields").add("radio", radioField);
|
registry.category("fields").add("radio", radioField);
|
||||||
|
|
||||||
export async function preloadRadio(orm, record, fieldName) {
|
export async function preloadRadio(orm, record, fieldName, { domain }) {
|
||||||
const field = record.fields[fieldName];
|
const field = record.fields[fieldName];
|
||||||
const context = record.evalContext;
|
|
||||||
const domain = record.getFieldDomain(fieldName).toList(context);
|
|
||||||
const records = await orm.searchRead(field.relation, domain, ["id"]);
|
const records = await orm.searchRead(field.relation, domain, ["id"]);
|
||||||
return await orm.call(field.relation, "name_get", [records.map((record) => record.id)]);
|
return await orm.call(field.relation, "name_get", [records.map((record) => record.id)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export const referenceField = {
|
|||||||
displayName: _lt("Reference"),
|
displayName: _lt("Reference"),
|
||||||
supportedTypes: ["reference", "char"],
|
supportedTypes: ["reference", "char"],
|
||||||
legacySpecialData: "_fetchSpecialReference",
|
legacySpecialData: "_fetchSpecialReference",
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ options }) {
|
||||||
/*
|
/*
|
||||||
1 - <field name="ref" options="{'model_field': 'model_id'}" />
|
1 - <field name="ref" options="{'model_field': 'model_id'}" />
|
||||||
2 - <field name="ref" options="{'hide_model': True}" />
|
2 - <field name="ref" options="{'hide_model': True}" />
|
||||||
@@ -140,9 +140,10 @@ export const referenceField = {
|
|||||||
|
|
||||||
We want to display the model selector only in the 4th case.
|
We want to display the model selector only in the 4th case.
|
||||||
*/
|
*/
|
||||||
...many2OneField.extractProps(fieldInfo),
|
const props = many2OneField.extractProps(...arguments);
|
||||||
hideModelSelector: !!fieldInfo.options.hide_model || !!fieldInfo.options.model_field,
|
props.hideModelSelector = !!options.hide_model || !!options.model_field;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("reference", referenceField);
|
registry.category("fields").add("reference", referenceField);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export class SelectionField extends Component {
|
|||||||
static props = {
|
static props = {
|
||||||
...standardFieldProps,
|
...standardFieldProps,
|
||||||
placeholder: { type: String, optional: true },
|
placeholder: { type: String, optional: true },
|
||||||
|
required: { type: Boolean, optional: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
@@ -45,9 +46,6 @@ export class SelectionField extends Component {
|
|||||||
? rawValue[0]
|
? rawValue[0]
|
||||||
: rawValue;
|
: rawValue;
|
||||||
}
|
}
|
||||||
get isRequired() {
|
|
||||||
return this.props.record.isRequired(this.props.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
stringify(value) {
|
stringify(value) {
|
||||||
return JSON.stringify(value);
|
return JSON.stringify(value);
|
||||||
@@ -81,17 +79,18 @@ export const selectionField = {
|
|||||||
supportedTypes: ["many2one", "selection"],
|
supportedTypes: ["many2one", "selection"],
|
||||||
legacySpecialData: "_fetchSpecialRelation",
|
legacySpecialData: "_fetchSpecialRelation",
|
||||||
isEmpty: (record, fieldName) => record.data[fieldName] === false,
|
isEmpty: (record, fieldName) => record.data[fieldName] === false,
|
||||||
extractProps: ({ attrs }) => ({
|
extractProps({ attrs }, dynamicInfo) {
|
||||||
placeholder: attrs.placeholder,
|
return {
|
||||||
}),
|
placeholder: attrs.placeholder,
|
||||||
|
required: dynamicInfo.required,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("selection", selectionField);
|
registry.category("fields").add("selection", selectionField);
|
||||||
|
|
||||||
export function preloadSelection(orm, record, fieldName) {
|
export function preloadSelection(orm, record, fieldName, { domain }) {
|
||||||
const field = record.fields[fieldName];
|
const field = record.fields[fieldName];
|
||||||
const context = record.evalContext;
|
|
||||||
const domain = record.getFieldDomain(fieldName).toList(context);
|
|
||||||
return orm.call(field.relation, "name_search", ["", domain]);
|
return orm.call(field.relation, "name_search", ["", domain]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
t-att-selected="false === value"
|
t-att-selected="false === value"
|
||||||
t-att-value="stringify(false)"
|
t-att-value="stringify(false)"
|
||||||
t-esc="this.props.placeholder || ''"
|
t-esc="this.props.placeholder || ''"
|
||||||
t-attf-style="{{ isRequired ? 'display:none' : '' }}"
|
t-attf-style="{{ props.required ? 'display:none' : '' }}"
|
||||||
/>
|
/>
|
||||||
<t t-foreach="options" t-as="option" t-key="option[0]">
|
<t t-foreach="options" t-as="option" t-key="option[0]">
|
||||||
<option
|
<option
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { Component } from "@odoo/owl";
|
||||||
import { useCommand } from "@web/core/commands/command_hook";
|
import { useCommand } from "@web/core/commands/command_hook";
|
||||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||||
|
import { _lt } from "@web/core/l10n/translation";
|
||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
import { sprintf } from "@web/core/utils/strings";
|
import { sprintf } from "@web/core/utils/strings";
|
||||||
import { _lt } from "@web/core/l10n/translation";
|
|
||||||
import { standardFieldProps } from "../standard_field_props";
|
|
||||||
import { formatSelection } from "../formatters";
|
import { formatSelection } from "../formatters";
|
||||||
import { Component } from "@odoo/owl";
|
import { standardFieldProps } from "../standard_field_props";
|
||||||
|
|
||||||
export class StateSelectionField extends Component {
|
export class StateSelectionField extends Component {
|
||||||
static template = "web.StateSelectionField";
|
static template = "web.StateSelectionField";
|
||||||
@@ -65,9 +65,6 @@ export class StateSelectionField extends Component {
|
|||||||
}
|
}
|
||||||
return formatSelection(this.currentValue, { selection: this.options });
|
return formatSelection(this.currentValue, { selection: this.options });
|
||||||
}
|
}
|
||||||
get isReadonly() {
|
|
||||||
return this.props.record.isReadonly(this.props.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
statusColor(value) {
|
statusColor(value) {
|
||||||
return this.colors[value] ? this.colorPrefix + this.colors[value] : "";
|
return this.colors[value] ? this.colorPrefix + this.colors[value] : "";
|
||||||
@@ -83,10 +80,13 @@ export const stateSelectionField = {
|
|||||||
component: StateSelectionField,
|
component: StateSelectionField,
|
||||||
displayName: _lt("Label Selection"),
|
displayName: _lt("Label Selection"),
|
||||||
supportedTypes: ["selection"],
|
supportedTypes: ["selection"],
|
||||||
extractProps: ({ options, viewType }) => ({
|
extractProps({ options, viewType }, dynamicInfo) {
|
||||||
showLabel: viewType === "list" && !options.hide_label,
|
return {
|
||||||
withCommand: viewType === "form",
|
showLabel: viewType === "list" && !options.hide_label,
|
||||||
}),
|
withCommand: viewType === "form",
|
||||||
|
readonly: dynamicInfo.readonly,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("state_selection", stateSelectionField);
|
registry.category("fields").add("state_selection", stateSelectionField);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<templates xml:space="preserve">
|
<templates xml:space="preserve">
|
||||||
|
|
||||||
<t t-name="web.StateSelectionField" owl="1">
|
<t t-name="web.StateSelectionField" owl="1">
|
||||||
<t t-if="isReadonly">
|
<t t-if="props.readonly">
|
||||||
<button class="d-flex align-items-center btn fw-normal p-0">
|
<button class="d-flex align-items-center btn fw-normal p-0">
|
||||||
<span t-attf-class="o_status {{ statusColor(currentValue) }} "/>
|
<span t-attf-class="o_status {{ statusColor(currentValue) }} "/>
|
||||||
<span t-if="props.showLabel" class="o_status_label ms-1" t-esc="label"/>
|
<span t-if="props.showLabel" class="o_status_label ms-1" t-esc="label"/>
|
||||||
|
|||||||
@@ -236,17 +236,17 @@ export const statusBarField = {
|
|||||||
|
|
||||||
registry.category("fields").add("statusbar", statusBarField);
|
registry.category("fields").add("statusbar", statusBarField);
|
||||||
|
|
||||||
export async function preloadStatusBar(orm, record, fieldName) {
|
export async function preloadStatusBar(orm, record, fieldName, { domain }) {
|
||||||
const fieldNames = ["id", "display_name"];
|
const fieldNames = ["id", "display_name"];
|
||||||
const foldField = record.activeFields[fieldName].options.fold_field;
|
const foldField = record.activeFields[fieldName].options.fold_field;
|
||||||
if (foldField) {
|
if (foldField) {
|
||||||
fieldNames.push(foldField);
|
fieldNames.push(foldField);
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = record.evalContext;
|
|
||||||
let domain = record.getFieldDomain(fieldName).toList(context);
|
|
||||||
if (domain.length && record.data[fieldName]) {
|
if (domain.length && record.data[fieldName]) {
|
||||||
domain = Domain.or([[["id", "=", record.data[fieldName][0]]], domain]).toList(context);
|
domain = Domain.or([[["id", "=", record.data[fieldName][0]]], domain]).toList(
|
||||||
|
record.evalContext
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const relation = record.fields[fieldName].relation;
|
const relation = record.fields[fieldName].relation;
|
||||||
|
|||||||
@@ -74,11 +74,12 @@ export const timezoneMismatchField = {
|
|||||||
...selectionField,
|
...selectionField,
|
||||||
component: TimezoneMismatchField,
|
component: TimezoneMismatchField,
|
||||||
additionalClasses: ["d-flex"],
|
additionalClasses: ["d-flex"],
|
||||||
extractProps: (fieldInfo) => ({
|
extractProps({ options }) {
|
||||||
...selectionField.extractProps(fieldInfo),
|
const props = selectionField.extractProps(...arguments);
|
||||||
tzOffsetField: fieldInfo.options.tz_offset_field,
|
props.tzOffsetField = options.tz_offset_field;
|
||||||
mismatchTitle: fieldInfo.options.mismatch_title,
|
props.mismatchTitle = options.mismatch_title;
|
||||||
}),
|
return props;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.category("fields").add("timezone_mismatch", timezoneMismatchField);
|
registry.category("fields").add("timezone_mismatch", timezoneMismatchField);
|
||||||
|
|||||||
@@ -32,9 +32,8 @@ export class X2ManyField extends Component {
|
|||||||
crudOptions: { type: Object, optional: true },
|
crudOptions: { type: Object, optional: true },
|
||||||
string: { type: String, optional: true },
|
string: { type: String, optional: true },
|
||||||
views: { type: Object, optional: true },
|
views: { type: Object, optional: true },
|
||||||
};
|
domain: { type: Array, optional: true },
|
||||||
static defaultProps = {
|
context: { type: Object },
|
||||||
archInfo: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
@@ -239,9 +238,8 @@ export class X2ManyField extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onAdd({ context, editable } = {}) {
|
async onAdd({ context, editable } = {}) {
|
||||||
const record = this.props.record;
|
const domain = this.props.domain || this.props.record.getFieldDomain(this.props.name);
|
||||||
const domain = record.getFieldDomain(this.props.name).toList();
|
context = makeContext([this.props.context, context]);
|
||||||
context = makeContext([record.getFieldContext(this.props.name), context]);
|
|
||||||
if (this.isMany2Many) {
|
if (this.isMany2Many) {
|
||||||
const { string } = this.props;
|
const { string } = this.props;
|
||||||
const title = sprintf(this.env._t("Add: %s"), string);
|
const title = sprintf(this.env._t("Add: %s"), string);
|
||||||
@@ -274,9 +272,11 @@ export const x2ManyField = {
|
|||||||
displayName: _lt("Relational table"),
|
displayName: _lt("Relational table"),
|
||||||
supportedTypes: ["one2many", "many2many"],
|
supportedTypes: ["one2many", "many2many"],
|
||||||
useSubView: true,
|
useSubView: true,
|
||||||
extractProps: ({ attrs, viewMode, views, widget, options, string }) => {
|
extractProps: ({ attrs, viewMode, views, widget, options, string }, dynamicInfo) => {
|
||||||
const props = {
|
const props = {
|
||||||
addLabel: attrs["add-label"],
|
addLabel: attrs["add-label"],
|
||||||
|
context: dynamicInfo.context,
|
||||||
|
domain: dynamicInfo.domain,
|
||||||
viewMode,
|
viewMode,
|
||||||
views,
|
views,
|
||||||
crudOptions: options,
|
crudOptions: options,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { useSortable } from "@web/core/utils/sortable";
|
|||||||
import { getTabableElements } from "@web/core/utils/ui";
|
import { getTabableElements } from "@web/core/utils/ui";
|
||||||
import { Field } from "@web/views/fields/field";
|
import { Field } from "@web/views/fields/field";
|
||||||
import { getTooltipInfo } from "@web/views/fields/field_tooltip";
|
import { getTooltipInfo } from "@web/views/fields/field_tooltip";
|
||||||
import { getClassNameFromDecoration } from "@web/views/utils";
|
import { evalDomain, getClassNameFromDecoration } from "@web/views/utils";
|
||||||
import { ViewButton } from "@web/views/view_button/view_button";
|
import { ViewButton } from "@web/views/view_button/view_button";
|
||||||
import { useBounceButton } from "@web/views/view_hook";
|
import { useBounceButton } from "@web/views/view_hook";
|
||||||
import { Widget } from "@web/views/widgets/widget";
|
import { Widget } from "@web/views/widgets/widget";
|
||||||
@@ -25,10 +25,10 @@ import {
|
|||||||
onPatched,
|
onPatched,
|
||||||
onWillPatch,
|
onWillPatch,
|
||||||
onWillUpdateProps,
|
onWillUpdateProps,
|
||||||
|
useEffect,
|
||||||
useExternalListener,
|
useExternalListener,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
|
||||||
} from "@odoo/owl";
|
} from "@odoo/owl";
|
||||||
|
|
||||||
const formatters = registry.category("formatters");
|
const formatters = registry.category("formatters");
|
||||||
@@ -258,7 +258,7 @@ export class ListRenderer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFieldProps(record, column) {
|
getFieldProps(record, column) {
|
||||||
if (this.getCellReadonly(column, record)) {
|
if (this.isCellReadonly(column, record)) {
|
||||||
return {
|
return {
|
||||||
readonly: true,
|
readonly: true,
|
||||||
};
|
};
|
||||||
@@ -445,7 +445,7 @@ export class ListRenderer extends Component {
|
|||||||
}
|
}
|
||||||
// in findNextFocusableOnRow test is done by using classList
|
// in findNextFocusableOnRow test is done by using classList
|
||||||
// refactor
|
// refactor
|
||||||
if (!this.getCellReadonly(column, editedRecord)) {
|
if (!this.isCellReadonly(column, editedRecord)) {
|
||||||
const cell = this.tableRef.el.querySelector(
|
const cell = this.tableRef.el.querySelector(
|
||||||
`.o_selected_row td[name='${column.name}']`
|
`.o_selected_row td[name='${column.name}']`
|
||||||
);
|
);
|
||||||
@@ -695,9 +695,8 @@ export class ListRenderer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSortable(column) {
|
isSortable(column) {
|
||||||
const { hasLabel, name } = column;
|
const { hasLabel, name, options } = column;
|
||||||
const { sortable } = this.fields[name];
|
const { sortable } = this.fields[name];
|
||||||
const { options } = this.props.list.activeFields[name];
|
|
||||||
return (sortable || options.allow_order) && hasLabel;
|
return (sortable || options.allow_order) && hasLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,20 +764,21 @@ export class ListRenderer extends Component {
|
|||||||
}
|
}
|
||||||
const classNames = [...this.cellClassByColumn[column.id]];
|
const classNames = [...this.cellClassByColumn[column.id]];
|
||||||
if (column.type === "field") {
|
if (column.type === "field") {
|
||||||
if (record.isRequired(column.name)) {
|
const { required } = column.modifiers;
|
||||||
|
if (required && evalDomain(required, record.evalContext)) {
|
||||||
classNames.push("o_required_modifier");
|
classNames.push("o_required_modifier");
|
||||||
}
|
}
|
||||||
if (record.isInvalid(column.name)) {
|
if (record.isInvalid(column.name)) {
|
||||||
classNames.push("o_invalid_cell");
|
classNames.push("o_invalid_cell");
|
||||||
}
|
}
|
||||||
if (this.getCellReadonly(column, record)) {
|
if (this.isCellReadonly(column, record)) {
|
||||||
classNames.push("o_readonly_modifier");
|
classNames.push("o_readonly_modifier");
|
||||||
}
|
}
|
||||||
if (this.canUseFormatter(column, record)) {
|
if (this.canUseFormatter(column, record)) {
|
||||||
// generate field decorations classNames (only if field-specific decorations
|
// generate field decorations classNames (only if field-specific decorations
|
||||||
// have been defined in an attribute, e.g. decoration-danger="other_field = 5")
|
// have been defined in an attribute, e.g. decoration-danger="other_field = 5")
|
||||||
// only handle the text-decoration.
|
// only handle the text-decoration.
|
||||||
const { decorations } = record.activeFields[column.name];
|
const { decorations } = column;
|
||||||
for (const decoName in decorations) {
|
for (const decoName in decorations) {
|
||||||
if (evaluateExpr(decorations[decoName], record.evalContext)) {
|
if (evaluateExpr(decorations[decoName], record.evalContext)) {
|
||||||
classNames.push(getClassNameFromDecoration(decoName));
|
classNames.push(getClassNameFromDecoration(decoName));
|
||||||
@@ -788,7 +788,7 @@ export class ListRenderer extends Component {
|
|||||||
if (
|
if (
|
||||||
record.isInEdition &&
|
record.isInEdition &&
|
||||||
this.props.list.editedRecord &&
|
this.props.list.editedRecord &&
|
||||||
this.props.list.editedRecord.isReadonly(column.name)
|
this.isCellReadonly(column, this.props.list.editedRecord)
|
||||||
) {
|
) {
|
||||||
classNames.push("text-muted");
|
classNames.push("text-muted");
|
||||||
} else {
|
} else {
|
||||||
@@ -798,10 +798,10 @@ export class ListRenderer extends Component {
|
|||||||
return classNames.join(" ");
|
return classNames.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
getCellReadonly(column, record) {
|
isCellReadonly(column, record) {
|
||||||
return (
|
return (
|
||||||
record.isReadonly(column.name) ||
|
(column.relatedPropertyField && record.selected && record.model.multiEdit) ||
|
||||||
(column.relatedPropertyField && record.selected && record.model.multiEdit)
|
evalDomain(column.modifiers.readonly, record.evalContext)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1780,7 +1780,7 @@ export class ListRenderer extends Component {
|
|||||||
viewMode: "list",
|
viewMode: "list",
|
||||||
resModel: this.props.list.resModel,
|
resModel: this.props.list.resModel,
|
||||||
field: this.fields[column.name],
|
field: this.fields[column.name],
|
||||||
fieldInfo: this.props.list.activeFields[column.name],
|
fieldInfo: column,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -240,7 +240,7 @@
|
|||||||
t-on-click="(ev) => this.onCellClicked(record, column, ev)" tabindex="-1">
|
t-on-click="(ev) => this.onCellClicked(record, column, ev)" tabindex="-1">
|
||||||
<t t-if="!isInvisible">
|
<t t-if="!isInvisible">
|
||||||
<t t-if="canUseFormatter(column, record)" t-out="getFormattedValue(column, record)"/>
|
<t t-if="canUseFormatter(column, record)" t-out="getFormattedValue(column, record)"/>
|
||||||
<Field t-else="" name="column.name" record="record" type="column.widget" class="getFieldClass(column)" fieldInfo="props.archInfo.activeFields[column.name]" t-props="getFieldProps(record, column)"/>
|
<Field t-else="" name="column.name" record="record" type="column.widget" class="getFieldClass(column)" fieldInfo="column" t-props="getFieldProps(record, column)"/>
|
||||||
</t>
|
</t>
|
||||||
</td>
|
</td>
|
||||||
</t>
|
</t>
|
||||||
|
|||||||
@@ -596,7 +596,7 @@ export class Record extends DataPoint {
|
|||||||
|
|
||||||
const isSet = activeField && activeField.field && activeField.field.isSet;
|
const isSet = activeField && activeField.field && activeField.field.isSet;
|
||||||
|
|
||||||
if (this.isRequired(fieldName) && isSet && !isSet(this.data[fieldName])) {
|
if (this._isRequired(fieldName) && isSet && !isSet(this.data[fieldName])) {
|
||||||
this.setInvalidField(fieldName);
|
this.setInvalidField(fieldName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -614,7 +614,7 @@ export class Record extends DataPoint {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (!isSet && this.isRequired(fieldName) && !this.data[fieldName]) {
|
if (!isSet && this._isRequired(fieldName) && !this.data[fieldName]) {
|
||||||
this.setInvalidField(fieldName);
|
this.setInvalidField(fieldName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -715,7 +715,7 @@ export class Record extends DataPoint {
|
|||||||
!allFields &&
|
!allFields &&
|
||||||
fieldName in this.activeFields &&
|
fieldName in this.activeFields &&
|
||||||
!this.activeFields[fieldName].forceSave &&
|
!this.activeFields[fieldName].forceSave &&
|
||||||
this.isReadonly(fieldName)
|
this._isReadonly(fieldName)
|
||||||
) {
|
) {
|
||||||
delete changes[fieldName];
|
delete changes[fieldName];
|
||||||
continue;
|
continue;
|
||||||
@@ -744,25 +744,18 @@ export class Record extends DataPoint {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getFieldContext(fieldName) {
|
getFieldDomain(fieldName) {
|
||||||
|
const { domain } = this.fields[fieldName];
|
||||||
|
return domain ? new Domain(domain).toList(this.evalContext) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFieldContext(fieldName) {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
this.context,
|
this.context,
|
||||||
makeContext([this.activeFields[fieldName].context], this.evalContext)
|
makeContext([this.activeFields[fieldName].context], this.evalContext)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFieldDomain(fieldName) {
|
|
||||||
const rawDomains = [
|
|
||||||
this._domains[fieldName] || [],
|
|
||||||
this.activeFields[fieldName].domain || this.fields[fieldName].domain || [],
|
|
||||||
];
|
|
||||||
|
|
||||||
const evalContext = this.evalContext;
|
|
||||||
return Domain.and(
|
|
||||||
rawDomains.map((d) => (typeof d === "string" ? evaluateExpr(d, evalContext) : d))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
isFieldDirty(fieldName) {
|
isFieldDirty(fieldName) {
|
||||||
return this.isDirty && !!this._changes[fieldName];
|
return this.isDirty && !!this._changes[fieldName];
|
||||||
}
|
}
|
||||||
@@ -787,7 +780,7 @@ export class Record extends DataPoint {
|
|||||||
* @param {string} fieldName
|
* @param {string} fieldName
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isReadonly(fieldName) {
|
_isReadonly(fieldName) {
|
||||||
const { readonly } = this.activeFields[fieldName].modifiers || {};
|
const { readonly } = this.activeFields[fieldName].modifiers || {};
|
||||||
return evalDomain(readonly, this.evalContext);
|
return evalDomain(readonly, this.evalContext);
|
||||||
}
|
}
|
||||||
@@ -797,7 +790,7 @@ export class Record extends DataPoint {
|
|||||||
* @param {string} fieldName
|
* @param {string} fieldName
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isRequired(fieldName) {
|
_isRequired(fieldName) {
|
||||||
const required = this._requiredFields[fieldName];
|
const required = this._requiredFields[fieldName];
|
||||||
return required ? evalDomain(required, this.evalContext) : false;
|
return required ? evalDomain(required, this.evalContext) : false;
|
||||||
}
|
}
|
||||||
@@ -845,12 +838,25 @@ export class Record extends DataPoint {
|
|||||||
if (!info.loadOnTypes.includes(this.fields[fieldName].type)) {
|
if (!info.loadOnTypes.includes(this.fields[fieldName].type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const domain = this.getFieldDomain(fieldName).toList(this.evalContext).toString();
|
|
||||||
|
const evalContext = this.evalContext;
|
||||||
|
const domain = this.activeFields[fieldName].domain
|
||||||
|
? new Domain(
|
||||||
|
evaluateExpr(this.activeFields[fieldName].domain, evalContext)
|
||||||
|
).toList()
|
||||||
|
: this.getFieldDomain(fieldName);
|
||||||
|
|
||||||
const getExtraKey = info.extraMemoizationKey || (() => null);
|
const getExtraKey = info.extraMemoizationKey || (() => null);
|
||||||
const key = JSON.stringify([domain, getExtraKey(this, fieldName)]);
|
const key = JSON.stringify([domain.toString(), getExtraKey(this, fieldName)]);
|
||||||
if (this.preloadedDataCaches[fieldName] !== key) {
|
if (this.preloadedDataCaches[fieldName] !== key) {
|
||||||
|
const context = this._getFieldContext(fieldName);
|
||||||
this.preloadedDataCaches[fieldName] = key;
|
this.preloadedDataCaches[fieldName] = key;
|
||||||
this.preloadedData[fieldName] = await info.preload(this.model.orm, this, fieldName);
|
this.preloadedData[fieldName] = await info.preload(
|
||||||
|
this.model.orm,
|
||||||
|
this,
|
||||||
|
fieldName,
|
||||||
|
{ context, domain }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1331,7 +1337,7 @@ export class Record extends DataPoint {
|
|||||||
value &&
|
value &&
|
||||||
(!value[1] || activeField.options.always_reload)
|
(!value[1] || activeField.options.always_reload)
|
||||||
) {
|
) {
|
||||||
const context = this.getFieldContext(fieldName);
|
const context = this._getFieldContext(fieldName);
|
||||||
const result = await this.model.orm.nameGet(relation, [value[0]], { context });
|
const result = await this.model.orm.nameGet(relation, [value[0]], { context });
|
||||||
return result[0];
|
return result[0];
|
||||||
}
|
}
|
||||||
@@ -1348,7 +1354,7 @@ export class Record extends DataPoint {
|
|||||||
value = { resModel, resId: parseInt(resId, 10) };
|
value = { resModel, resId: parseInt(resId, 10) };
|
||||||
}
|
}
|
||||||
const { resModel, resId } = value;
|
const { resModel, resId } = value;
|
||||||
const context = this.getFieldContext(fieldName);
|
const context = this._getFieldContext(fieldName);
|
||||||
const nameGet = await this.model.orm.nameGet(resModel, [resId], { context });
|
const nameGet = await this.model.orm.nameGet(resModel, [resId], { context });
|
||||||
return {
|
return {
|
||||||
resModel,
|
resModel,
|
||||||
@@ -1739,8 +1745,8 @@ class DynamicList extends DataPoint {
|
|||||||
if (
|
if (
|
||||||
!Object.keys(changes).filter(
|
!Object.keys(changes).filter(
|
||||||
(fieldName) =>
|
(fieldName) =>
|
||||||
record.isReadonly(fieldName) ||
|
record._isReadonly(fieldName) ||
|
||||||
(record.isRequired(fieldName) && !changes[fieldName])
|
(record._isRequired(fieldName) && !changes[fieldName])
|
||||||
).length
|
).length
|
||||||
) {
|
) {
|
||||||
result.push(record);
|
result.push(record);
|
||||||
|
|||||||
@@ -2015,62 +2015,6 @@ odoo.define('web.basic_model_tests', function (require) {
|
|||||||
model.destroy();
|
model.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('has a proper evaluation context', async function (assert) {
|
|
||||||
assert.expect(6);
|
|
||||||
|
|
||||||
const unpatchDate = testUtils.mock.patchDate(1997, 0, 9, 12, 0, 0);
|
|
||||||
this.params.fieldNames = Object.keys(this.data.partner.fields);
|
|
||||||
this.params.res_id = 1;
|
|
||||||
|
|
||||||
var model = await createModel({
|
|
||||||
Model: BasicModel,
|
|
||||||
data: this.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
var resultID = await model.load(this.params);
|
|
||||||
const { evalContext } = model.get(resultID);
|
|
||||||
assert.strictEqual(typeof evalContext.datetime, "object");
|
|
||||||
assert.strictEqual(typeof evalContext.relativedelta, "object");
|
|
||||||
assert.strictEqual(typeof evalContext.time, "object");
|
|
||||||
assert.strictEqual(typeof evalContext.context_today, "function");
|
|
||||||
assert.strictEqual(typeof evalContext.tz_offset, "function");
|
|
||||||
const blackListedKeys = [
|
|
||||||
"time",
|
|
||||||
"datetime",
|
|
||||||
"relativedelta",
|
|
||||||
"context_today",
|
|
||||||
"tz_offset",
|
|
||||||
];
|
|
||||||
// Remove uncomparable values from the evaluation context
|
|
||||||
for (const key of blackListedKeys) {
|
|
||||||
delete evalContext[key];
|
|
||||||
}
|
|
||||||
assert.deepEqual(evalContext, {
|
|
||||||
active: true,
|
|
||||||
active_id: 1,
|
|
||||||
active_ids: [1],
|
|
||||||
active_model: "partner",
|
|
||||||
bar: 1,
|
|
||||||
category: [12],
|
|
||||||
current_company_id: false,
|
|
||||||
current_date: moment().format('YYYY-MM-DD'),
|
|
||||||
today: moment().format('YYYY-MM-DD'),
|
|
||||||
now: moment().utc().format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
date: "2017-01-25",
|
|
||||||
display_name: "first partner",
|
|
||||||
foo: "blip",
|
|
||||||
id: 1,
|
|
||||||
product_id: 37,
|
|
||||||
product_ids: [],
|
|
||||||
qux: false,
|
|
||||||
reference: false,
|
|
||||||
total: 0,
|
|
||||||
x_active: true,
|
|
||||||
}, "should use the proper eval context");
|
|
||||||
model.destroy();
|
|
||||||
unpatchDate();
|
|
||||||
});
|
|
||||||
|
|
||||||
QUnit.test('x2manys in contexts and domains are correctly evaluated', async function (assert) {
|
QUnit.test('x2manys in contexts and domains are correctly evaluated', async function (assert) {
|
||||||
assert.expect(4);
|
assert.expect(4);
|
||||||
|
|
||||||
|
|||||||
@@ -3760,60 +3760,6 @@ QUnit.module("Fields", (hooks) => {
|
|||||||
await click(target, ".o_field_widget[name='trululu'] input");
|
await click(target, ".o_field_widget[name='trululu'] input");
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("many2one in one2many: domain updated by an onchange", async function (assert) {
|
|
||||||
assert.expect(3);
|
|
||||||
|
|
||||||
serverData.models.partner.onchanges = {
|
|
||||||
trululu() {},
|
|
||||||
};
|
|
||||||
|
|
||||||
let domain = [];
|
|
||||||
await makeView({
|
|
||||||
type: "form",
|
|
||||||
resModel: "partner",
|
|
||||||
resId: 1,
|
|
||||||
serverData,
|
|
||||||
arch: `
|
|
||||||
<form>
|
|
||||||
<field name="p">
|
|
||||||
<tree editable="bottom">
|
|
||||||
<field name="foo" />
|
|
||||||
<field name="trululu" />
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</form>`,
|
|
||||||
mockRPC(route, { kwargs, method }) {
|
|
||||||
if (method === "onchange") {
|
|
||||||
return Promise.resolve({
|
|
||||||
domain: {
|
|
||||||
trululu: domain,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (method === "name_search") {
|
|
||||||
assert.deepEqual(kwargs.args, domain, "sent domain should be correct");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// add a first row with a specific domain for the m2o
|
|
||||||
domain = [["id", "in", [10]]]; // domain for subrecord 1
|
|
||||||
await click(target, ".o_field_x2many_list_row_add a");
|
|
||||||
await click(target, ".o_field_widget[name=trululu] input");
|
|
||||||
// add some value to foo field to make record dirty
|
|
||||||
await editInput(target, "tr.o_selected_row .o_field_widget[name='foo'] input", "new value");
|
|
||||||
|
|
||||||
// add a second row with another domain for the m2o
|
|
||||||
domain = [["id", "in", [5]]]; // domain for subrecord 2
|
|
||||||
await click(target, ".o_field_x2many_list_row_add a");
|
|
||||||
await click(target, ".o_field_widget[name=trululu] input");
|
|
||||||
|
|
||||||
// check again the first row to ensure that the domain hasn't change
|
|
||||||
domain = [["id", "in", [10]]]; // domain for subrecord 1 should have been kept
|
|
||||||
await click(target.querySelectorAll(".o_data_row .o_data_cell")[1]);
|
|
||||||
await click(target, ".o_field_widget[name=trululu] input");
|
|
||||||
});
|
|
||||||
|
|
||||||
QUnit.test("search more in many2one: no text in input", async function (assert) {
|
QUnit.test("search more in many2one: no text in input", async function (assert) {
|
||||||
// when the user clicks on 'Search More...' in a many2one dropdown, and there is no text
|
// when the user clicks on 'Search More...' in a many2one dropdown, and there is no text
|
||||||
// in the input (i.e. no value to search on), we bypass the name_search that is meant to
|
// in the input (i.e. no value to search on), we bypass the name_search that is meant to
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ QUnit.module("Fields", (hooks) => {
|
|||||||
serverData.views = {
|
serverData.views = {
|
||||||
"turtle,false,form": `
|
"turtle,false,form": `
|
||||||
<form>
|
<form>
|
||||||
<field name="parent_id" domain="[('id', 'in', parent.turtles)]"/>
|
<field name="parent_id"/>
|
||||||
</form>`,
|
</form>`,
|
||||||
};
|
};
|
||||||
await makeView({
|
await makeView({
|
||||||
@@ -367,6 +367,9 @@ QUnit.module("Fields", (hooks) => {
|
|||||||
<tree>
|
<tree>
|
||||||
<field name="parent_id"/>
|
<field name="parent_id"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
<form>
|
||||||
|
<field name="parent_id" domain="[('id', 'in', parent.turtles)]"/>
|
||||||
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</form>`,
|
</form>`,
|
||||||
mockRPC(route, { kwargs }) {
|
mockRPC(route, { kwargs }) {
|
||||||
|
|||||||
@@ -8304,26 +8304,6 @@ QUnit.module("Views", (hooks) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("fields and record contexts are not mixed", async function (assert) {
|
|
||||||
assert.expect(2);
|
|
||||||
|
|
||||||
await makeView({
|
|
||||||
type: "form",
|
|
||||||
resModel: "partner",
|
|
||||||
serverData,
|
|
||||||
arch: `<form><field name="trululu" context="{'test': 1}"/></form>`,
|
|
||||||
mockRPC(route, args) {
|
|
||||||
if (args.method === "name_search") {
|
|
||||||
assert.strictEqual(args.kwargs.context.test, 1);
|
|
||||||
assert.notOk("mainContext" in args.kwargs.context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resId: 2,
|
|
||||||
context: { mainContext: 3 },
|
|
||||||
});
|
|
||||||
await click(target.querySelector(".o_field_widget[name=trululu] input"));
|
|
||||||
});
|
|
||||||
|
|
||||||
QUnit.test(
|
QUnit.test(
|
||||||
"do not activate an hidden tab when switching between records",
|
"do not activate an hidden tab when switching between records",
|
||||||
async function (assert) {
|
async function (assert) {
|
||||||
@@ -10879,69 +10859,6 @@ QUnit.module("Views", (hooks) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
QUnit.test("domain returned by onchange is cleared on discard", async function (assert) {
|
|
||||||
serverData.models.partner.onchanges = {
|
|
||||||
foo: function () {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const domain = [["id", "=", 1]];
|
|
||||||
let expectedDomain = domain;
|
|
||||||
await makeView({
|
|
||||||
type: "form",
|
|
||||||
resModel: "partner",
|
|
||||||
serverData,
|
|
||||||
arch: `
|
|
||||||
<form>
|
|
||||||
<field name="foo"/>
|
|
||||||
<field name="trululu"/>
|
|
||||||
</form>`,
|
|
||||||
mockRPC(route, args) {
|
|
||||||
if (args.method === "onchange" && args.args[0][0] === 1) {
|
|
||||||
// onchange returns a domain only on record 1
|
|
||||||
return Promise.resolve({
|
|
||||||
domain: {
|
|
||||||
trululu: domain,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (args.method === "name_search") {
|
|
||||||
assert.deepEqual(args.kwargs.args, expectedDomain);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resId: 1,
|
|
||||||
resIds: [1, 2],
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.strictEqual(
|
|
||||||
target.querySelector(".o_field_widget[name=foo] input").value,
|
|
||||||
"yop",
|
|
||||||
"should be on record 1"
|
|
||||||
);
|
|
||||||
|
|
||||||
// change foo to trigger the onchange
|
|
||||||
await editInput(target, ".o_field_widget[name=foo] input", "new value");
|
|
||||||
|
|
||||||
// open many2one dropdown to check if the domain is applied
|
|
||||||
target.querySelector(".o-autocomplete.dropdown input").focus(); // autocomplete closes with the blur, so it must have the focus
|
|
||||||
await click(target.querySelector(".o-autocomplete.dropdown input"));
|
|
||||||
|
|
||||||
// switch to another record (should ask to discard changes, and reset the domain)
|
|
||||||
target.querySelector(".o_pager_next").focus(); // change the focus before click, this will close the autocomplete
|
|
||||||
await click(target.querySelector(".o_pager_next"));
|
|
||||||
|
|
||||||
assert.containsNone(document.body, ".modal", "should not open modal");
|
|
||||||
|
|
||||||
assert.strictEqual(
|
|
||||||
target.querySelector(".o_field_widget[name=foo] input").value,
|
|
||||||
"blip",
|
|
||||||
"should be on record 2"
|
|
||||||
);
|
|
||||||
|
|
||||||
// open many2one dropdown to check if the domain is applied
|
|
||||||
expectedDomain = [];
|
|
||||||
await click(target.querySelector(".o-autocomplete.dropdown input"));
|
|
||||||
});
|
|
||||||
|
|
||||||
QUnit.test("discard after a failed save (and close notifications)", async function (assert) {
|
QUnit.test("discard after a failed save (and close notifications)", async function (assert) {
|
||||||
patchWithCleanup(browser, { setTimeout: () => 1 });
|
patchWithCleanup(browser, { setTimeout: () => 1 });
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export class HtmlField extends Component {
|
|||||||
// Ensure all external links are opened in a new tab.
|
// Ensure all external links are opened in a new tab.
|
||||||
retargetLinks(this.readonlyElementRef.el);
|
retargetLinks(this.readonlyElementRef.el);
|
||||||
|
|
||||||
const hasReadonlyModifiers = Boolean(this.props.record.isReadonly(this.props.name));
|
const hasReadonlyModifiers = this.props.hasReadonlyModifiers;
|
||||||
if (!hasReadonlyModifiers) {
|
if (!hasReadonlyModifiers) {
|
||||||
const $el = $(this.readonlyElementRef.el);
|
const $el = $(this.readonlyElementRef.el);
|
||||||
$el.off('.checklistBinding');
|
$el.off('.checklistBinding');
|
||||||
@@ -631,13 +631,14 @@ HtmlField.props = {
|
|||||||
isInlineStyle: { type: Boolean, optional: true },
|
isInlineStyle: { type: Boolean, optional: true },
|
||||||
wrapper: { type: String, optional: true },
|
wrapper: { type: String, optional: true },
|
||||||
wysiwygOptions: { type: Object },
|
wysiwygOptions: { type: Object },
|
||||||
|
hasReadonlyModifiers: { type: Boolean, optional: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const htmlField = {
|
export const htmlField = {
|
||||||
component: HtmlField,
|
component: HtmlField,
|
||||||
displayName: _lt("Html"),
|
displayName: _lt("Html"),
|
||||||
supportedTypes: ["html"],
|
supportedTypes: ["html"],
|
||||||
extractProps: ({ attrs, options }) => {
|
extractProps({ attrs, options }, dynamicInfo) {
|
||||||
const wysiwygOptions = {
|
const wysiwygOptions = {
|
||||||
placeholder: attrs.placeholder,
|
placeholder: attrs.placeholder,
|
||||||
noAttachment: options['no-attachment'],
|
noAttachment: options['no-attachment'],
|
||||||
@@ -683,6 +684,7 @@ export const htmlField = {
|
|||||||
wrapper: options.wrapper,
|
wrapper: options.wrapper,
|
||||||
|
|
||||||
wysiwygOptions,
|
wysiwygOptions,
|
||||||
|
hasReadonlyModifiers: dynamicInfo.readonly,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user