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 Domain = require('web.Domain');
var DomainSelector = require('web.DomainSelector');
var DomainSelectorDialog = require('web.DomainSelectorDialog');
var field_utils = require('web.field_utils');
var framework = require('web.framework');
var session = require('web.session');
var utils = require('web.utils');
var view_dialogs = require('web.view_dialogs');
var qweb = core.qweb;
var _t = core._t;
var DebouncedField = AbstractField.extend({
events: _.extend({}, AbstractField.prototype.events, {
'input': '_onInput',
'change': '_onChange',
}),
/**
* for field widgets that may have a large number of field changes quickly,
* it could be a good idea to debounce the changes. In that case, this is
* the suggested value.
*/
DEBOUNCE: 1000,
/**
* Override init to debounce the input changes to make sure they are not
* done too quickly. Note that this is done here and not on the prototype,
* so each inputfield has its own debounced function to work with.
* Also, if the debounce value is set to 0, no debouncing is done, which is
* really useful for the unit tests.
*
* @override
*/
init: function () {
this._super.apply(this, arguments);
if (this.DEBOUNCE && this.mode === 'edit') {
this._doDebouncedAction = _.debounce(this._doDebouncedAction.bind(this), this.DEBOUNCE);
}
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Debounced fields do not notify the rest of the environment immediately of
* their new state, so it is necessary to actually get their value when this
* method is called.
*
* @override
*/
commitChanges: function () {
if (this.mode === 'edit') {
this._setValue(this._getValue());
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Should return the current value of the field, in the DOM (for example,
* the content of the input)
*
* @abstract
* @private
* @returns {*}
*/
_getValue: function () {
},
/**
* Notifies the outside world of the new value (checked from the DOM).
* This method is debounced so that onchanges are not triggered too quickly.
*
* @private
*/
_doDebouncedAction: function () {
this._setValue(this._getValue());
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* We immediately notify the outside world when this field confirms its
* changes.
*
* @private
*/
_onChange: function () {
this._setValue(this._getValue());
},
/**
* Called when the user is typing text -> By default this only calls a
* debounced method to notify the outside world of the changes.
* @see _doDebouncedAction
*
* @private
*/
_onInput: function () {
this._doDebouncedAction();
},
});
var InputField = DebouncedField.extend({
/**
* The very purpose of this field is to be an input tag in edit mode.
*
* @override
*/
init: function () {
this._super.apply(this, arguments);
if (this.mode === 'edit') {
this.tagName = 'input';
}
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Returns the associated element.
*
* @override
*/
getFocusableElement: function () {
return this.$input || $();
},
/**
* Do not re-render this field if it was the origin of the onchange call.
* FIXME: make the onchange work on itself without disturbing the user typing
*
* @override
*/
reset: function (record, event) {
this._reset(record, event);
if (event && event.target === this) {
return $.when();
} else {
return this._render();
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
* @returns {string} the content of the input
*/
_getValue: function () {
return this.$input.val();
},
/**
* Formats an input element for edit mode. This is in a separate function so
* extending widgets can use it on their input without having input as tagName.
*
* @private
* @param {jQuery|undefined} $input
* The element to prepare and save as the $input attribute.
* If no element is given, the is created.
* @returns {jQuery} the prepared this.$input element
*/
_prepareInput: function ($input) {
this.$input = $input || $("");
this.$input.addClass('o_input');
this.$input.attr({
type: 'text',
placeholder: this.attrs.placeholder || "",
});
// Save cursor position to restore it after updating value
var selectionStart = this.$input[0].selectionStart;
var selectionEnd = this.$input[0].selectionEnd;
this.$input.val(this._formatValue(this.value));
this.$input[0].selectionStart = selectionStart;
this.$input[0].selectionEnd = selectionEnd;
return this.$input;
},
/**
* Formats the HTML input tag for edit mode and stores selection status.
*
* @override
* @private
*/
_renderEdit: function () {
// Keep a reference to the input so $el can become something else
// without losing track of the actual input.
this._prepareInput(this.$el);
},
/**
* Resets the content to the formated value in readonly mode.
*
* @override
* @private
*/
_renderReadonly: function () {
this.$el.html(this._formatValue(this.value));
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Implement keyboard movements. Mostly useful for its environment, such
* as a list view.
*
* @override
* @private
* @param {any} event
*/
_onKeydown: function (event) {
var input = this.$input[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);
},
});
var FieldChar = InputField.extend({
className: 'o_field_char',
tagName: 'span',
supportedFieldTypes: ['char'],
});
var FieldDate = InputField.extend({
className: "o_field_date",
tagName: "span",
supportedFieldTypes: ['date'],
/**
* In edit mode, instantiates a DateWidget datepicker and listen to changes.
*
* @override
*/
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._getValue();
if ((!value && this.value) || (value && !value.isSame(this.value))) {
this._setValue(value);
}
});
def = this.datewidget.appendTo('