From 74fcd3ac3b0445c5fc0d9239900a9a41bfd04de8 Mon Sep 17 00:00:00 2001 From: svs-odoo Date: Tue, 4 Sep 2018 13:44:46 +0200 Subject: [PATCH] [IMP] web: FieldInteger and FieldFloat can use input of type number This allow the browser/device to handle the experience of handling number in a more native way. FieldInteger and FieldFloat now inherits from NumericField because we want to format it or not dependent of the view (readonly or edit) and the NumericField will manage it. We do that because input type number can't take a value with comma on Chrome (FireFox is more permissive but not perfect because comma is the separator for decimal in this case). Task #1880376 --- .../web/static/src/js/fields/basic_fields.js | 91 ++++++++---- .../static/tests/fields/basic_fields_tests.js | 131 ++++++++++++++++++ doc/reference/javascript_reference.rst | 35 +++++ 3 files changed, 227 insertions(+), 30 deletions(-) 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 + + + + - step: set the step to the value up and down when the user click on buttons + (only for input of type number, 1 by default) + + .. code-block:: xml + + - float (FieldFloat) This is the default field type for fields of type *float*. @@ -1411,6 +1428,24 @@ order. + 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 + + + + - step: set the step to the value up and down when the user click on buttons + (only for input of type number, 1 by default) + + .. code-block:: xml + + + - float_time (FieldFloatTime) The goal of this widget is to display properly a float value that represents a time interval (in hours). So, for example, 0.5 should be formatted as 0:30,