[REF] *: js fields: extractProps receives fieldInfo
This commit is part of the preliminary work to rewrite the form, list and kanban model. We want this new Model to only be aware of field related information it needs (whereas in its current implementation, the model stores all the information extracted from the field node in the arch). This would allow to properly manage multiple occurrences of the same field in views, that is, each occurrence would be represented by a field component (if visible of course), and that field component would use the field information of the arch node it represents. To this end, we want fields from not using anymore information stored in activeFields in the record datapoint. Instead, we now call extractProps with the whole fieldInfo (the information extracted from the arch), s.t. each field can generate the props it needs from those information (e.g. sub views for x2manys). This commit doesn't remove the use of record.activeFields in concrete fields (this will come later), but reworks the fieldInfo object generated by parseFieldNode, and provide it to the calls of extractProps. In fieldInfo, the `options` key is no longer inside `attrs`, as it is now top-level, alongside several other generic keys that have been processed (like on_change, modifiers...). For that reason, a lot of extractProps definitions had to be adapted. Part of task 3179751 closes odoo/odoo#113092 Related: odoo/enterprise#37266 Signed-off-by: Jorge Pinna Puissant (jpp) <jpp@odoo.com>
This commit is contained in:
+6
-6
@@ -592,13 +592,13 @@ export const analyticDistribution = {
|
||||
component: AnalyticDistribution,
|
||||
supportedTypes: ["char", "text"],
|
||||
fieldDependencies: [{ name:"analytic_precision", type: "integer" }],
|
||||
extractProps: ({ attrs }) => ({
|
||||
business_domain: attrs.options.business_domain,
|
||||
account_field: attrs.options.account_field,
|
||||
product_field: attrs.options.product_field,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
business_domain: options.business_domain,
|
||||
account_field: options.account_field,
|
||||
product_field: options.product_field,
|
||||
business_domain_compute: attrs.business_domain_compute,
|
||||
force_applicability: attrs.options.force_applicability,
|
||||
allow_save: !attrs.options.disable_save,
|
||||
force_applicability: options.force_applicability,
|
||||
allow_save: !options.disable_save,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ export const iconSelectionField = {
|
||||
component: IconSelectionField,
|
||||
displayName: _lt("Icon Selection"),
|
||||
supportedTypes: ["char", "text", "selection"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
icons: attrs.options,
|
||||
extractProps: ({ options }) => ({
|
||||
icons: options,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
+4
-4
@@ -36,10 +36,10 @@ FilterableSelectionField.props = {
|
||||
export const filterableSelectionField = {
|
||||
...selectionField,
|
||||
component: FilterableSelectionField,
|
||||
extractProps: (params) => ({
|
||||
...selectionField.extractProps(params),
|
||||
whitelisted_values: params.attrs.options.whitelisted_values,
|
||||
blacklisted_values: params.attrs.options.blacklisted_values,
|
||||
extractProps: (fieldInfo) => ({
|
||||
...selectionField.extractProps(fieldInfo),
|
||||
whitelisted_values: fieldInfo.options.whitelisted_values,
|
||||
blacklisted_values: fieldInfo.options.blacklisted_values,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -52,21 +52,21 @@ TextField.props = {
|
||||
};
|
||||
|
||||
const charExtractProps = charField.extractProps;
|
||||
charField.extractProps = (attrs) => {
|
||||
return Object.assign(charExtractProps(attrs), {
|
||||
onchangeOnKeydown: archParseBoolean(attrs.onchange_on_keydown),
|
||||
keydownDebounceDelay: attrs.keydown_debounce_delay
|
||||
? Number(attrs.keydown_debounce_delay)
|
||||
charField.extractProps = (fieldInfo) => {
|
||||
return Object.assign(charExtractProps(fieldInfo), {
|
||||
onchangeOnKeydown: archParseBoolean(fieldInfo.attrs.onchange_on_keydown),
|
||||
keydownDebounceDelay: fieldInfo.attrs.keydown_debounce_delay
|
||||
? Number(fieldInfo.attrs.keydown_debounce_delay)
|
||||
: 2000,
|
||||
});
|
||||
};
|
||||
|
||||
const textExtractProps = textField.extractProps;
|
||||
textField.extractProps = (params) => {
|
||||
return Object.assign(textExtractProps(params), {
|
||||
onchangeOnKeydown: archParseBoolean(params.attrs.onchange_on_keydown),
|
||||
keydownDebounceDelay: params.attrs.keydown_debounce_delay
|
||||
? Number(params.attrs.keydown_debounce_delay)
|
||||
textField.extractProps = (fieldInfo) => {
|
||||
return Object.assign(textExtractProps(fieldInfo), {
|
||||
onchangeOnKeydown: archParseBoolean(fieldInfo.attrs.onchange_on_keydown),
|
||||
keydownDebounceDelay: fieldInfo.attrs.keydown_debounce_delay
|
||||
? Number(fieldInfo.attrs.keydown_debounce_delay)
|
||||
: 2000,
|
||||
});
|
||||
};
|
||||
|
||||
+3
-3
@@ -50,9 +50,9 @@ KanbanMany2OneAvatarUserField.props = {
|
||||
export const kanbanMany2OneAvatarUserField = {
|
||||
...many2OneAvatarUserField,
|
||||
component: KanbanMany2OneAvatarUserField,
|
||||
extractProps: (params) => ({
|
||||
...many2OneAvatarUserField.extractProps(params),
|
||||
displayAvatarName: params.attrs.options.display_avatar_name || false,
|
||||
extractProps: (fieldInfo) => ({
|
||||
...many2OneAvatarUserField.extractProps(fieldInfo),
|
||||
displayAvatarName: fieldInfo.options.display_avatar_name || false,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -153,10 +153,10 @@ FieldMany2OneMailingFilter.defaultProps = {
|
||||
export const fieldMany2OneMailingFilter = {
|
||||
...many2OneField,
|
||||
component: FieldMany2OneMailingFilter,
|
||||
extractProps: (params) => ({
|
||||
...many2OneField.extractProps(params),
|
||||
domain_field: params.attrs.options.domain_field,
|
||||
model_field: params.attrs.options.model_field,
|
||||
extractProps: (fieldInfo) => ({
|
||||
...many2OneField.extractProps(fieldInfo),
|
||||
domain_field: fieldInfo.options.domain_field,
|
||||
model_field: fieldInfo.options.model_field,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -599,11 +599,11 @@ export const massMailingHtmlField = {
|
||||
...htmlField,
|
||||
component: MassMailingHtmlField,
|
||||
displayName: _lt("Email"),
|
||||
extractProps: (params) => ({
|
||||
...htmlField.extractProps(params),
|
||||
filterTemplates: params.attrs.options.filterTemplates,
|
||||
inlineField: params.attrs.options['inline-field'],
|
||||
iframeHtmlClass: params.attrs['iframeHtmlClass'],
|
||||
extractProps: (fieldInfo) => ({
|
||||
...htmlField.extractProps(fieldInfo),
|
||||
filterTemplates: fieldInfo.options.filterTemplates,
|
||||
inlineField: fieldInfo.options['inline-field'],
|
||||
iframeHtmlClass: fieldInfo.attrs.iframeHtmlClass,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ patch(PhoneField, "sms.PhoneField", {
|
||||
});
|
||||
|
||||
const patchDescr = {
|
||||
extractProps({ attrs }) {
|
||||
extractProps(fieldInfo) {
|
||||
return {
|
||||
...this._super({ attrs }),
|
||||
enableButton: attrs.options.enable_sms,
|
||||
...this._super(fieldInfo),
|
||||
enableButton: fieldInfo.options.enable_sms,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<t t-foreach="slot.record.fieldNames" t-as="fieldName" t-key="fieldName">
|
||||
<t t-if="!slot.record.isInvisible(fieldName)">
|
||||
<t t-set="fieldInfo" t-value="props.model.popoverFields[fieldName]" />
|
||||
<li class="list-group-item flex-shrink-0 d-flex flex-wrap align-items-center" t-att-class="fieldInfo.rawAttrs.class">
|
||||
<li class="list-group-item flex-shrink-0 d-flex flex-wrap align-items-center" t-att-class="fieldInfo.attrs.class">
|
||||
<strong class="me-2">
|
||||
<t t-if="fieldInfo.options.icon">
|
||||
<b>
|
||||
|
||||
@@ -123,8 +123,8 @@ export const aceField = {
|
||||
component: AceField,
|
||||
displayName: _lt("Ace Editor"),
|
||||
supportedTypes: ["text"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
mode: attrs.options.mode,
|
||||
extractProps: ({ options }) => ({
|
||||
mode: options.mode,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "../standard_field_props";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
const formatters = registry.category("formatters");
|
||||
@@ -11,6 +12,10 @@ export class BadgeField extends Component {
|
||||
static template = "web.BadgeField";
|
||||
static props = {
|
||||
...standardFieldProps,
|
||||
decorations: { type: Object, optional: true },
|
||||
};
|
||||
static defaultProps = {
|
||||
decorations: {},
|
||||
};
|
||||
|
||||
get formattedValue() {
|
||||
@@ -21,8 +26,9 @@ export class BadgeField extends Component {
|
||||
}
|
||||
|
||||
get classFromDecoration() {
|
||||
const evalContext = this.props.record.evalContext;
|
||||
for (const decorationName in this.props.decorations) {
|
||||
if (this.props.decorations[decorationName]) {
|
||||
if (evaluateExpr(this.props.decorations[decorationName], evalContext)) {
|
||||
return `text-bg-${decorationName}`;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +40,9 @@ export const badgeField = {
|
||||
component: BadgeField,
|
||||
displayName: _lt("Badge"),
|
||||
supportedTypes: ["selection", "many2one", "char"],
|
||||
extractProps: ({ decorations }) => {
|
||||
return { decorations };
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("fields").add("badge", badgeField);
|
||||
|
||||
@@ -69,8 +69,8 @@ export const binaryField = {
|
||||
component: BinaryField,
|
||||
displayName: _lt("File"),
|
||||
supportedTypes: ["binary"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
acceptedFileExtensions: attrs.options.accepted_file_extensions,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
acceptedFileExtensions: options.accepted_file_extensions,
|
||||
fileNameField: attrs.filename,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -28,8 +28,8 @@ export const booleanIconField = {
|
||||
component: BooleanIconField,
|
||||
displayName: _lt("Boolean Icon"),
|
||||
supportedTypes: ["boolean"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
icon: attrs.options.icon,
|
||||
extractProps: ({ options }) => ({
|
||||
icon: options.icon,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -98,9 +98,9 @@ export const charField = {
|
||||
component: CharField,
|
||||
displayName: _lt("Text"),
|
||||
supportedTypes: ["char"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
isPassword: archParseBoolean(attrs.password),
|
||||
dynamicPlaceholder: attrs.options.dynamic_placeholder,
|
||||
dynamicPlaceholder: options.dynamic_placeholder,
|
||||
autocomplete: attrs.autocomplete,
|
||||
placeholder: attrs.placeholder,
|
||||
}),
|
||||
|
||||
@@ -62,8 +62,8 @@ export const dateField = {
|
||||
component: DateField,
|
||||
displayName: _lt("Date"),
|
||||
supportedTypes: ["date", "datetime"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
pickerOptions: attrs.options.datepicker,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
pickerOptions: options.datepicker,
|
||||
placeholder: attrs.placeholder,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -154,11 +154,11 @@ export class DateRangeField extends Component {
|
||||
export const dateRangeField = {
|
||||
component: DateRangeField,
|
||||
supportedTypes: ["date", "datetime"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
relatedEndDateField: attrs.options.related_end_date,
|
||||
relatedStartDateField: attrs.options.related_start_date,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
relatedEndDateField: options.related_end_date,
|
||||
relatedStartDateField: options.related_start_date,
|
||||
formatType: options.format_type,
|
||||
placeholder: attrs.placeholder,
|
||||
formatType: attrs.options.format_type,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ export const dateTimeField = {
|
||||
component: DateTimeField,
|
||||
displayName: _lt("Date & Time"),
|
||||
supportedTypes: ["datetime"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
pickerOptions: attrs.options.datepicker,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
pickerOptions: options.datepicker,
|
||||
placeholder: attrs.placeholder,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -144,9 +144,9 @@ export const domainField = {
|
||||
displayName: _lt("Domain"),
|
||||
supportedTypes: ["char"],
|
||||
isEmpty: () => false,
|
||||
extractProps: ({ attrs }) => ({
|
||||
editInDialog: attrs.options.in_dialog,
|
||||
resModel: attrs.options.model,
|
||||
extractProps: ({ options }) => ({
|
||||
editInDialog: options.in_dialog,
|
||||
resModel: options.model,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -18,39 +18,24 @@ const fieldRegistry = registry.category("fields");
|
||||
class DefaultField extends Component {}
|
||||
DefaultField.template = xml``;
|
||||
|
||||
function getFieldClassFromRegistry(fieldType, widget, viewType, jsClass) {
|
||||
if (jsClass && widget) {
|
||||
const name = `${jsClass}.${widget}`;
|
||||
if (fieldRegistry.contains(name)) {
|
||||
return fieldRegistry.get(name);
|
||||
function getFieldFromRegistry(fieldType, widget, viewType, jsClass) {
|
||||
const prefixes = jsClass ? [jsClass, viewType, ""] : [viewType, ""];
|
||||
const findInRegistry = (key) => {
|
||||
for (const prefix of prefixes) {
|
||||
const _key = prefix ? `${prefix}.${key}` : key;
|
||||
if (fieldRegistry.contains(_key)) {
|
||||
return fieldRegistry.get(_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (viewType && widget) {
|
||||
const name = `${viewType}.${widget}`;
|
||||
if (fieldRegistry.contains(name)) {
|
||||
return fieldRegistry.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
if (widget) {
|
||||
if (fieldRegistry.contains(widget)) {
|
||||
return fieldRegistry.get(widget);
|
||||
const field = findInRegistry(widget);
|
||||
if (field) {
|
||||
return field;
|
||||
}
|
||||
console.warn(`Missing widget: ${widget} for field of type ${fieldType}`);
|
||||
}
|
||||
|
||||
if (viewType && fieldType) {
|
||||
const name = `${viewType}.${fieldType}`;
|
||||
if (fieldRegistry.contains(name)) {
|
||||
return fieldRegistry.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldRegistry.contains(fieldType)) {
|
||||
return fieldRegistry.get(fieldType);
|
||||
}
|
||||
|
||||
return { component: DefaultField };
|
||||
return findInRegistry(fieldType) || { component: DefaultField };
|
||||
}
|
||||
|
||||
export function fieldVisualFeedback(field, record, fieldName, fieldInfo) {
|
||||
@@ -75,10 +60,11 @@ export function fieldVisualFeedback(field, record, fieldName, fieldInfo) {
|
||||
|
||||
export class Field extends Component {
|
||||
setup() {
|
||||
this.field = this.props.fieldInfo.field;
|
||||
if (!this.field) {
|
||||
if (this.props.fieldInfo) {
|
||||
this.field = this.props.fieldInfo.field;
|
||||
} else {
|
||||
const fieldType = this.props.record.fields[this.props.name].type;
|
||||
this.field = getFieldClassFromRegistry(fieldType, this.props.type);
|
||||
this.field = getFieldFromRegistry(fieldType, this.props.type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +74,7 @@ export class Field extends Component {
|
||||
this.field,
|
||||
record,
|
||||
name,
|
||||
fieldInfo
|
||||
fieldInfo || {}
|
||||
);
|
||||
const classNames = {
|
||||
o_field_widget: true,
|
||||
@@ -108,11 +94,13 @@ export class Field extends Component {
|
||||
// generate field decorations classNames (only if field-specific decorations
|
||||
// have been defined in an attribute, e.g. decoration-danger="other_field = 5")
|
||||
// only handle the text-decoration.
|
||||
const { decorations } = fieldInfo;
|
||||
const evalContext = record.evalContext;
|
||||
for (const decoName in decorations) {
|
||||
const value = evaluateExpr(decorations[decoName], evalContext);
|
||||
classNames[getClassNameFromDecoration(decoName)] = value;
|
||||
if (fieldInfo && fieldInfo.decorations) {
|
||||
const { decorations } = fieldInfo;
|
||||
const evalContext = record.evalContext;
|
||||
for (const decoName in decorations) {
|
||||
const value = evaluateExpr(decorations[decoName], evalContext);
|
||||
classNames[getClassNameFromDecoration(decoName)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return classNames;
|
||||
@@ -125,28 +113,22 @@ export class Field extends Component {
|
||||
get fieldComponentProps() {
|
||||
const record = this.props.record;
|
||||
const evalContext = record.evalContext;
|
||||
const fieldInfo = this.props.fieldInfo;
|
||||
|
||||
const modifiers = fieldInfo.modifiers || {};
|
||||
const readonlyFromModifiers = evalDomain(modifiers.readonly, evalContext);
|
||||
let readonlyFromModifiers = false;
|
||||
let propsFromNode = {};
|
||||
if (this.props.fieldInfo) {
|
||||
let fieldInfo = this.props.fieldInfo;
|
||||
|
||||
// Decoration props
|
||||
const decorationMap = {};
|
||||
const { decorations } = fieldInfo;
|
||||
for (const decoName in decorations) {
|
||||
const value = evaluateExpr(decorations[decoName], evalContext);
|
||||
decorationMap[decoName] = value;
|
||||
}
|
||||
const modifiers = fieldInfo.modifiers || {};
|
||||
readonlyFromModifiers = evalDomain(modifiers.readonly, evalContext);
|
||||
|
||||
let propsFromAttrs = fieldInfo.propsFromAttrs || {};
|
||||
if (this.props.attrs) {
|
||||
const extractProps = this.field.extractProps || (() => ({}));
|
||||
propsFromAttrs = extractProps.call(this.field, {
|
||||
attrs: {
|
||||
...this.props.attrs,
|
||||
options: evaluateExpr(this.props.attrs.options || "{}"),
|
||||
},
|
||||
});
|
||||
if (this.props.attrs) {
|
||||
fieldInfo = {
|
||||
...fieldInfo,
|
||||
attrs: { ...fieldInfo.attrs, ...this.props.attrs },
|
||||
};
|
||||
}
|
||||
propsFromNode = this.field.extractProps ? this.field.extractProps(fieldInfo) : {};
|
||||
}
|
||||
|
||||
const props = { ...this.props };
|
||||
@@ -158,11 +140,9 @@ export class Field extends Component {
|
||||
delete props.type;
|
||||
|
||||
return {
|
||||
...fieldInfo.props,
|
||||
value: this.props.record.data[this.props.name],
|
||||
decorations: decorationMap,
|
||||
readonly: !record.isInEdition || readonlyFromModifiers || false,
|
||||
...propsFromAttrs,
|
||||
...propsFromNode,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
@@ -171,7 +151,7 @@ export class Field extends Component {
|
||||
if (this.props.showTooltip) {
|
||||
const tooltip = getTooltipInfo({
|
||||
field: this.props.record.fields[this.props.name],
|
||||
fieldInfo: this.props.fieldInfo,
|
||||
fieldInfo: this.props.fieldInfo || {},
|
||||
});
|
||||
if (Boolean(odoo.debug) || (tooltip && JSON.parse(tooltip).field.help)) {
|
||||
return tooltip;
|
||||
@@ -187,51 +167,35 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) {
|
||||
const widget = node.getAttribute("widget");
|
||||
const fields = models[modelName];
|
||||
const modifiers = JSON.parse(node.getAttribute("modifiers") || "{}");
|
||||
const field = getFieldClassFromRegistry(fields[name].type, widget, viewType, jsClass);
|
||||
const field = getFieldFromRegistry(fields[name].type, widget, viewType, jsClass);
|
||||
const fieldInfo = {
|
||||
name,
|
||||
viewType,
|
||||
widget,
|
||||
modifiers,
|
||||
field,
|
||||
context: node.getAttribute("context") || "{}",
|
||||
string: node.getAttribute("string") || fields[name].string,
|
||||
help: node.getAttribute("help"),
|
||||
widget,
|
||||
modifiers,
|
||||
onChange: archParseBoolean(node.getAttribute("on_change")),
|
||||
field,
|
||||
forceSave: archParseBoolean(node.getAttribute("force_save")),
|
||||
decorations: {}, // populated below
|
||||
noLabel: archParseBoolean(node.getAttribute("nolabel")),
|
||||
props: {},
|
||||
rawAttrs: {},
|
||||
options: evaluateExpr(node.getAttribute("options") || "{}"),
|
||||
alwaysInvisible: modifiers.invisible === true || modifiers.column_invisible === true,
|
||||
decorations: {}, // populated below
|
||||
attrs: {}, // populated below
|
||||
};
|
||||
if (node.getAttribute("domain")) {
|
||||
// TODO WOWl: remove with new model?
|
||||
fieldInfo.domain = node.getAttribute("domain");
|
||||
}
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.name in Field.forbiddenAttributeNames) {
|
||||
throw new Error(Field.forbiddenAttributeNames[attribute.name]);
|
||||
for (const { name, value } of node.attributes) {
|
||||
if (name.startsWith("decoration-")) {
|
||||
// prepare field decorations
|
||||
fieldInfo.decorations[name.replace("decoration-", "")] = value;
|
||||
} else if (!name.startsWith("t-att")) {
|
||||
// all other (non dynamic) attributes
|
||||
fieldInfo.attrs[name] = value;
|
||||
}
|
||||
|
||||
// prepare field decorations
|
||||
if (attribute.name.startsWith("decoration-")) {
|
||||
const decorationName = attribute.name.replace("decoration-", "");
|
||||
fieldInfo.decorations[decorationName] = attribute.value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!attribute.name.startsWith("t-att")) {
|
||||
fieldInfo.rawAttrs[attribute.name] = attribute.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (viewType !== "kanban") {
|
||||
// FIXME WOWL: find a better solution
|
||||
const extractProps = field.extractProps || (() => ({}));
|
||||
fieldInfo.propsFromAttrs = extractProps.call(field, {
|
||||
attrs: { ...fieldInfo.rawAttrs, options: fieldInfo.options },
|
||||
});
|
||||
}
|
||||
|
||||
if (X2M_TYPES.includes(fields[name].type)) {
|
||||
@@ -262,7 +226,6 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) {
|
||||
viewMode = viewMode.replace("tree", "list");
|
||||
}
|
||||
fieldInfo.viewMode = viewMode;
|
||||
fieldInfo.relation = fields[name].relation; // not really necessary
|
||||
fieldInfo.views = views;
|
||||
|
||||
let fieldsToFetch = field.fieldsToFetch;
|
||||
@@ -277,8 +240,5 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) {
|
||||
return fieldInfo;
|
||||
};
|
||||
|
||||
Field.forbiddenAttributeNames = {
|
||||
decorations: `You cannot use the "decorations" attribute name as it is used as generated prop name for the composite decoration-<something> attributes.`,
|
||||
};
|
||||
Field.props = ["fieldInfo?", "*"];
|
||||
Field.defaultProps = { fieldInfo: {}, setDirty: () => {} };
|
||||
Field.defaultProps = { setDirty: () => {} };
|
||||
|
||||
@@ -57,19 +57,19 @@ export const floatField = {
|
||||
displayName: _lt("Float"),
|
||||
supportedTypes: ["float"],
|
||||
isEmpty: () => false,
|
||||
extractProps: ({ attrs }) => {
|
||||
extractProps: ({ attrs, options }) => {
|
||||
// Sadly, digits param was available as an option and an attr.
|
||||
// The option version could be removed with some xml refactoring.
|
||||
let digits;
|
||||
if (attrs.digits) {
|
||||
digits = JSON.parse(attrs.digits);
|
||||
} else if (attrs.options.digits) {
|
||||
digits = attrs.options.digits;
|
||||
} else if (options.digits) {
|
||||
digits = options.digits;
|
||||
}
|
||||
|
||||
return {
|
||||
inputType: attrs.options.type,
|
||||
step: attrs.options.step,
|
||||
inputType: options.type,
|
||||
step: options.step,
|
||||
digits,
|
||||
placeholder: attrs.placeholder,
|
||||
};
|
||||
|
||||
@@ -29,9 +29,9 @@ export class FloatFactorField extends FloatField {
|
||||
export const floatFactorField = {
|
||||
...floatField,
|
||||
component: FloatFactorField,
|
||||
extractProps: (params) => ({
|
||||
...floatField.extractProps(params),
|
||||
factor: params.attrs.options.factor,
|
||||
extractProps: (fieldInfo) => ({
|
||||
...floatField.extractProps(fieldInfo),
|
||||
factor: fieldInfo.options.factor,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -54,21 +54,21 @@ export const floatToggleField = {
|
||||
component: FloatToggleField,
|
||||
supportedTypes: ["float"],
|
||||
isEmpty: () => false,
|
||||
extractProps: ({ attrs }) => {
|
||||
extractProps: ({ attrs, options }) => {
|
||||
// Sadly, digits param was available as an option and an attr.
|
||||
// The option version could be removed with some xml refactoring.
|
||||
let digits;
|
||||
if (attrs.digits) {
|
||||
digits = JSON.parse(attrs.digits);
|
||||
} else if (attrs.options.digits) {
|
||||
digits = attrs.options.digits;
|
||||
} else if (options.digits) {
|
||||
digits = options.digits;
|
||||
}
|
||||
|
||||
return {
|
||||
digits,
|
||||
range: attrs.options.range,
|
||||
factor: attrs.options.factor,
|
||||
disableReadOnly: attrs.options.force_button || false,
|
||||
range: options.range,
|
||||
factor: options.factor,
|
||||
disableReadOnly: options.force_button || false,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -133,21 +133,14 @@ export const imageField = {
|
||||
displayName: _lt("Image"),
|
||||
supportedTypes: ["binary"],
|
||||
fieldDependencies: [{ name: "write_date", type: "datetime" }],
|
||||
extractProps: ({ attrs }) => ({
|
||||
enableZoom: attrs.options.zoom,
|
||||
zoomDelay: attrs.options.zoom_delay,
|
||||
previewImage: attrs.options.preview_image,
|
||||
acceptedFileExtensions: attrs.options.accepted_file_extensions,
|
||||
width:
|
||||
attrs.options.size && Boolean(attrs.options.size[0])
|
||||
? attrs.options.size[0]
|
||||
: attrs.width,
|
||||
height:
|
||||
attrs.options.size && Boolean(attrs.options.size[1])
|
||||
? attrs.options.size[1]
|
||||
: attrs.height,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
enableZoom: options.zoom,
|
||||
zoomDelay: options.zoom_delay,
|
||||
previewImage: options.preview_image,
|
||||
acceptedFileExtensions: options.accepted_file_extensions,
|
||||
width: options.size && Boolean(options.size[0]) ? options.size[0] : attrs.width,
|
||||
height: options.size && Boolean(options.size[1]) ? options.size[1] : attrs.height,
|
||||
}),
|
||||
};
|
||||
|
||||
registry.category("fields").add("image", imageField);
|
||||
registry.category("fields").add("kanban.image", imageField); // FIXME WOWL: s.t. we don't use the legacy one
|
||||
|
||||
@@ -53,12 +53,10 @@ export const imageUrlField = {
|
||||
component: ImageUrlField,
|
||||
displayName: _lt("Image"),
|
||||
supportedTypes: ["char"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
width: attrs.options.size ? attrs.options.size[0] : attrs.width,
|
||||
height: attrs.options.size ? attrs.options.size[1] : attrs.height,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
width: options.size ? options.size[0] : attrs.width,
|
||||
height: options.size ? options.size[1] : attrs.height,
|
||||
}),
|
||||
};
|
||||
|
||||
registry.category("fields").add("image_url", imageUrlField);
|
||||
// TODO WOWL: remove below when old registry is removed.
|
||||
registry.category("fields").add("kanban.image_url", imageUrlField);
|
||||
|
||||
@@ -44,9 +44,9 @@ export const integerField = {
|
||||
displayName: _lt("Integer"),
|
||||
supportedTypes: ["integer"],
|
||||
isEmpty: (record, fieldName) => record.data[fieldName] === false,
|
||||
extractProps: ({ attrs }) => ({
|
||||
inputType: attrs.options.type,
|
||||
step: attrs.options.step,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
inputType: options.type,
|
||||
step: options.step,
|
||||
placeholder: attrs.placeholder,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -31,8 +31,8 @@ export const labelSelectionField = {
|
||||
component: LabelSelectionField,
|
||||
displayName: _lt("Label Selection"),
|
||||
supportedTypes: ["selection"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
classesObj: attrs.options.classes,
|
||||
extractProps: ({ options }) => ({
|
||||
classesObj: options.classes,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -66,8 +66,8 @@ export const many2ManyBinaryField = {
|
||||
{ name: "name", type: "char" },
|
||||
{ name: "mimetype", type: "char" },
|
||||
],
|
||||
extractProps: ({ attrs }) => ({
|
||||
acceptedFileExtensions: attrs.options.accepted_file_extensions,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
acceptedFileExtensions: options.accepted_file_extensions,
|
||||
className: attrs.class,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -250,25 +250,25 @@ export const many2ManyTagsField = {
|
||||
displayName: _lt("Tags"),
|
||||
supportedTypes: ["many2many"],
|
||||
isSet: (value) => value.count > 0,
|
||||
fieldsToFetch: (fieldInfo) => {
|
||||
fieldsToFetch: ({ options }) => {
|
||||
const fieldsToFetch = [{ name: "display_name", type: "char" }];
|
||||
if (fieldInfo.options.color_field) {
|
||||
fieldsToFetch.push({ name: fieldInfo.options.color_field, type: "integer" });
|
||||
if (options.color_field) {
|
||||
fieldsToFetch.push({ name: options.color_field, type: "integer" });
|
||||
}
|
||||
return fieldsToFetch;
|
||||
},
|
||||
extractProps: ({ attrs }) => {
|
||||
const noCreate = Boolean(attrs.options.no_create);
|
||||
extractProps: ({ attrs, options }) => {
|
||||
const noCreate = Boolean(options.no_create);
|
||||
const canCreate = attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !noCreate;
|
||||
const noQuickCreate = Boolean(attrs.options.no_quick_create);
|
||||
const noCreateEdit = Boolean(attrs.options.no_create_edit);
|
||||
const noQuickCreate = Boolean(options.no_quick_create);
|
||||
const noCreateEdit = Boolean(options.no_create_edit);
|
||||
return {
|
||||
colorField: attrs.options.color_field,
|
||||
nameCreateField: attrs.options.create_name_field,
|
||||
colorField: options.color_field,
|
||||
nameCreateField: options.create_name_field,
|
||||
canCreate,
|
||||
canQuickCreate: canCreate && !noQuickCreate,
|
||||
canCreateEdit: canCreate && !noCreateEdit,
|
||||
createDomain: attrs.options.create,
|
||||
createDomain: options.create,
|
||||
placeholder: attrs.placeholder,
|
||||
};
|
||||
},
|
||||
@@ -359,9 +359,9 @@ export class Many2ManyTagsFieldColorEditable extends Many2ManyTagsField {
|
||||
export const many2ManyTagsFieldColorEditable = {
|
||||
...many2ManyTagsField,
|
||||
component: Many2ManyTagsFieldColorEditable,
|
||||
extractProps: (params) => ({
|
||||
...many2ManyTagsField.extractProps(params),
|
||||
canEditColor: !params.attrs.options.no_edit_color && !!params.attrs.options.color_field,
|
||||
extractProps: (fieldInfo) => ({
|
||||
...many2ManyTagsField.extractProps(fieldInfo),
|
||||
canEditColor: !fieldInfo.options.no_edit_color && !!fieldInfo.options.color_field,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -297,20 +297,20 @@ export const many2OneField = {
|
||||
component: Many2OneField,
|
||||
displayName: _lt("Many2one"),
|
||||
supportedTypes: ["many2one"],
|
||||
extractProps: ({ attrs }) => {
|
||||
extractProps: ({ attrs, options, string }) => {
|
||||
const canCreate =
|
||||
attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !attrs.options.no_create;
|
||||
attrs.can_create && Boolean(JSON.parse(attrs.can_create)) && !options.no_create;
|
||||
return {
|
||||
placeholder: attrs.placeholder,
|
||||
canOpen: !attrs.options.no_open,
|
||||
canOpen: !options.no_open,
|
||||
canCreate,
|
||||
canWrite: attrs.can_write && Boolean(JSON.parse(attrs.can_write)),
|
||||
canQuickCreate: canCreate && !attrs.options.no_quick_create,
|
||||
canCreateEdit: canCreate && !attrs.options.no_create_edit,
|
||||
nameCreateField: attrs.options.create_name_field,
|
||||
canScanBarcode: !!attrs.options.can_scan_barcode,
|
||||
canQuickCreate: canCreate && !options.no_quick_create,
|
||||
canCreateEdit: canCreate && !options.no_create_edit,
|
||||
nameCreateField: options.create_name_field,
|
||||
canScanBarcode: !!options.can_scan_barcode,
|
||||
openTarget: attrs.open_target,
|
||||
string: attrs.string,
|
||||
string,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,8 +15,8 @@ export const many2OneBarcodeField = {
|
||||
...many2OneField,
|
||||
component: Many2OneBarcodeField,
|
||||
displayName: _lt("Many2OneBarcode"),
|
||||
extractProps: (params) => ({
|
||||
...many2OneField.extractProps(params),
|
||||
extractProps: (fieldInfo) => ({
|
||||
...many2OneField.extractProps(fieldInfo),
|
||||
canScanBarcode: true,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -80,11 +80,11 @@ export const monetaryField = {
|
||||
component: MonetaryField,
|
||||
supportedTypes: ["monetary", "float"],
|
||||
displayName: _lt("Monetary"),
|
||||
extractProps: ({ attrs }) => ({
|
||||
currencyField: attrs.options.currency_field,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
currencyField: options.currency_field,
|
||||
inputType: attrs.type,
|
||||
useFieldDigits: attrs.options.field_digits,
|
||||
hideSymbol: attrs.options.no_symbol,
|
||||
useFieldDigits: options.field_digits,
|
||||
hideSymbol: options.no_symbol,
|
||||
placeholder: attrs.placeholder,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -89,9 +89,9 @@ export const pdfViewerField = {
|
||||
component: PdfViewerField,
|
||||
displayName: _lt("PDF Viewer"),
|
||||
supportedTypes: ["binary"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
fileNameField: attrs.filename,
|
||||
previewImage: attrs.options.preview_image,
|
||||
previewImage: options.preview_image,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -28,9 +28,7 @@ export const percentPieField = {
|
||||
displayName: _lt("PercentPie"),
|
||||
supportedTypes: ["float", "integer"],
|
||||
additionalClasses: ["o_field_percent_pie"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
string: attrs.string,
|
||||
}),
|
||||
extractProps: ({ string }) => ({ string }),
|
||||
};
|
||||
|
||||
registry.category("fields").add("percentpie", percentPieField);
|
||||
|
||||
@@ -46,14 +46,14 @@ export const percentageField = {
|
||||
component: PercentageField,
|
||||
displayName: _lt("Percentage"),
|
||||
supportedTypes: ["integer", "float"],
|
||||
extractProps: ({ attrs }) => {
|
||||
extractProps: ({ attrs, options }) => {
|
||||
// Sadly, digits param was available as an option and an attr.
|
||||
// The option version could be removed with some xml refactoring.
|
||||
let digits;
|
||||
if (attrs.digits) {
|
||||
digits = JSON.parse(attrs.digits);
|
||||
} else if (attrs.options.digits) {
|
||||
digits = attrs.options.digits;
|
||||
} else if (options.digits) {
|
||||
digits = options.digits;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -155,15 +155,14 @@ export const progressBarField = {
|
||||
component: ProgressBarField,
|
||||
displayName: _lt("Progress Bar"),
|
||||
supportedTypes: ["integer", "float"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
maxValueField: attrs.options.max_value,
|
||||
currentValueField: attrs.options.current_value,
|
||||
isEditable: !attrs.options.readonly && attrs.options.editable,
|
||||
isEditableInReadonly: attrs.options.editable_readonly,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
maxValueField: options.max_value,
|
||||
currentValueField: options.current_value,
|
||||
isEditable: !options.readonly && options.editable,
|
||||
isEditableInReadonly: options.editable_readonly,
|
||||
isCurrentValueEditable:
|
||||
attrs.options.editable &&
|
||||
(!attrs.options.edit_max_value || attrs.options.edit_current_value),
|
||||
isMaxValueEditable: attrs.options.editable && attrs.options.edit_max_value,
|
||||
options.editable && (!options.edit_max_value || options.edit_current_value),
|
||||
isMaxValueEditable: options.editable && options.edit_max_value,
|
||||
title: attrs.title,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -72,8 +72,8 @@ export const radioField = {
|
||||
displayName: _lt("Radio"),
|
||||
supportedTypes: ["many2one", "selection"],
|
||||
isEmpty: (record, fieldName) => record.data[fieldName] === false,
|
||||
extractProps: ({ attrs }) => ({
|
||||
orientation: attrs.options.horizontal ? "horizontal" : "vertical",
|
||||
extractProps: ({ options }) => ({
|
||||
orientation: options.horizontal ? "horizontal" : "vertical",
|
||||
}),
|
||||
legacySpecialData: "_fetchSpecialMany2ones",
|
||||
};
|
||||
|
||||
@@ -128,7 +128,7 @@ export const referenceField = {
|
||||
displayName: _lt("Reference"),
|
||||
supportedTypes: ["reference", "char"],
|
||||
legacySpecialData: "_fetchSpecialReference",
|
||||
extractProps: (params) => ({
|
||||
extractProps: (fieldInfo) => ({
|
||||
/*
|
||||
1 - <field name="ref" options="{'model_field': 'model_id'}" />
|
||||
2 - <field name="ref" options="{'hide_model': True}" />
|
||||
@@ -137,9 +137,8 @@ export const referenceField = {
|
||||
|
||||
We want to display the model selector only in the 4th case.
|
||||
*/
|
||||
...many2OneField.extractProps(params),
|
||||
hideModelSelector:
|
||||
!!params.attrs.options["hide_model"] || !!params.attrs.options["model_field"],
|
||||
...many2OneField.extractProps(fieldInfo),
|
||||
hideModelSelector: !!fieldInfo.options.hide_model || !!fieldInfo.options.model_field,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -136,12 +136,12 @@ export class SignatureField extends Component {
|
||||
|
||||
export const signatureField = {
|
||||
component: SignatureField,
|
||||
extractProps: ({ attrs }) => ({
|
||||
defaultFont: attrs.options.default_font || "",
|
||||
fullName: attrs.options.full_name,
|
||||
height: attrs.options.size ? attrs.options.size[1] || undefined : attrs.height,
|
||||
previewImage: attrs.options.preview_image,
|
||||
width: attrs.options.size ? attrs.options.size[0] || undefined : attrs.width,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
defaultFont: options.default_font || "",
|
||||
fullName: options.full_name,
|
||||
height: options.size ? options.size[1] || undefined : attrs.height,
|
||||
previewImage: options.preview_image,
|
||||
width: options.size ? options.size[0] || undefined : attrs.width,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,5 @@ export const standardFieldProps = {
|
||||
readonly: { type: Boolean, optional: true },
|
||||
record: { type: Object },
|
||||
value: true,
|
||||
decorations: { type: Object, optional: true },
|
||||
setDirty: { type: Function, optional: true },
|
||||
};
|
||||
|
||||
@@ -37,19 +37,19 @@ export const statInfoField = {
|
||||
displayName: _lt("Stat Info"),
|
||||
supportedTypes: ["float", "integer", "monetary"],
|
||||
isEmpty: () => false,
|
||||
extractProps: ({ attrs }) => {
|
||||
extractProps: ({ attrs, options }) => {
|
||||
// Sadly, digits param was available as an option and an attr.
|
||||
// The option version could be removed with some xml refactoring.
|
||||
let digits;
|
||||
if (attrs.digits) {
|
||||
digits = JSON.parse(attrs.digits);
|
||||
} else if (attrs.options.digits) {
|
||||
digits = attrs.options.digits;
|
||||
} else if (options.digits) {
|
||||
digits = options.digits;
|
||||
}
|
||||
|
||||
return {
|
||||
digits,
|
||||
labelField: attrs.options.label_field,
|
||||
labelField: options.label_field,
|
||||
noLabel: archParseBoolean(attrs.nolabel),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -91,8 +91,8 @@ export const stateSelectionField = {
|
||||
component: StateSelectionField,
|
||||
displayName: _lt("Label Selection"),
|
||||
supportedTypes: ["selection"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
hideLabel: !!attrs.options.hide_label,
|
||||
extractProps: ({ options }) => ({
|
||||
hideLabel: !!options.hide_label,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -225,10 +225,10 @@ export const statusBarField = {
|
||||
supportedTypes: ["many2one", "selection"],
|
||||
isEmpty: (record, fieldName) => record.model.env.isSmall && !record.data[fieldName],
|
||||
legacySpecialData: "_fetchSpecialStatus",
|
||||
extractProps: ({ attrs }) => ({
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
canCreate: Boolean(attrs.can_create),
|
||||
canWrite: Boolean(attrs.can_write),
|
||||
isDisabled: !attrs.options.clickable,
|
||||
isDisabled: !options.clickable,
|
||||
visibleSelection:
|
||||
attrs.statusbar_visible && attrs.statusbar_visible.trim().split(/\s*,\s*/g),
|
||||
}),
|
||||
|
||||
@@ -129,9 +129,9 @@ export const textField = {
|
||||
component: TextField,
|
||||
displayName: _lt("Multiline Text"),
|
||||
supportedTypes: ["html", "text"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
placeholder: attrs.placeholder,
|
||||
dynamicPlaceholder: attrs.options.dynamic_placeholder,
|
||||
dynamicPlaceholder: options.dynamic_placeholder,
|
||||
rowCount: attrs.rows && parseInteger(attrs.rows),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -74,10 +74,10 @@ export const timezoneMismatchField = {
|
||||
...selectionField,
|
||||
component: TimezoneMismatchField,
|
||||
additionalClasses: ["d-flex"],
|
||||
extractProps: (params) => ({
|
||||
...selectionField.extractProps(params),
|
||||
tzOffsetField: params.attrs.options.tz_offset_field,
|
||||
mismatchTitle: params.attrs.options.mismatch_title,
|
||||
extractProps: (fieldInfo) => ({
|
||||
...selectionField.extractProps(fieldInfo),
|
||||
tzOffsetField: fieldInfo.options.tz_offset_field,
|
||||
mismatchTitle: fieldInfo.options.mismatch_title,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -38,9 +38,9 @@ export const urlField = {
|
||||
component: UrlField,
|
||||
displayName: _lt("URL"),
|
||||
supportedTypes: ["char"],
|
||||
extractProps: ({ attrs }) => ({
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
text: attrs.text,
|
||||
websitePath: attrs.options.website_path,
|
||||
websitePath: options.website_path,
|
||||
placeholder: attrs.placeholder,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -116,7 +116,9 @@ export class ListArchParser extends XMLParser {
|
||||
className: node.getAttribute("class"), // for oe_edit_only and oe_read_only
|
||||
optional: node.getAttribute("optional") || false,
|
||||
type: "field",
|
||||
hasLabel: !(fieldInfo.noLabel || fieldInfo.field.noLabel),
|
||||
hasLabel: !(
|
||||
archParseBoolean(fieldInfo.attrs.nolabel) || fieldInfo.field.noLabel
|
||||
),
|
||||
label: (fieldInfo.widget && label && label.toString()) || fieldInfo.string,
|
||||
});
|
||||
}
|
||||
@@ -133,7 +135,7 @@ export class ListArchParser extends XMLParser {
|
||||
name: widgetInfo.name,
|
||||
// FIXME: this is dumb, we encode it into a weird object so that the widget
|
||||
// can decode it later...
|
||||
node: encodeObjectForTemplate({ attrs: widgetInfo.rawAttrs }).slice(1, -1),
|
||||
node: encodeObjectForTemplate({ attrs: widgetInfo.attrs }).slice(1, -1),
|
||||
className: node.getAttribute("class") || "",
|
||||
};
|
||||
columns.push({
|
||||
|
||||
@@ -535,12 +535,12 @@ export class ListRenderer extends Component {
|
||||
if (type !== "integer" && type !== "float" && type !== "monetary") {
|
||||
continue;
|
||||
}
|
||||
const { rawAttrs, widget } = column;
|
||||
const { attrs, widget } = column;
|
||||
const func =
|
||||
(rawAttrs.sum && "sum") ||
|
||||
(rawAttrs.avg && "avg") ||
|
||||
(rawAttrs.max && "max") ||
|
||||
(rawAttrs.min && "min");
|
||||
(attrs.sum && "sum") ||
|
||||
(attrs.avg && "avg") ||
|
||||
(attrs.max && "max") ||
|
||||
(attrs.min && "min");
|
||||
if (func) {
|
||||
let aggregateValue = 0;
|
||||
if (func === "max") {
|
||||
@@ -556,11 +556,11 @@ export class ListRenderer extends Component {
|
||||
|
||||
const formatter = formatters.get(widget, false) || formatters.get(type, false);
|
||||
const formatOptions = {
|
||||
digits: rawAttrs.digits ? JSON.parse(rawAttrs.digits) : undefined,
|
||||
digits: attrs.digits ? JSON.parse(attrs.digits) : undefined,
|
||||
escape: true,
|
||||
};
|
||||
aggregates[fieldName] = {
|
||||
help: rawAttrs[func],
|
||||
help: attrs[func],
|
||||
value: formatter ? formatter(aggregateValue, formatOptions) : aggregateValue,
|
||||
};
|
||||
}
|
||||
@@ -569,7 +569,7 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
|
||||
formatAggregateValue(group, column) {
|
||||
const { widget, rawAttrs } = column;
|
||||
const { widget, attrs } = column;
|
||||
const fieldType = this.props.list.fields[column.name].type;
|
||||
const aggregateValue = group.aggregates[column.name];
|
||||
if (!(column.name in group.aggregates)) {
|
||||
@@ -577,7 +577,7 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
const formatter = formatters.get(widget, false) || formatters.get(fieldType, false);
|
||||
const formatOptions = {
|
||||
digits: rawAttrs.digits ? JSON.parse(rawAttrs.digits) : undefined,
|
||||
digits: attrs.digits ? JSON.parse(attrs.digits) : undefined,
|
||||
escape: true,
|
||||
};
|
||||
return formatter ? formatter(aggregateValue, formatOptions) : aggregateValue;
|
||||
@@ -677,12 +677,8 @@ export class ListRenderer extends Component {
|
||||
classNames.push("o_list_button");
|
||||
} else if (column.type === "field") {
|
||||
classNames.push("o_field_cell");
|
||||
if (
|
||||
column.rawAttrs &&
|
||||
column.rawAttrs.class &&
|
||||
this.canUseFormatter(column, record)
|
||||
) {
|
||||
classNames.push(column.rawAttrs.class);
|
||||
if (column.attrs && column.attrs.class && this.canUseFormatter(column, record)) {
|
||||
classNames.push(column.attrs.class);
|
||||
}
|
||||
const typeClass = FIELD_CLASSES[this.fields[column.name].type];
|
||||
if (typeClass) {
|
||||
@@ -743,7 +739,7 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
|
||||
getFieldClass(column) {
|
||||
return column.rawAttrs && column.rawAttrs.class;
|
||||
return column.attrs && column.attrs.class;
|
||||
}
|
||||
|
||||
getFormattedValue(column, record) {
|
||||
@@ -753,8 +749,8 @@ export class ListRenderer extends Component {
|
||||
const formatOptions = {
|
||||
escape: false,
|
||||
data: record.data,
|
||||
isPassword: "password" in column.rawAttrs,
|
||||
digits: column.rawAttrs.digits ? JSON.parse(column.rawAttrs.digits) : field.digits,
|
||||
isPassword: "password" in column.attrs,
|
||||
digits: column.attrs.digits ? JSON.parse(column.attrs.digits) : field.digits,
|
||||
field: record.fields[fieldName],
|
||||
};
|
||||
return formatter(record.data[fieldName], formatOptions);
|
||||
@@ -1660,8 +1656,8 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
|
||||
calculateColumnWidth(column) {
|
||||
if (column.options && column.rawAttrs.width) {
|
||||
return { type: "absolute", value: column.rawAttrs.width };
|
||||
if (column.options && column.attrs.width) {
|
||||
return { type: "absolute", value: column.attrs.width };
|
||||
}
|
||||
|
||||
if (column.type !== "field") {
|
||||
|
||||
@@ -69,7 +69,7 @@ const DEFAULT_QUICK_CREATE_VIEW = {
|
||||
export function isAllowedDateField(groupByField) {
|
||||
return (
|
||||
["date", "datetime"].includes(groupByField.type) &&
|
||||
archParseBoolean(groupByField.rawAttrs.allow_group_range_value)
|
||||
archParseBoolean(groupByField.attrs.allow_group_range_value)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1483,7 +1483,7 @@ class DynamicList extends DataPoint {
|
||||
}
|
||||
const [groupByFieldName, granularity] = this.firstGroupBy.split(":");
|
||||
return {
|
||||
rawAttrs: {},
|
||||
attrs: {},
|
||||
...this.fields[groupByFieldName],
|
||||
...this.activeFields[groupByFieldName],
|
||||
granularity: granularity,
|
||||
|
||||
@@ -28,7 +28,7 @@ export function addFieldDependencies(activeFields, fields, dependencies = []) {
|
||||
for (const dependency of dependencies) {
|
||||
const { name } = dependency;
|
||||
if (!(name in activeFields)) {
|
||||
activeFields[name] = Object.assign({ name, rawAttrs: {} }, dependency, {
|
||||
activeFields[name] = Object.assign({ name, attrs: {} }, dependency, {
|
||||
modifiers: { invisible: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ export const standardWidgetProps = {
|
||||
record: { type: Object },
|
||||
// TODO TO REMOVE - ListView - FGE
|
||||
options: { type: Object, optional: true },
|
||||
rawAttrs: { type: Object, optional: true },
|
||||
attrs: { type: Object, optional: true },
|
||||
className: { type: String, optional: true },
|
||||
};
|
||||
|
||||
@@ -70,7 +70,7 @@ Widget.parseWidgetNode = function (node) {
|
||||
return {
|
||||
options: evaluateExpr(node.getAttribute("options") || "{}"),
|
||||
name,
|
||||
rawAttrs: attrs,
|
||||
attrs,
|
||||
widget,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { uiService } from "@web/core/ui/ui_service";
|
||||
import { createElement } from "@web/core/utils/xml";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env";
|
||||
import { click, getFixture, mount, nextTick, triggerEvent } from "../../helpers/utils";
|
||||
import { setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
@@ -206,6 +208,8 @@ export const FAKE_FIELDS = {
|
||||
};
|
||||
|
||||
function makeFakeModelState() {
|
||||
const fakeFieldNode = createElement("field", { name: "name" });
|
||||
const fakeModels = { event: FAKE_FIELDS };
|
||||
return {
|
||||
canCreate: true,
|
||||
canDelete: true,
|
||||
@@ -228,7 +232,7 @@ function makeFakeModelState() {
|
||||
hasEditDialog: false,
|
||||
hasQuickCreate: false,
|
||||
popoverFields: {
|
||||
name: { rawAttrs: {}, options: {} },
|
||||
name: Field.parseFieldNode(fakeFieldNode, fakeModels, "event", "calendar"),
|
||||
},
|
||||
rangeEnd: makeFakeDate().endOf("month"),
|
||||
rangeStart: makeFakeDate().startOf("month"),
|
||||
|
||||
@@ -3566,9 +3566,9 @@ QUnit.module("Views", (hooks) => {
|
||||
MyField.template = owl.xml`<div>ok</div>`;
|
||||
fieldRegistry.add("my_field", {
|
||||
component: MyField,
|
||||
extractProps: function ({ attrs }) {
|
||||
assert.deepEqual(attrs.options, { horizontal: true });
|
||||
return { horizontal: attrs.options.horizontal };
|
||||
extractProps: function ({ options }) {
|
||||
assert.deepEqual(options, { horizontal: true });
|
||||
return { horizontal: options.horizontal };
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -866,7 +866,6 @@ QUnit.module("Views", (hooks) => {
|
||||
str: "some string",
|
||||
bool: "true",
|
||||
num: "4.5",
|
||||
options: {},
|
||||
field_id: "int_field",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -630,49 +630,49 @@ export const htmlField = {
|
||||
component: HtmlField,
|
||||
displayName: _lt("Html"),
|
||||
supportedTypes: ["html"],
|
||||
extractProps: ({ attrs }) => {
|
||||
extractProps: ({ attrs, options }) => {
|
||||
const wysiwygOptions = {
|
||||
placeholder: attrs.placeholder,
|
||||
noAttachment: attrs.options['no-attachment'],
|
||||
inIframe: Boolean(attrs.options.cssEdit),
|
||||
iframeCssAssets: attrs.options.cssEdit,
|
||||
noAttachment: options['no-attachment'],
|
||||
inIframe: Boolean(options.cssEdit),
|
||||
iframeCssAssets: options.cssEdit,
|
||||
iframeHtmlClass: attrs.iframeHtmlClass,
|
||||
snippets: attrs.options.snippets,
|
||||
snippets: options.snippets,
|
||||
mediaModalParams: {
|
||||
noVideos: 'noVideos' in attrs.options ? attrs.options.noVideos : true,
|
||||
noVideos: 'noVideos' in options ? options.noVideos : true,
|
||||
useMediaLibrary: true,
|
||||
},
|
||||
linkForceNewWindow: true,
|
||||
tabsize: 0,
|
||||
height: attrs.options.height,
|
||||
minHeight: attrs.options.minHeight,
|
||||
maxHeight: attrs.options.maxHeight,
|
||||
resizable: 'resizable' in attrs.options ? attrs.options.resizable : false,
|
||||
height: options.height,
|
||||
minHeight: options.minHeight,
|
||||
maxHeight: options.maxHeight,
|
||||
resizable: 'resizable' in options ? options.resizable : false,
|
||||
editorPlugins: [QWebPlugin],
|
||||
};
|
||||
if ('collaborative' in attrs.options) {
|
||||
wysiwygOptions.collaborative = attrs.options.collaborative;
|
||||
if ('collaborative' in options) {
|
||||
wysiwygOptions.collaborative = options.collaborative;
|
||||
}
|
||||
if ('allowCommandImage' in attrs.options) {
|
||||
if ('allowCommandImage' in options) {
|
||||
// Set the option only if it is explicitly set in the view so a default
|
||||
// can be set elsewhere otherwise.
|
||||
wysiwygOptions.allowCommandImage = Boolean(attrs.options.allowCommandImage);
|
||||
wysiwygOptions.allowCommandImage = Boolean(options.allowCommandImage);
|
||||
}
|
||||
if ('allowCommandVideo' in attrs.options) {
|
||||
if ('allowCommandVideo' in options) {
|
||||
// Set the option only if it is explicitly set in the view so a default
|
||||
// can be set elsewhere otherwise.
|
||||
wysiwygOptions.allowCommandVideo = Boolean(attrs.options.allowCommandVideo);
|
||||
wysiwygOptions.allowCommandVideo = Boolean(options.allowCommandVideo);
|
||||
}
|
||||
return {
|
||||
codeview: Boolean(odoo.debug && attrs.options.codeview),
|
||||
codeview: Boolean(odoo.debug && options.codeview),
|
||||
placeholder: attrs.placeholder,
|
||||
|
||||
isCollaborative: attrs.options.collaborative,
|
||||
cssReadonlyAssetId: attrs.options.cssReadonly,
|
||||
dynamicPlaceholder: attrs.options.dynamic_placeholder,
|
||||
cssEditAssetId: attrs.options.cssEdit,
|
||||
isInlineStyle: attrs.options['style-inline'],
|
||||
wrapper: attrs.options.wrapper,
|
||||
isCollaborative: options.collaborative,
|
||||
cssReadonlyAssetId: options.cssReadonly,
|
||||
dynamicPlaceholder: options.dynamic_placeholder,
|
||||
cssEditAssetId: options.cssEdit,
|
||||
isInlineStyle: options['style-inline'],
|
||||
wrapper: options.wrapper,
|
||||
|
||||
wysiwygOptions,
|
||||
};
|
||||
|
||||
@@ -94,9 +94,9 @@ GaugeField.props = {
|
||||
|
||||
export const gaugeField = {
|
||||
component: GaugeField,
|
||||
extractProps: ({ attrs }) => ({
|
||||
maxValueField: attrs.options.max_field,
|
||||
title: attrs.options.title,
|
||||
extractProps: ({ options }) => ({
|
||||
maxValueField: options.max_field,
|
||||
title: options.title,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -99,8 +99,8 @@ ImageRadioField.props = {
|
||||
export const imageRadioField = {
|
||||
component: ImageRadioField,
|
||||
supportedTypes: ['selection'],
|
||||
extractProps: ({ attrs }) => ({
|
||||
images: attrs.options.images,
|
||||
extractProps: ({ options }) => ({
|
||||
images: options.images,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user