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)" >