[FIX] mail,sms: restore "onchange_on_keydown" feature
Previously, we had an option on char/text fields called "onchange_on_keydown", which allowed to trigger the onchange while the user was typing (instead of when the field is blurred). It was notably used when typing a phone number in the sms composer, to warn the user that the phone number is invalid. This feature was lost during the port to OWL, this commit restores it. Task-3033107 closes odoo/odoo#106193 X-original-commit: 54def8969d49d6e5284cd97ab8df75e08043cd4e Related: odoo/enterprise#34211 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com> Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
This commit is contained in:
@@ -1,61 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import { FieldChar } from "web.basic_fields";
|
||||
|
||||
FieldChar.include({
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Support a key-based onchange in text field.
|
||||
* The _triggerOnchange method is debounced to run after given debouce delay
|
||||
* (or 2 seconds by default) on typing ends.
|
||||
*
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.nodeOptions.keydown_debounce_delay === undefined) {
|
||||
this.nodeOptions.keydown_debounce_delay = 2000;
|
||||
}
|
||||
this._triggerOnchange = debounce(
|
||||
this._triggerOnchange,
|
||||
this.nodeOptions.keydown_debounce_delay
|
||||
);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Trigger the 'change' event at key down. It allows to trigger an onchange
|
||||
* while typing which may be interesting in some cases. Otherwise onchange
|
||||
* is triggered only on blur.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onKeydown: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.nodeOptions.onchange_on_keydown) {
|
||||
this._triggerOnchange();
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Triggers the 'change' event to refresh the value.
|
||||
* This method is debounced to run after given debouce delay on typing ends.
|
||||
* (to avoid spamming the server while the user is typing their message)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_triggerOnchange: function () {
|
||||
this.$input.trigger("change");
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import { CharField } from "@web/views/fields/char/char_field";
|
||||
import { TextField } from '@web/views/fields/text/text_field';
|
||||
import { archParseBoolean } from "@web/views/utils";
|
||||
|
||||
const { useEffect } = owl;
|
||||
|
||||
/**
|
||||
* Support a key-based onchange in text fields.
|
||||
* The triggerOnChange method is debounced to run after given debounce delay
|
||||
* (or 2 seconds by default) when typing ends.
|
||||
*
|
||||
*/
|
||||
const onchangeOnKeydownMixin = {
|
||||
setup() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (this.props.onchangeOnKeydown) {
|
||||
const input = this.input || this.textareaRef;
|
||||
|
||||
const triggerOnChange = debounce(this.triggerOnChange, this.props.keydownDebounceDelay);
|
||||
useEffect(() => {
|
||||
if (input.el) {
|
||||
input.el.addEventListener('keydown', triggerOnChange.bind(this));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
triggerOnChange() {
|
||||
const input = this.input || this.textareaRef;
|
||||
input.el.dispatchEvent(new Event('change'));
|
||||
}
|
||||
};
|
||||
|
||||
patch(CharField.prototype, 'char_field_onchange_on_keydown', onchangeOnKeydownMixin);
|
||||
patch(TextField.prototype, 'text_field_onchange_on_keydown', onchangeOnKeydownMixin);
|
||||
|
||||
CharField.props = {
|
||||
...CharField.props,
|
||||
onchangeOnKeydown: { type: Boolean, optional: true },
|
||||
keydownDebounceDelay: { type: Number, optional: true },
|
||||
};
|
||||
|
||||
TextField.props = {
|
||||
...TextField.props,
|
||||
onchangeOnKeydown: { type: Boolean, optional: true },
|
||||
keydownDebounceDelay: { type: Number, optional: true },
|
||||
};
|
||||
|
||||
const charExtractProps = CharField.extractProps;
|
||||
CharField.extractProps = ({ attrs, field }) => {
|
||||
return Object.assign(charExtractProps({ attrs, field }), {
|
||||
onchangeOnKeydown: archParseBoolean(attrs.onchange_on_keydown),
|
||||
keydownDebounceDelay: attrs.keydown_debounce_delay ? Number(attrs.keydown_debounce_delay) : 2000,
|
||||
});
|
||||
};
|
||||
|
||||
const textExtractProps = TextField.extractProps;
|
||||
TextField.extractProps = ({ attrs, field }) => {
|
||||
return Object.assign(textExtractProps({ attrs, field }), {
|
||||
onchangeOnKeydown: archParseBoolean(attrs.onchange_on_keydown),
|
||||
keydownDebounceDelay: attrs.keydown_debounce_delay ? Number(attrs.keydown_debounce_delay) : 2000,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { getFixture, nextTick } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"
|
||||
import { dom } from 'web.test_utils';
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
||||
QUnit.module('mail', {}, function () {
|
||||
QUnit.module('onchange on keydown', {
|
||||
async beforeEach() {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
'res.partner': {
|
||||
fields: {
|
||||
id: {type: 'integer'},
|
||||
description: {type: 'text'},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
description: '',
|
||||
}],
|
||||
onchanges: {
|
||||
description: () => {},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
setupViewRegistries();
|
||||
}
|
||||
}, function () {
|
||||
QUnit.test('Test that onchange_on_keydown option triggers the onchange properly', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: 'res.partner',
|
||||
serverData,
|
||||
arch: '<form><field name="description" onchange_on_keydown="True" keydown_debounce_delay="0"/></form>',
|
||||
mockRPC(route, params) {
|
||||
if (params.method === 'onchange') {
|
||||
// the onchange will be called twice: at record creation & when keydown is detected
|
||||
// the second call should have our description value completed.
|
||||
assert.ok(true);
|
||||
if (params.args[1] && params.args[1].description === 'testing the keydown event') {
|
||||
assert.ok(true);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const textarea = target.querySelector('textarea[id="description"]');
|
||||
await dom.click(textarea);
|
||||
for (let key of 'testing the keydown event') {
|
||||
// trigger each key separately to simulate a user typing
|
||||
textarea.value = textarea.value + key;
|
||||
await dom.triggerEvent(textarea, 'input', { key: key });
|
||||
};
|
||||
|
||||
// only trigger the keydown when typing ends to avoid getting a lot of onchange since the
|
||||
// delay is set to 0 for test purposes
|
||||
// for real use cases there will be a debounce delay set to avoid spamming the event
|
||||
await dom.triggerEvent(textarea, 'keydown');
|
||||
await nextTick();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -43,7 +43,7 @@
|
||||
attrs="{'invisible': [('comment_single_recipient', '=', False)]}"/>
|
||||
<div attrs="{'invisible': [('comment_single_recipient', '=', False)]}">
|
||||
<field name="recipient_single_description" class="oe_inline"/>
|
||||
<field name="recipient_single_number_itf" class="oe_inline" nolabel="1" options="{'onchange_on_keydown': True}" placeholder="e.g. +1 415 555 0100"/>
|
||||
<field name="recipient_single_number_itf" class="oe_inline" nolabel="1" onchange_on_keydown="True" placeholder="e.g. +1 415 555 0100"/>
|
||||
</div>
|
||||
<field name="body" widget="sms_widget" attrs="{'invisible': ['|', ('comment_single_recipient', '=', False), ('recipient_single_valid', '=', True)]}"/>
|
||||
<field name="body" widget="sms_widget" attrs="{'invisible': [('comment_single_recipient', '=', True), ('recipient_single_valid', '=', False)]}" default_focus="1"/>
|
||||
|
||||
Reference in New Issue
Block a user