From 9374a6f2b7fc44c8588cd16580eec2e0133ec911 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Tue, 21 Feb 2023 10:47:59 +0000 Subject: [PATCH] [REF] *: js fields: extractProps receives fieldInfo This commit is part of the preliminary work to rewrite the form, list and kanban model. We want this new Model to only be aware of field related information it needs (whereas in its current implementation, the model stores all the information extracted from the field node in the arch). This would allow to properly manage multiple occurrences of the same field in views, that is, each occurrence would be represented by a field component (if visible of course), and that field component would use the field information of the arch node it represents. To this end, we want fields from not using anymore information stored in activeFields in the record datapoint. Instead, we now call extractProps with the whole fieldInfo (the information extracted from the arch), s.t. each field can generate the props it needs from those information (e.g. sub views for x2manys). This commit doesn't remove the use of record.activeFields in concrete fields (this will come later), but reworks the fieldInfo object generated by parseFieldNode, and provide it to the calls of extractProps. In fieldInfo, the `options` key is no longer inside `attrs`, as it is now top-level, alongside several other generic keys that have been processed (like on_change, modifiers...). For that reason, a lot of extractProps definitions had to be adapted. Part of task 3179751 closes odoo/odoo#113092 Related: odoo/enterprise#37266 Signed-off-by: Jorge Pinna Puissant (jpp) --- .../analytic_distribution.js | 12 +- .../icon_selection_field.js | 4 +- .../filterable_selection_field.js | 8 +- .../mail/static/src/js/onchange_on_keydown.js | 20 +-- .../many2one_avatar_user_field.js | 6 +- .../static/src/js/mailing_m2o_filter.js | 8 +- .../static/src/js/mass_mailing_html_field.js | 10 +- .../src/components/phone_field/phone_field.js | 6 +- .../calendar_common_popover.xml | 2 +- .../static/src/views/fields/ace/ace_field.js | 4 +- .../src/views/fields/badge/badge_field.js | 13 +- .../src/views/fields/binary/binary_field.js | 4 +- .../fields/boolean_icon/boolean_icon_field.js | 4 +- .../src/views/fields/char/char_field.js | 4 +- .../src/views/fields/date/date_field.js | 4 +- .../views/fields/daterange/daterange_field.js | 8 +- .../views/fields/datetime/datetime_field.js | 4 +- .../src/views/fields/domain/domain_field.js | 6 +- addons/web/static/src/views/fields/field.js | 150 +++++++----------- .../src/views/fields/float/float_field.js | 10 +- .../fields/float_factor/float_factor_field.js | 6 +- .../fields/float_toggle/float_toggle_field.js | 12 +- .../src/views/fields/image/image_field.js | 21 +-- .../views/fields/image_url/image_url_field.js | 8 +- .../src/views/fields/integer/integer_field.js | 6 +- .../label_selection/label_selection_field.js | 4 +- .../many2many_binary_field.js | 4 +- .../many2many_tags/many2many_tags_field.js | 26 +-- .../views/fields/many2one/many2one_field.js | 16 +- .../many2one_barcode_field.js | 4 +- .../views/fields/monetary/monetary_field.js | 8 +- .../fields/pdf_viewer/pdf_viewer_field.js | 4 +- .../fields/percent_pie/percent_pie_field.js | 4 +- .../fields/percentage/percentage_field.js | 6 +- .../fields/progress_bar/progress_bar_field.js | 15 +- .../src/views/fields/radio/radio_field.js | 4 +- .../views/fields/reference/reference_field.js | 7 +- .../views/fields/signature/signature_field.js | 12 +- .../src/views/fields/standard_field_props.js | 1 - .../views/fields/stat_info/stat_info_field.js | 8 +- .../state_selection/state_selection_field.js | 4 +- .../views/fields/statusbar/statusbar_field.js | 4 +- .../src/views/fields/text/text_field.js | 4 +- .../timezone_mismatch_field.js | 8 +- .../static/src/views/fields/url/url_field.js | 4 +- .../static/src/views/list/list_arch_parser.js | 6 +- .../static/src/views/list/list_renderer.js | 36 ++--- .../web/static/src/views/relational_model.js | 4 +- addons/web/static/src/views/utils.js | 2 +- .../views/widgets/standard_widget_props.js | 2 +- addons/web/static/src/views/widgets/widget.js | 2 +- .../static/tests/views/calendar/helpers.js | 6 +- .../tests/views/form/form_view_tests.js | 6 +- .../static/tests/views/kanban_view_tests.js | 1 - .../static/src/js/backend/html_field.js | 46 +++--- .../static/src/gauge_field.js | 6 +- .../static/src/components/fields/fields.js | 4 +- 57 files changed, 282 insertions(+), 326 deletions(-) diff --git a/addons/analytic/static/src/components/analytic_distribution/analytic_distribution.js b/addons/analytic/static/src/components/analytic_distribution/analytic_distribution.js index e81315fd89b..05b5109af28 100644 --- a/addons/analytic/static/src/components/analytic_distribution/analytic_distribution.js +++ b/addons/analytic/static/src/components/analytic_distribution/analytic_distribution.js @@ -592,13 +592,13 @@ export const analyticDistribution = { component: AnalyticDistribution, supportedTypes: ["char", "text"], fieldDependencies: [{ name:"analytic_precision", type: "integer" }], - extractProps: ({ attrs }) => ({ - business_domain: attrs.options.business_domain, - account_field: attrs.options.account_field, - product_field: attrs.options.product_field, + extractProps: ({ attrs, options }) => ({ + business_domain: options.business_domain, + account_field: options.account_field, + product_field: options.product_field, business_domain_compute: attrs.business_domain_compute, - force_applicability: attrs.options.force_applicability, - allow_save: !attrs.options.disable_save, + force_applicability: options.force_applicability, + allow_save: !options.disable_save, }), }; diff --git a/addons/event/static/src/icon_selection_field/icon_selection_field.js b/addons/event/static/src/icon_selection_field/icon_selection_field.js index 21c96537f1e..2389f7b23fe 100644 --- a/addons/event/static/src/icon_selection_field/icon_selection_field.js +++ b/addons/event/static/src/icon_selection_field/icon_selection_field.js @@ -24,8 +24,8 @@ export const iconSelectionField = { component: IconSelectionField, displayName: _lt("Icon Selection"), supportedTypes: ["char", "text", "selection"], - extractProps: ({ attrs }) => ({ - icons: attrs.options, + extractProps: ({ options }) => ({ + icons: options, }), }; diff --git a/addons/loyalty/static/src/js/filterable_selection_field/filterable_selection_field.js b/addons/loyalty/static/src/js/filterable_selection_field/filterable_selection_field.js index b2c9b898f08..70b9c5a8197 100644 --- a/addons/loyalty/static/src/js/filterable_selection_field/filterable_selection_field.js +++ b/addons/loyalty/static/src/js/filterable_selection_field/filterable_selection_field.js @@ -36,10 +36,10 @@ FilterableSelectionField.props = { export const filterableSelectionField = { ...selectionField, component: FilterableSelectionField, - extractProps: (params) => ({ - ...selectionField.extractProps(params), - whitelisted_values: params.attrs.options.whitelisted_values, - blacklisted_values: params.attrs.options.blacklisted_values, + extractProps: (fieldInfo) => ({ + ...selectionField.extractProps(fieldInfo), + whitelisted_values: fieldInfo.options.whitelisted_values, + blacklisted_values: fieldInfo.options.blacklisted_values, }), }; diff --git a/addons/mail/static/src/js/onchange_on_keydown.js b/addons/mail/static/src/js/onchange_on_keydown.js index ca8229635dc..a0ab1953e82 100644 --- a/addons/mail/static/src/js/onchange_on_keydown.js +++ b/addons/mail/static/src/js/onchange_on_keydown.js @@ -52,21 +52,21 @@ TextField.props = { }; const charExtractProps = charField.extractProps; -charField.extractProps = (attrs) => { - return Object.assign(charExtractProps(attrs), { - onchangeOnKeydown: archParseBoolean(attrs.onchange_on_keydown), - keydownDebounceDelay: attrs.keydown_debounce_delay - ? Number(attrs.keydown_debounce_delay) +charField.extractProps = (fieldInfo) => { + return Object.assign(charExtractProps(fieldInfo), { + onchangeOnKeydown: archParseBoolean(fieldInfo.attrs.onchange_on_keydown), + keydownDebounceDelay: fieldInfo.attrs.keydown_debounce_delay + ? Number(fieldInfo.attrs.keydown_debounce_delay) : 2000, }); }; const textExtractProps = textField.extractProps; -textField.extractProps = (params) => { - return Object.assign(textExtractProps(params), { - onchangeOnKeydown: archParseBoolean(params.attrs.onchange_on_keydown), - keydownDebounceDelay: params.attrs.keydown_debounce_delay - ? Number(params.attrs.keydown_debounce_delay) +textField.extractProps = (fieldInfo) => { + return Object.assign(textExtractProps(fieldInfo), { + onchangeOnKeydown: archParseBoolean(fieldInfo.attrs.onchange_on_keydown), + keydownDebounceDelay: fieldInfo.attrs.keydown_debounce_delay + ? Number(fieldInfo.attrs.keydown_debounce_delay) : 2000, }); }; diff --git a/addons/mail/static/src/views/fields/many2one_avatar_user_field/many2one_avatar_user_field.js b/addons/mail/static/src/views/fields/many2one_avatar_user_field/many2one_avatar_user_field.js index 82bf6d80145..7ea2f78989e 100644 --- a/addons/mail/static/src/views/fields/many2one_avatar_user_field/many2one_avatar_user_field.js +++ b/addons/mail/static/src/views/fields/many2one_avatar_user_field/many2one_avatar_user_field.js @@ -50,9 +50,9 @@ KanbanMany2OneAvatarUserField.props = { export const kanbanMany2OneAvatarUserField = { ...many2OneAvatarUserField, component: KanbanMany2OneAvatarUserField, - extractProps: (params) => ({ - ...many2OneAvatarUserField.extractProps(params), - displayAvatarName: params.attrs.options.display_avatar_name || false, + extractProps: (fieldInfo) => ({ + ...many2OneAvatarUserField.extractProps(fieldInfo), + displayAvatarName: fieldInfo.options.display_avatar_name || false, }), }; diff --git a/addons/mass_mailing/static/src/js/mailing_m2o_filter.js b/addons/mass_mailing/static/src/js/mailing_m2o_filter.js index f4d442b8cbe..740100d5081 100644 --- a/addons/mass_mailing/static/src/js/mailing_m2o_filter.js +++ b/addons/mass_mailing/static/src/js/mailing_m2o_filter.js @@ -153,10 +153,10 @@ FieldMany2OneMailingFilter.defaultProps = { export const fieldMany2OneMailingFilter = { ...many2OneField, component: FieldMany2OneMailingFilter, - extractProps: (params) => ({ - ...many2OneField.extractProps(params), - domain_field: params.attrs.options.domain_field, - model_field: params.attrs.options.model_field, + extractProps: (fieldInfo) => ({ + ...many2OneField.extractProps(fieldInfo), + domain_field: fieldInfo.options.domain_field, + model_field: fieldInfo.options.model_field, }), }; diff --git a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js index 6786d416c5e..6730c93a57a 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js @@ -599,11 +599,11 @@ export const massMailingHtmlField = { ...htmlField, component: MassMailingHtmlField, displayName: _lt("Email"), - extractProps: (params) => ({ - ...htmlField.extractProps(params), - filterTemplates: params.attrs.options.filterTemplates, - inlineField: params.attrs.options['inline-field'], - iframeHtmlClass: params.attrs['iframeHtmlClass'], + extractProps: (fieldInfo) => ({ + ...htmlField.extractProps(fieldInfo), + filterTemplates: fieldInfo.options.filterTemplates, + inlineField: fieldInfo.options['inline-field'], + iframeHtmlClass: fieldInfo.attrs.iframeHtmlClass, }), }; diff --git a/addons/sms/static/src/components/phone_field/phone_field.js b/addons/sms/static/src/components/phone_field/phone_field.js index a83b41d15b1..31b9a5a6148 100644 --- a/addons/sms/static/src/components/phone_field/phone_field.js +++ b/addons/sms/static/src/components/phone_field/phone_field.js @@ -20,10 +20,10 @@ patch(PhoneField, "sms.PhoneField", { }); const patchDescr = { - extractProps({ attrs }) { + extractProps(fieldInfo) { return { - ...this._super({ attrs }), - enableButton: attrs.options.enable_sms, + ...this._super(fieldInfo), + enableButton: fieldInfo.options.enable_sms, }; }, }; diff --git a/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.xml b/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.xml index 361a41d9484..0a169c4bd83 100644 --- a/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.xml +++ b/addons/web/static/src/views/calendar/calendar_common/calendar_common_popover.xml @@ -48,7 +48,7 @@ -
  • +
  • diff --git a/addons/web/static/src/views/fields/ace/ace_field.js b/addons/web/static/src/views/fields/ace/ace_field.js index 894d797b48e..76c2d0cb575 100644 --- a/addons/web/static/src/views/fields/ace/ace_field.js +++ b/addons/web/static/src/views/fields/ace/ace_field.js @@ -123,8 +123,8 @@ export const aceField = { component: AceField, displayName: _lt("Ace Editor"), supportedTypes: ["text"], - extractProps: ({ attrs }) => ({ - mode: attrs.options.mode, + extractProps: ({ options }) => ({ + mode: options.mode, }), }; diff --git a/addons/web/static/src/views/fields/badge/badge_field.js b/addons/web/static/src/views/fields/badge/badge_field.js index 473a52a0f35..1f5675c38d7 100644 --- a/addons/web/static/src/views/fields/badge/badge_field.js +++ b/addons/web/static/src/views/fields/badge/badge_field.js @@ -1,8 +1,9 @@ /** @odoo-module **/ import { _lt } from "@web/core/l10n/translation"; +import { evaluateExpr } from "@web/core/py_js/py"; import { registry } from "@web/core/registry"; -import { standardFieldProps } from "../standard_field_props"; +import { standardFieldProps } from "@web/views/fields/standard_field_props"; import { Component } from "@odoo/owl"; const formatters = registry.category("formatters"); @@ -11,6 +12,10 @@ export class BadgeField extends Component { static template = "web.BadgeField"; static props = { ...standardFieldProps, + decorations: { type: Object, optional: true }, + }; + static defaultProps = { + decorations: {}, }; get formattedValue() { @@ -21,8 +26,9 @@ export class BadgeField extends Component { } get classFromDecoration() { + const evalContext = this.props.record.evalContext; for (const decorationName in this.props.decorations) { - if (this.props.decorations[decorationName]) { + if (evaluateExpr(this.props.decorations[decorationName], evalContext)) { return `text-bg-${decorationName}`; } } @@ -34,6 +40,9 @@ export const badgeField = { component: BadgeField, displayName: _lt("Badge"), supportedTypes: ["selection", "many2one", "char"], + extractProps: ({ decorations }) => { + return { decorations }; + }, }; registry.category("fields").add("badge", badgeField); diff --git a/addons/web/static/src/views/fields/binary/binary_field.js b/addons/web/static/src/views/fields/binary/binary_field.js index 0eaccda2c0c..b1f64b6718a 100644 --- a/addons/web/static/src/views/fields/binary/binary_field.js +++ b/addons/web/static/src/views/fields/binary/binary_field.js @@ -69,8 +69,8 @@ export const binaryField = { component: BinaryField, displayName: _lt("File"), supportedTypes: ["binary"], - extractProps: ({ attrs }) => ({ - acceptedFileExtensions: attrs.options.accepted_file_extensions, + extractProps: ({ attrs, options }) => ({ + acceptedFileExtensions: options.accepted_file_extensions, fileNameField: attrs.filename, }), }; diff --git a/addons/web/static/src/views/fields/boolean_icon/boolean_icon_field.js b/addons/web/static/src/views/fields/boolean_icon/boolean_icon_field.js index 4d3e95c7286..338c688d994 100644 --- a/addons/web/static/src/views/fields/boolean_icon/boolean_icon_field.js +++ b/addons/web/static/src/views/fields/boolean_icon/boolean_icon_field.js @@ -28,8 +28,8 @@ export const booleanIconField = { component: BooleanIconField, displayName: _lt("Boolean Icon"), supportedTypes: ["boolean"], - extractProps: ({ attrs }) => ({ - icon: attrs.options.icon, + extractProps: ({ options }) => ({ + icon: options.icon, }), }; diff --git a/addons/web/static/src/views/fields/char/char_field.js b/addons/web/static/src/views/fields/char/char_field.js index 44db4909596..a7c77a3fd71 100644 --- a/addons/web/static/src/views/fields/char/char_field.js +++ b/addons/web/static/src/views/fields/char/char_field.js @@ -98,9 +98,9 @@ export const charField = { component: CharField, displayName: _lt("Text"), supportedTypes: ["char"], - extractProps: ({ attrs }) => ({ + extractProps: ({ attrs, options }) => ({ isPassword: archParseBoolean(attrs.password), - dynamicPlaceholder: attrs.options.dynamic_placeholder, + dynamicPlaceholder: options.dynamic_placeholder, autocomplete: attrs.autocomplete, placeholder: attrs.placeholder, }), diff --git a/addons/web/static/src/views/fields/date/date_field.js b/addons/web/static/src/views/fields/date/date_field.js index 3de21385ab9..b3028897d98 100644 --- a/addons/web/static/src/views/fields/date/date_field.js +++ b/addons/web/static/src/views/fields/date/date_field.js @@ -62,8 +62,8 @@ export const dateField = { component: DateField, displayName: _lt("Date"), supportedTypes: ["date", "datetime"], - extractProps: ({ attrs }) => ({ - pickerOptions: attrs.options.datepicker, + extractProps: ({ attrs, options }) => ({ + pickerOptions: options.datepicker, placeholder: attrs.placeholder, }), }; diff --git a/addons/web/static/src/views/fields/daterange/daterange_field.js b/addons/web/static/src/views/fields/daterange/daterange_field.js index 2d050aba75b..d9a114feb3a 100644 --- a/addons/web/static/src/views/fields/daterange/daterange_field.js +++ b/addons/web/static/src/views/fields/daterange/daterange_field.js @@ -154,11 +154,11 @@ export class DateRangeField extends Component { export const dateRangeField = { component: DateRangeField, supportedTypes: ["date", "datetime"], - extractProps: ({ attrs }) => ({ - relatedEndDateField: attrs.options.related_end_date, - relatedStartDateField: attrs.options.related_start_date, + extractProps: ({ attrs, options }) => ({ + relatedEndDateField: options.related_end_date, + relatedStartDateField: options.related_start_date, + formatType: options.format_type, placeholder: attrs.placeholder, - formatType: attrs.options.format_type, }), }; diff --git a/addons/web/static/src/views/fields/datetime/datetime_field.js b/addons/web/static/src/views/fields/datetime/datetime_field.js index b333ec6b6d1..7d884274f73 100644 --- a/addons/web/static/src/views/fields/datetime/datetime_field.js +++ b/addons/web/static/src/views/fields/datetime/datetime_field.js @@ -52,8 +52,8 @@ export const dateTimeField = { component: DateTimeField, displayName: _lt("Date & Time"), supportedTypes: ["datetime"], - extractProps: ({ attrs }) => ({ - pickerOptions: attrs.options.datepicker, + extractProps: ({ attrs, options }) => ({ + pickerOptions: options.datepicker, placeholder: attrs.placeholder, }), }; diff --git a/addons/web/static/src/views/fields/domain/domain_field.js b/addons/web/static/src/views/fields/domain/domain_field.js index e49f076dcf1..a28a018566f 100644 --- a/addons/web/static/src/views/fields/domain/domain_field.js +++ b/addons/web/static/src/views/fields/domain/domain_field.js @@ -144,9 +144,9 @@ export const domainField = { displayName: _lt("Domain"), supportedTypes: ["char"], isEmpty: () => false, - extractProps: ({ attrs }) => ({ - editInDialog: attrs.options.in_dialog, - resModel: attrs.options.model, + extractProps: ({ options }) => ({ + editInDialog: options.in_dialog, + resModel: options.model, }), }; diff --git a/addons/web/static/src/views/fields/field.js b/addons/web/static/src/views/fields/field.js index df7622b0756..5e92fab5dbd 100644 --- a/addons/web/static/src/views/fields/field.js +++ b/addons/web/static/src/views/fields/field.js @@ -18,39 +18,24 @@ const fieldRegistry = registry.category("fields"); class DefaultField extends Component {} DefaultField.template = xml``; -function getFieldClassFromRegistry(fieldType, widget, viewType, jsClass) { - if (jsClass && widget) { - const name = `${jsClass}.${widget}`; - if (fieldRegistry.contains(name)) { - return fieldRegistry.get(name); +function getFieldFromRegistry(fieldType, widget, viewType, jsClass) { + const prefixes = jsClass ? [jsClass, viewType, ""] : [viewType, ""]; + const findInRegistry = (key) => { + for (const prefix of prefixes) { + const _key = prefix ? `${prefix}.${key}` : key; + if (fieldRegistry.contains(_key)) { + return fieldRegistry.get(_key); + } } - } - if (viewType && widget) { - const name = `${viewType}.${widget}`; - if (fieldRegistry.contains(name)) { - return fieldRegistry.get(name); - } - } - + }; if (widget) { - if (fieldRegistry.contains(widget)) { - return fieldRegistry.get(widget); + const field = findInRegistry(widget); + if (field) { + return field; } console.warn(`Missing widget: ${widget} for field of type ${fieldType}`); } - - if (viewType && fieldType) { - const name = `${viewType}.${fieldType}`; - if (fieldRegistry.contains(name)) { - return fieldRegistry.get(name); - } - } - - if (fieldRegistry.contains(fieldType)) { - return fieldRegistry.get(fieldType); - } - - return { component: DefaultField }; + return findInRegistry(fieldType) || { component: DefaultField }; } export function fieldVisualFeedback(field, record, fieldName, fieldInfo) { @@ -75,10 +60,11 @@ export function fieldVisualFeedback(field, record, fieldName, fieldInfo) { export class Field extends Component { setup() { - this.field = this.props.fieldInfo.field; - if (!this.field) { + if (this.props.fieldInfo) { + this.field = this.props.fieldInfo.field; + } else { const fieldType = this.props.record.fields[this.props.name].type; - this.field = getFieldClassFromRegistry(fieldType, this.props.type); + this.field = getFieldFromRegistry(fieldType, this.props.type); } } @@ -88,7 +74,7 @@ export class Field extends Component { this.field, record, name, - fieldInfo + fieldInfo || {} ); const classNames = { o_field_widget: true, @@ -108,11 +94,13 @@ export class Field extends Component { // generate field decorations classNames (only if field-specific decorations // have been defined in an attribute, e.g. decoration-danger="other_field = 5") // only handle the text-decoration. - const { decorations } = fieldInfo; - const evalContext = record.evalContext; - for (const decoName in decorations) { - const value = evaluateExpr(decorations[decoName], evalContext); - classNames[getClassNameFromDecoration(decoName)] = value; + if (fieldInfo && fieldInfo.decorations) { + const { decorations } = fieldInfo; + const evalContext = record.evalContext; + for (const decoName in decorations) { + const value = evaluateExpr(decorations[decoName], evalContext); + classNames[getClassNameFromDecoration(decoName)] = value; + } } return classNames; @@ -125,28 +113,22 @@ export class Field extends Component { get fieldComponentProps() { const record = this.props.record; const evalContext = record.evalContext; - const fieldInfo = this.props.fieldInfo; - const modifiers = fieldInfo.modifiers || {}; - const readonlyFromModifiers = evalDomain(modifiers.readonly, evalContext); + let readonlyFromModifiers = false; + let propsFromNode = {}; + if (this.props.fieldInfo) { + let fieldInfo = this.props.fieldInfo; - // Decoration props - const decorationMap = {}; - const { decorations } = fieldInfo; - for (const decoName in decorations) { - const value = evaluateExpr(decorations[decoName], evalContext); - decorationMap[decoName] = value; - } + const modifiers = fieldInfo.modifiers || {}; + readonlyFromModifiers = evalDomain(modifiers.readonly, evalContext); - let propsFromAttrs = fieldInfo.propsFromAttrs || {}; - if (this.props.attrs) { - const extractProps = this.field.extractProps || (() => ({})); - propsFromAttrs = extractProps.call(this.field, { - attrs: { - ...this.props.attrs, - options: evaluateExpr(this.props.attrs.options || "{}"), - }, - }); + if (this.props.attrs) { + fieldInfo = { + ...fieldInfo, + attrs: { ...fieldInfo.attrs, ...this.props.attrs }, + }; + } + propsFromNode = this.field.extractProps ? this.field.extractProps(fieldInfo) : {}; } const props = { ...this.props }; @@ -158,11 +140,9 @@ export class Field extends Component { delete props.type; return { - ...fieldInfo.props, value: this.props.record.data[this.props.name], - decorations: decorationMap, readonly: !record.isInEdition || readonlyFromModifiers || false, - ...propsFromAttrs, + ...propsFromNode, ...props, }; } @@ -171,7 +151,7 @@ export class Field extends Component { if (this.props.showTooltip) { const tooltip = getTooltipInfo({ field: this.props.record.fields[this.props.name], - fieldInfo: this.props.fieldInfo, + fieldInfo: this.props.fieldInfo || {}, }); if (Boolean(odoo.debug) || (tooltip && JSON.parse(tooltip).field.help)) { return tooltip; @@ -187,51 +167,35 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) { const widget = node.getAttribute("widget"); const fields = models[modelName]; const modifiers = JSON.parse(node.getAttribute("modifiers") || "{}"); - const field = getFieldClassFromRegistry(fields[name].type, widget, viewType, jsClass); + const field = getFieldFromRegistry(fields[name].type, widget, viewType, jsClass); const fieldInfo = { name, viewType, + widget, + modifiers, + field, context: node.getAttribute("context") || "{}", string: node.getAttribute("string") || fields[name].string, help: node.getAttribute("help"), - widget, - modifiers, onChange: archParseBoolean(node.getAttribute("on_change")), - field, forceSave: archParseBoolean(node.getAttribute("force_save")), - decorations: {}, // populated below - noLabel: archParseBoolean(node.getAttribute("nolabel")), - props: {}, - rawAttrs: {}, options: evaluateExpr(node.getAttribute("options") || "{}"), alwaysInvisible: modifiers.invisible === true || modifiers.column_invisible === true, + decorations: {}, // populated below + attrs: {}, // populated below }; if (node.getAttribute("domain")) { + // TODO WOWl: remove with new model? fieldInfo.domain = node.getAttribute("domain"); } - for (const attribute of node.attributes) { - if (attribute.name in Field.forbiddenAttributeNames) { - throw new Error(Field.forbiddenAttributeNames[attribute.name]); + for (const { name, value } of node.attributes) { + if (name.startsWith("decoration-")) { + // prepare field decorations + fieldInfo.decorations[name.replace("decoration-", "")] = value; + } else if (!name.startsWith("t-att")) { + // all other (non dynamic) attributes + fieldInfo.attrs[name] = value; } - - // prepare field decorations - if (attribute.name.startsWith("decoration-")) { - const decorationName = attribute.name.replace("decoration-", ""); - fieldInfo.decorations[decorationName] = attribute.value; - continue; - } - - if (!attribute.name.startsWith("t-att")) { - fieldInfo.rawAttrs[attribute.name] = attribute.value; - } - } - - if (viewType !== "kanban") { - // FIXME WOWL: find a better solution - const extractProps = field.extractProps || (() => ({})); - fieldInfo.propsFromAttrs = extractProps.call(field, { - attrs: { ...fieldInfo.rawAttrs, options: fieldInfo.options }, - }); } if (X2M_TYPES.includes(fields[name].type)) { @@ -262,7 +226,6 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) { viewMode = viewMode.replace("tree", "list"); } fieldInfo.viewMode = viewMode; - fieldInfo.relation = fields[name].relation; // not really necessary fieldInfo.views = views; let fieldsToFetch = field.fieldsToFetch; @@ -277,8 +240,5 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) { return fieldInfo; }; -Field.forbiddenAttributeNames = { - decorations: `You cannot use the "decorations" attribute name as it is used as generated prop name for the composite decoration- attributes.`, -}; Field.props = ["fieldInfo?", "*"]; -Field.defaultProps = { fieldInfo: {}, setDirty: () => {} }; +Field.defaultProps = { setDirty: () => {} }; diff --git a/addons/web/static/src/views/fields/float/float_field.js b/addons/web/static/src/views/fields/float/float_field.js index e7c5999e58f..5f0767b2095 100644 --- a/addons/web/static/src/views/fields/float/float_field.js +++ b/addons/web/static/src/views/fields/float/float_field.js @@ -57,19 +57,19 @@ export const floatField = { displayName: _lt("Float"), supportedTypes: ["float"], isEmpty: () => false, - extractProps: ({ attrs }) => { + extractProps: ({ attrs, options }) => { // Sadly, digits param was available as an option and an attr. // The option version could be removed with some xml refactoring. let digits; if (attrs.digits) { digits = JSON.parse(attrs.digits); - } else if (attrs.options.digits) { - digits = attrs.options.digits; + } else if (options.digits) { + digits = options.digits; } return { - inputType: attrs.options.type, - step: attrs.options.step, + inputType: options.type, + step: options.step, digits, placeholder: attrs.placeholder, }; diff --git a/addons/web/static/src/views/fields/float_factor/float_factor_field.js b/addons/web/static/src/views/fields/float_factor/float_factor_field.js index f95504e31f2..4e0b4c290c3 100644 --- a/addons/web/static/src/views/fields/float_factor/float_factor_field.js +++ b/addons/web/static/src/views/fields/float_factor/float_factor_field.js @@ -29,9 +29,9 @@ export class FloatFactorField extends FloatField { export const floatFactorField = { ...floatField, component: FloatFactorField, - extractProps: (params) => ({ - ...floatField.extractProps(params), - factor: params.attrs.options.factor, + extractProps: (fieldInfo) => ({ + ...floatField.extractProps(fieldInfo), + factor: fieldInfo.options.factor, }), }; diff --git a/addons/web/static/src/views/fields/float_toggle/float_toggle_field.js b/addons/web/static/src/views/fields/float_toggle/float_toggle_field.js index 0a7212c64c7..9b6e7b0e874 100644 --- a/addons/web/static/src/views/fields/float_toggle/float_toggle_field.js +++ b/addons/web/static/src/views/fields/float_toggle/float_toggle_field.js @@ -54,21 +54,21 @@ export const floatToggleField = { component: FloatToggleField, supportedTypes: ["float"], isEmpty: () => false, - extractProps: ({ attrs }) => { + extractProps: ({ attrs, options }) => { // Sadly, digits param was available as an option and an attr. // The option version could be removed with some xml refactoring. let digits; if (attrs.digits) { digits = JSON.parse(attrs.digits); - } else if (attrs.options.digits) { - digits = attrs.options.digits; + } else if (options.digits) { + digits = options.digits; } return { digits, - range: attrs.options.range, - factor: attrs.options.factor, - disableReadOnly: attrs.options.force_button || false, + range: options.range, + factor: options.factor, + disableReadOnly: options.force_button || false, }; }, }; diff --git a/addons/web/static/src/views/fields/image/image_field.js b/addons/web/static/src/views/fields/image/image_field.js index b4b48923246..da63b46ef2a 100644 --- a/addons/web/static/src/views/fields/image/image_field.js +++ b/addons/web/static/src/views/fields/image/image_field.js @@ -133,21 +133,14 @@ export const imageField = { displayName: _lt("Image"), supportedTypes: ["binary"], fieldDependencies: [{ name: "write_date", type: "datetime" }], - extractProps: ({ attrs }) => ({ - enableZoom: attrs.options.zoom, - zoomDelay: attrs.options.zoom_delay, - previewImage: attrs.options.preview_image, - acceptedFileExtensions: attrs.options.accepted_file_extensions, - width: - attrs.options.size && Boolean(attrs.options.size[0]) - ? attrs.options.size[0] - : attrs.width, - height: - attrs.options.size && Boolean(attrs.options.size[1]) - ? attrs.options.size[1] - : attrs.height, + extractProps: ({ attrs, options }) => ({ + enableZoom: options.zoom, + zoomDelay: options.zoom_delay, + previewImage: options.preview_image, + acceptedFileExtensions: options.accepted_file_extensions, + width: options.size && Boolean(options.size[0]) ? options.size[0] : attrs.width, + height: options.size && Boolean(options.size[1]) ? options.size[1] : attrs.height, }), }; registry.category("fields").add("image", imageField); -registry.category("fields").add("kanban.image", imageField); // FIXME WOWL: s.t. we don't use the legacy one diff --git a/addons/web/static/src/views/fields/image_url/image_url_field.js b/addons/web/static/src/views/fields/image_url/image_url_field.js index eab3bca39f1..c13e542e4d4 100644 --- a/addons/web/static/src/views/fields/image_url/image_url_field.js +++ b/addons/web/static/src/views/fields/image_url/image_url_field.js @@ -53,12 +53,10 @@ export const imageUrlField = { component: ImageUrlField, displayName: _lt("Image"), supportedTypes: ["char"], - extractProps: ({ attrs }) => ({ - width: attrs.options.size ? attrs.options.size[0] : attrs.width, - height: attrs.options.size ? attrs.options.size[1] : attrs.height, + extractProps: ({ attrs, options }) => ({ + width: options.size ? options.size[0] : attrs.width, + height: options.size ? options.size[1] : attrs.height, }), }; registry.category("fields").add("image_url", imageUrlField); -// TODO WOWL: remove below when old registry is removed. -registry.category("fields").add("kanban.image_url", imageUrlField); diff --git a/addons/web/static/src/views/fields/integer/integer_field.js b/addons/web/static/src/views/fields/integer/integer_field.js index f8e7338bca9..ba47eb82586 100644 --- a/addons/web/static/src/views/fields/integer/integer_field.js +++ b/addons/web/static/src/views/fields/integer/integer_field.js @@ -44,9 +44,9 @@ export const integerField = { displayName: _lt("Integer"), supportedTypes: ["integer"], isEmpty: (record, fieldName) => record.data[fieldName] === false, - extractProps: ({ attrs }) => ({ - inputType: attrs.options.type, - step: attrs.options.step, + extractProps: ({ attrs, options }) => ({ + inputType: options.type, + step: options.step, placeholder: attrs.placeholder, }), }; diff --git a/addons/web/static/src/views/fields/label_selection/label_selection_field.js b/addons/web/static/src/views/fields/label_selection/label_selection_field.js index 263bbd98457..59ab13b1106 100644 --- a/addons/web/static/src/views/fields/label_selection/label_selection_field.js +++ b/addons/web/static/src/views/fields/label_selection/label_selection_field.js @@ -31,8 +31,8 @@ export const labelSelectionField = { component: LabelSelectionField, displayName: _lt("Label Selection"), supportedTypes: ["selection"], - extractProps: ({ attrs }) => ({ - classesObj: attrs.options.classes, + extractProps: ({ options }) => ({ + classesObj: options.classes, }), }; diff --git a/addons/web/static/src/views/fields/many2many_binary/many2many_binary_field.js b/addons/web/static/src/views/fields/many2many_binary/many2many_binary_field.js index 0d9a396cb98..6af81cde805 100644 --- a/addons/web/static/src/views/fields/many2many_binary/many2many_binary_field.js +++ b/addons/web/static/src/views/fields/many2many_binary/many2many_binary_field.js @@ -66,8 +66,8 @@ export const many2ManyBinaryField = { { name: "name", type: "char" }, { name: "mimetype", type: "char" }, ], - extractProps: ({ attrs }) => ({ - acceptedFileExtensions: attrs.options.accepted_file_extensions, + extractProps: ({ attrs, options }) => ({ + acceptedFileExtensions: options.accepted_file_extensions, className: attrs.class, }), }; diff --git a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js index 781864a3bb4..bd320a8e5f2 100644 --- a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js +++ b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js @@ -250,25 +250,25 @@ export const many2ManyTagsField = { displayName: _lt("Tags"), supportedTypes: ["many2many"], isSet: (value) => value.count > 0, - fieldsToFetch: (fieldInfo) => { + fieldsToFetch: ({ options }) => { const fieldsToFetch = [{ name: "display_name", type: "char" }]; - if (fieldInfo.options.color_field) { - fieldsToFetch.push({ name: fieldInfo.options.color_field, type: "integer" }); + if (options.color_field) { + fieldsToFetch.push({ name: options.color_field, type: "integer" }); } return fieldsToFetch; }, - extractProps: ({ attrs }) => { - const noCreate = Boolean(attrs.options.no_create); + extractProps: ({ attrs, options }) => { + const noCreate = Boolean(options.no_create); const canCreate = attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !noCreate; - const noQuickCreate = Boolean(attrs.options.no_quick_create); - const noCreateEdit = Boolean(attrs.options.no_create_edit); + const noQuickCreate = Boolean(options.no_quick_create); + const noCreateEdit = Boolean(options.no_create_edit); return { - colorField: attrs.options.color_field, - nameCreateField: attrs.options.create_name_field, + colorField: options.color_field, + nameCreateField: options.create_name_field, canCreate, canQuickCreate: canCreate && !noQuickCreate, canCreateEdit: canCreate && !noCreateEdit, - createDomain: attrs.options.create, + createDomain: options.create, placeholder: attrs.placeholder, }; }, @@ -359,9 +359,9 @@ export class Many2ManyTagsFieldColorEditable extends Many2ManyTagsField { export const many2ManyTagsFieldColorEditable = { ...many2ManyTagsField, component: Many2ManyTagsFieldColorEditable, - extractProps: (params) => ({ - ...many2ManyTagsField.extractProps(params), - canEditColor: !params.attrs.options.no_edit_color && !!params.attrs.options.color_field, + extractProps: (fieldInfo) => ({ + ...many2ManyTagsField.extractProps(fieldInfo), + canEditColor: !fieldInfo.options.no_edit_color && !!fieldInfo.options.color_field, }), }; diff --git a/addons/web/static/src/views/fields/many2one/many2one_field.js b/addons/web/static/src/views/fields/many2one/many2one_field.js index c92f62b3ecb..360034e22c2 100644 --- a/addons/web/static/src/views/fields/many2one/many2one_field.js +++ b/addons/web/static/src/views/fields/many2one/many2one_field.js @@ -297,20 +297,20 @@ export const many2OneField = { component: Many2OneField, displayName: _lt("Many2one"), supportedTypes: ["many2one"], - extractProps: ({ attrs }) => { + extractProps: ({ attrs, options, string }) => { const canCreate = - attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !attrs.options.no_create; + attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !options.no_create; return { placeholder: attrs.placeholder, - canOpen: !attrs.options.no_open, + canOpen: !options.no_open, canCreate, canWrite: attrs.can_write && Boolean(JSON.parse(attrs.can_write)), - canQuickCreate: canCreate && !attrs.options.no_quick_create, - canCreateEdit: canCreate && !attrs.options.no_create_edit, - nameCreateField: attrs.options.create_name_field, - canScanBarcode: !!attrs.options.can_scan_barcode, + canQuickCreate: canCreate && !options.no_quick_create, + canCreateEdit: canCreate && !options.no_create_edit, + nameCreateField: options.create_name_field, + canScanBarcode: !!options.can_scan_barcode, openTarget: attrs.open_target, - string: attrs.string, + string, }; }, }; diff --git a/addons/web/static/src/views/fields/many2one_barcode/many2one_barcode_field.js b/addons/web/static/src/views/fields/many2one_barcode/many2one_barcode_field.js index bff69c3b110..aa0295e740b 100644 --- a/addons/web/static/src/views/fields/many2one_barcode/many2one_barcode_field.js +++ b/addons/web/static/src/views/fields/many2one_barcode/many2one_barcode_field.js @@ -15,8 +15,8 @@ export const many2OneBarcodeField = { ...many2OneField, component: Many2OneBarcodeField, displayName: _lt("Many2OneBarcode"), - extractProps: (params) => ({ - ...many2OneField.extractProps(params), + extractProps: (fieldInfo) => ({ + ...many2OneField.extractProps(fieldInfo), canScanBarcode: true, }), }; diff --git a/addons/web/static/src/views/fields/monetary/monetary_field.js b/addons/web/static/src/views/fields/monetary/monetary_field.js index 22b2973b8a7..1dc75a37787 100644 --- a/addons/web/static/src/views/fields/monetary/monetary_field.js +++ b/addons/web/static/src/views/fields/monetary/monetary_field.js @@ -80,11 +80,11 @@ export const monetaryField = { component: MonetaryField, supportedTypes: ["monetary", "float"], displayName: _lt("Monetary"), - extractProps: ({ attrs }) => ({ - currencyField: attrs.options.currency_field, + extractProps: ({ attrs, options }) => ({ + currencyField: options.currency_field, inputType: attrs.type, - useFieldDigits: attrs.options.field_digits, - hideSymbol: attrs.options.no_symbol, + useFieldDigits: options.field_digits, + hideSymbol: options.no_symbol, placeholder: attrs.placeholder, }), }; diff --git a/addons/web/static/src/views/fields/pdf_viewer/pdf_viewer_field.js b/addons/web/static/src/views/fields/pdf_viewer/pdf_viewer_field.js index e629d5cad51..431bc2063bf 100644 --- a/addons/web/static/src/views/fields/pdf_viewer/pdf_viewer_field.js +++ b/addons/web/static/src/views/fields/pdf_viewer/pdf_viewer_field.js @@ -89,9 +89,9 @@ export const pdfViewerField = { component: PdfViewerField, displayName: _lt("PDF Viewer"), supportedTypes: ["binary"], - extractProps: ({ attrs }) => ({ + extractProps: ({ attrs, options }) => ({ fileNameField: attrs.filename, - previewImage: attrs.options.preview_image, + previewImage: options.preview_image, }), }; diff --git a/addons/web/static/src/views/fields/percent_pie/percent_pie_field.js b/addons/web/static/src/views/fields/percent_pie/percent_pie_field.js index 64c2de3bfae..7d219ee8e2c 100644 --- a/addons/web/static/src/views/fields/percent_pie/percent_pie_field.js +++ b/addons/web/static/src/views/fields/percent_pie/percent_pie_field.js @@ -28,9 +28,7 @@ export const percentPieField = { displayName: _lt("PercentPie"), supportedTypes: ["float", "integer"], additionalClasses: ["o_field_percent_pie"], - extractProps: ({ attrs }) => ({ - string: attrs.string, - }), + extractProps: ({ string }) => ({ string }), }; registry.category("fields").add("percentpie", percentPieField); diff --git a/addons/web/static/src/views/fields/percentage/percentage_field.js b/addons/web/static/src/views/fields/percentage/percentage_field.js index ff02f831576..481629c99dc 100644 --- a/addons/web/static/src/views/fields/percentage/percentage_field.js +++ b/addons/web/static/src/views/fields/percentage/percentage_field.js @@ -46,14 +46,14 @@ export const percentageField = { component: PercentageField, displayName: _lt("Percentage"), supportedTypes: ["integer", "float"], - extractProps: ({ attrs }) => { + extractProps: ({ attrs, options }) => { // Sadly, digits param was available as an option and an attr. // The option version could be removed with some xml refactoring. let digits; if (attrs.digits) { digits = JSON.parse(attrs.digits); - } else if (attrs.options.digits) { - digits = attrs.options.digits; + } else if (options.digits) { + digits = options.digits; } return { diff --git a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js index c9861843f01..0b3dc0ed5c5 100644 --- a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js +++ b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js @@ -155,15 +155,14 @@ export const progressBarField = { component: ProgressBarField, displayName: _lt("Progress Bar"), supportedTypes: ["integer", "float"], - extractProps: ({ attrs }) => ({ - maxValueField: attrs.options.max_value, - currentValueField: attrs.options.current_value, - isEditable: !attrs.options.readonly && attrs.options.editable, - isEditableInReadonly: attrs.options.editable_readonly, + extractProps: ({ attrs, options }) => ({ + maxValueField: options.max_value, + currentValueField: options.current_value, + isEditable: !options.readonly && options.editable, + isEditableInReadonly: options.editable_readonly, isCurrentValueEditable: - attrs.options.editable && - (!attrs.options.edit_max_value || attrs.options.edit_current_value), - isMaxValueEditable: attrs.options.editable && attrs.options.edit_max_value, + options.editable && (!options.edit_max_value || options.edit_current_value), + isMaxValueEditable: options.editable && options.edit_max_value, title: attrs.title, }), }; diff --git a/addons/web/static/src/views/fields/radio/radio_field.js b/addons/web/static/src/views/fields/radio/radio_field.js index ac42fb9a6db..4edab220a11 100644 --- a/addons/web/static/src/views/fields/radio/radio_field.js +++ b/addons/web/static/src/views/fields/radio/radio_field.js @@ -72,8 +72,8 @@ export const radioField = { displayName: _lt("Radio"), supportedTypes: ["many2one", "selection"], isEmpty: (record, fieldName) => record.data[fieldName] === false, - extractProps: ({ attrs }) => ({ - orientation: attrs.options.horizontal ? "horizontal" : "vertical", + extractProps: ({ options }) => ({ + orientation: options.horizontal ? "horizontal" : "vertical", }), legacySpecialData: "_fetchSpecialMany2ones", }; diff --git a/addons/web/static/src/views/fields/reference/reference_field.js b/addons/web/static/src/views/fields/reference/reference_field.js index f88c073d7a7..6acee8fae91 100644 --- a/addons/web/static/src/views/fields/reference/reference_field.js +++ b/addons/web/static/src/views/fields/reference/reference_field.js @@ -128,7 +128,7 @@ export const referenceField = { displayName: _lt("Reference"), supportedTypes: ["reference", "char"], legacySpecialData: "_fetchSpecialReference", - extractProps: (params) => ({ + extractProps: (fieldInfo) => ({ /* 1 - 2 - @@ -137,9 +137,8 @@ export const referenceField = { We want to display the model selector only in the 4th case. */ - ...many2OneField.extractProps(params), - hideModelSelector: - !!params.attrs.options["hide_model"] || !!params.attrs.options["model_field"], + ...many2OneField.extractProps(fieldInfo), + hideModelSelector: !!fieldInfo.options.hide_model || !!fieldInfo.options.model_field, }), }; diff --git a/addons/web/static/src/views/fields/signature/signature_field.js b/addons/web/static/src/views/fields/signature/signature_field.js index 06a37b80bac..1afad51c821 100644 --- a/addons/web/static/src/views/fields/signature/signature_field.js +++ b/addons/web/static/src/views/fields/signature/signature_field.js @@ -136,12 +136,12 @@ export class SignatureField extends Component { export const signatureField = { component: SignatureField, - extractProps: ({ attrs }) => ({ - defaultFont: attrs.options.default_font || "", - fullName: attrs.options.full_name, - height: attrs.options.size ? attrs.options.size[1] || undefined : attrs.height, - previewImage: attrs.options.preview_image, - width: attrs.options.size ? attrs.options.size[0] || undefined : attrs.width, + extractProps: ({ attrs, options }) => ({ + defaultFont: options.default_font || "", + fullName: options.full_name, + height: options.size ? options.size[1] || undefined : attrs.height, + previewImage: options.preview_image, + width: options.size ? options.size[0] || undefined : attrs.width, }), }; diff --git a/addons/web/static/src/views/fields/standard_field_props.js b/addons/web/static/src/views/fields/standard_field_props.js index 589ccc805ef..8343503d3e8 100644 --- a/addons/web/static/src/views/fields/standard_field_props.js +++ b/addons/web/static/src/views/fields/standard_field_props.js @@ -6,6 +6,5 @@ export const standardFieldProps = { readonly: { type: Boolean, optional: true }, record: { type: Object }, value: true, - decorations: { type: Object, optional: true }, setDirty: { type: Function, optional: true }, }; diff --git a/addons/web/static/src/views/fields/stat_info/stat_info_field.js b/addons/web/static/src/views/fields/stat_info/stat_info_field.js index 02fc06d6607..7f350a08756 100644 --- a/addons/web/static/src/views/fields/stat_info/stat_info_field.js +++ b/addons/web/static/src/views/fields/stat_info/stat_info_field.js @@ -37,19 +37,19 @@ export const statInfoField = { displayName: _lt("Stat Info"), supportedTypes: ["float", "integer", "monetary"], isEmpty: () => false, - extractProps: ({ attrs }) => { + extractProps: ({ attrs, options }) => { // Sadly, digits param was available as an option and an attr. // The option version could be removed with some xml refactoring. let digits; if (attrs.digits) { digits = JSON.parse(attrs.digits); - } else if (attrs.options.digits) { - digits = attrs.options.digits; + } else if (options.digits) { + digits = options.digits; } return { digits, - labelField: attrs.options.label_field, + labelField: options.label_field, noLabel: archParseBoolean(attrs.nolabel), }; }, diff --git a/addons/web/static/src/views/fields/state_selection/state_selection_field.js b/addons/web/static/src/views/fields/state_selection/state_selection_field.js index acd97912c04..30827b56f24 100644 --- a/addons/web/static/src/views/fields/state_selection/state_selection_field.js +++ b/addons/web/static/src/views/fields/state_selection/state_selection_field.js @@ -91,8 +91,8 @@ export const stateSelectionField = { component: StateSelectionField, displayName: _lt("Label Selection"), supportedTypes: ["selection"], - extractProps: ({ attrs }) => ({ - hideLabel: !!attrs.options.hide_label, + extractProps: ({ options }) => ({ + hideLabel: !!options.hide_label, }), }; diff --git a/addons/web/static/src/views/fields/statusbar/statusbar_field.js b/addons/web/static/src/views/fields/statusbar/statusbar_field.js index dae47071984..31b0abf3b2f 100644 --- a/addons/web/static/src/views/fields/statusbar/statusbar_field.js +++ b/addons/web/static/src/views/fields/statusbar/statusbar_field.js @@ -225,10 +225,10 @@ export const statusBarField = { supportedTypes: ["many2one", "selection"], isEmpty: (record, fieldName) => record.model.env.isSmall && !record.data[fieldName], legacySpecialData: "_fetchSpecialStatus", - extractProps: ({ attrs }) => ({ + extractProps: ({ attrs, options }) => ({ canCreate: Boolean(attrs.can_create), canWrite: Boolean(attrs.can_write), - isDisabled: !attrs.options.clickable, + isDisabled: !options.clickable, visibleSelection: attrs.statusbar_visible && attrs.statusbar_visible.trim().split(/\s*,\s*/g), }), diff --git a/addons/web/static/src/views/fields/text/text_field.js b/addons/web/static/src/views/fields/text/text_field.js index 7c400b82152..3290c464d86 100644 --- a/addons/web/static/src/views/fields/text/text_field.js +++ b/addons/web/static/src/views/fields/text/text_field.js @@ -129,9 +129,9 @@ export const textField = { component: TextField, displayName: _lt("Multiline Text"), supportedTypes: ["html", "text"], - extractProps: ({ attrs }) => ({ + extractProps: ({ attrs, options }) => ({ placeholder: attrs.placeholder, - dynamicPlaceholder: attrs.options.dynamic_placeholder, + dynamicPlaceholder: options.dynamic_placeholder, rowCount: attrs.rows && parseInteger(attrs.rows), }), }; diff --git a/addons/web/static/src/views/fields/timezone_mismatch/timezone_mismatch_field.js b/addons/web/static/src/views/fields/timezone_mismatch/timezone_mismatch_field.js index 5fcd79d2273..6d5e506b211 100644 --- a/addons/web/static/src/views/fields/timezone_mismatch/timezone_mismatch_field.js +++ b/addons/web/static/src/views/fields/timezone_mismatch/timezone_mismatch_field.js @@ -74,10 +74,10 @@ export const timezoneMismatchField = { ...selectionField, component: TimezoneMismatchField, additionalClasses: ["d-flex"], - extractProps: (params) => ({ - ...selectionField.extractProps(params), - tzOffsetField: params.attrs.options.tz_offset_field, - mismatchTitle: params.attrs.options.mismatch_title, + extractProps: (fieldInfo) => ({ + ...selectionField.extractProps(fieldInfo), + tzOffsetField: fieldInfo.options.tz_offset_field, + mismatchTitle: fieldInfo.options.mismatch_title, }), }; diff --git a/addons/web/static/src/views/fields/url/url_field.js b/addons/web/static/src/views/fields/url/url_field.js index 88194ad8c94..cdd8aa6edae 100644 --- a/addons/web/static/src/views/fields/url/url_field.js +++ b/addons/web/static/src/views/fields/url/url_field.js @@ -38,9 +38,9 @@ export const urlField = { component: UrlField, displayName: _lt("URL"), supportedTypes: ["char"], - extractProps: ({ attrs }) => ({ + extractProps: ({ attrs, options }) => ({ text: attrs.text, - websitePath: attrs.options.website_path, + websitePath: options.website_path, placeholder: attrs.placeholder, }), }; diff --git a/addons/web/static/src/views/list/list_arch_parser.js b/addons/web/static/src/views/list/list_arch_parser.js index 4487d840697..c664dc2c818 100644 --- a/addons/web/static/src/views/list/list_arch_parser.js +++ b/addons/web/static/src/views/list/list_arch_parser.js @@ -116,7 +116,9 @@ export class ListArchParser extends XMLParser { className: node.getAttribute("class"), // for oe_edit_only and oe_read_only optional: node.getAttribute("optional") || false, type: "field", - hasLabel: !(fieldInfo.noLabel || fieldInfo.field.noLabel), + hasLabel: !( + archParseBoolean(fieldInfo.attrs.nolabel) || fieldInfo.field.noLabel + ), label: (fieldInfo.widget && label && label.toString()) || fieldInfo.string, }); } @@ -133,7 +135,7 @@ export class ListArchParser extends XMLParser { name: widgetInfo.name, // FIXME: this is dumb, we encode it into a weird object so that the widget // can decode it later... - node: encodeObjectForTemplate({ attrs: widgetInfo.rawAttrs }).slice(1, -1), + node: encodeObjectForTemplate({ attrs: widgetInfo.attrs }).slice(1, -1), className: node.getAttribute("class") || "", }; columns.push({ diff --git a/addons/web/static/src/views/list/list_renderer.js b/addons/web/static/src/views/list/list_renderer.js index 2829c5906a7..51d982e3eb3 100644 --- a/addons/web/static/src/views/list/list_renderer.js +++ b/addons/web/static/src/views/list/list_renderer.js @@ -535,12 +535,12 @@ export class ListRenderer extends Component { if (type !== "integer" && type !== "float" && type !== "monetary") { continue; } - const { rawAttrs, widget } = column; + const { attrs, widget } = column; const func = - (rawAttrs.sum && "sum") || - (rawAttrs.avg && "avg") || - (rawAttrs.max && "max") || - (rawAttrs.min && "min"); + (attrs.sum && "sum") || + (attrs.avg && "avg") || + (attrs.max && "max") || + (attrs.min && "min"); if (func) { let aggregateValue = 0; if (func === "max") { @@ -556,11 +556,11 @@ export class ListRenderer extends Component { const formatter = formatters.get(widget, false) || formatters.get(type, false); const formatOptions = { - digits: rawAttrs.digits ? JSON.parse(rawAttrs.digits) : undefined, + digits: attrs.digits ? JSON.parse(attrs.digits) : undefined, escape: true, }; aggregates[fieldName] = { - help: rawAttrs[func], + help: attrs[func], value: formatter ? formatter(aggregateValue, formatOptions) : aggregateValue, }; } @@ -569,7 +569,7 @@ export class ListRenderer extends Component { } formatAggregateValue(group, column) { - const { widget, rawAttrs } = column; + const { widget, attrs } = column; const fieldType = this.props.list.fields[column.name].type; const aggregateValue = group.aggregates[column.name]; if (!(column.name in group.aggregates)) { @@ -577,7 +577,7 @@ export class ListRenderer extends Component { } const formatter = formatters.get(widget, false) || formatters.get(fieldType, false); const formatOptions = { - digits: rawAttrs.digits ? JSON.parse(rawAttrs.digits) : undefined, + digits: attrs.digits ? JSON.parse(attrs.digits) : undefined, escape: true, }; return formatter ? formatter(aggregateValue, formatOptions) : aggregateValue; @@ -677,12 +677,8 @@ export class ListRenderer extends Component { classNames.push("o_list_button"); } else if (column.type === "field") { classNames.push("o_field_cell"); - if ( - column.rawAttrs && - column.rawAttrs.class && - this.canUseFormatter(column, record) - ) { - classNames.push(column.rawAttrs.class); + if (column.attrs && column.attrs.class && this.canUseFormatter(column, record)) { + classNames.push(column.attrs.class); } const typeClass = FIELD_CLASSES[this.fields[column.name].type]; if (typeClass) { @@ -743,7 +739,7 @@ export class ListRenderer extends Component { } getFieldClass(column) { - return column.rawAttrs && column.rawAttrs.class; + return column.attrs && column.attrs.class; } getFormattedValue(column, record) { @@ -753,8 +749,8 @@ export class ListRenderer extends Component { const formatOptions = { escape: false, data: record.data, - isPassword: "password" in column.rawAttrs, - digits: column.rawAttrs.digits ? JSON.parse(column.rawAttrs.digits) : field.digits, + isPassword: "password" in column.attrs, + digits: column.attrs.digits ? JSON.parse(column.attrs.digits) : field.digits, field: record.fields[fieldName], }; return formatter(record.data[fieldName], formatOptions); @@ -1660,8 +1656,8 @@ export class ListRenderer extends Component { } calculateColumnWidth(column) { - if (column.options && column.rawAttrs.width) { - return { type: "absolute", value: column.rawAttrs.width }; + if (column.options && column.attrs.width) { + return { type: "absolute", value: column.attrs.width }; } if (column.type !== "field") { diff --git a/addons/web/static/src/views/relational_model.js b/addons/web/static/src/views/relational_model.js index 6170c951a5d..9aa5df223f2 100644 --- a/addons/web/static/src/views/relational_model.js +++ b/addons/web/static/src/views/relational_model.js @@ -69,7 +69,7 @@ const DEFAULT_QUICK_CREATE_VIEW = { export function isAllowedDateField(groupByField) { return ( ["date", "datetime"].includes(groupByField.type) && - archParseBoolean(groupByField.rawAttrs.allow_group_range_value) + archParseBoolean(groupByField.attrs.allow_group_range_value) ); } @@ -1483,7 +1483,7 @@ class DynamicList extends DataPoint { } const [groupByFieldName, granularity] = this.firstGroupBy.split(":"); return { - rawAttrs: {}, + attrs: {}, ...this.fields[groupByFieldName], ...this.activeFields[groupByFieldName], granularity: granularity, diff --git a/addons/web/static/src/views/utils.js b/addons/web/static/src/views/utils.js index 2e4d1e1865f..87a75bd15a4 100644 --- a/addons/web/static/src/views/utils.js +++ b/addons/web/static/src/views/utils.js @@ -28,7 +28,7 @@ export function addFieldDependencies(activeFields, fields, dependencies = []) { for (const dependency of dependencies) { const { name } = dependency; if (!(name in activeFields)) { - activeFields[name] = Object.assign({ name, rawAttrs: {} }, dependency, { + activeFields[name] = Object.assign({ name, attrs: {} }, dependency, { modifiers: { invisible: true }, }); } diff --git a/addons/web/static/src/views/widgets/standard_widget_props.js b/addons/web/static/src/views/widgets/standard_widget_props.js index b8f103bdc82..93ac34c18d7 100644 --- a/addons/web/static/src/views/widgets/standard_widget_props.js +++ b/addons/web/static/src/views/widgets/standard_widget_props.js @@ -5,6 +5,6 @@ export const standardWidgetProps = { record: { type: Object }, // TODO TO REMOVE - ListView - FGE options: { type: Object, optional: true }, - rawAttrs: { type: Object, optional: true }, + attrs: { type: Object, optional: true }, className: { type: String, optional: true }, }; diff --git a/addons/web/static/src/views/widgets/widget.js b/addons/web/static/src/views/widgets/widget.js index c82957ad1fd..108074c1e44 100644 --- a/addons/web/static/src/views/widgets/widget.js +++ b/addons/web/static/src/views/widgets/widget.js @@ -70,7 +70,7 @@ Widget.parseWidgetNode = function (node) { return { options: evaluateExpr(node.getAttribute("options") || "{}"), name, - rawAttrs: attrs, + attrs, widget, }; }; diff --git a/addons/web/static/tests/views/calendar/helpers.js b/addons/web/static/tests/views/calendar/helpers.js index 165e71f6d4c..c98f923216d 100644 --- a/addons/web/static/tests/views/calendar/helpers.js +++ b/addons/web/static/tests/views/calendar/helpers.js @@ -1,7 +1,9 @@ /** @odoo-module **/ import { uiService } from "@web/core/ui/ui_service"; +import { createElement } from "@web/core/utils/xml"; import { registry } from "@web/core/registry"; +import { Field } from "@web/views/fields/field"; import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env"; import { click, getFixture, mount, nextTick, triggerEvent } from "../../helpers/utils"; import { setupViewRegistries } from "@web/../tests/views/helpers"; @@ -206,6 +208,8 @@ export const FAKE_FIELDS = { }; function makeFakeModelState() { + const fakeFieldNode = createElement("field", { name: "name" }); + const fakeModels = { event: FAKE_FIELDS }; return { canCreate: true, canDelete: true, @@ -228,7 +232,7 @@ function makeFakeModelState() { hasEditDialog: false, hasQuickCreate: false, popoverFields: { - name: { rawAttrs: {}, options: {} }, + name: Field.parseFieldNode(fakeFieldNode, fakeModels, "event", "calendar"), }, rangeEnd: makeFakeDate().endOf("month"), rangeStart: makeFakeDate().startOf("month"), diff --git a/addons/web/static/tests/views/form/form_view_tests.js b/addons/web/static/tests/views/form/form_view_tests.js index b375089c625..0b9293bb8a0 100644 --- a/addons/web/static/tests/views/form/form_view_tests.js +++ b/addons/web/static/tests/views/form/form_view_tests.js @@ -3566,9 +3566,9 @@ QUnit.module("Views", (hooks) => { MyField.template = owl.xml`
    ok
    `; fieldRegistry.add("my_field", { component: MyField, - extractProps: function ({ attrs }) { - assert.deepEqual(attrs.options, { horizontal: true }); - return { horizontal: attrs.options.horizontal }; + extractProps: function ({ options }) { + assert.deepEqual(options, { horizontal: true }); + return { horizontal: options.horizontal }; }, }); diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index 67fa712e452..a3ccefc6929 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -866,7 +866,6 @@ QUnit.module("Views", (hooks) => { str: "some string", bool: "true", num: "4.5", - options: {}, field_id: "int_field", }); } diff --git a/addons/web_editor/static/src/js/backend/html_field.js b/addons/web_editor/static/src/js/backend/html_field.js index 24a188c2d84..4a15cf30a0f 100644 --- a/addons/web_editor/static/src/js/backend/html_field.js +++ b/addons/web_editor/static/src/js/backend/html_field.js @@ -630,49 +630,49 @@ export const htmlField = { component: HtmlField, displayName: _lt("Html"), supportedTypes: ["html"], - extractProps: ({ attrs }) => { + extractProps: ({ attrs, options }) => { const wysiwygOptions = { placeholder: attrs.placeholder, - noAttachment: attrs.options['no-attachment'], - inIframe: Boolean(attrs.options.cssEdit), - iframeCssAssets: attrs.options.cssEdit, + noAttachment: options['no-attachment'], + inIframe: Boolean(options.cssEdit), + iframeCssAssets: options.cssEdit, iframeHtmlClass: attrs.iframeHtmlClass, - snippets: attrs.options.snippets, + snippets: options.snippets, mediaModalParams: { - noVideos: 'noVideos' in attrs.options ? attrs.options.noVideos : true, + noVideos: 'noVideos' in options ? options.noVideos : true, useMediaLibrary: true, }, linkForceNewWindow: true, tabsize: 0, - height: attrs.options.height, - minHeight: attrs.options.minHeight, - maxHeight: attrs.options.maxHeight, - resizable: 'resizable' in attrs.options ? attrs.options.resizable : false, + height: options.height, + minHeight: options.minHeight, + maxHeight: options.maxHeight, + resizable: 'resizable' in options ? options.resizable : false, editorPlugins: [QWebPlugin], }; - if ('collaborative' in attrs.options) { - wysiwygOptions.collaborative = attrs.options.collaborative; + if ('collaborative' in options) { + wysiwygOptions.collaborative = options.collaborative; } - if ('allowCommandImage' in attrs.options) { + if ('allowCommandImage' in options) { // Set the option only if it is explicitly set in the view so a default // can be set elsewhere otherwise. - wysiwygOptions.allowCommandImage = Boolean(attrs.options.allowCommandImage); + wysiwygOptions.allowCommandImage = Boolean(options.allowCommandImage); } - if ('allowCommandVideo' in attrs.options) { + if ('allowCommandVideo' in options) { // Set the option only if it is explicitly set in the view so a default // can be set elsewhere otherwise. - wysiwygOptions.allowCommandVideo = Boolean(attrs.options.allowCommandVideo); + wysiwygOptions.allowCommandVideo = Boolean(options.allowCommandVideo); } return { - codeview: Boolean(odoo.debug && attrs.options.codeview), + codeview: Boolean(odoo.debug && options.codeview), placeholder: attrs.placeholder, - isCollaborative: attrs.options.collaborative, - cssReadonlyAssetId: attrs.options.cssReadonly, - dynamicPlaceholder: attrs.options.dynamic_placeholder, - cssEditAssetId: attrs.options.cssEdit, - isInlineStyle: attrs.options['style-inline'], - wrapper: attrs.options.wrapper, + isCollaborative: options.collaborative, + cssReadonlyAssetId: options.cssReadonly, + dynamicPlaceholder: options.dynamic_placeholder, + cssEditAssetId: options.cssEdit, + isInlineStyle: options['style-inline'], + wrapper: options.wrapper, wysiwygOptions, }; diff --git a/addons/web_kanban_gauge/static/src/gauge_field.js b/addons/web_kanban_gauge/static/src/gauge_field.js index eeca9aeb990..e7ee5ee6f46 100644 --- a/addons/web_kanban_gauge/static/src/gauge_field.js +++ b/addons/web_kanban_gauge/static/src/gauge_field.js @@ -94,9 +94,9 @@ GaugeField.props = { export const gaugeField = { component: GaugeField, - extractProps: ({ attrs }) => ({ - maxValueField: attrs.options.max_field, - title: attrs.options.title, + extractProps: ({ options }) => ({ + maxValueField: options.max_field, + title: options.title, }), }; diff --git a/addons/website/static/src/components/fields/fields.js b/addons/website/static/src/components/fields/fields.js index 3cd15eb92ca..8a6b3492ce9 100644 --- a/addons/website/static/src/components/fields/fields.js +++ b/addons/website/static/src/components/fields/fields.js @@ -99,8 +99,8 @@ ImageRadioField.props = { export const imageRadioField = { component: ImageRadioField, supportedTypes: ['selection'], - extractProps: ({ attrs }) => ({ - images: attrs.options.images, + extractProps: ({ options }) => ({ + images: options.images, }), };