odoo.define('web.basic_fields', function (require) { "use strict"; /** * This module contains most of the basic (meaning: non relational) field * widgets. Field widgets are supposed to be used in views inheriting from * BasicView, so, they can work with the records obtained from a BasicModel. */ var AbstractField = require('web.AbstractField'); var ajax = require('web.ajax'); var config = require('web.config'); var core = require('web.core'); var crash_manager = require('web.crash_manager'); var datepicker = require('web.datepicker'); var dom = require('web.dom'); var field_utils = require('web.field_utils'); var framework = require('web.framework'); var session = require('web.session'); var utils = require('web.utils'); var qweb = core.qweb; var _t = core._t; var InputField = AbstractField.extend({ events: _.extend({}, AbstractField.prototype.events, { 'input': '_onInput', }), init: function () { this._super.apply(this, arguments); if (this.mode === 'edit') { this.tagName = 'input'; } }, //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- activate: function () { this.$el.focus(); setTimeout(this.$el.select.bind(this.$el), 0); }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- _renderEdit: function () { this.$el.addClass('o_form_input'); this.$el.attr('type', 'text'); if (this.attrs.placeholder) { this.$el.attr('placeholder', this.attrs.placeholder); } this.$el.attr('id', this.idForLabel); // save cursor position to restore it after updating value var selectionStart = this.el.selectionStart; var selectionEnd = this.el.selectionEnd; this.$el.val(this._formatValue(this.value)); this.el.selectionStart = selectionStart; this.el.selectionEnd = selectionEnd; }, _renderReadonly: function () { this.$el.empty().html(this._formatValue(this.value)); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- /** * Implement keyboard movements. Mostly useful for its environment, such * as a list view. * * @override * @param {any} event */ _onKeydown: function (event) { var input = this.$el[0]; var is_not_selecting; switch (event.which) { case $.ui.keyCode.DOWN: this.trigger_up('move_down'); break; case $.ui.keyCode.UP: this.trigger_up('move_up'); break; case $.ui.keyCode.LEFT: is_not_selecting = input.selectionEnd === input.selectionStart; if (is_not_selecting && input.selectionStart === 0) { this.trigger_up('move_left'); } break; case $.ui.keyCode.RIGHT: is_not_selecting = input.selectionEnd === input.selectionStart; if (is_not_selecting && input.selectionEnd === input.value.length) { this.trigger_up('move_right'); } break; case $.ui.keyCode.ENTER: this.trigger_up('move_next_line'); break; } this._super.apply(this, arguments); }, /** * We notify the outside world for each change in this input value. It does * not necessarily mean that onchanges will be triggered instantly. */ _onInput: function () { this._setValue(this.$el.val()); }, }); var FieldChar = InputField.extend({ supportedFieldTypes: ['char', 'text'], tagName: 'span', }); var FieldDate = InputField.extend({ className: "o_form_field_date", tagName: "span", replace_element: true, supportedFieldTypes: ['date', 'datetime'], start: function () { var self = this; var def; if (this.mode === 'edit') { this.datewidget = this._makeDatePicker(); this.datewidget.on('datetime_changed', this, function () { var value = this._parseValue(this.datewidget.get_value()); if (value !== this._parseValue(this.value)) { this._setValue(value); } }); def = this.datewidget.appendTo('
').done(function () { self.datewidget.$el.addClass(self.$el.attr('class')); self.datewidget.$input.addClass('o_form_input'); self.datewidget.$input.attr('id', self.idForLabel); self.replaceElement(self.datewidget.$el); }); } return $.when(def, this._super.apply(this, arguments)); }, //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- activate: function () { var $input = this.datewidget.$input; $input.focus(); setTimeout($input.select.bind($input), 0); }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- _makeDatePicker: function () { return new datepicker.DateWidget(this); }, _renderEdit: function () { this.datewidget.set_value(this.value); }, }); var FieldDateTime = FieldDate.extend({ supportedFieldTypes: ['datetime'], //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- _makeDatePicker: function () { return new datepicker.DateTimeWidget(this); }, }); var FieldMonetary = InputField.extend({ className: 'o_list_number', supportedFieldTypes: ['float', 'monetary'], // FieldMonetary overrides _formatValue to ensure that the format_monetary // method is used. This allows this widget to be used with other field // types, such as float. _formatValue: function (value) { var options = _.extend({}, this.nodeOptions, { data: this.recordData }); return field_utils.format_monetary(value, this.field, options); }, isSet: function () { return this.value !== false; } }); var FieldBoolean = AbstractField.extend({ className: 'o_field_boolean', events: _.extend({}, AbstractField.prototype.events, { change: '_onChange', keydown: '_onKeyDown', }), replace_element: true, supportedFieldTypes: ['boolean'], //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- activate: function () { this.$input.focus(); setTimeout(this.$input.select.bind(this.$input), 0); }, isSet: function () { return true; }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- _renderReadonly: function () { var $checkbox = this._formatValue(this.value); this.$input = $checkbox.find('input'); this.$el.empty().append($checkbox); }, _renderEdit: function () { this._renderReadonly(); this.$input.prop('disabled', false); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- _onChange: function () { this._setValue(this.$input[0].checked); }, /** * @param {KeyEvent} event */ _onKeydown: function (event) { switch (event.which) { case $.ui.keyCode.DOWN: this.trigger_up('move_down'); break; case $.ui.keyCode.UP: this.trigger_up('move_up'); break; case $.ui.keyCode.LEFT: this.trigger_up('move_left'); break; case $.ui.keyCode.RIGHT: this.trigger_up('move_right'); break; case $.ui.keyCode.TAB: this.trigger_up('move_next'); event.preventDefault(); break; case $.ui.keyCode.ENTER: this.$input.prop('checked', !this.value); this._setValue(!this.value); break; } }, }); var FieldInteger = InputField.extend({ supportedFieldTypes: ['integer'], isSet: function () { return this.value !== false; }, }); var FieldFloat = InputField.extend({ supportedFieldTypes: ['float'], _renderReadonly: function () { var precision; var value = this.value; if (this.attrs.digits) { precision = this.node.attrs.digits; } else { precision = this.field.digits; } if (precision && precision.length === 2) { value = utils.round_decimals(value, precision[1]); } var $span = $('').addClass('o_form_field o_form_field_number').text(this._formatValue(value)); this.$el.html($span); }, isSet: function () { return this.value !== ''; }, }); var FieldFloatTime = FieldFloat.extend({ supportedFieldTypes: ['float'], _formatValue: function (value) { var pattern = '%02d:%02d'; if (value < 0) { value = Math.abs(value); pattern = '-' + pattern; } var hour = Math.floor(value); var min = Math.round((value % 1) * 60); if (min === 60){ min = 0; hour = hour + 1; } return _.str.sprintf(pattern, hour, min); }, _parseValue: function (value) { var factor = 1; if (value[0] === '-') { value = value.slice(1); factor = -1; } var float_time_pair = value.split(":"); if (float_time_pair.length !== 2) { return factor * field_utils.parse_float(value, this.field); } var hours = field_utils.parse_integer(float_time_pair[0]); var minutes = field_utils.parse_integer(float_time_pair[1]); return factor * (hours + (minutes / 60)); }, }); var FieldText = AbstractField.extend({ events: _.extend({}, AbstractField.prototype.events, { 'input': function () { this._setValue(this.$textarea.val()); }, }), supportedFieldTypes: ['text'], start: function () { this.$el.addClass('o_list_text o_form_textarea'); this.$el.attr('id', this.idForLabel); if (this.mode === 'edit') { // in edit mode, the widget contains a textarea. We append it in // start() instead of _renderEdit() to keep the same textarea even // if several _renderEdit are done. This allows to keep the cursor // position and to autoresize only once this.$textarea = $('