From d85609501d2fe59b21ace21558f2c8367ae17e5d Mon Sep 17 00:00:00 2001 From: "Michael (mcm)" Date: Thu, 7 Jul 2022 12:56:03 +0000 Subject: [PATCH] [REF] web: change `field.extractProps` API Before this commit, the record was given to `field.extractProps` so it could return props based on the record. Now, only the attrs and field is given so it can be called only once when parsing the arch (except for kanban). closes odoo/odoo#95389 Signed-off-by: Aaron Bohy (aab) --- .../static/src/core/datepicker/datepicker.js | 1 + .../static/src/views/fields/ace/ace_field.js | 2 +- .../badge_selection/badge_selection_field.js | 39 ++--- .../badge_selection/badge_selection_field.xml | 5 +- .../src/views/fields/binary/binary_field.js | 57 +++---- .../src/views/fields/binary/binary_field.xml | 2 +- .../src/views/fields/boolean/boolean_field.js | 13 +- .../views/fields/boolean/boolean_field.xml | 2 +- .../boolean_favorite_field.js | 2 +- .../boolean_toggle/boolean_toggle_field.js | 11 +- .../boolean_toggle/boolean_toggle_field.xml | 2 +- .../list_boolean_toggle_field.js | 5 +- .../list_boolean_toggle_field.xml | 2 +- .../src/views/fields/char/char_field.js | 12 +- .../src/views/fields/char/char_field.xml | 4 +- .../src/views/fields/color/color_field.js | 10 +- .../src/views/fields/color/color_field.xml | 4 +- .../fields/color_picker/color_picker_field.js | 1 + .../src/views/fields/date/date_field.js | 12 +- .../src/views/fields/date/date_field.xml | 1 + .../views/fields/daterange/daterange_field.js | 60 ++++--- .../fields/daterange/daterange_field.xml | 2 +- .../views/fields/datetime/datetime_field.js | 4 +- .../views/fields/datetime/datetime_field.xml | 1 + .../src/views/fields/domain/domain_field.js | 49 +++--- .../src/views/fields/domain/domain_field.xml | 4 +- .../src/views/fields/email/email_field.js | 2 +- addons/web/static/src/views/fields/field.js | 27 +++- .../src/views/fields/float/float_field.js | 9 +- .../fields/float_factor/float_factor_field.js | 15 +- .../fields/float_time/float_time_field.js | 7 +- .../fields/float_toggle/float_toggle_field.js | 6 +- .../font_selection/font_selection_field.js | 17 +- .../font_selection/font_selection_field.xml | 10 +- .../src/views/fields/image/image_field.js | 10 +- .../views/fields/image_url/image_url_field.js | 2 +- .../src/views/fields/input_field_hook.js | 4 +- .../src/views/fields/integer/integer_field.js | 5 +- .../journal_dashboard_graph_field.js | 3 +- .../label_selection/label_selection_field.js | 8 +- .../label_selection/label_selection_field.xml | 2 +- .../many2many_binary_field.js | 5 +- .../many2many_checkboxes_field.js | 10 +- .../many2many_checkboxes_field.xml | 2 +- .../many2many_tags/many2many_tags_field.js | 43 ++--- .../many2many_tags/many2many_tags_field.xml | 4 +- .../many2many_tags_avatar_field.js | 31 +--- .../many2many_tags_avatar_field.xml | 4 +- .../views/fields/many2one/many2one_field.js | 40 ++--- .../views/fields/many2one/many2one_field.xml | 6 +- .../many2one_avatar/many2one_avatar_field.js | 20 +-- .../views/fields/monetary/monetary_field.js | 29 ++-- .../fields/pdf_viewer/pdf_viewer_field.js | 33 ++-- .../fields/percent_pie/percent_pie_field.js | 2 +- .../fields/percentage/percentage_field.js | 11 +- .../src/views/fields/phone/phone_field.js | 2 +- .../views/fields/priority/priority_field.js | 23 +-- .../views/fields/priority/priority_field.xml | 4 +- .../fields/progress_bar/progress_bar_field.js | 151 ++++++++++-------- .../progress_bar/progress_bar_field.xml | 26 +-- .../src/views/fields/radio/radio_field.js | 33 ++-- .../src/views/fields/radio/radio_field.xml | 4 +- .../views/fields/reference/reference_field.js | 98 +++++------- .../fields/reference/reference_field.xml | 2 +- .../views/fields/selection/selection_field.js | 33 ++-- .../fields/selection/selection_field.xml | 4 +- .../views/fields/stat_info/stat_info_field.js | 19 +-- .../fields/stat_info/stat_info_field.xml | 2 +- .../state_selection/state_selection_field.js | 36 +++-- .../state_selection/state_selection_field.xml | 6 +- .../views/fields/statusbar/statusbar_field.js | 36 ++--- .../src/views/fields/text/text_field.js | 32 +--- .../src/views/fields/text/text_field.xml | 4 +- .../static/src/views/fields/url/url_field.js | 2 +- .../src/views/fields/x2many/x2many_field.js | 2 +- addons/web/static/src/views/view_compiler.js | 12 +- .../fields/boolean_toggle_field_tests.js | 44 +++++ .../views/fields/progress_bar_field_tests.js | 6 +- .../tests/views/form/form_compiler_tests.js | 5 +- .../tests/views/form/form_view_tests.js | 2 +- .../static/tests/views/kanban_view_tests.js | 32 ++-- 81 files changed, 631 insertions(+), 663 deletions(-) diff --git a/addons/web/static/src/core/datepicker/datepicker.js b/addons/web/static/src/core/datepicker/datepicker.js index 48cc42b07f9..ca2097ebb77 100644 --- a/addons/web/static/src/core/datepicker/datepicker.js +++ b/addons/web/static/src/core/datepicker/datepicker.js @@ -320,6 +320,7 @@ DatePicker.props = { useCurrent: { type: Boolean, optional: true }, widgetParent: { type: String, optional: true }, daysOfWeekDisabled: { type: Array, optional: true }, + placeholder: { type: String, optional: true }, }; DatePicker.template = "web.DatePicker"; 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 f35597a171b..e213fbcba7f 100644 --- a/addons/web/static/src/views/fields/ace/ace_field.js +++ b/addons/web/static/src/views/fields/ace/ace_field.js @@ -101,7 +101,7 @@ AceField.defaultProps = { AceField.displayName = _lt("Ace Editor"); AceField.supportedTypes = ["text"]; -AceField.extractProps = (fieldName, record, attrs) => { +AceField.extractProps = ({ attrs }) => { return { mode: attrs.options.mode, }; diff --git a/addons/web/static/src/views/fields/badge_selection/badge_selection_field.js b/addons/web/static/src/views/fields/badge_selection/badge_selection_field.js index fa8a4ca560b..9df99813ee6 100644 --- a/addons/web/static/src/views/fields/badge_selection/badge_selection_field.js +++ b/addons/web/static/src/views/fields/badge_selection/badge_selection_field.js @@ -7,13 +7,27 @@ import { standardFieldProps } from "../standard_field_props"; const { Component } = owl; export class BadgeSelectionField extends Component { + get options() { + switch (this.props.record.fields[this.props.name].type) { + case "many2one": + // WOWL: conversion needed while we keep using the legacy model + return Object.values(this.props.record.preloadedData[this.props.name]).map((v) => { + return [v.id, v.display_name]; + }); + case "selection": + return this.props.record.fields[this.props.name].selection; + default: + return []; + } + } + get string() { switch (this.props.type) { case "many2one": return this.props.value ? this.props.value[1] : ""; case "selection": return this.props.value !== false - ? this.props.options.find((o) => o[0] === this.props.value)[1] + ? this.options.find((o) => o[0] === this.props.value)[1] : ""; default: return ""; @@ -37,7 +51,7 @@ export class BadgeSelectionField extends Component { if (value === false) { this.props.update(false); } else { - this.props.update(this.props.options.find((option) => option[0] === value)); + this.props.update(this.options.find((option) => option[0] === value)); } break; case "selection": @@ -50,33 +64,12 @@ export class BadgeSelectionField extends Component { BadgeSelectionField.template = "web.BadgeSelectionField"; BadgeSelectionField.props = { ...standardFieldProps, - horizontal: { type: Boolean, optional: true }, - options: Object, - placeholder: { type: String, optional: true }, }; BadgeSelectionField.displayName = _lt("Badges"); BadgeSelectionField.supportedTypes = ["many2one", "selection"]; BadgeSelectionField.isEmpty = (record, fieldName) => record.data[fieldName] === false; -BadgeSelectionField.extractProps = (fieldName, record, attrs) => { - const getOptions = () => { - switch (record.fields[fieldName].type) { - case "many2one": - // WOWL: conversion needed while we keep using the legacy model - return Object.values(record.preloadedData[fieldName]).map((v) => { - return [v.id, v.display_name]; - }); - case "selection": - return record.fields[fieldName].selection; - default: - return []; - } - }; - return { - options: getOptions(), - }; -}; registry.category("fields").add("selection_badge", BadgeSelectionField); diff --git a/addons/web/static/src/views/fields/badge_selection/badge_selection_field.xml b/addons/web/static/src/views/fields/badge_selection/badge_selection_field.xml index 43d80328198..452fd430380 100644 --- a/addons/web/static/src/views/fields/badge_selection/badge_selection_field.xml +++ b/addons/web/static/src/views/fields/badge_selection/badge_selection_field.xml @@ -6,9 +6,10 @@ - + { @@ -26,34 +26,52 @@ export class BinaryField extends Component { } }); } + + get fileName() { + return this.props.record.data[this.props.fileNameField]; + } get file() { return { - data: this.props.fileData, - name: this.state.fileName || this.props.fileData || null, + data: this.props.value, + name: this.state.fileName || this.props.value || null, }; } + get isDownloadable() { + return !( + this.props.record.isReadonly(this.props.name) && this.props.record.mode === "edit" + ); + } + + update(file) { + const changes = { [this.props.name]: file.data || false }; + if (this.props.fileNameField && this.props.fileNameField !== this.props.name) { + changes[this.props.fileNameField] = file.name || false; + } + return this.props.record.update(changes); + } + async onFileDownload() { await download({ data: { - model: this.props.resModel, - id: this.props.resId, + model: this.props.record.resModel, + id: this.props.record.resId, field: this.props.name, filename_field: this.file.name, filename: this.file.name || "", download: true, - data: isBinarySize(this.props.fileData) ? null : this.props.fileData, + data: isBinarySize(this.props.value) ? null : this.props.value, }, url: "/web/content", }); } onFileRemove() { this.state.isValid = true; - this.props.update(false); + this.update(false); } onFileUploaded(file) { this.state.fileName = file.name; this.state.isValid = true; - this.props.update(file); + this.update(file); } onLoadFailed() { this.state.isValid = false; @@ -71,36 +89,19 @@ BinaryField.components = { BinaryField.props = { ...standardFieldProps, acceptedFileExtensions: { type: String, optional: true }, - fileData: { type: String, optional: true }, - fileName: { type: String, optional: true }, - isDownloadable: { type: Boolean, optional: true }, - resId: { type: [Number, Boolean], optional: true }, - resModel: { type: String, optional: true }, + fileNameField: { type: String, optional: true }, }; BinaryField.defaultProps = { acceptedFileExtensions: "*", - fileData: "", - isDownloadable: true, }; BinaryField.displayName = _lt("File"); BinaryField.supportedTypes = ["binary"]; -BinaryField.extractProps = (fieldName, record, attrs) => { +BinaryField.extractProps = ({ attrs }) => { return { acceptedFileExtensions: attrs.options.accepted_file_extensions, - fileData: record.data[fieldName] || "", - fileName: record.data[attrs.filename] || "", - isDownloadable: !(record.isReadonly(fieldName) && record.mode === "edit"), - resId: record.resId, - resModel: record.resModel, - update: (file) => { - const changes = { [fieldName]: file.data || false }; - if (attrs.filename && attrs.filename !== fieldName) { - changes[attrs.filename] = file.name || false; - } - return record.update(changes); - }, + fileNameField: attrs.filename, }; }; diff --git a/addons/web/static/src/views/fields/binary/binary_field.xml b/addons/web/static/src/views/fields/binary/binary_field.xml index c96700d0ec6..db9f7021e67 100644 --- a/addons/web/static/src/views/fields/binary/binary_field.xml +++ b/addons/web/static/src/views/fields/binary/binary_field.xml @@ -38,7 +38,7 @@ - + diff --git a/addons/web/static/src/views/fields/boolean/boolean_field.js b/addons/web/static/src/views/fields/boolean/boolean_field.js index 29cf4b62dea..ed1ce848f74 100644 --- a/addons/web/static/src/views/fields/boolean/boolean_field.js +++ b/addons/web/static/src/views/fields/boolean/boolean_field.js @@ -8,6 +8,10 @@ import { CheckBox } from "@web/core/checkbox/checkbox"; const { Component } = owl; export class BooleanField extends Component { + get isReadonly() { + return !(this.props.record.isInEdition && !this.props.record.isReadonly(this.props.name)); + } + /** * @param {boolean} newValue */ @@ -20,20 +24,11 @@ BooleanField.template = "web.BooleanField"; BooleanField.components = { CheckBox }; BooleanField.props = { ...standardFieldProps, - isReadonlyEnabled: { type: Boolean, optional: true }, }; BooleanField.displayName = _lt("Checkbox"); BooleanField.supportedTypes = ["boolean"]; BooleanField.isEmpty = () => false; -BooleanField.extractProps = (fieldName, record) => { - return { - // should we make a list specialization? - readonly: !(record.isInEdition && !record.isReadonly(fieldName)), - isReadonlyEnabled: - record.activeFields[fieldName].viewType === "list" && !record.isReadonly(fieldName), - }; -}; registry.category("fields").add("boolean", BooleanField); diff --git a/addons/web/static/src/views/fields/boolean/boolean_field.xml b/addons/web/static/src/views/fields/boolean/boolean_field.xml index 81fa67fe142..db358c66f03 100644 --- a/addons/web/static/src/views/fields/boolean/boolean_field.xml +++ b/addons/web/static/src/views/fields/boolean/boolean_field.xml @@ -2,7 +2,7 @@ - + diff --git a/addons/web/static/src/views/fields/boolean_favorite/boolean_favorite_field.js b/addons/web/static/src/views/fields/boolean_favorite/boolean_favorite_field.js index b314ade9c80..855c516dcca 100644 --- a/addons/web/static/src/views/fields/boolean_favorite/boolean_favorite_field.js +++ b/addons/web/static/src/views/fields/boolean_favorite/boolean_favorite_field.js @@ -22,7 +22,7 @@ BooleanFavoriteField.displayName = _lt("Favorite"); BooleanFavoriteField.supportedTypes = ["boolean"]; BooleanFavoriteField.isEmpty = () => false; -BooleanFavoriteField.extractProps = (fieldName, record, attrs) => { +BooleanFavoriteField.extractProps = ({ attrs }) => { return { noLabel: archParseBoolean(attrs.nolabel), }; diff --git a/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js b/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js index 462ee427f37..7fddabbe030 100644 --- a/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js +++ b/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js @@ -7,7 +7,11 @@ import { standardFieldProps } from "../standard_field_props"; const { Component } = owl; -export class BooleanToggleField extends Component {} +export class BooleanToggleField extends Component { + get isReadonly() { + return this.props.record.isReadonly(this.props.name); + } +} BooleanToggleField.template = "web.BooleanToggleField"; BooleanToggleField.components = { CheckBox }; @@ -19,10 +23,5 @@ BooleanToggleField.displayName = _lt("Toggle"); BooleanToggleField.supportedTypes = ["boolean"]; BooleanToggleField.isEmpty = () => false; -BooleanToggleField.extractProps = (fieldName, record) => { - return { - readonly: record.isReadonly(fieldName), - }; -}; registry.category("fields").add("boolean_toggle", BooleanToggleField); diff --git a/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.xml b/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.xml index 7b7a7662656..af51fa1ccda 100644 --- a/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.xml +++ b/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.xml @@ -6,7 +6,7 @@ className="'o_field_boolean o_boolean_toggle'" id="props.id" value="props.value or false" - disabled="props.readonly" + disabled="isReadonly" onChange="props.update" > ​ diff --git a/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js b/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js index 1038867ccfd..8fa48458ff5 100644 --- a/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js +++ b/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js @@ -3,9 +3,7 @@ import { registry } from "@web/core/registry"; import { BooleanToggleField } from "./boolean_toggle_field"; -const { Component } = owl; - -export class ListBooleanToggleField extends Component { +export class ListBooleanToggleField extends BooleanToggleField { onClick() { if (!this.props.readonly) { this.props.update(!this.props.value); @@ -14,6 +12,5 @@ export class ListBooleanToggleField extends Component { } ListBooleanToggleField.template = "web.ListBooleanToggleField"; -ListBooleanToggleField.components = { BooleanToggleField }; registry.category("fields").add("list.boolean_toggle", ListBooleanToggleField); diff --git a/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.xml b/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.xml index 288bb316a3c..9446f654f64 100644 --- a/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.xml +++ b/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.xml @@ -3,7 +3,7 @@
- +
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 b206c1270b0..b02396baba4 100644 --- a/addons/web/static/src/views/fields/char/char_field.js +++ b/addons/web/static/src/views/fields/char/char_field.js @@ -39,20 +39,16 @@ CharField.props = { shouldTrim: { type: Boolean, optional: true }, maxLength: { type: Number, optional: true }, isTranslatable: { type: Boolean, optional: true }, - resId: { type: [Number, Boolean], optional: true }, - resModel: { type: String, optional: true }, }; CharField.displayName = _lt("Text"); CharField.supportedTypes = ["char"]; -CharField.extractProps = (fieldName, record, attrs) => { +CharField.extractProps = ({ attrs, field }) => { return { - shouldTrim: record.fields[fieldName].trim, - maxLength: record.fields[fieldName].size, - isTranslatable: record.fields[fieldName].translate, - resId: record.resId, - resModel: record.resModel, + shouldTrim: field.trim, + maxLength: field.size, + isTranslatable: field.translate, autocomplete: attrs.autocomplete, isPassword: archParseBoolean(attrs.password), placeholder: attrs.placeholder, diff --git a/addons/web/static/src/views/fields/char/char_field.xml b/addons/web/static/src/views/fields/char/char_field.xml index f01c33b5505..3f77912f466 100644 --- a/addons/web/static/src/views/fields/char/char_field.xml +++ b/addons/web/static/src/views/fields/char/char_field.xml @@ -19,8 +19,8 @@ diff --git a/addons/web/static/src/views/fields/color/color_field.js b/addons/web/static/src/views/fields/color/color_field.js index 653ffbe8387..b22a145ec99 100644 --- a/addons/web/static/src/views/fields/color/color_field.js +++ b/addons/web/static/src/views/fields/color/color_field.js @@ -11,17 +11,17 @@ export class ColorField extends Component { color: this.props.value || "#000000", }); } + + get isReadonly() { + return this.props.record.isReadonly(this.props.name); + } } ColorField.template = "web.ColorField"; ColorField.props = { ...standardFieldProps, }; -ColorField.extractProps = (fieldName, record) => { - return { - readonly: record.isReadonly(fieldName), - }; -}; + ColorField.supportedTypes = ["char"]; registry.category("fields").add("color", ColorField); diff --git a/addons/web/static/src/views/fields/color/color_field.xml b/addons/web/static/src/views/fields/color/color_field.xml index 7442de8d740..f6080c85fc2 100644 --- a/addons/web/static/src/views/fields/color/color_field.xml +++ b/addons/web/static/src/views/fields/color/color_field.xml @@ -2,8 +2,8 @@ -
- +
+
diff --git a/addons/web/static/src/views/fields/color_picker/color_picker_field.js b/addons/web/static/src/views/fields/color_picker/color_picker_field.js index b71f763358b..01c748121e0 100644 --- a/addons/web/static/src/views/fields/color_picker/color_picker_field.js +++ b/addons/web/static/src/views/fields/color_picker/color_picker_field.js @@ -19,6 +19,7 @@ ColorPickerField.components = { ColorPickerField.props = { ...standardFieldProps, }; + ColorPickerField.supportedTypes = ["integer"]; ColorPickerField.RECORD_COLORS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; 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 e68eb60ddc8..eb2ddeccd1d 100644 --- a/addons/web/static/src/views/fields/date/date_field.js +++ b/addons/web/static/src/views/fields/date/date_field.js @@ -9,6 +9,9 @@ import { standardFieldProps } from "../standard_field_props"; const { Component } = owl; export class DateField extends Component { + get isDateTime() { + return this.props.record.fields[this.props.name].type === "datetime"; + } get date() { return this.props.value && this.props.value.startOf("day"); } @@ -16,7 +19,7 @@ export class DateField extends Component { get formattedValue() { return formatDate(this.props.value, { // get local date if field type is datetime - timezone: this.props.isDateTime, + timezone: this.isDateTime, }); } @@ -33,21 +36,20 @@ DateField.components = { }; DateField.props = { ...standardFieldProps, - isDateTime: { type: Boolean, optional: true }, pickerOptions: { type: Object, optional: true }, + placeholder: { type: String, optional: true }, }; DateField.defaultProps = { - isDateTime: false, pickerOptions: {}, }; DateField.displayName = _lt("Date"); DateField.supportedTypes = ["date", "datetime"]; -DateField.extractProps = (fieldName, record, attrs) => { +DateField.extractProps = ({ attrs }) => { return { - isDateTime: record.fields[fieldName].type === "datetime", pickerOptions: attrs.options.datepicker, + placeholder: attrs.placeholder, }; }; diff --git a/addons/web/static/src/views/fields/date/date_field.xml b/addons/web/static/src/views/fields/date/date_field.xml index 196c44146fb..5326d2db462 100644 --- a/addons/web/static/src/views/fields/date/date_field.xml +++ b/addons/web/static/src/views/fields/date/date_field.xml @@ -9,6 +9,7 @@ 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 5117e551637..34502b2092f 100644 --- a/addons/web/static/src/views/fields/daterange/daterange_field.js +++ b/addons/web/static/src/views/fields/daterange/daterange_field.js @@ -4,6 +4,7 @@ import { localization } from "@web/core/l10n/localization"; import { registry } from "@web/core/registry"; import { loadJS } from "@web/core/assets"; import { useService } from "@web/core/utils/hooks"; +import { standardFieldProps } from "../standard_field_props"; const { Component, onWillStart, useExternalListener, useRef, useEffect } = owl; const formatters = registry.category("formatters"); @@ -61,13 +62,22 @@ export class DateRangeField extends Component { get formattedValue() { return this.formatValue(this.props.formatType, this.props.value); } - get formattedEndDate() { - return this.formatValue(this.props.formatType, this.props.endDate); + return this.formatValue(this.props.formatType, this.endDate); } - get formattedStartDate() { - return this.formatValue(this.props.formatType, this.props.startDate); + return this.formatValue(this.props.formatType, this.startDate); + } + get startDate() { + return this.props.record.data[this.props.relatedStartDateField || this.props.name]; + } + get endDate() { + return this.props.record.data[this.props.relatedEndDateField || this.props.name]; + } + get relatedDateRangeField() { + return this.props.relatedStartDateField + ? this.props.relatedStartDateField + : this.props.relatedEndDateField; } formatValue(format, value) { @@ -76,18 +86,25 @@ export class DateRangeField extends Component { try { formattedValue = formatter(value, { timezone: this.isDateTime }); } catch { - this.props.invalidate(); + this.props.record.setInvalidField(this.props.name); } return formattedValue; } + updateRange(start, end) { + return this.props.record.update({ + [this.props.relatedStartDateField || this.props.name]: start, + [this.props.relatedEndDateField || this.props.name]: end, + }); + } + onChangeInput(ev) { const parse = parsers.get(this.props.formatType); let value; try { value = parse(ev.target.value, { timezone: this.isDateTime }); } catch { - this.props.invalidate(); + this.props.record.setInvalidField(this.props.name); return; } this.props.update(value); @@ -114,9 +131,9 @@ export class DateRangeField extends Component { timezone: this.isDateTime, }); }); - await this.props.updateRange(dates[0], dates[1]); + await this.updateRange(dates[0], dates[1]); const input = document.querySelector( - `.o_field_daterange[name='${this.props.relatedDateRange}'] input` + `.o_field_daterange[name='${this.relatedDateRangeField}'] input` ); const target = window.$(input).data("daterangepicker"); target.setStartDate(picker.startDate); @@ -130,25 +147,22 @@ export class DateRangeField extends Component { } } DateRangeField.template = "web.DateRangeField"; +DateRangeField.props = { + ...standardFieldProps, + relatedEndDateField: { type: String, optional: true }, + relatedStartDateField: { type: String, optional: true }, + formatType: { type: String, optional: true }, + placeholder: { type: String, optional: true }, +}; DateRangeField.supportedTypes = ["date", "datetime"]; -DateRangeField.extractProps = (fieldName, record, attrs) => { - const relatedEndDate = attrs.options.related_end_date; - const relatedStartDate = attrs.options.related_start_date; - +DateRangeField.extractProps = ({ attrs, field }) => { return { - relatedDateRange: relatedEndDate ? relatedEndDate : relatedStartDate, - endDate: record.data[relatedEndDate || fieldName], - formatType: attrs.options.format_type || record.fields[fieldName].type, - invalidate: () => record.setInvalidField(fieldName), - startDate: record.data[relatedStartDate || fieldName], - async updateRange(start, end) { - await record.update({ - [relatedStartDate || fieldName]: start, - [relatedEndDate || fieldName]: end, - }); - }, + relatedEndDateField: attrs.options.related_end_date, + relatedStartDateField: attrs.options.related_start_date, + formatType: attrs.options.format_type || field.type, + placeholder: attrs.placeholder, }; }; diff --git a/addons/web/static/src/views/fields/daterange/daterange_field.xml b/addons/web/static/src/views/fields/daterange/daterange_field.xml index b0beea56949..bd56ca488ba 100644 --- a/addons/web/static/src/views/fields/daterange/daterange_field.xml +++ b/addons/web/static/src/views/fields/daterange/daterange_field.xml @@ -6,7 +6,7 @@ - + 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 156516623d7..ffdc6fe7a06 100644 --- a/addons/web/static/src/views/fields/datetime/datetime_field.js +++ b/addons/web/static/src/views/fields/datetime/datetime_field.js @@ -27,6 +27,7 @@ DateTimeField.components = { DateTimeField.props = { ...standardFieldProps, pickerOptions: { type: Object, optional: true }, + placeholder: { type: String, optional: true }, }; DateTimeField.defaultProps = { pickerOptions: {}, @@ -35,9 +36,10 @@ DateTimeField.defaultProps = { DateTimeField.displayName = _lt("Date & Time"); DateTimeField.supportedTypes = ["datetime"]; -DateTimeField.extractProps = (fieldName, record, attrs) => { +DateTimeField.extractProps = ({ attrs }) => { return { pickerOptions: attrs.options.datepicker, + placeholder: attrs.placeholder, }; }; diff --git a/addons/web/static/src/views/fields/datetime/datetime_field.xml b/addons/web/static/src/views/fields/datetime/datetime_field.xml index de7e2212277..ea08911fde1 100644 --- a/addons/web/static/src/views/fields/datetime/datetime_field.xml +++ b/addons/web/static/src/views/fields/datetime/datetime_field.xml @@ -9,6 +9,7 @@ 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 a0576231ca0..7bcf9d166c9 100644 --- a/addons/web/static/src/views/fields/domain/domain_field.js +++ b/addons/web/static/src/views/fields/domain/domain_field.js @@ -40,20 +40,31 @@ export class DomainField extends Component { ev.detail.proms.push(prom); await prom; if (!this.state.isValid) { - this.props.invalidate(); + this.props.record.setInvalidField(this.props.name); } } }); } + getContext(p) { + return p.record.getFieldContext(p.name); + } + getResModel(p) { + let resModel = p.resModel; + if (p.record.fieldNames.includes(resModel)) { + resModel = p.record.data[resModel]; + } + return resModel; + } + onButtonClick() { this.dialog.add(SelectCreateDialog, { title: this.env._t("Selected records"), noCreate: true, multiSelect: false, - resModel: this.props.resModel, - domain: this.getDomain(this.props.value).toList(this.props.context) || [], - context: this.props.context || {}, + resModel: this.getResModel(this.props), + domain: this.getDomain(this.props.value).toList(this.getContext(this.props)) || [], + context: this.getContext(this.props) || {}, }); } get isValidDomain() { @@ -70,16 +81,19 @@ export class DomainField extends Component { return new Domain(value || "[]"); } async loadCount(props) { - if (!props.resModel) { + if (!this.getResModel(props)) { Object.assign(this.state, { recordCount: 0, isValid: true }); } let recordCount; try { - const domain = this.getDomain(props.value).toList(props.context); - recordCount = await this.orm.silent.call(props.resModel, "search_count", [domain], { - context: props.context, - }); + const domain = this.getDomain(props.value).toList(this.getContext(props)); + recordCount = await this.orm.silent.call( + this.getResModel(props), + "search_count", + [domain], + { context: this.getContext(props) } + ); } catch (_e) { // WOWL TODO: rethrow error when not the expected type Object.assign(this.state, { recordCount: 0, isValid: false }); @@ -100,29 +114,16 @@ DomainField.components = { }; DomainField.props = { ...standardFieldProps, - context: { type: Object, optional: true }, - invalidate: { type: Function, optional: true }, resModel: { type: String, optional: true }, }; -DomainField.defaultProps = { - context: {}, - invalidate: () => {}, -}; DomainField.displayName = _lt("Domain"); DomainField.supportedTypes = ["char"]; DomainField.isEmpty = () => false; -DomainField.extractProps = (fieldName, record, attrs) => { - let resModel = attrs.options.model; - if (record.fieldNames.includes(resModel)) { - resModel = record.data[resModel]; - } - +DomainField.extractProps = ({ attrs }) => { return { - context: record.getFieldContext(fieldName), - invalidate: () => record.setInvalidField(fieldName), - resModel, + resModel: attrs.options.model, }; }; diff --git a/addons/web/static/src/views/fields/domain/domain_field.xml b/addons/web/static/src/views/fields/domain/domain_field.xml index 9f38f0aaf79..f7670b6c9dd 100644 --- a/addons/web/static/src/views/fields/domain/domain_field.xml +++ b/addons/web/static/src/views/fields/domain/domain_field.xml @@ -3,9 +3,9 @@
- + { +EmailField.extractProps = ({ attrs }) => { return { placeholder: attrs.placeholder, }; diff --git a/addons/web/static/src/views/fields/field.js b/addons/web/static/src/views/fields/field.js index 9282d2af818..14ba2e2c746 100644 --- a/addons/web/static/src/views/fields/field.js +++ b/addons/web/static/src/views/fields/field.js @@ -132,12 +132,16 @@ export class Field extends Component { decorationMap[decoName] = value; } - let propsFromAttrs = {}; - if (this.FieldComponent.extractProps) { - const attrs = Object.assign({}, this.props.attrs || fieldInfo.rawAttrs, { - options: fieldInfo.options || {}, + let propsFromAttrs = fieldInfo.propsFromAttrs; + if (this.props.attrs) { + const extractProps = this.FieldComponent.extractProps || (() => ({})); + propsFromAttrs = extractProps({ + field, + attrs: { + ...this.props.attrs, + options: evaluateExpr(this.props.attrs.options || "{}"), + }, }); - propsFromAttrs = this.FieldComponent.extractProps(this.props.name, record, attrs); } const props = { ...this.props }; @@ -221,7 +225,18 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) { continue; } - fieldInfo.rawAttrs[attribute.name] = attribute.value; + if (!attribute.name.startsWith("t-att")) { + fieldInfo.rawAttrs[attribute.name] = attribute.value; + } + } + + if (viewType !== "kanban") { + // FIXME WOWL: find a better solution + const extractProps = fieldInfo.FieldComponent.extractProps || (() => ({})); + fieldInfo.propsFromAttrs = extractProps({ + field, + attrs: { ...fieldInfo.rawAttrs, options: fieldInfo.options }, + }); } if (fieldInfo.modifiers.invisible !== true && X2M_TYPES.includes(field.type)) { 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 e23967678a0..b835343fcf5 100644 --- a/addons/web/static/src/views/fields/float/float_field.js +++ b/addons/web/static/src/views/fields/float/float_field.js @@ -52,28 +52,23 @@ FloatField.props = { inputType: { type: String, optional: true }, step: { type: Number, optional: true }, digits: { type: Array, optional: true }, - invalidate: { type: Function, optional: true }, placeholder: { type: String, optional: true }, }; FloatField.defaultProps = { inputType: "text", - invalidate: () => {}, }; FloatField.displayName = _lt("Float"); FloatField.supportedTypes = ["float"]; FloatField.isEmpty = () => false; -FloatField.extractProps = (fieldName, record, attrs) => { +FloatField.extractProps = ({ attrs, field }) => { return { - invalidate: () => record.setInvalidField(fieldName), inputType: attrs.options.type, step: attrs.options.step, // Sadly, digits param was available as an option and an attr. // The option version could be removed with some xml refactoring. - digits: - (attrs.digits ? JSON.parse(attrs.digits) : attrs.options.digits) || - record.fields[fieldName].digits, + digits: (attrs.digits ? JSON.parse(attrs.digits) : attrs.options.digits) || field.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 054b9582363..0bf1bd4a967 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 @@ -2,7 +2,6 @@ import { registry } from "@web/core/registry"; import { FloatField } from "../float/float_field"; -import { standardFieldProps } from "../standard_field_props"; const { Component } = owl; export class FloatFactorField extends Component { @@ -20,26 +19,20 @@ export class FloatFactorField extends Component { FloatFactorField.template = "web.FloatFactorField"; FloatFactorField.components = { FloatField }; FloatFactorField.props = { - ...standardFieldProps, - inputType: { type: String, optional: true }, - digits: { type: Array, optional: true }, - invalidate: { type: Function, optional: true }, + ...FloatField.props, factor: { type: Number, optional: true }, }; FloatFactorField.defaultProps = { - inputType: "text", - invalidate: () => {}, + ...FloatField.defaultProps, factor: 1, }; FloatFactorField.supportedTypes = ["float"]; FloatFactorField.isEmpty = () => false; -FloatFactorField.extractProps = (fieldName, record, attrs) => { +FloatFactorField.extractProps = ({ attrs, field }) => { return { - invalidate: record.setInvalidField.bind(record), - inputType: attrs.options.type, - digits: attrs.digits ? JSON.parse(attrs.digits) : attrs.options.digits, + ...FloatField.extractProps({ attrs, field }), factor: attrs.options.factor, }; }; diff --git a/addons/web/static/src/views/fields/float_time/float_time_field.js b/addons/web/static/src/views/fields/float_time/float_time_field.js index bdd517b0810..f3cc12aa417 100644 --- a/addons/web/static/src/views/fields/float_time/float_time_field.js +++ b/addons/web/static/src/views/fields/float_time/float_time_field.js @@ -28,20 +28,15 @@ export class FloatTimeField extends Component { FloatTimeField.template = "web.FloatTimeField"; FloatTimeField.props = { ...standardFieldProps, - invalidate: { type: Function, optional: true }, placeholder: { type: String, optional: true }, }; -FloatTimeField.defaultProps = { - invalidate: () => {}, -}; FloatTimeField.displayName = _lt("Time"); FloatTimeField.supportedTypes = ["float"]; FloatTimeField.isEmpty = () => false; -FloatTimeField.extractProps = (fieldName, record, attrs) => { +FloatTimeField.extractProps = ({ attrs }) => { return { - invalidate: () => record.setInvalidField(fieldName), placeholder: attrs.placeholder, }; }; 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 b08a7628e95..a5d298fd455 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 @@ -42,11 +42,9 @@ FloatToggleField.defaultProps = { FloatToggleField.supportedTypes = ["float"]; FloatToggleField.isEmpty = () => false; -FloatToggleField.extractProps = (fieldName, record, attrs) => { +FloatToggleField.extractProps = ({ attrs, field }) => { return { - digits: - (attrs.digits ? JSON.parse(attrs.digits) : attrs.options.digits) || - record.fields[fieldName].digits, + digits: (attrs.digits ? JSON.parse(attrs.digits) : attrs.options.digits) || field.digits, range: attrs.options.range, factor: attrs.options.factor, disableReadOnly: attrs.options.force_button || false, diff --git a/addons/web/static/src/views/fields/font_selection/font_selection_field.js b/addons/web/static/src/views/fields/font_selection/font_selection_field.js index bf19642f06b..fb10e1a7401 100644 --- a/addons/web/static/src/views/fields/font_selection/font_selection_field.js +++ b/addons/web/static/src/views/fields/font_selection/font_selection_field.js @@ -8,8 +8,14 @@ import { formatSelection } from "../formatters"; const { Component } = owl; export class FontSelectionField extends Component { + get options() { + return Array.from(this.props.record.fields[this.props.name].selection); + } + get isRequired() { + return this.props.record.isRequired(this.props.name); + } get string() { - return formatSelection(this.props.value, { selection: this.props.options }); + return formatSelection(this.props.value, { selection: this.options }); } stringify(value) { @@ -28,21 +34,14 @@ export class FontSelectionField extends Component { FontSelectionField.template = "web.FontSelectionField"; FontSelectionField.props = { ...standardFieldProps, - options: Object, placeholder: { type: String, optional: true }, - required: { type: Boolean, optional: true }, -}; -FontSelectionField.defaultProps = { - required: false, }; FontSelectionField.displayName = _lt("Font Selection"); FontSelectionField.supportedTypes = ["selection"]; -FontSelectionField.extractProps = (fieldName, record, attrs) => { +FontSelectionField.extractProps = ({ attrs }) => { return { - options: Array.from(record.fields[fieldName].selection), - required: record.isRequired(fieldName), placeholder: attrs.placeholder, }; }; diff --git a/addons/web/static/src/views/fields/font_selection/font_selection_field.xml b/addons/web/static/src/views/fields/font_selection/font_selection_field.xml index 73afb687392..93ae586dbc1 100644 --- a/addons/web/static/src/views/fields/font_selection/font_selection_field.xml +++ b/addons/web/static/src/views/fields/font_selection/font_selection_field.xml @@ -3,23 +3,23 @@ - + - + - + 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 abff62323fa..37566b7fc2f 100644 --- a/addons/web/static/src/views/fields/image/image_field.js +++ b/addons/web/static/src/views/fields/image/image_field.js @@ -53,8 +53,8 @@ export class ImageField extends Component { if (this.state.isValid && this.props.value) { if (previewFieldName !== this.props.name || isBinarySize(this.props.value)) { return url("/web/image", { - model: this.props.resModel, - id: this.props.resId, + model: this.props.record.resModel, + id: this.props.record.resId, field: previewFieldName, }); } else { @@ -93,8 +93,6 @@ ImageField.props = { acceptedFileExtensions: { type: String, optional: true }, width: { type: Number, optional: true }, height: { type: Number, optional: true }, - resId: { type: [Number, Boolean], optional: true }, - resModel: { type: String, optional: true }, }; ImageField.defaultProps = { acceptedFileExtensions: "image/*", @@ -103,7 +101,7 @@ ImageField.defaultProps = { ImageField.displayName = _lt("Image"); ImageField.supportedTypes = ["binary"]; -ImageField.extractProps = (fieldName, record, attrs) => { +ImageField.extractProps = ({ attrs }) => { return { enableZoom: attrs.options.zoom, zoomDelay: attrs.options.zoom_delay, @@ -111,8 +109,6 @@ ImageField.extractProps = (fieldName, record, attrs) => { acceptedFileExtensions: attrs.options.accepted_file_extensions, width: attrs.options.size ? attrs.options.size[0] : attrs.width, height: attrs.options.size ? attrs.options.size[1] : attrs.height, - resId: record.resId, - resModel: record.resModel, }; }; 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 a14e49bf731..9980157e590 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 @@ -52,7 +52,7 @@ ImageUrlField.props = { ImageUrlField.displayName = _lt("Image"); ImageUrlField.supportedTypes = ["char"]; -ImageUrlField.extractProps = (fieldName, record, attrs) => { +ImageUrlField.extractProps = ({ attrs }) => { return { width: attrs.options.size ? attrs.options.size[0] : attrs.width, height: attrs.options.size ? attrs.options.size[1] : attrs.height, diff --git a/addons/web/static/src/views/fields/input_field_hook.js b/addons/web/static/src/views/fields/input_field_hook.js index 94c9601369e..0f383b354eb 100644 --- a/addons/web/static/src/views/fields/input_field_hook.js +++ b/addons/web/static/src/views/fields/input_field_hook.js @@ -33,7 +33,7 @@ export function useInputField(params) { const val = params.parse ? params.parse(ev.target.value) : ev.target.value; component.props.update(val); } catch (_e) { - component.props.invalidate(); + component.props.record.setInvalidField(component.props.name); } if (component.props.setDirty) { component.props.setDirty(isDirty); @@ -73,7 +73,7 @@ export function useInputField(params) { if (urgent) { return; } else { - component.props.invalidate(); + component.props.record.setInvalidField(component.props.name); } } if (component.props.setDirty) { 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 9811209319d..082f317d714 100644 --- a/addons/web/static/src/views/fields/integer/integer_field.js +++ b/addons/web/static/src/views/fields/integer/integer_field.js @@ -33,23 +33,20 @@ IntegerField.props = { ...standardFieldProps, inputType: { type: String, optional: true }, step: { type: Number, optional: true }, - invalidate: { type: Function, optional: true }, placeholder: { type: String, optional: true }, }; IntegerField.defaultProps = { inputType: "text", - invalidate: () => {}, }; IntegerField.displayName = _lt("Integer"); IntegerField.supportedTypes = ["integer"]; IntegerField.isEmpty = (record, fieldName) => (record.data[fieldName] === false ? true : false); -IntegerField.extractProps = (fieldName, record, attrs) => { +IntegerField.extractProps = ({ attrs }) => { return { inputType: attrs.options.type, step: attrs.options.step, - invalidate: () => record.setInvalidField(fieldName), placeholder: attrs.placeholder, }; }; diff --git a/addons/web/static/src/views/fields/journal_dashboard_graph/journal_dashboard_graph_field.js b/addons/web/static/src/views/fields/journal_dashboard_graph/journal_dashboard_graph_field.js index 10a6402841c..d9088ac0b95 100644 --- a/addons/web/static/src/views/fields/journal_dashboard_graph/journal_dashboard_graph_field.js +++ b/addons/web/static/src/views/fields/journal_dashboard_graph/journal_dashboard_graph_field.js @@ -136,13 +136,12 @@ export class JournalDashboardGraphField extends Component { JournalDashboardGraphField.template = "web.JournalDashboardGraphField"; JournalDashboardGraphField.props = { ...standardFieldProps, - className: { type: String, optional: true }, graphType: String, }; JournalDashboardGraphField.supportedTypes = ["text"]; -JournalDashboardGraphField.extractProps = (fieldName, record, attrs) => { +JournalDashboardGraphField.extractProps = ({ attrs }) => { return { graphType: attrs.graph_type, }; 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 1339e466aad..89de3bf5d0b 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 @@ -12,7 +12,9 @@ export class LabelSelectionField extends Component { return this.props.classesObj[this.props.value] || "primary"; } get string() { - return formatSelection(this.props.value, { selection: this.props.options }); + return formatSelection(this.props.value, { + selection: Array.from(this.props.record.fields[this.props.name].selection), + }); } } @@ -20,7 +22,6 @@ LabelSelectionField.template = "web.LabelSelectionField"; LabelSelectionField.props = { ...standardFieldProps, classesObj: { type: Object, optional: true }, - options: Object, }; LabelSelectionField.defaultProps = { classesObj: {}, @@ -29,10 +30,9 @@ LabelSelectionField.defaultProps = { LabelSelectionField.displayName = _lt("Label Selection"); LabelSelectionField.supportedTypes = ["selection"]; -LabelSelectionField.extractProps = (fieldName, record, attrs) => { +LabelSelectionField.extractProps = ({ attrs }) => { return { classesObj: attrs.options.classes, - options: Array.from(record.fields[fieldName].selection), }; }; diff --git a/addons/web/static/src/views/fields/label_selection/label_selection_field.xml b/addons/web/static/src/views/fields/label_selection/label_selection_field.xml index 803e496292b..6c008ca2951 100644 --- a/addons/web/static/src/views/fields/label_selection/label_selection_field.xml +++ b/addons/web/static/src/views/fields/label_selection/label_selection_field.xml @@ -2,7 +2,7 @@ - + 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 7b5a737bf41..90227832bda 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 @@ -52,17 +52,16 @@ Many2ManyBinaryField.props = { acceptedFileExtensions: { type: String, optional: true }, className: { type: String, optional: true }, uploadText: { type: String, optional: true }, - items: { type: Object, optional: true }, }; Many2ManyBinaryField.supportedTypes = ["many2many"]; Many2ManyBinaryField.isEmpty = () => false; -Many2ManyBinaryField.extractProps = (fieldName, record, attrs) => { +Many2ManyBinaryField.extractProps = ({ attrs, field }) => { return { acceptedFileExtensions: attrs.options.accepted_file_extensions, className: attrs.class, - uploadText: record.fields[fieldName].string, + uploadText: field.string, }; }; diff --git a/addons/web/static/src/views/fields/many2many_checkboxes/many2many_checkboxes_field.js b/addons/web/static/src/views/fields/many2many_checkboxes/many2many_checkboxes_field.js index c047e276e91..ceeec9726e0 100644 --- a/addons/web/static/src/views/fields/many2many_checkboxes/many2many_checkboxes_field.js +++ b/addons/web/static/src/views/fields/many2many_checkboxes/many2many_checkboxes_field.js @@ -8,6 +8,10 @@ import { standardFieldProps } from "../standard_field_props"; const { Component } = owl; export class Many2ManyCheckboxesField extends Component { + get items() { + return this.props.record.preloadedData[this.props.name]; + } + isSelected(item) { return this.props.value.currentIds.includes(item[0]); } @@ -30,18 +34,12 @@ Many2ManyCheckboxesField.template = "web.Many2ManyCheckboxesField"; Many2ManyCheckboxesField.components = { CheckBox }; Many2ManyCheckboxesField.props = { ...standardFieldProps, - items: Object, }; Many2ManyCheckboxesField.displayName = _lt("Checkboxes"); Many2ManyCheckboxesField.supportedTypes = ["many2many"]; Many2ManyCheckboxesField.isEmpty = () => false; -Many2ManyCheckboxesField.extractProps = (fieldName, record) => { - return { - items: record.preloadedData[fieldName], - }; -}; registry.category("fields").add("many2many_checkboxes", Many2ManyCheckboxesField); diff --git a/addons/web/static/src/views/fields/many2many_checkboxes/many2many_checkboxes_field.xml b/addons/web/static/src/views/fields/many2many_checkboxes/many2many_checkboxes_field.xml index 45b30814bbd..6e47fe6cfd0 100644 --- a/addons/web/static/src/views/fields/many2many_checkboxes/many2many_checkboxes_field.xml +++ b/addons/web/static/src/views/fields/many2many_checkboxes/many2many_checkboxes_field.xml @@ -3,7 +3,7 @@
- +
{ return { - evalContext: props.evalContext, + evalContext: this.evalContext, readonly: props.readonly, }; }, @@ -62,13 +62,29 @@ export class Many2ManyTagsField extends Component { if (this.props.canQuickCreate) { this.quickCreate = async (name) => { const created = await this.orm.call(this.props.relation, "name_create", [name], { - context: this.props.context, + context: this.context, }); return saveRecord([created[0]]); }; } } + get domain() { + return this.props.record.getFieldDomain(this.props.name); + } + get context() { + return this.props.record.getFieldContext(this.props.name); + } + get evalContext() { + return this.props.record.evalContext; + } + get canEditColor() { + return this.props.canEditColor && this.props.record.viewType !== "list"; + } + get string() { + return this.props.record.activeFields[this.props.name].string; + } + get tags() { return this.props.value.records.map((record) => ({ id: record.id, // datapoint_X @@ -80,7 +96,7 @@ export class Many2ManyTagsField extends Component { } get canOpenColorDropdown() { - return this.handlesColor() && this.props.canEditColor; + return this.handlesColor() && this.canEditColor; } get showM2OSelectionField() { @@ -123,9 +139,9 @@ export class Many2ManyTagsField extends Component { getDomain() { return Domain.and([ - this.props.domain, + this.domain, Domain.not([["id", "in", this.props.value.currentIds]]), - ]).toList(this.props.context); + ]).toList(this.context); } onBadgeClick(ev, record) { @@ -271,12 +287,7 @@ Many2ManyTagsField.props = { createDomain: { type: Array, optional: true }, placeholder: { type: String, optional: true }, relation: { type: String }, - domain: { type: Domain }, - context: { type: Object }, - evalContext: { type: Object }, nameCreateField: { type: String, optional: true }, - itemsVisible: { type: Number, optional: true }, - string: { type: String }, }; Many2ManyTagsField.defaultProps = { canEditColor: true, @@ -290,19 +301,15 @@ Many2ManyTagsField.fieldsToFetch = { display_name: { name: "display_name", type: "char" }, }; -Many2ManyTagsField.extractProps = (fieldName, record, attrs) => { - const activeField = record.activeFields[fieldName]; +Many2ManyTagsField.extractProps = ({ attrs, field }) => { return { colorField: attrs.options.color_field, nameCreateField: attrs.options.create_name_field, - canEditColor: !attrs.options.no_edit_color && activeField.viewType !== "list", - relation: activeField.relation, - domain: record.getFieldDomain(fieldName), - context: record.getFieldContext(fieldName), + canEditColor: !attrs.options.no_edit_color, + relation: field.relation, canQuickCreate: !attrs.options.no_quick_create, createDomain: attrs.options.create, - evalContext: record.evalContext, - string: activeField.string, + placeholder: attrs.placeholder, }; }; diff --git a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.xml b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.xml index afe25ddfcfb..d90a924edff 100644 --- a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.xml +++ b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.xml @@ -9,11 +9,11 @@ placeholder="props.placeholder" resModel="props.relation" autoSelect="true" - fieldString="props.string" + fieldString="string" activeActions="activeActions" update="update" quickCreate="activeActions.canCreate ? quickCreate : null" - context="props.context" + context="context" getDomain.bind="getDomain" isToMany="true" nameCreateField="props.nameCreateField" diff --git a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.js b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.js index 4a99d59fb00..e13e4388ae2 100644 --- a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.js +++ b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.js @@ -22,23 +22,13 @@ Many2ManyTagsAvatarField.components = { TagsList, }; -Many2ManyTagsAvatarField.supportedTypes = ["many2many"]; - -Many2ManyTagsAvatarField.extractProps = (fieldName, record, attrs) => { - return { - relation: record.activeFields[fieldName].relation, - domain: record.getFieldDomain(fieldName), - context: record.getFieldContext(fieldName), - evalContext: record.evalContext, - canQuickCreate: !attrs.options.no_quick_create, - createDomain: attrs.options.create, - string: record.activeFields[fieldName].string, - }; -}; - registry.category("fields").add("many2many_tags_avatar", Many2ManyTagsAvatarField); class ListKanbanMany2ManyTagsAvatarField extends Many2ManyTagsAvatarField { + get itemsVisible() { + return this.props.record.activeFields[this.props.name].viewType === "list" ? 5 : 3; + } + get tags() { return this.props.value.records.map((record) => ({ id: record.id, // datapoint_X @@ -48,18 +38,5 @@ class ListKanbanMany2ManyTagsAvatarField extends Many2ManyTagsAvatarField { } } -ListKanbanMany2ManyTagsAvatarField.extractProps = (fieldName, record, attrs) => { - return { - itemsVisible: record.activeFields[fieldName].viewType === "list" ? 5 : 3, - relation: record.activeFields[fieldName].relation, - domain: record.getFieldDomain(fieldName), - context: record.getFieldContext(fieldName), - evalContext: record.evalContext, - canQuickCreate: !attrs.options.no_quick_create, - createDomain: attrs.options.create, - string: record.activeFields[fieldName].string, - }; -}; - registry.category("fields").add("list.many2many_tags_avatar", ListKanbanMany2ManyTagsAvatarField); registry.category("fields").add("kanban.many2many_tags_avatar", ListKanbanMany2ManyTagsAvatarField); diff --git a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.xml b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.xml index 7cbf4c419b2..b72bf8a5f5d 100644 --- a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.xml +++ b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.xml @@ -2,7 +2,7 @@ - +
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 50285dbcc94..2f1797eac2f 100644 --- a/addons/web/static/src/views/fields/many2one/many2one_field.js +++ b/addons/web/static/src/views/fields/many2one/many2one_field.js @@ -6,7 +6,6 @@ import { _lt } from "@web/core/l10n/translation"; import { useChildRef, useOwnedDialogs, useService } from "@web/core/utils/hooks"; import { sprintf } from "@web/core/utils/strings"; import { standardFieldProps } from "../standard_field_props"; -import { Domain } from "@web/core/domain"; import { Many2XAutocomplete, useOpenMany2XRecord } from "@web/views/fields/relational_utils"; const { Component, onWillUpdateProps, useState } = owl; @@ -62,7 +61,7 @@ export class Many2OneField extends Component { computeActiveActions(this.props); this.openMany2X = useOpenMany2XRecord({ - resModel: this.props.relation, + resModel: this.relation, activeActions: this.state.activeActions, isToMany: false, onRecordSaved: async (record) => { @@ -106,6 +105,16 @@ export class Many2OneField extends Component { }); } + get relation() { + return this.props.relation || this.props.record.fields[this.props.name].relation; + } + + get context() { + return this.props.record.getFieldContext(this.props.name); + } + get domain() { + return this.props.record.getFieldDomain(this.props.name); + } get hasExternalButton() { return this.props.canOpen && !!this.props.value && !this.state.isFloating; } @@ -128,19 +137,16 @@ export class Many2OneField extends Component { } getDomain() { - return this.props.getDomain().toList(this.props.getContext()); + return this.domain.toList(this.context); } async openAction() { - const action = await this.orm.call( - this.props.relation, - "get_formview_action", - [[this.resId]], - { context: this.props.getContext() } - ); + const action = await this.orm.call(this.relation, "get_formview_action", [[this.resId]], { + context: this.context, + }); await this.action.doAction(action); } async openDialog(resId) { - return this.openMany2X({ resId, context: this.props.getContext() }); + return this.openMany2X({ resId, context: this.context }); } async openConfirmationDialog(request) { @@ -187,10 +193,8 @@ Many2OneField.props = { canCreateEdit: { type: Boolean, optional: true }, createNameField: { type: String, optional: true }, searchLimit: { type: Number, optional: true }, - relation: String, + relation: { type: String, optional: true }, string: { type: String, optional: true }, - getContext: { type: Function, optional: true }, - getDomain: { type: Function, optional: true }, }; Many2OneField.defaultProps = { canOpen: true, @@ -201,14 +205,12 @@ Many2OneField.defaultProps = { createNameField: "name", searchLimit: 7, string: "", - getContext: () => ({}), - getDomain: () => new Domain(), }; Many2OneField.displayName = _lt("Many2one"); Many2OneField.supportedTypes = ["many2one"]; -Many2OneField.extractProps = (fieldName, record, attrs) => { +Many2OneField.extractProps = ({ attrs, field }) => { const noOpen = Boolean(attrs.options.no_open); const noCreate = Boolean(attrs.options.no_create); const canCreate = attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !noCreate; @@ -223,10 +225,8 @@ Many2OneField.extractProps = (fieldName, record, attrs) => { canWrite, canQuickCreate: canCreate && !noQuickCreate, canCreateEdit: canCreate && !noCreateEdit, - relation: record.fields[fieldName].relation, - string: attrs.string || record.fields[fieldName].string, - getContext: () => record.getFieldContext(fieldName), - getDomain: () => record.getFieldDomain(fieldName), + relation: field.relation, + string: attrs.string || field.string, createNameField: attrs.options.create_name_field, }; }; diff --git a/addons/web/static/src/views/fields/many2one/many2one_field.xml b/addons/web/static/src/views/fields/many2one/many2one_field.xml index dc63a816ad2..2c85454007f 100644 --- a/addons/web/static/src/views/fields/many2one/many2one_field.xml +++ b/addons/web/static/src/views/fields/many2one/many2one_field.xml @@ -27,7 +27,7 @@ @@ -44,13 +44,13 @@ value="displayName" id="props.id" placeholder="props.placeholder" - resModel="props.relation" + resModel="relation" autoSelect="true" fieldString="props.string" activeActions="state.activeActions" update="update" quickCreate="quickCreate" - context="props.getContext()" + context="context" getDomain.bind="getDomain" nameCreateField="props.nameCreateField" setInputFloats="setFloating" diff --git a/addons/web/static/src/views/fields/many2one_avatar/many2one_avatar_field.js b/addons/web/static/src/views/fields/many2one_avatar/many2one_avatar_field.js index e565d57dcb6..9497b11bd43 100644 --- a/addons/web/static/src/views/fields/many2one_avatar/many2one_avatar_field.js +++ b/addons/web/static/src/views/fields/many2one_avatar/many2one_avatar_field.js @@ -1,9 +1,7 @@ /** @odoo-module **/ import { registry } from "@web/core/registry"; -import { Domain } from "@web/core/domain"; import { Many2OneField } from "../many2one/many2one_field"; -import { standardFieldProps } from "../standard_field_props"; const { Component } = owl; @@ -14,25 +12,11 @@ Many2OneAvatarField.components = { Many2OneField, }; Many2OneAvatarField.props = { - ...standardFieldProps, - placeholder: { type: String, optional: true }, - relation: String, - getContext: { type: Function, optional: true }, - getDomain: { type: Function, optional: true }, -}; -Many2OneAvatarField.defaultProps = { - getContext: () => ({}), - getDomain: () => new Domain(), + ...Many2OneField.props, }; Many2OneAvatarField.supportedTypes = ["many2one"]; -Many2OneAvatarField.extractProps = (fieldName, record) => { - return { - relation: record.fields[fieldName].relation, - getContext: () => record.getFieldContext(fieldName), - getDomain: () => record.getFieldDomain(fieldName), - }; -}; +Many2OneAvatarField.extractProps = Many2OneField.extractProps; registry.category("fields").add("many2one_avatar", Many2OneAvatarField); 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 ec374ef19f5..a85e7285eaa 100644 --- a/addons/web/static/src/views/fields/monetary/monetary_field.js +++ b/addons/web/static/src/views/fields/monetary/monetary_field.js @@ -16,14 +16,20 @@ export class MonetaryField extends Component { useInputField({ getValue: () => this.formattedValue, refName: "numpadDecimal", - parse: (v) => parseMonetary(v, { currencyId: this.props.currencyId }), + parse: (v) => parseMonetary(v, { currencyId: this.currencyId }), }); useNumpadDecimal(); } + get currencyId() { + return this.props.currencyField + ? this.props.record.data[this.props.currencyField][0] + : (this.props.record.data.currency_id && this.props.record.data.currency_id[0]) || + undefined; + } get currency() { - if (!isNaN(this.props.currencyId) && this.props.currencyId in session.currencies) { - return session.currencies[this.props.currencyId]; + if (!isNaN(this.currencyId) && this.currencyId in session.currencies) { + return session.currencies[this.currencyId]; } return null; } @@ -43,7 +49,7 @@ export class MonetaryField extends Component { if (!this.currency) { return null; } - return session.currencies[this.props.currencyId].digits; + return session.currencies[this.currencyId].digits; } get formattedValue() { @@ -52,7 +58,7 @@ export class MonetaryField extends Component { } return formatMonetary(this.props.value, { digits: this.currencyDigits, - currencyId: this.props.currencyId, + currencyId: this.currencyId, noSymbol: !this.props.readonly || this.props.hideSymbol, }); } @@ -61,31 +67,26 @@ export class MonetaryField extends Component { MonetaryField.template = "web.MonetaryField"; MonetaryField.props = { ...standardFieldProps, - currencyId: { type: Number, optional: true }, + currencyField: { type: String, optional: true }, inputType: { type: String, optional: true }, digits: { type: Array, optional: true }, hideSymbol: { type: Boolean, optional: true }, - invalidate: { type: Function, optional: true }, placeholder: { type: String, optional: true }, }; MonetaryField.defaultProps = { hideSymbol: false, inputType: "text", - invalidate: () => {}, }; MonetaryField.supportedTypes = ["monetary", "float"]; MonetaryField.displayName = _lt("Monetary"); -MonetaryField.extractProps = function (fieldName, record, attrs) { +MonetaryField.extractProps = ({ attrs }) => { return { - currencyId: attrs.options.currency_field - ? record.data[attrs.options.currency_field][0] - : (record.data.currency_id && record.data.currency_id[0]) || undefined, + currencyField: attrs.options.currency_field, inputType: attrs.type, - digits: [16, 2], + digits: [16, 2], // FIXME WOWL hideSymbol: attrs.options.no_symbol, - invalidate: () => record.setInvalidField(fieldName), 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 bfb354c8c96..7c57903dd8c 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 @@ -13,7 +13,7 @@ export class PdfViewerField extends Component { setup() { this.notification = useService("notification"); this.state = useState({ - fileName: this.props.fileName || "", + fileName: this.fileName || "", isValid: true, objectUrl: "", }); @@ -24,10 +24,16 @@ export class PdfViewerField extends Component { } }); } + get defaultPage() { + return this.props.record.data[`${this.props.name}_page`]; + } + get fileName() { + return this.props.record.data[this.props.fileNameField]; + } get file() { return { - data: this.props.fileData, - name: this.state.fileName || this.props.fileData || null, + data: this.props.value || "", + name: this.state.fileName || this.props.value || null, }; } get url() { @@ -37,8 +43,8 @@ export class PdfViewerField extends Component { encodeURIComponent( this.state.objectUrl || url("/web/content", { - model: this.props.resModel, - id: this.props.resId, + model: this.props.record.resModel, + id: this.props.record.resId, field: this.props.previewImage || this.props.name, }) ) + @@ -71,28 +77,17 @@ PdfViewerField.components = { }; PdfViewerField.props = { ...standardFieldProps, - defaultPage: { type: Number, optional: true }, - fileData: { type: String, optional: true }, - fileName: { type: String, optional: true }, + fileNameField: { type: String, optional: true }, previewImage: { type: String, optional: true }, - resId: { type: [Number, Boolean], optional: true }, - resModel: { type: String, optional: true }, -}; -PdfViewerField.defaultProps = { - fileData: "", }; PdfViewerField.displayName = _lt("PDF Viewer"); PdfViewerField.supportedTypes = ["binary"]; -PdfViewerField.extractProps = (fieldName, record, attrs) => { +PdfViewerField.extractProps = ({ attrs }) => { return { - defaultPage: record.data[fieldName + "_page"], - fileData: record.data[fieldName] || "", - fileName: record.data[attrs.filename] || "", + fileNameField: attrs.filename, previewImage: attrs.options.preview_image, - resId: record.resId, - resModel: record.resModel, }; }; 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 31a5e74b11e..bdf40607a15 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 @@ -26,7 +26,7 @@ PercentPieField.props = { PercentPieField.displayName = _lt("PercentPie"); PercentPieField.supportedTypes = ["float", "integer"]; -PercentPieField.extractProps = (fieldName, record, attrs) => { +PercentPieField.extractProps = ({ attrs }) => { return { string: attrs.string, }; 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 798ae0a025b..23283c64b42 100644 --- a/addons/web/static/src/views/fields/percentage/percentage_field.js +++ b/addons/web/static/src/views/fields/percentage/percentage_field.js @@ -30,23 +30,16 @@ export class PercentageField extends Component { PercentageField.template = "web.PercentageField"; PercentageField.props = { ...standardFieldProps, - invalidate: { type: Function, optional: true }, digits: { type: Array, optional: true }, placeholder: { type: String, optional: true }, }; -PercentageField.defaultProps = { - invalidate: () => {}, -}; PercentageField.displayName = _lt("Percentage"); PercentageField.supportedTypes = ["integer", "float"]; -PercentageField.extractProps = (fieldName, record, attrs) => { +PercentageField.extractProps = ({ attrs, field }) => { return { - invalidate: () => record.setInvalidField(fieldName), - digits: - (attrs.digits ? JSON.parse(attrs.digits) : attrs.options.digits) || - record.fields[fieldName].digits, + digits: (attrs.digits ? JSON.parse(attrs.digits) : attrs.options.digits) || field.digits, placeholder: attrs.placeholder, }; }; diff --git a/addons/web/static/src/views/fields/phone/phone_field.js b/addons/web/static/src/views/fields/phone/phone_field.js index b32de9805a1..63539bc1089 100644 --- a/addons/web/static/src/views/fields/phone/phone_field.js +++ b/addons/web/static/src/views/fields/phone/phone_field.js @@ -22,7 +22,7 @@ PhoneField.props = { PhoneField.displayName = _lt("Phone"); PhoneField.supportedTypes = ["char"]; -PhoneField.extractProps = (fieldName, record, attrs) => { +PhoneField.extractProps = ({ attrs }) => { return { placeholder: attrs.placeholder, }; diff --git a/addons/web/static/src/views/fields/priority/priority_field.js b/addons/web/static/src/views/fields/priority/priority_field.js index 67e71ca121f..13a1316a64e 100644 --- a/addons/web/static/src/views/fields/priority/priority_field.js +++ b/addons/web/static/src/views/fields/priority/priority_field.js @@ -12,15 +12,21 @@ export class PriorityField extends Component { this.state = useState({ index: -1, }); - if (this.props.addCommand) { + if (this.props.record.activeFields[this.props.name].viewType === "form") { this.initiateCommand(); } } + get options() { + return Array.from(this.props.record.fields[this.props.name].selection); + } get index() { return this.state.index > -1 ? this.state.index - : this.props.options.findIndex((o) => o[0] === this.props.value); + : this.options.findIndex((o) => o[0] === this.props.value); + } + get isReadonly() { + return this.props.record.isReadonly(this.props.name); } getTooltip(value) { @@ -32,7 +38,7 @@ export class PriorityField extends Component { onStarClicked(value) { if (this.props.value === value) { this.state.index = -1; - this.props.update(this.props.options[0][0]); + this.props.update(this.options[0][0]); } else { this.props.update(value); } @@ -41,7 +47,7 @@ export class PriorityField extends Component { try { const commandService = useService("command"); const provide = () => { - return this.props.options.map((value) => ({ + return this.options.map((value) => ({ name: value[1], action: () => { this.props.update(value[0]); @@ -68,21 +74,16 @@ export class PriorityField extends Component { PriorityField.template = "web.PriorityField"; PriorityField.props = { - options: Object, ...standardFieldProps, - addCommand: { type: Boolean, optional: true }, tooltipLabel: { type: String, optional: true }, }; PriorityField.displayName = _lt("Priority"); PriorityField.supportedTypes = ["selection"]; -PriorityField.extractProps = (fieldName, record) => { +PriorityField.extractProps = ({ field }) => { return { - addCommand: record.activeFields[fieldName].viewType === "form", - options: Array.from(record.fields[fieldName].selection), - tooltipLabel: record.fields[fieldName].string, - readonly: record.isReadonly(fieldName), + tooltipLabel: field.string, }; }; diff --git a/addons/web/static/src/views/fields/priority/priority_field.xml b/addons/web/static/src/views/fields/priority/priority_field.xml index add46f1bee8..1cbfb2debd2 100644 --- a/addons/web/static/src/views/fields/priority/priority_field.xml +++ b/addons/web/static/src/views/fields/priority/priority_field.xml @@ -3,9 +3,9 @@
- + - + { Object.assign(this.state, { - currentValue: nextProps.currentValue.value, - maxValue: nextProps.maxValue.value, + currentValue: this.getCurrentValue(nextProps), + maxValue: this.getMaxValue(nextProps), }); if (nextProps.readonly) { this.state.isEditing = false; @@ -29,31 +29,76 @@ export class ProgressBarField extends Component { }); } - getFormattedValue(part, humanReadable = false) { - const formatter = formatters.get(this.props[part].type); - return formatter(this.state[part], { humanReadable }); + get isCurrentValueInteger() { + return this.state.currentValue % 1 === 0; + } + get isMaxValueInteger() { + return this.state.maxValue % 1 === 0; } - /** - * @param {String} value - * @param {String} part - */ - onChangeValue(value, part) { + getCurrentValueField(p) { + return typeof p.currentValueField === "string" ? p.currentValueField : p.name; + } + getMaxValueField(p) { + return typeof p.maxValueField === "string" ? p.maxValueField : p.name; + } + + getCurrentValue(p) { + return p.record.data[this.getCurrentValueField(p)] || 0; + } + getMaxValue(p) { + if (p.maxValueField) { + return p.record.data[p.maxValueField] || 100; + } + return 100; + } + + formatCurrentValue(humanReadable = false) { + const formatter = formatters.get(this.isCurrentValueInteger ? "integer" : "float"); + return formatter(this.state.currentValue, { humanReadable }); + } + formatMaxValue(humanReadable = false) { + const formatter = formatters.get(this.isMaxValueInteger ? "integer" : "float"); + return formatter(this.state.maxValue, { humanReadable }); + } + + onCurrentValueChange(value) { + let parsedValue; try { - let parsedValue = parseFloat(value); - if (this.props[part].type === "integer") { - parsedValue = Math.floor(parsedValue); - } - this.state[part] = parsedValue; - this.props.updatePart(part, parsedValue); - if (this.props.readonly) { - this.state.isEditing = false; - this.props.record.save(); - } + parsedValue = parseFloat(value); } catch { - this.props.invalidate(); + this.props.record.setInvalidField(this.props.name); return; } + + if (this.isCurrentValueInteger) { + parsedValue = Math.floor(parsedValue); + } + this.state.currentValue = parsedValue; + this.props.record.update({ [this.getCurrentValueField(this.props)]: parsedValue }); + if (this.props.readonly) { + this.state.isEditing = false; + this.props.record.save(); + } + } + onMaxValueChange(value) { + let parsedValue; + try { + parsedValue = parseFloat(value); + } catch { + this.props.record.setInvalidField(this.props.name); + return; + } + + if (this.isMaxValueInteger) { + parsedValue = Math.floor(parsedValue); + } + this.state.maxValue = parsedValue; + this.props.record.update({ [this.getMaxValueField(this.props)]: parsedValue }); + if (this.props.readonly) { + this.state.isEditing = false; + this.props.record.save(); + } } onClick() { @@ -61,17 +106,24 @@ export class ProgressBarField extends Component { this.state.isEditing = true; } } - onBlur() { if (this.props.readonly) { this.state.isEditing = false; } } - onInput(ev, part) { - const parser = parsers.get(this.props[part].type); + onCurrentValueInput(ev) { + const parser = parsers.get(this.isCurrentValueInteger ? "integer" : "float"); try { - this.state[part] = parser(ev.target.value); + this.state.currentValue = parser(ev.target.value); + } catch { + // pass + } + } + onMaxValueInput(ev) { + const parser = parsers.get(this.isMaxValueInteger ? "integer" : "float"); + try { + this.state.maxValue = parser(ev.target.value); } catch { // pass } @@ -81,55 +133,22 @@ export class ProgressBarField extends Component { ProgressBarField.template = "web.ProgressBarField"; ProgressBarField.props = { ...standardFieldProps, - currentValue: { type: Object, optional: true }, + maxValueField: { type: [String, Number], optional: true }, + currentValueField: { type: String, optional: true }, isPercentage: { type: Boolean, optional: true }, - maxValue: { type: Object, optional: true }, isEditable: { type: Boolean, optional: true }, isEditableInReadonly: { type: Boolean, optional: true }, isCurrentValueEditable: { type: Boolean, optional: true }, isMaxValueEditable: { type: Boolean, optional: true }, - invalidate: { type: Function, optional: true }, - updatePart: { type: Function, optional: true }, -}; -ProgressBarField.defaultProps = { - invalidate: () => {}, - updatePart: () => {}, }; ProgressBarField.displayName = _lt("Progress Bar"); ProgressBarField.supportedTypes = ["integer", "float"]; -ProgressBarField.extractProps = (fieldName, record, attrs) => { - const getPart = (part) => { - if (attrs.options[part]) { - let value = attrs.options[part]; - let name; - if (isNaN(value)) { - value = - (record.data[attrs.options[part]] !== undefined && - record.data[attrs.options[part]]) || - 0; - name = attrs.options[part]; - } - return { - fieldName: name, - value, - type: value % 1 === 0 ? "integer" : "float", - }; - } - const value = part === "max_value" ? 100 : record.data[fieldName] || 0; - return { - fieldName: fieldName, - value, - type: value % 1 === 0 ? "integer" : "float", - }; - }; - const parts = { - currentValue: getPart("current_value"), - maxValue: getPart("max_value"), - }; +ProgressBarField.extractProps = ({ attrs }) => { return { - ...parts, + maxValueField: attrs.options.max_value, + currentValueField: attrs.options.current_value, isPercentage: !attrs.options.max_value, isEditable: attrs.options.editable, isEditableInReadonly: attrs.options.editable_readonly, @@ -137,8 +156,6 @@ ProgressBarField.extractProps = (fieldName, record, attrs) => { attrs.options.editable && (!attrs.options.edit_max_value || attrs.options.edit_current_value), isMaxValueEditable: attrs.options.editable && attrs.options.edit_max_value, - invalidate: () => record.setInvalidField(fieldName), - updatePart: (part, value) => record.update({ [parts[part].fieldName]: value }), }; }; diff --git a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.xml b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.xml index 7db30f5fea6..22a00963d27 100644 --- a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.xml +++ b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.xml @@ -8,10 +8,10 @@
- + -
+
@@ -26,11 +26,11 @@ class="o_input o_progressbar_value" type="text" inputmode="numeric" - t-att-value="getFormattedValue('currentValue') or ''" + t-att-value="formatCurrentValue() or ''" t-att-required="props.required" - t-on-change="(ev) => this.onChangeValue(ev.target.value, 'currentValue')" - t-on-input="(ev) => this.onInput(ev, 'currentValue')" - t-on-blur="(ev) => this.onBlur(ev.target.value, 'currentValue')" + t-on-change="(ev) => this.onCurrentValueChange(ev.target.value)" + t-on-input="(ev) => this.onCurrentValueInput(ev)" + t-on-blur="onBlur" /> %
@@ -41,10 +41,10 @@ class="o_progressbar_value o_input" type="text" inputmode="numeric" - t-att-value="getFormattedValue('currentValue')" - t-on-change="(ev) => this.onChangeValue(ev.target.value, 'currentValue')" - t-on-input="(ev) => this.onInput(ev, 'currentValue')" - t-on-blur="(ev) => this.onBlur(ev.target.value, 'currentValue')" + t-att-value="formatCurrentValue()" + t-on-change="(ev) => this.onCurrentValueChange(ev.target.value)" + t-on-input="(ev) => this.onCurrentValueInput(ev)" + t-on-blur="onBlur" />
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 49765fa4721..d5d05930ab8 100644 --- a/addons/web/static/src/views/fields/radio/radio_field.js +++ b/addons/web/static/src/views/fields/radio/radio_field.js @@ -11,6 +11,21 @@ export class RadioField extends Component { this.id = `radio_field_${++RadioField.nextId}`; } + get items() { + switch (this.props.record.fields[this.props.name].type) { + case "selection": + return this.props.record.fields[this.props.name].selection; + case "many2one": { + const value = this.props.record.preloadedData[this.props.name] || []; + return value.map((item) => [item.id, item.display_name]); + } + default: + return []; + } + } + get string() { + return this.props.record.activeFields[this.props.name].string; + } get value() { switch (this.props.type) { case "selection": @@ -43,8 +58,6 @@ RadioField.template = "web.RadioField"; RadioField.props = { ...standardFieldProps, orientation: { type: String, optional: true }, - radioItems: Array, - string: { type: String, optional: true }, }; RadioField.defaultProps = { orientation: "vertical", @@ -54,23 +67,9 @@ RadioField.displayName = _lt("Radio"); RadioField.supportedTypes = ["many2one", "selection"]; RadioField.isEmpty = () => false; -RadioField.extractProps = (fieldName, record, attrs) => { - const getRadioItems = () => { - switch (record.fields[fieldName].type) { - case "selection": - return record.fields[fieldName].selection; - case "many2one": { - const value = record.preloadedData[fieldName] || []; - return value.map((item) => [item.id, item.display_name]); - } - default: - return []; - } - }; +RadioField.extractProps = ({ attrs }) => { return { orientation: attrs.options.horizontal ? "horizontal" : "vertical", - radioItems: getRadioItems(), - string: record.activeFields[fieldName].string, }; }; diff --git a/addons/web/static/src/views/fields/radio/radio_field.xml b/addons/web/static/src/views/fields/radio/radio_field.xml index d67925c0ec9..8997c66ef66 100644 --- a/addons/web/static/src/views/fields/radio/radio_field.xml +++ b/addons/web/static/src/views/fields/radio/radio_field.xml @@ -5,9 +5,9 @@
- +
{ if ( - valuesEqual(this.props.value || {}, nextProps.value || {}) && - nextProps.modelFieldValue !== this.props.modelFieldValue + valuesEqual(this.getValue(this.props) || {}, this.getValue(nextProps) || {}) && + this.getModelName(nextProps) !== modelName ) { + modelName = this.getModelName(nextProps); nextProps.update(false); } }); } + getPreloadedData(p) { + return p.record.preloadedData[p.name]; + } + getValue(p) { + if (p.type === "char") { + return { + resModel: this.getPreloadedData(p).model, + resId: this.getPreloadedData(p).data.id, + displayName: this.getPreloadedData(p).data.display_name, + }; + } else { + return p.value; + } + } get m2oProps() { + const value = this.getValue(this.props); const p = { ...this.props, relation: this.relation, - value: this.props.value && [this.props.value.resId, this.props.value.displayName], + value: value && [value.resId, value.displayName], update: this.updateM2O.bind(this), }; delete p.hideModelSelector; - delete p.modelSelection; - delete p.modelFieldValue; return p; } + get selection() { + if (this.props.type !== "char" && !this.props.hideModelSelector) { + return this.props.record.fields[this.props.name].selection; + } + return []; + } get relation() { - if (this.props.modelFieldValue) { - return this.props.modelFieldValue; - } else if (this.props.value && this.props.value.resModel) { - return this.props.value.resModel; + if (this.getModelName(this.props)) { + return this.getModelName(this.props); + } else if (this.getValue(this.props) && this.getValue(this.props).resModel) { + return this.getValue(this.props).resModel; } else { return this.state && this.state.resModel; } } + getModelName(p) { + if (p.hideModelSelector && p.record.preloadedData[p.name]) { + return p.record.preloadedData[p.name].modelName; + } + return null; + } + updateModel(value) { this.state.resModel = value; this.props.update(false); @@ -75,60 +101,20 @@ ReferenceField.components = { Many2OneField, }; ReferenceField.props = { - ...standardFieldProps, + ...Many2OneField.props, hideModelSelector: { type: Boolean, optional: true }, - modelFieldValue: { type: String, optional: true }, - value: [ - Boolean, - { - type: Object, - shape: { - resModel: String, - resId: Number, - displayName: String, - }, - }, - ], - getContext: { type: Function, optional: true }, - getDomain: { type: Function, optional: true }, - modelSelection: { type: Array, optional: true }, - string: { type: String, optional: true }, }; ReferenceField.defaultProps = { - getContext: () => ({}), - getDomain: () => new Domain(), + ...Many2OneField.defaultProps, }; ReferenceField.displayName = _lt("Reference"); ReferenceField.supportedTypes = ["reference", "char"]; -ReferenceField.extractProps = (fieldName, record, attrs) => { - let props = {}; - const preloadedData = record.preloadedData[fieldName]; - if (record.fields[fieldName].type === "char") { - props = { - value: { - resModel: preloadedData.model, - resId: preloadedData.data.id, - displayName: preloadedData.data.display_name, - }, - }; - } else if (attrs.options["model_field"]) { - props = { - hideModelSelector: true, - modelFieldValue: preloadedData && preloadedData.modelName, - }; - } else { - props = { - modelSelection: record.fields[fieldName].selection, - }; - } - +ReferenceField.extractProps = ({ attrs, field }) => { return { - ...props, - getContext: () => record.getFieldContext(fieldName), - getDomain: () => record.getFieldDomain(fieldName), - string: attrs.string || record.fields[fieldName].string, + ...Many2OneField.extractProps({ attrs, field }), + hideModelSelector: !!attrs.options["model_field"], }; }; diff --git a/addons/web/static/src/views/fields/reference/reference_field.xml b/addons/web/static/src/views/fields/reference/reference_field.xml index 1fa6962665c..82604929b20 100644 --- a/addons/web/static/src/views/fields/reference/reference_field.xml +++ b/addons/web/static/src/views/fields/reference/reference_field.xml @@ -9,7 +9,7 @@ t-on-change="(ev) => this.updateModel(ev.target.value || false)" >