odoo technology development white paper Part III Chapter 11 abstract fields

Posted by sandsquid on Fri, 28 Jan 2022 06:32:22 +0100

Reprint: http://book.odoomommy.com/chapter3/README11.html

Chapter 11 abstract fields

Abstract field is the abstract basis of field components used in all views. These are common components in views, especially forms and tree lists.

The main function of the field component is to render the visual elements of the current value of the field in edit and read-only mode, and notify the system when the user modifies the field value.

be careful:

  • Components should not actively switch modes. If they need to switch modes, the view should be responsible for initializing another component.
  • When the field value changes, notify the system and switch the mode to read-only.
  • When some action needs to be taken, such as opening a record and notifying the system.
  • Field components should not enter any kind of cycle, and trigger up should be used to communicate with other components of the system. These events are captured and processed by the parent component in a bubbling manner.

In some scenarios, it is not recommended that all views use the same component. You can use the naming method of view plus component to solve this problem. For example, the specified many2one part in a form view should be registered as "form.many2one"

Component properties

  • fieldsDependencies: a field object obtained by the model, even if the field does not appear in the view. This object is composed of a field name as a key and an object as a value. The value object must contain a key of type. You can refer to the FieldBinaryImage field.
  • resetOnAnyFieldChange: if this value is true, this field will be reset whenever the view changes.
  • specialData: if this attribute is assigned a character, the relevant basic model will initialize the specialData required by the field, which will be displayed in this record. Is accessed in the specialData [this. Name] attribute.
  • supportedFieldTypes: overrides the supported types of field parts
  • noLabel: Boolean value, whether to display Label label

initialization

Abstract fields need to be initialized in the first step before use:

init: function (parent, name, record, options) {
    ...
}

Its constructor receives four parameters:

  • Parent: parent object
  • Name: the name of the field to be rendered and displayed by the part
  • Record: the data point (dataset record) obtained from the model class
  • options: optional parameters

options optional values:

  • Mode: read only or edit mode (readonly, edit)

Initialization of abstract fields

In addition to storing the four parameters of the construction method with the same name during the initialization of the abstract field, the value of the default field (name) in the data set (record) will also be extracted and assigned to the field attribute. Users can directly use this Field property to access the data content related to this field. For the values of other fields, you can still use this Read by record [field name].

During the initialization of the abstract field, the viewType type in options will be assigned to this viewType refers to the view type to which the field is instantiated. For independent components, its value is default.

Abstract field all relevant field information will be stored in this record. Fields, and the description information of all fields will exist in this record. In the fieldinfo property. During initialization, if the optional parameter options contains attrs attribute information, the attribute information will be stored in this In attrs attribute, if there is no attrs information in options, the system will replace it with the description information of this field (name) in fieldsInfo (if the description information is empty, attrs is also empty). It should be noted here that fields and fieldsInfo do not contain all the fields of the object, but all the fields that appear in the view, that is, the fields that appear in the view file (xml).

The difference between fields and fieldsInfo is that the information contained in fields is the attribute information defined in the background model data, such as readonly, field type, etc., while fieldsInfo refers to the description information of the front end, such as fieldDependencies, views and other attribute information.

After storing the field and field information, the system will store the value corresponding to this field in the value attribute. Users can use this Value to read. In fact, similar to the weight raising process of field, the value is also from this The data attribute of record extracts the value of this field and makes a simplified "shortcut". Users can also use this record. Data [name], or the values of other fields.

We mentioned this record. Data attribute, odoo also makes a shortcut in the abstract field: recordData. The purpose is to facilitate users to read other fields of the same record. It should be noted that this value should be treated as a read-only field, which is only used for reading and cannot and should not be updated. Don't use this either recordData [this. Name] is read in this way, because if used in_ After setValue method, dirty data may be read.

The string attribute will be assigned during initialization. If there is in attrs, the value in attrs will be taken. Otherwise, the value of string in field attribute will be taken. If there is no value for string, use the name attribute instead.

optons in attrs attribute will be assigned to nodeOptions attribute.

Abstract fields also assign the data model to the model attribute, which is usually used for rpc calls.

The id of record will be assigned to the dataPointID to inform the upstream view which record has changed. The id here is the same as the res below_ id does not have any association, and it is purely a concept of web-side localization.

Abstract field parts have two modes, edit and read-only. During initialization, they will be assigned to the model attribute. The mode of the part cannot be changed. If the view mode changes, the part will be destroyed and regenerated.

res_id refers to the record id in the database, which is obviously a read-only value. At the same time, when the user creates a record, res_ If the id is empty, after the record is created, the field part will be destroyed (when the form view is switched to read-only mode), and a record with res will be created at the same time_ id new part.

There is an attribute in the assembly_ isValid is used to identify whether the current value is a legal value. Legal means that it can be parsed and saved correctly. For example, a float field can only accept numbers but not characters. When a character is assigned to a float field, it will be marked as false. The default value is true.

In addition, there is also a lastSetValue to record the value set by the user last time that has not been parsed. In order to avoid the same record being assigned twice.

In the process of reading and writing, abstract fields need to go through two steps: formatting and parsing to ensure that the values entered by users or read by the system meet the requirements of components. Read with_ The format method is used when writing_ parseValue method. The basis for odoo to call these two methods is the formatType attribute in the initialization process. By default, this value is equal to this field. Type, and if the user defines the widget attribute in attrs, the value of the widget is used. formatOptions and parseOptions are used to pass parameters to formatting and parsing methods.

If decorations is defined in the attrs attribute, the abstract field part needs to re estimate the value of the field when any value of the person in the record changes.

start-up

When the part is added to the DOM, the start method will be called (the start method of the parent Widget). After the part is started, the internal rendering method will be called to render the template style file of the part and output it to the page.

start: function () {
    var self = this;
    return this._super.apply(this, arguments).then(function () {
        self.$el.attr('name', self.name);
        self.$el.addClass('o_field_widget');
        return self._render();
    });
}

Its internal logic is to call the parent method first. After the parent method is completed, name the current element as the name of the field and load the style o_field_widget, then call render method renders the part.

Public method

Abstract fields define several public methods to update or set the properties of fields. The list of methods and functions are as follows:

activate

Activate the field part. By default, activation means to obtain the focus and select the appropriate element.

activate: function (options) {
    if (this.isFocusable()) {
        var $focusable = this.getFocusableElement();
        $focusable.focus();
        if ($focusable.is('input[type="text"], textarea')) {
            $focusable[0].selectionStart = $focusable[0].selectionEnd = $focusable[0].value.length;
            if (options && !options.noselect) {
                $focusable.select();
            }
        }
        return true;
    }
    return false;
}

The options parameter can have the following values:

  • noselect: if it is set to false and the input type is text or textarea, the content content will also be selected.

commitChanges

The method of submitting changes. This method is an abstract method. The specific implementation needs to be implemented by subclass components.

commitChanges: function () {},

The method of submitting changes should be implemented by components that cannot notify the external environment that their values have changed. These components may not be aware of their own values. This scenario usually occurs when trying to save the value of the field, so it should be called when the value changes and the external environment is not notified_ setValue method.

getFocusableElement

Get focused element

getFocusableElement: function () {
        return $();
}

isFocusable

Returns whether this part can get focus

isFocusable: function () {
    var $focusable = this.getFocusableElement();
    return $focusable.length && $focusable.is(':visible');
}

isSet

Determining whether a field is set to a valid value is usually used to determine whether the field should be set to empty.

isSet: function () {
    return !!this.value;
}

reset

Reset the component to its original value. The use scene is called from outside the component. A typical use scene is that the onchange method changes the value of the component. This method will restore the value of the component to its original value and render it again.

reset: function (record, event) {
    this._reset(record, event);
    return this._render() || Promise.resolve();
}

This method receives two values, a data point record and an event. Event refers to the event that triggers the reset action. This is an optional parameter, which can be used as the information sharing of the component from the moment the value changes to the time when the restore operation is applied.

From the internal implementation of the method, we can see that the private method is called internally_ reset, and then use_ The render method re renders the part.

removeInvalidClass

Remove invalid style class

removeInvalidClass: function () {
    this.$el.removeClass('o_field_invalid');
    this.$el.removeAttr('aria-invalid');
}

setIDForLabel

Sets the given ID for the element that gets the focus

setIDForLabel: function (id) {
    this.getFocusableElement().attr('id', id);
}

setInvalidClass

The tag element is an invalid style class as opposed to the removeInvalidClass method

setInvalidClass: function () {
    this.$el.addClass('o_field_invalid');
    this.$el.attr('aria-invalid', 'true');
}

updateModifiersValue

Update the latest value of modifiers.

updateModifiersValue: function(modifiers) {
    this.attrs.modifiersValue = modifiers || {};
}

Private method

Although private methods should not be used to modify or inherit, we often use them when constructing new widget s.

_applyDecorations

Application part decorator (only field oriented decorators in attributes are defined)

_applyDecorations: function () {
    var self = this;
    this.attrs.decorations.forEach(function (dec) {
        var isToggled = py.PY_isTrue(
            py.evaluate(dec.expression, self.record.evalContext)
        );
        self.$el.toggleClass(dec.className, isToggled);
    });
}

_formatValue

The value of the formatted field is a character value. As mentioned earlier in the initialization section_ formatValue is used to format the value of the field to be compatible with the component.

_formatValue: function (value) {
    var options = _.extend({}, this.nodeOptions, { data: this.recordData }, this.formatOptions);
    return field_utils.format[this.formatType](value, this.field, options);
}

_parseValue

The opposite of formatting is to convert the value of a string to a valid value of a field_ The parseValue method is usually used to verify whether the user's input is legal.

_parseValue: function (value) {
    return field_utils.parse[this.formatType](value, this.field, this.parseOptions);
}

_ Field was called internally in the parseValue method_ parse method in utils.

_render

The main method of rendering a part. If your part has the same effect in read-only and edit mode, overload this method. The effect of calling twice and once is the same.

_render: function () {
    if (this.attrs.decorations) {
        this._applyDecorations();
    }
    if (this.mode === 'edit') {
        return this._renderEdit();
    } else if (this.mode === 'readonly') {
        return this._renderReadonly();
    }
}

From the code, we can see_ The render method first calls the method of applying the decorator, and then calls it separately according to the mode of the current part_ renderEdit or_ Renderereadonly method. If we just want to overload the effect in one of the modes, overload it separately_ renderEdit or_ The renderereadonly method is sufficient.

_renderEdit

For the rendering method in editing mode, the abstract field itself does not realize any logic, and the specific rendering method is left to the subclass.

_renderEdit: function () {
},

_renderReadonly

For the rendering method in read-only mode, the abstract field itself does not realize any logic, and the specific rendering method is left to the subclass.

_renderReadonly: function () {
}

_reset

The low-order implementation of the reset method can be overloaded in_ Called before the render method is called.

_reset: function (record, event) {
    this.lastSetValue = undefined;
    this.record = record;
    this.value = record.data[this.name];
    this.recordData = record.data;
}

From the internal implementation, we can see that event, as a parameter, does not participate in the specific business logic. However, when the subclass is overloaded, the logic design can be carried out according to this parameter.

_setValue

Finally, let's look at a weight method_ setValue.

_ setValue is called by the component itself to change the value of the component and notify the external environment that its value has changed. This method also verifies the new value to prevent the input data from not meeting the requirements of the component. The method itself does not re render the part, and the rendering action should be rendered by the part as needed.

_setValue: function (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 === '')) {
        return Promise.resolve();
    }
    this.lastSetValue = value;
    try {
        value = this._parseValue(value);
        this._isValid = true;
    } catch (e) {
        this._isValid = false;
        this.trigger_up('set_dirty', {dataPointID: this.dataPointID});
        return Promise.reject({message: "Value set is not valid"});
    }
    if (!(options && options.forceChange) && this._isSameValue(value)) {
        return Promise.resolve();
    }
    var self = this;
    return new Promise(function (resolve, reject) {
        var changes = {};
        changes[self.name] = value;
        self.trigger_up('field_changed', {
            dataPointID: self.dataPointID,
            changes: changes,
            viewType: self.viewType,
            doNotSetDirty: options && options.doNotSetDirty,
            notifyChange: !options || options.notifyChange !== false,
            allowWarning: options && options.allowWarning,
            onSuccess: resolve,
            onFailure: reject,
        });
    })
}

This method receives two parameters, value and options. Value is the new value of the part and options is an optional parameter.

options optional values are:

  • doNotSetDirty: the default value is false. If it is set to true, the basic model will not think that this field has dirty data. Do not set this parameter to true unless you really need it.
  • notifyChange: the default value is true. If it is set to false, the model will not notify or trigger the onchange event, even if the value of the part has changed.
  • As like as two peas, forceChange: is forced to update false by default, even if the new value is exactly the same as the old one.

As you can see from the code_ The setValue method first determines_ Whether the lastSetValue is equal to the forging value. If it is equal or the part value is not a valid value, it will be returned directly to avoid unnecessary work.

If_ If the lastSetValue is not equal to the part value, update the part value to value, and then call the method_ The parseValue method checks the validity of the value value. If it fails, the part will be marked as dirty data and returned.

After that, check the options parameter. If forced update is not set or the old and new values of the part are equal, return. Otherwise, trigger field_ The changed event notifies that the value of the model component has changed and performs the operation of updating data.

event processing

_onKeydown

The key event is mainly used to prevent the default event and overload the processing method of the event.

_onKeydown: function (ev) {
    switch (ev.which) {
        case $.ui.keyCode.TAB:
            var event = this.trigger_up('navigation_move', {
                direction: ev.shiftKey ? 'previous' : 'next',
            });
            if (event.is_stopped()) {
                ev.preventDefault();
                ev.stopPropagation();
            }
            break;
        case $.ui.keyCode.ENTER:
            // We preventDefault the ENTER key because of two coexisting behaviours:
            // - In HTML5, pressing ENTER on a <button> triggers two events: a 'keydown' AND a 'click'
            // - When creating and opening a dialog, the focus is automatically given to the primary button
            // The end result caused some issues where a modal opened by an ENTER keypress (e.g. saving
            // changes in multiple edition) confirmed the modal without any intentionnal user input.
            ev.preventDefault();
            ev.stopPropagation();
            this.trigger_up('navigation_move', {direction: 'next_line'});
            break;
        case $.ui.keyCode.ESCAPE:
            this.trigger_up('navigation_move', {direction: 'cancel', originalEvent: ev});
            break;
        case $.ui.keyCode.UP:
            ev.stopPropagation();
            this.trigger_up('navigation_move', {direction: 'up'});
            break;
        case $.ui.keyCode.RIGHT:
            ev.stopPropagation();
            this.trigger_up('navigation_move', {direction: 'right'});
            break;
        case $.ui.keyCode.DOWN:
            ev.stopPropagation();
            this.trigger_up('navigation_move', {direction: 'down'});
            break;
        case $.ui.keyCode.LEFT:
            ev.stopPropagation();
            this.trigger_up('navigation_move', {direction: 'left'});
            break;
    }
}

_onNavigationMove

Update target data with an instance of the assembly.

_onNavigationMove: function (ev) {
    ev.data.target = this;
},

summary

So far, we have roughly understood the whole design structure of the abstract field, because the abstract field is the basis of the basic field type. On this basis, the basic field has been expanded, but its top-level logical structure is the content introduced in this chapter.

 

Topics: Javascript ECMAScript erp