diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 00a4532c2e1..243a28bb937 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -346,6 +346,64 @@ var InputField = DebouncedField.extend({ }, }); +var NumericField = InputField.extend({ + tagName: 'span', + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * For numeric fields, 0 is a valid value. + * + * @override + */ + isSet: function () { + return this.value === 0 || this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Format numerical value (integer or float) + * + * Note: We have to overwrite this method to skip the format if we are into + * edit mode on a input type number. + * + * @override + * @private + */ + _formatValue: function (value) { + if (this.mode === 'edit' && this.nodeOptions.type === 'number') { + return value; + } + return this._super.apply(this, arguments); + }, + + /** + * 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. + * + * Note: We have to overwrite this method to set the input's type to number if + * option setted into the field. + * + * @override + * @private + */ + _prepareInput: function ($input) { + var result = this._super.apply(this, arguments); + if (this.nodeOptions.type === 'number') { + this.$input.attr({type: 'number'}); + } + if (this.nodeOptions.step) { + this.$input.attr({step: this.nodeOptions.step}); + } + return result; + } +}); + var FieldChar = InputField.extend(TranslatableFieldMixin, { className: 'o_field_char', tagName: 'span', @@ -811,24 +869,10 @@ var FieldBoolean = AbstractField.extend({ }, }); -var FieldInteger = InputField.extend({ +var FieldInteger = NumericField.extend({ className: 'o_field_integer o_field_number', - tagName: 'span', supportedFieldTypes: ['integer'], - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * For integer fields, 0 is a valid value. - * - * @override - */ - isSet: function () { - return this.value === 0 || this._super.apply(this, arguments); - }, - //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -858,9 +902,8 @@ var FieldInteger = InputField.extend({ }, }); -var FieldFloat = InputField.extend({ +var FieldFloat = NumericField.extend({ className: 'o_field_float o_field_number', - tagName: 'span', supportedFieldTypes: ['float'], /** @@ -875,19 +918,6 @@ var FieldFloat = InputField.extend({ this.nodeOptions.digits = JSON.parse(this.attrs.digits); } }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * For float fields, 0 is a valid value. - * - * @override - */ - isSet: function () { - return this.value === 0 || this._super.apply(this, arguments); - }, }); var FieldFloatTime = FieldFloat.extend({ @@ -2884,6 +2914,7 @@ return { FieldToggleBoolean: FieldToggleBoolean, HandleWidget: HandleWidget, InputField: InputField, + NumericField: NumericField, AttachmentImage: AttachmentImage, LabelSelection: LabelSelection, StateSelectionWidget: StateSelectionWidget, diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index 583be0a90a5..830855d8c1d 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -707,6 +707,71 @@ QUnit.module('basic_fields', { form.destroy(); }); + QUnit.test('float field with type number option', function (assert) { + assert.expect(4); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
', + res_id: 4, + translateParameters: { + thousands_sep: ",", + grouping: [3, 0], + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + assert.ok(form.$('.o_field_widget')[0].hasAttribute('type'), + 'Float field with option type must have a type attribute.'); + assert.strictEqual(form.$('.o_field_widget').attr('type'), 'number', + 'Float field with option type must have a type attribute equals to "number".'); + form.$('input').val('123456.7890').trigger('input'); + form.$buttons.find('.o_form_button_save').click(); + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_field_widget').val(), '123456.789', + 'Float value must be not formatted if input type is number.'); + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('.o_field_widget').text(), '123,456.8', + 'Float value must be formatted in readonly view even if the input type is number.'); + + form.destroy(); + }); + + QUnit.test('float field without type number option', function (assert) { + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '', + res_id: 4, + translateParameters: { + thousands_sep: ",", + grouping: [3, 0], + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_field_widget').attr('type'), 'text', + 'Float field with option type must have a text type (default type).'); + + form.$('input').val('123456.7890').trigger('input'); + form.$buttons.find('.o_form_button_save').click(); + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_field_widget').val(), '123,456.8', + 'Float value must be formatted if input type isn\'t number.'); + + form.destroy(); + }); + + QUnit.module('FieldEmail'); QUnit.test('email field in form view', function (assert) { @@ -3359,6 +3424,72 @@ QUnit.module('basic_fields', { list.destroy(); }); + QUnit.test('integer field with type number option', function (assert) { + assert.expect(4); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '', + res_id: 4, + translateParameters: { + thousands_sep: ",", + grouping: [3, 0], + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + assert.ok(form.$('.o_field_widget')[0].hasAttribute('type'), + 'Integer field with option type must have a type attribute.'); + assert.strictEqual(form.$('.o_field_widget').attr('type'), 'number', + 'Integer field with option type must have a type attribute equals to "number".'); + + form.$('input').val('1234567890').trigger('input'); + form.$buttons.find('.o_form_button_save').click(); + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_field_widget').val(), '1234567890', + 'Integer value must be not formatted if input type is number.'); + form.$buttons.find('.o_form_button_save').click(); + assert.strictEqual(form.$('.o_field_widget').text(), '1,234,567,890', + 'Integer value must be formatted in readonly view even if the input type is number.'); + + form.destroy(); + }); + + QUnit.test('integer field without type number option', function (assert) { + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '', + res_id: 4, + translateParameters: { + thousands_sep: ",", + grouping: [3, 0], + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_field_widget').attr('type'), 'text', + 'Integer field without option type must have a text type (default type).'); + + form.$('input').val('1234567890').trigger('input'); + form.$buttons.find('.o_form_button_save').click(); + form.$buttons.find('.o_form_button_edit').click(); + assert.strictEqual(form.$('.o_field_widget').val(), '1,234,567,890', + 'Integer value must be formatted if input type isn\'t number.'); + + form.destroy(); + }); + + QUnit.module('FieldFloatTime'); QUnit.test('float_time field in form view', function (assert) { diff --git a/doc/reference/javascript_reference.rst b/doc/reference/javascript_reference.rst index 14647955afa..c1f2ae9cb0d 100644 --- a/doc/reference/javascript_reference.rst +++ b/doc/reference/javascript_reference.rst @@ -1397,6 +1397,23 @@ order. - Supported field types: *integer* + Options: + + - type: setting the input type (*text* by default, can be set on *number*) + On edit mode, the field is rendered as an input with the HTML attribute type + setted on *number* (so user can benefit the native support, especially on + mobile). In this case, the default formatting is disabled to avoid incompability. + + .. code-block:: xml + +