[REF] web: add missing features of abstract field
This commit adds missing features of AbstractField written in owl. task id: 2193996
This commit is contained in:
@@ -445,6 +445,14 @@ var utils = {
|
||||
is_bin_size: function (v) {
|
||||
return (/^\d+(\.\d*)? [^0-9]+$/).test(v);
|
||||
},
|
||||
/**
|
||||
* Checks if a class is an extension of owl.Component.
|
||||
*
|
||||
* @param {any} value A class reference
|
||||
*/
|
||||
isComponent: function (value) {
|
||||
return value.prototype instanceof owl.Component;
|
||||
},
|
||||
/**
|
||||
* Returns whether the given anchor is valid.
|
||||
*
|
||||
|
||||
@@ -9,34 +9,39 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
/**
|
||||
* This file defines the Owl version of the AbstractField. Specific fields
|
||||
* written in Owl should override this component.
|
||||
*
|
||||
* =========================================================================
|
||||
*
|
||||
* /!\ This api works almost exactly like the legacy one but
|
||||
* /!\ it still could change! There are already a few methods that will be
|
||||
* /!\ removed like setIdForLabel, setInvalidClass, etc..
|
||||
*
|
||||
* =========================================================================
|
||||
*
|
||||
* Note that the API is not complete yet. Some features may not work properly
|
||||
* yet (e.g. part of keyboard navigation, invalid fields notification).
|
||||
*
|
||||
* This is the basic field widget used by all the views to render a field in a view.
|
||||
* These field widgets are mostly common to all views, in particular form and list
|
||||
* This is the basic field component used by all the views to render a field in a view.
|
||||
* These field components are mostly common to all views, in particular form and list
|
||||
* views.
|
||||
*
|
||||
* The responsabilities of a field widget are mainly:
|
||||
* The responsabilities of a field component are mainly:
|
||||
* - render a visual representation of the current value of a field
|
||||
* - that representation is either in 'readonly' or in 'edit' mode
|
||||
* - notify the rest of the system when the field has been changed by
|
||||
* the user (in edit mode)
|
||||
*
|
||||
* Notes
|
||||
* - the widget is not supposed to be able to switch between modes. If another
|
||||
* mode is required, the view will take care of instantiating another widget.
|
||||
* - the component is not supposed to be able to switch between modes. If another
|
||||
* mode is required, the view will take care of instantiating another component.
|
||||
* - notify the system when its value has changed and its mode is changed to 'readonly'
|
||||
* - notify the system when some action has to be taken, such as opening a record
|
||||
* - the Field widget should not, ever, under any circumstance, be aware of
|
||||
* its parent. The way it communicates changes with the rest of the system is by
|
||||
* triggering events (with trigger_up). These events bubble up and are interpreted
|
||||
* - the Field component should not, ever, under any circumstance, be aware of
|
||||
* its parent. The way it communicates changes with the rest of the system is by
|
||||
* triggering events. These events bubble up and are interpreted
|
||||
* by the most appropriate parent.
|
||||
*
|
||||
* Also, in some cases, it may not be practical to have the same widget for all
|
||||
* views. In that situation, you can have a 'view specific widget'. Just register
|
||||
* the widget in the registry prefixed by the view type and a dot. So, for example,
|
||||
* a form specific many2one widget should be registered as 'form.many2one'.
|
||||
* Also, in some cases, it may not be practical to have the same component for all
|
||||
* views. In that situation, you can have a 'view specific component'. Just register
|
||||
* the component in the registry prefixed by the view type and a dot. So, for example,
|
||||
* a form specific many2one component should be registered as 'form.many2one'.
|
||||
*
|
||||
* @module web.AbstractFieldOwl
|
||||
*/
|
||||
@@ -45,118 +50,28 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
* Abstract field class
|
||||
*
|
||||
* @constructor
|
||||
* @param {Widget} parent
|
||||
* @param {string} name The field name defined in the model
|
||||
* @param {Object} record A record object (result of the get method of
|
||||
* a basic model)
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.mode=readonly] should be 'readonly' or 'edit'
|
||||
* @param {Component} parent
|
||||
* @param {Object} props
|
||||
* @param {string} props.fieldName The field name defined in the model
|
||||
* @param {Object} props.record A record object (result of the get method
|
||||
* of a basic model)
|
||||
* @param {Object} [props.options]
|
||||
* @param {string} [props.options.mode=readonly] should be 'readonly' or 'edit'
|
||||
* @param {string} [props.options.viewType=default]
|
||||
*/
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
const options = Object.assign({}, props.options);
|
||||
const record = props.record;
|
||||
// 'name' is the field name displayed by this widget
|
||||
this.name = props.fieldName;
|
||||
|
||||
// the datapoint fetched from the model
|
||||
this.record = record;
|
||||
|
||||
// the 'field' property is a description of all the various field properties,
|
||||
// such as the type, the comodel (relation), ...
|
||||
this.field = record.fields[this.name];
|
||||
|
||||
// the 'viewType' is the type of the view in which the field widget is
|
||||
// instantiated. For standalone widgets, a 'default' viewType is set.
|
||||
this.viewType = options.viewType || 'default';
|
||||
|
||||
// the 'attrs' property contains the attributes of the xml 'field' tag,
|
||||
// the inner views...
|
||||
const fieldsInfo = record.fieldsInfo[this.viewType];
|
||||
this.attrs = options.attrs || (fieldsInfo && fieldsInfo[this.name]) || {};
|
||||
|
||||
// the 'additionalContext' property contains the attributes to pass through the context.
|
||||
this.additionalContext = options.additionalContext || {};
|
||||
|
||||
// this property tracks the current (parsed if needed) value of the field.
|
||||
// Note that we don't use an event system anymore, using this.get('value')
|
||||
// is no longer valid.
|
||||
this.value = record.data[this.name];
|
||||
|
||||
// recordData tracks the values for the other fields for the same record.
|
||||
// note that it is expected to be mostly a readonly property, you cannot
|
||||
// use this to try to change other fields value, this is not how it is
|
||||
// supposed to work. Also, do not use this.recordData[this.name] to get
|
||||
// the current value, this could be out of sync after a _setValue.
|
||||
this.recordData = record.data;
|
||||
|
||||
// the 'string' property is a human readable (and translated) description
|
||||
// of the field. Mostly useful to be displayed in various places in the
|
||||
// UI, such as tooltips or create dialogs.
|
||||
this.string = this.attrs.string || this.field.string || this.name;
|
||||
|
||||
// Widget can often be configured in the 'options' attribute in the
|
||||
// xml 'field' tag. These options are saved (and evaled) in nodeOptions
|
||||
this.nodeOptions = this.attrs.options || {};
|
||||
|
||||
// dataPointID is the id corresponding to the current record in the model.
|
||||
// Its intended use is to be able to tag any messages going upstream,
|
||||
// so the view knows which records was changed for example.
|
||||
this.dataPointID = record.id;
|
||||
|
||||
// this is the res_id for the record in database. Obviously, it is
|
||||
// readonly. Also, when the user is creating a new record, there is
|
||||
// no res_id. When the record will be created, the field widget will
|
||||
// be destroyed (when the form view switches to readonly mode) and a new
|
||||
// widget with a res_id in mode readonly will be created.
|
||||
this.res_id = record.res_id;
|
||||
|
||||
// useful mostly to trigger rpcs on the correct model
|
||||
this.model = record.model;
|
||||
|
||||
// a widget can be in two modes: 'edit' or 'readonly'. This mode should
|
||||
// never be changed, if a view changes its mode, it will destroy and
|
||||
// recreate a new field widget.
|
||||
this.mode = options.mode || "readonly";
|
||||
|
||||
// this flag tracks if the widget is in a valid state, meaning that the
|
||||
// current value represented in the DOM is a value that can be parsed
|
||||
// and saved. For example, a float field can only use a number and not
|
||||
// a string.
|
||||
this._isValid = true;
|
||||
|
||||
// this is the last value that was set by the user, unparsed. This is
|
||||
// this is the last value that was set by the user, unparsed. This is
|
||||
// used to avoid setting the value twice in a row with the exact value.
|
||||
this.lastSetValue = undefined;
|
||||
|
||||
// formatType is used to determine which format (and parse) functions
|
||||
// to call to format the field's value to insert into the DOM (typically
|
||||
// put into a span or an input), and to parse the value from the input
|
||||
// to send it to the server. These functions are chosen according to
|
||||
// the 'widget' attrs if is is given, and if it is a valid key, with a
|
||||
// fallback on the field type, ensuring that the value is formatted and
|
||||
// displayed according to the chosen widget, if any.
|
||||
this.formatType = this.attrs.widget in field_utils.format ?
|
||||
this.attrs.widget :
|
||||
this.field.type;
|
||||
// formatOptions (resp. parseOptions) is a dict of options passed to
|
||||
// calls to the format (resp. parse) function.
|
||||
this.formatOptions = {};
|
||||
this.parseOptions = {};
|
||||
|
||||
// if we add decorations, we need to reevaluate the field whenever any
|
||||
// value from the record is changed
|
||||
if (this.attrs.decorations) {
|
||||
this.resetOnAnyFieldChange = true;
|
||||
}
|
||||
this._lastSetValue = undefined;
|
||||
|
||||
useListener('keydown', this._onKeydown);
|
||||
useListener('navigation-move', this._onNavigationMove);
|
||||
onMounted(() => this._applyDecorations());
|
||||
onPatched(() => this._applyDecorations());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack: studio tries to find the field with a selector base on its
|
||||
* name, before it is mounted into the DOM. Ideally, this should be
|
||||
@@ -174,38 +89,91 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
this.el.classList.add('o_field_widget');
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* @param {Object} [nextProps]
|
||||
* @param {Object} [nextProps.record]
|
||||
* @param {Object} [nextProps.event]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async willUpdateProps(nextProps) {
|
||||
this.record = nextProps.record;
|
||||
this.recordData = this.record.data;
|
||||
this.value = this.recordData[this.name];
|
||||
this.lastSetValue = undefined;
|
||||
this._lastSetValue = undefined;
|
||||
return super.willUpdateProps(nextProps);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------
|
||||
// Getters
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the main field's DOM element (jQuery form) which can be focused
|
||||
* by the browser.
|
||||
* This contains the attributes to pass through the context.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
get additionalContext() {
|
||||
return this.options.additionalContext || {};
|
||||
}
|
||||
/**
|
||||
* This contains the attributes of the xml 'field' tag, the inner views...
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
get attrs() {
|
||||
const fieldsInfo = this.record.fieldsInfo[this.viewType];
|
||||
return this.options.attrs || (fieldsInfo && fieldsInfo[this.name]) || {};
|
||||
}
|
||||
/**
|
||||
* Id corresponding to the current record in the model.
|
||||
* Its intended use is to be able to tag any messages going upstream,
|
||||
* so the view knows which records was changed for example.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get dataPointId() {
|
||||
return this.record.id;
|
||||
}
|
||||
/**
|
||||
* This is a description of all the various field properties,
|
||||
* such as the type, the comodel (relation), ...
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get field() {
|
||||
return this.record.fields[this.name];
|
||||
}
|
||||
/**
|
||||
* Returns the main field's DOM element which can be focused by the browser.
|
||||
*
|
||||
* @returns {HTMLElement|null} main focusable element inside the widget
|
||||
* @returns {HTMLElement|null} main focusable element inside the component
|
||||
*/
|
||||
get focusableElement() {
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Returns the additional options pass to the format function.
|
||||
* Override this getter to add options.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
get formatOptions() {
|
||||
return {};
|
||||
}
|
||||
/**
|
||||
* Used to determine which format (and parse) functions
|
||||
* to call to format the field's value to insert into the DOM (typically
|
||||
* put into a span or an input), and to parse the value from the input
|
||||
* to send it to the server. These functions are chosen according to
|
||||
* the 'widget' attrs if is is given, and if it is a valid key, with a
|
||||
* fallback on the field type, ensuring that the value is formatted and
|
||||
* displayed according to the chosen widget, if any.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get formatType() {
|
||||
return this.attrs.widget in field_utils.format ?
|
||||
this.attrs.widget : this.field.type;
|
||||
}
|
||||
/**
|
||||
* Returns whether or not the field is empty and can thus be hidden. This
|
||||
* method is typically called when the widget is in readonly, to hide it
|
||||
* method is typically called when the component is in readonly, to hide it
|
||||
* (and its label) if it is empty.
|
||||
*
|
||||
* @returns {boolean}
|
||||
@@ -214,39 +182,171 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
return !this.isSet;
|
||||
}
|
||||
/**
|
||||
* Returns true if the widget has a visible element that can take the focus
|
||||
* Returns true if the component has a visible element that can take the focus
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isFocusable() {
|
||||
const focusable = this.focusableElement;
|
||||
// check if element is visible
|
||||
return focusable && !!(focusable.offsetWidth || focusable.offsetHeight
|
||||
|| focusable.getClientRects().length);
|
||||
return focusable && !!(focusable.offsetWidth ||
|
||||
focusable.offsetHeight || focusable.getClientRects().length);
|
||||
}
|
||||
/**
|
||||
* Determines if the field value is set to a meaningful
|
||||
* value. This is useful to determine if a field should be displayed as empty
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isSet() {
|
||||
return !!this.value;
|
||||
}
|
||||
/**
|
||||
* Tracks if the component is in a valid state, meaning that the current
|
||||
* value represented in the DOM is a value that can be parsed and saved.
|
||||
* For example, a float field can only use a number and not a string.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isValid() {
|
||||
return this._isValid;
|
||||
}
|
||||
/**
|
||||
* Activates the field widget. By default, activation means focusing and
|
||||
* Fields can be in two modes: 'edit' or 'readonly'.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get mode() {
|
||||
return this.options.mode || "readonly";
|
||||
}
|
||||
/**
|
||||
* Useful mostly to trigger rpcs on the correct model.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get model() {
|
||||
return this.record.model;
|
||||
}
|
||||
/**
|
||||
* The field name displayed by this component.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get name() {
|
||||
return this.props.fieldName;
|
||||
}
|
||||
/**
|
||||
* Component can often be configured in the 'options' attribute in the
|
||||
* xml 'field' tag. These options are saved (and evaled) in nodeOptions.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
get nodeOptions() {
|
||||
return this.attrs.options || {};
|
||||
}
|
||||
/**
|
||||
* @returns {Object}
|
||||
*/
|
||||
get options() {
|
||||
return this.props.options || {};
|
||||
}
|
||||
/**
|
||||
* Returns the additional options passed to the parse function.
|
||||
* Override this getter to add options.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
get parseOptions() {
|
||||
return {};
|
||||
}
|
||||
/**
|
||||
* The datapoint fetched from the model.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
get record() {
|
||||
return this.props.record;
|
||||
}
|
||||
/**
|
||||
* Tracks the values for the other fields for the same record.
|
||||
* note that it is expected to be mostly a readonly property, you cannot
|
||||
* use this to try to change other fields value, this is not how it is
|
||||
* supposed to work. Also, do not use this.recordData[this.name] to get
|
||||
* the current value, this could be out of sync after a _setValue.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
get recordData() {
|
||||
return this.record.data;
|
||||
}
|
||||
/**
|
||||
* If this flag is set to true, the field component will be reset on
|
||||
* every change which is made in the view (if the view supports it).
|
||||
* This is currently a form view feature.
|
||||
*
|
||||
* /!\ This getter could be removed when basic views (form, list, kanban)
|
||||
* are converted.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get resetOnAnyFieldChange() {
|
||||
return !!this.attrs.decorations;
|
||||
}
|
||||
/**
|
||||
* The res_id of the record in database.
|
||||
* When the user is creating a new record, there is no res_id.
|
||||
* When the record will be created, the field component will
|
||||
* be destroyed (when the form view switches to readonly mode) and a
|
||||
* new component with a res_id in mode readonly will be created.
|
||||
*
|
||||
* @returns {Number}
|
||||
*/
|
||||
get resId() {
|
||||
return this.record.res_id;
|
||||
}
|
||||
/**
|
||||
* Human readable (and translated) description of the field.
|
||||
* Mostly useful to be displayed in various places in the
|
||||
* UI, such as tooltips or create dialogs.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get string() {
|
||||
return this.attrs.string || this.field.string || this.name;
|
||||
}
|
||||
/**
|
||||
* Tracks the current (parsed if needed) value of the field.
|
||||
*
|
||||
* @returns {any}
|
||||
*/
|
||||
get value() {
|
||||
return this.record.data[this.name];
|
||||
}
|
||||
/**
|
||||
* The type of the view in which the field component is instantiated.
|
||||
* For standalone components, a 'default' viewType is set.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get viewType() {
|
||||
return this.options.viewType || 'default';
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Activates the field component. By default, activation means focusing and
|
||||
* selecting (if possible) the associated focusable element. The selecting
|
||||
* part can be disabled. In that case, note that the focused input/textarea
|
||||
* part can be disabled. In that case, note that the focused input/textarea
|
||||
* will have the cursor at the very end.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.noselect=false] if false and the input
|
||||
* is of type text or textarea, the content will also be selected
|
||||
* @param {Event} [options.event] the event which fired this activation
|
||||
* @returns {boolean} true if the widget was activated, false if the
|
||||
* @returns {boolean} true if the component was activated, false if the
|
||||
* focusable element was not found or invisible
|
||||
*/
|
||||
activate(options) {
|
||||
@@ -264,50 +364,60 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* This function should be implemented by widgets that are not able to
|
||||
* This function should be implemented by components that are not able to
|
||||
* notify their environment when their value changes (maybe because their
|
||||
* are not aware of the changes) or that may have a value in a temporary
|
||||
* state (maybe because some action should be performed to validate it
|
||||
* before notifying it). This is typically called before trying to save the
|
||||
* widget's value, so it should call _setValue() to notify the environment
|
||||
* component's value, so it should call _setValue() to notify the environment
|
||||
* if the value changed but was not notified.
|
||||
*
|
||||
* @abstract
|
||||
* @returns {Promise|undefined}
|
||||
*/
|
||||
commitChanges() {}
|
||||
/**
|
||||
* Remove the invalid class on a field
|
||||
*
|
||||
* This function should be removed when BasicRenderer will be rewritten in owl
|
||||
*/
|
||||
removeInvalidClass() {
|
||||
this.el.classList.remove('o_field_invalid');
|
||||
this.el.removeAttribute('aria-invalid');
|
||||
}
|
||||
/**
|
||||
* Sets the given id on the focusable element of the field and as 'for'
|
||||
* attribute of potential internal labels.
|
||||
*
|
||||
* This function should be removed when BasicRenderer will be rewritten in owl
|
||||
*
|
||||
* @param {string} id
|
||||
*/
|
||||
setIDForLabel(id) {
|
||||
setIdForLabel(id) {
|
||||
if (this.focusableElement) {
|
||||
this.focusableElement.setAttribute('id', id);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update the modifiers with the newest value.
|
||||
* Now this.attrs.modifiersValue can be used consistantly even with
|
||||
* conditional modifiers inside field widgets, and without needing new
|
||||
* events or synchronization between the widgets, renderer and controller
|
||||
*
|
||||
* @param {Object | null} modifiers the updated modifiers
|
||||
* @override
|
||||
* add the invalid class on a field
|
||||
*
|
||||
* This function should be removed when BasicRenderer will be rewritten in owl
|
||||
*/
|
||||
updateModifiersValue(modifiers) {
|
||||
this.attrs.modifiersValue = modifiers || {};
|
||||
setInvalidClass() {
|
||||
this.el.classList.add('o_field_invalid');
|
||||
this.el.setAttribute('aria-invalid', 'true');
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Apply field decorations (only if field-specific decorations have been
|
||||
* defined in an attribute).
|
||||
*
|
||||
* This function should be removed when BasicRenderer will be rewritten in owl
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_applyDecorations() {
|
||||
@@ -326,13 +436,13 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
* @returns {string}
|
||||
*/
|
||||
_formatValue(value) {
|
||||
const options = _.extend({}, this.nodeOptions,
|
||||
const options = Object.assign({}, this.nodeOptions,
|
||||
{ data: this.recordData }, this.formatOptions);
|
||||
return field_utils.format[this.formatType](value, this.field, options);
|
||||
}
|
||||
/**
|
||||
* This method check if a value is the same as the current value of the
|
||||
* field. For example, a fieldDate widget might want to use the moment
|
||||
* field. For example, a fieldDate component might want to use the moment
|
||||
* specific value isSame instead of ===.
|
||||
*
|
||||
* This method is used by the _setValue method.
|
||||
@@ -355,18 +465,18 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
return field_utils.parse[this.formatType](value, this.field, this.parseOptions);
|
||||
}
|
||||
/**
|
||||
* This method is called by the widget, to change its value and to notify
|
||||
* the outside world of its new state. This method also validates the new
|
||||
* value. Note that this method does not rerender the widget, it should be
|
||||
* handled by the widget itself, if necessary.
|
||||
* This method is called by the component, to change its value and to notify
|
||||
* the outside world of its new state. This method also validates the new
|
||||
* value. Note that this method does not rerender the component, it should be
|
||||
* handled by the component itself, if necessary.
|
||||
*
|
||||
* @private
|
||||
* @param {any} value
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.doNotSetDirty=false] if true, the basic model
|
||||
* will not consider that this field is dirty, even though it was changed.
|
||||
* Please do not use this flag unless you really need it. Our only use
|
||||
* case is currently the pad widget, which does a _setValue in the
|
||||
* Please do not use this flag unless you really need it. Our only use
|
||||
* case is currently the pad component, which does a _setValue in the
|
||||
* renderEdit method.
|
||||
* @param {boolean} [options.notifyChange=true] if false, the basic model
|
||||
* will not notify and not trigger the onchange, even though it was changed.
|
||||
@@ -377,16 +487,16 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
_setValue(value, options) {
|
||||
// we try to avoid doing useless work, if the value given has not
|
||||
// changed. Note that we compare the unparsed values.
|
||||
if (this.lastSetValue === value || (this.value === false && value === '')) {
|
||||
if (this._lastSetValue === value || (this.value === false && value === '')) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.lastSetValue = value;
|
||||
this._lastSetValue = value;
|
||||
try {
|
||||
value = this._parseValue(value);
|
||||
this._isValid = true;
|
||||
} catch (e) {
|
||||
this._isValid = false;
|
||||
this.trigger('set-dirty', {dataPointID: this.dataPointID});
|
||||
this.trigger('set-dirty', {dataPointID: this.dataPointId});
|
||||
return Promise.reject({message: "Value set is not valid"});
|
||||
}
|
||||
if (!(options && options.forceChange) && this._isSameValue(value)) {
|
||||
@@ -396,7 +506,7 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
const changes = {};
|
||||
changes[this.name] = value;
|
||||
this.trigger('field-changed', {
|
||||
dataPointID: this.dataPointID,
|
||||
dataPointID: this.dataPointId,
|
||||
changes: changes,
|
||||
viewType: this.viewType,
|
||||
doNotSetDirty: options && options.doNotSetDirty,
|
||||
@@ -408,20 +518,14 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Intercepts navigation keyboard events to prevent their default behavior
|
||||
* and notifies the view so that it can handle it its own way.
|
||||
*
|
||||
* Note: the navigation keyboard events are stopped so that potential parent
|
||||
* abstract field does not trigger the navigation_move event a second time.
|
||||
* However, this might be controversial, we might wanna let the event
|
||||
* continue its propagation and flag it to say that navigation has already
|
||||
* been handled (TODO ?).
|
||||
*
|
||||
* @private
|
||||
* @param {KeyEvent} ev
|
||||
*/
|
||||
@@ -430,9 +534,8 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
case $.ui.keyCode.TAB:
|
||||
this.trigger('navigation-move', {
|
||||
direction: ev.shiftKey ? 'previous' : 'next',
|
||||
originalEvent: ev,
|
||||
});
|
||||
// TODO: stop/prevent original event if navigation-move event
|
||||
// has been handled
|
||||
break;
|
||||
case $.ui.keyCode.ENTER:
|
||||
// We preventDefault the ENTER key because of two coexisting behaviours:
|
||||
@@ -479,18 +582,13 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
}
|
||||
|
||||
/**
|
||||
* An object representing fields to be fetched by the model eventhough not present in the view
|
||||
* An object representing fields to be fetched by the model even though not
|
||||
* present in the view.
|
||||
* This object contains "field name" as key and an object as value.
|
||||
* That value object must contain the key "type"
|
||||
* see FieldBinaryImage for an example.
|
||||
* @see FieldBinaryImage for an example.
|
||||
*/
|
||||
AbstractField.fieldDependencies = {};
|
||||
/**
|
||||
* If this flag is set to true, the field widget will be reset on every
|
||||
* change which is made in the view (if the view supports it). This is
|
||||
* currently a form view feature.
|
||||
*/
|
||||
AbstractField.resetOnAnyFieldChange = false;
|
||||
/**
|
||||
* If this flag is given a string, the related BasicModel will be used to
|
||||
* initialize specialData the field might need. This data will be available
|
||||
@@ -500,15 +598,15 @@ odoo.define('web.AbstractFieldOwl', function (require) {
|
||||
*/
|
||||
AbstractField.specialData = false;
|
||||
/**
|
||||
* to override to indicate which field types are supported by the widget
|
||||
* to override to indicate which field types are supported by the component
|
||||
*
|
||||
* @type Array<String>
|
||||
* @type Array<string>
|
||||
*/
|
||||
AbstractField.supportedFieldTypes = [];
|
||||
/**
|
||||
* To override to give a user friendly name to the widget.
|
||||
* To override to give a user friendly name to the component.
|
||||
*
|
||||
* @type <string>
|
||||
* @type string
|
||||
*/
|
||||
AbstractField.description = "";
|
||||
/**
|
||||
|
||||
@@ -33,8 +33,6 @@ odoo.define('web.FieldWrapper', function (require) {
|
||||
this._data.res_id = record.res_id;
|
||||
this._data.model = record.model;
|
||||
this._data.mode = options.mode || "readonly";
|
||||
this._data._isValid = true;
|
||||
this._data.lastSetValue = undefined;
|
||||
this._data.formatType = this._data.attrs.widget in field_utils.format ?
|
||||
this._data.attrs.widget :
|
||||
this._data.field.type;
|
||||
@@ -43,9 +41,19 @@ odoo.define('web.FieldWrapper', function (require) {
|
||||
if (this._data.attrs.decorations) {
|
||||
this._data.resetOnAnyFieldChange = true;
|
||||
}
|
||||
for (const key in this._data) {
|
||||
|
||||
for (let key in this._data) {
|
||||
Object.defineProperty(this, key, {
|
||||
get: () => (this.el ? this.componentRef.comp : this._data)[key],
|
||||
get: () => {
|
||||
if (this.el) {
|
||||
if (key === 'dataPointID') {
|
||||
return this.componentRef.comp.dataPointId;
|
||||
} else if (key === 'res_id') {
|
||||
return this.componentRef.comp.resId;
|
||||
}
|
||||
}
|
||||
return (this.el ? this.componentRef.comp : this._data)[key];
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -60,9 +68,6 @@ odoo.define('web.FieldWrapper', function (require) {
|
||||
get fieldDependencies() {
|
||||
return this.Component.fieldDependencies;
|
||||
}
|
||||
get resetOnAnyFieldChange() {
|
||||
return this.Component.resetOnAnyFieldChange;
|
||||
}
|
||||
get specialData() {
|
||||
return this.Component.specialData;
|
||||
}
|
||||
@@ -80,11 +85,11 @@ odoo.define('web.FieldWrapper', function (require) {
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
activate(options) {
|
||||
return this.componentRef.comp.activate(options);
|
||||
activate() {
|
||||
return this.componentRef.comp.activate(...arguments);
|
||||
}
|
||||
commitChanges() {
|
||||
return this.componentRef.comp.commitChanges();
|
||||
return this.componentRef.comp.commitChanges(...arguments);
|
||||
}
|
||||
getFocusableElement() {
|
||||
return $(this.componentRef.comp.focusableElement);
|
||||
@@ -99,7 +104,7 @@ odoo.define('web.FieldWrapper', function (require) {
|
||||
if (this.componentRef.comp) {
|
||||
return this.componentRef.comp.isSet;
|
||||
}
|
||||
// because of the willStart, the real field widget may not be
|
||||
// because of the willStart, the real field component may not be
|
||||
// instantiated yet when the renderer first asks if it is set
|
||||
// (only the wrapper is instantiated), so we instantiate one
|
||||
// with the same props, get its 'isSet' status, and destroy it.
|
||||
@@ -111,14 +116,27 @@ odoo.define('web.FieldWrapper', function (require) {
|
||||
isValid() {
|
||||
return this.componentRef.comp.isValid;
|
||||
}
|
||||
removeInvalidClass() {
|
||||
return this.componentRef.comp.removeInvalidClass(...arguments);
|
||||
}
|
||||
reset(record, event) {
|
||||
return this.update({record, event});
|
||||
}
|
||||
setIDForLabel(id) {
|
||||
return this.componentRef.comp.setIDForLabel(id);
|
||||
setIDForLabel() {
|
||||
return this.componentRef.comp.setIdForLabel(...arguments);
|
||||
}
|
||||
setInvalidClass() {
|
||||
return this.componentRef.comp.setInvalidClass(...arguments);
|
||||
}
|
||||
updateModifiersValue(modifiers) {
|
||||
return this.componentRef.comp.updateModifiersValue(modifiers);
|
||||
if (this.props.options.attrs) {
|
||||
this.props.options.attrs.modifiersValue = modifiers || {};
|
||||
} else {
|
||||
const viewType = this.props.options.viewType || 'default';
|
||||
const fieldsInfo = this.props.record.fieldsInfo[viewType];
|
||||
fieldsInfo[this.props.fieldName].modifiersValue = modifiers || {};
|
||||
}
|
||||
this.componentRef.comp.props = this.props;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user