[IMP] web: use user's decimal separator for numeric fields

Until now, the numpad decimal key was not properly handled for some
keyboard layouts.

This commit will ensure that when this key will get pressed inside a
numeric field, the user's display language decimal separator will get inserted.

closes odoo/odoo#56962

Taskid: 1913999
Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
This commit is contained in:
Bruno Boi
2020-10-05 08:21:00 +00:00
parent 574154eb47
commit e654b32bc1
3 changed files with 149 additions and 6 deletions
+14
View File
@@ -500,6 +500,20 @@ var utils = {
isComponent: function (value) {
return value.prototype instanceof owl.Component;
},
/**
* Checks if a keyboard event concerns
* the numpad decimal separator key.
*
* Some countries may emit a comma instead
* of a period when this key get pressed.
* More info: https://www.iso.org/schema/isosts/v1.0/doc/n-cdf0.html
*
* @param {KeyboardEvent} ev
* @returns {boolean}
*/
isNumpadDecimalSeparatorKey(ev) {
return ['.', ','].includes(ev.key) && ev.code === 'NumpadDecimal';
},
/**
* Returns whether the given anchor is valid.
*
@@ -245,6 +245,15 @@ var InputField = DebouncedField.extend({
_getValue: function () {
return this.$input.val();
},
/**
* By default this only calls a debounced method to notify the outside world
* of the changes if the actual value is not the same than the previous one.
* @see _doDebouncedAction
*/
_notifyChanges() {
this.isDirty = !this._isLastSetValue(this.$input.val());
this._doDebouncedAction();
},
/**
* 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.
@@ -319,15 +328,13 @@ var InputField = DebouncedField.extend({
this.lastChangeEvent = event;
},
/**
* 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
* Called when the user is typing text
* @see _notifyChanges
*
* @private
*/
_onInput: function () {
this.isDirty = !this._isLastSetValue(this.$input.val());
this._doDebouncedAction();
_onInput() {
this._notifyChanges();
},
/**
* Stops the left/right navigation move event if the cursor is not at the
@@ -508,6 +515,43 @@ var NumericField = InputField.extend({
}
return this._super(value, options);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Replace the decimal separator of the numpad decimal key
* by the decimal separator from the user's language setting.
*
* @private
* @param {OdooEvent} ev
*/
_onKeydown(ev) {
const kbdEvt = ev.originalEvent;
if (kbdEvt && utils.isNumpadDecimalSeparatorKey(kbdEvt)) {
const inputField = this.$input[0];
const curVal = inputField.value;
const from = inputField.selectionStart;
const to = inputField.selectionEnd;
const point = _t.database.parameters.decimal_point;
// Make sure the correct decimal separator
// from the user's settings is inserted
inputField.value = curVal.slice(0, from) + point + curVal.slice(to);
// Put the user caret at the right place
inputField.selectionStart = inputField.selectionEnd = from + point.length;
// Tell the world we made some changes and
// return preventing event default behaviour.
this._notifyChanges();
kbdEvt.preventDefault();
return;
}
return this._super(...arguments);
},
});
var FieldChar = InputField.extend(TranslatableFieldMixin, {
@@ -479,6 +479,91 @@ QUnit.module('basic_fields', {
form.destroy();
});
QUnit.module('FieldNumeric');
QUnit.test('numeric field: fields with keydown on numpad decimal key', async function (assert) {
assert.expect(6);
this.data.partner.fields.float_factor_field = { string: "Float Factor", type: 'float_factor' };
this.data.partner.records[0].float_factor_field = 9.99;
this.data.partner.fields.float_time_field = { string: "Float Time", type: 'float_time' };
this.data.partner.records[0].float_time_field = 9.99;
this.data.partner.fields.monetary = { string: "Monetary", type: 'monetary' };
this.data.partner.records[0].monetary = 9.99;
this.data.partner.records[0].currency_id = 1;
this.data.partner.fields.percentage = { string: "Percentage", type: 'percentage' };
this.data.partner.records[0].percentage = .99;
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form string="Partners">
<field name="float_factor_field" options="{'factor': 0.5}"/>
<field name="qux"/>
<field name="float_time_field"/>
<field name="int_field"/>
<field name="monetary"/>
<field name="currency_id" invisible="1"/>
<field name="percentage"/>
</form>
`,
res_id: 1,
translateParameters: {
decimal_point: "🇧🇪",
},
});
// Record edit mode
await testUtilsDom.click(form.el.querySelector('.o_form_button_edit'));
// Get all inputs
const floatFactorField = form.el.querySelector('.o_input[name="float_factor_field"]');
const floatInput = form.el.querySelector('.o_input[name="qux"]');
const floatTimeInput = form.el.querySelector('.o_input[name="float_time_field"]');
const integerInput = form.el.querySelector('.o_input[name="int_field"]');
const monetaryInput = form.el.querySelector('.o_input[name="monetary"]');
const percentageInput = form.el.querySelector('.o_input[name="percentage"]');
// Dispatch numpad "dot" and numpad "comma" keydown events to all inputs and check
// Numpad "comma" is specific to some countries (Brazil...)
floatFactorField.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
floatFactorField.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
await testUtils.nextTick();
assert.ok(floatFactorField.value.endsWith('🇧🇪🇧🇪'));
floatInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
floatInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
await testUtils.nextTick();
assert.ok(floatInput.value.endsWith('🇧🇪🇧🇪'));
floatTimeInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
floatTimeInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
await testUtils.nextTick();
assert.ok(floatTimeInput.value.endsWith('🇧🇪🇧🇪'));
integerInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
integerInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
await testUtils.nextTick();
assert.ok(integerInput.value.endsWith('🇧🇪🇧🇪'));
monetaryInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
monetaryInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
await testUtils.nextTick();
assert.ok(monetaryInput.querySelector('input.o_input') .value.endsWith('🇧🇪🇧🇪'));
percentageInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: '.' }));
percentageInput.dispatchEvent(new KeyboardEvent('keydown', { code: 'NumpadDecimal', key: ',' }));
await testUtils.nextTick();
assert.ok(percentageInput.querySelector('input.o_input').value.endsWith('🇧🇪🇧🇪'));
form.destroy();
});
QUnit.module('FieldFloat');
QUnit.test('float field when unset', async function (assert) {