From b78385c02d21ff8c52bcaffd94bfab2f1bec8bfc Mon Sep 17 00:00:00 2001 From: Antoine Guenet Date: Fri, 23 Sep 2022 08:59:34 +0000 Subject: [PATCH] [IMP] sms, mass_mailing_sms : port sms widget field to OWL This ports the `sms_widget` field to OWL. closes odoo/odoo#101350 X-original-commit: 89109de6d3752388f0b2f976c8a32588f760ec48 Related: odoo/enterprise#31882 Signed-off-by: Thibault Delavallee (tde) --- .../views/mailing_mailing_views.xml | 2 +- .../sms_widget/fields_sms_widget.js | 184 +++++------------- .../sms_widget/fields_sms_widget.xml | 18 ++ addons/sms/static/tests/sms_widget_test.js | 81 ++++---- 4 files changed, 109 insertions(+), 176 deletions(-) create mode 100644 addons/sms/static/src/components/sms_widget/fields_sms_widget.xml diff --git a/addons/mass_mailing_sms/views/mailing_mailing_views.xml b/addons/mass_mailing_sms/views/mailing_mailing_views.xml index b4395824cee..b5cc0c20905 100644 --- a/addons/mass_mailing_sms/views/mailing_mailing_views.xml +++ b/addons/mass_mailing_sms/views/mailing_mailing_views.xml @@ -120,7 +120,7 @@ + options="{'dynamic_placeholder': true}" enable_emojis="true"/> diff --git a/addons/sms/static/src/components/sms_widget/fields_sms_widget.js b/addons/sms/static/src/components/sms_widget/fields_sms_widget.js index 7bd29885547..fcb604c9880 100644 --- a/addons/sms/static/src/components/sms_widget/fields_sms_widget.js +++ b/addons/sms/static/src/components/sms_widget/fields_sms_widget.js @@ -1,39 +1,33 @@ /** @odoo-module **/ -import { _t } from 'web.core'; import basic_fields from 'web.basic_fields'; -import fieldRegistry from 'web.field_registry'; -import EmojisTextField from '@mail/views/fields/emojis_text_field'; +import { patch } from "@web/core/utils/patch"; +import { EmojisTextField} from '@mail/views/fields/emojis_text_field/emojis_text_field'; +import { useService } from "@web/core/utils/hooks"; +import { registry } from "@web/core/registry"; -var DynamicPlaceholderFieldMixin = basic_fields.DynamicPlaceholderFieldMixin; +const DynamicPlaceholderFieldMixin = basic_fields.DynamicPlaceholderFieldMixin; /** * SmsWidget is a widget to display a textarea (the body) and a text representing * the number of SMS and the number of characters. This text is computed every * time the user changes the body. */ -var SmsWidget = EmojisTextField.extend(DynamicPlaceholderFieldMixin, { - className: 'o_field_text', - enableEmojis: false, - /** - * @constructor - */ - init: function () { - this._super.apply(this, arguments); - this.nbrChar = 0; - this.nbrSMS = 0; - this.encoding = 'GSM7'; - this.enableEmojis = !!this.nodeOptions.enable_emojis; - }, +export class SmsWidget extends EmojisTextField { + setup() { + super.setup(); + this.notification = useService('notification'); + } - /** - * @override - *"This will add the emoji dropdown to a target field (controlled by the "enableEmojis" attribute) - */ - on_attach_callback: function () { - if (this.enableEmojis) { - this._super.apply(this, arguments); - } - }, + get encoding() { + return this._extractEncoding(this.props.value || ''); + } + get nbrChar() { + const content = this.props.value || ''; + return content.length + (content.match(/\n/g) || []).length; + } + get nbrSMS() { + return this._countSMS(this.nbrChar, this.encoding); + } /** * Open a Model Field Selector in order to select fields @@ -45,67 +39,35 @@ var SmsWidget = EmojisTextField.extend(DynamicPlaceholderFieldMixin, { * @param {Array} chain * */ - openDynamicPlaceholder: async function (baseModel, chain = []) { + async openDynamicPlaceholder(baseModel, chain = []) { const modelSelector = await this._openNewModelSelector(baseModel, chain); modelSelector.$el.css('margin-top', 4); - }, - - //-------------------------------------------------------------------------- - // Private: override widget - //-------------------------------------------------------------------------- - - /** - * @private - * @override - */ - _renderEdit: function () { - var def = this._super.apply(this, arguments); - - this._compute(); - var $sms_container = $('
'); - $sms_container.append(this._renderSMSInfo()); - $sms_container.append(this._renderIAPButton()); - this.$el = this.$el.add($sms_container); - - return def; - }, + } //-------------------------------------------------------------------------- // Private: SMS //-------------------------------------------------------------------------- - /** - * Compute the number of characters and sms - * @private - */ - _compute: function () { - var content = this._getValue(); - this.encoding = this._extractEncoding(content); - this.nbrChar = content.length; - this.nbrChar += (content.match(/\n/g) || []).length; - this.nbrSMS = this._countSMS(this.nbrChar, this.encoding); - }, - /** * Count the number of SMS of the content * @private * @returns {integer} Number of SMS */ - _countSMS: function () { - if (this.nbrChar === 0) { + _countSMS(nbrChar, encoding) { + if (nbrChar === 0) { return 0; } - if (this.encoding === 'UNICODE') { - if (this.nbrChar <= 70) { + if (encoding === 'UNICODE') { + if (nbrChar <= 70) { return 1; } - return Math.ceil(this.nbrChar / 67); + return Math.ceil(nbrChar / 67); } - if (this.nbrChar <= 160) { + if (nbrChar <= 160) { return 1; } - return Math.ceil(this.nbrChar / 153); - }, + return Math.ceil(nbrChar / 153); + } /** * Extract the encoding depending on the characters in the content @@ -113,49 +75,12 @@ var SmsWidget = EmojisTextField.extend(DynamicPlaceholderFieldMixin, { * @param {String} content Content of the SMS * @returns {String} Encoding of the content (GSM7 or UNICODE) */ - _extractEncoding: function (content) { + _extractEncoding(content) { if (String(content).match(RegExp("^[@£$¥èéùìòÇ\\nØø\\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\\\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà]*$"))) { return 'GSM7'; } return 'UNICODE'; - }, - - /** - * Render the IAP button to redirect to IAP pricing - * @private - */ - _renderIAPButton: function () { - return $('', { - 'href': 'https://iap-services.odoo.com/iap/sms/pricing', - 'target': '_blank', - 'title': _t('SMS Pricing'), - 'aria-label': _t('SMS Pricing'), - 'class': 'fa fa-lg fa-info-circle', - }); - }, - - /** - * Render the number of characters, sms and the encoding. - * @private - */ - _renderSMSInfo: function () { - var string = _.str.sprintf(_t('%s characters, fits in %s SMS (%s) '), this.nbrChar, this.nbrSMS, this.encoding); - var $span = $('', { - 'class': 'text-muted o_sms_count', - }); - $span.text(string); - return $span; - }, - - /** - * Update widget SMS information with re-computed info about length, ... - * @private - */ - _updateSMSInfo: function () { - this._compute(); - var string = _.str.sprintf(_t('%s characters, fits in %s SMS (%s) '), this.nbrChar, this.nbrSMS, this.encoding); - this.$('.o_sms_count').text(string); - }, + } //-------------------------------------------------------------------------- // Handlers @@ -165,43 +90,34 @@ var SmsWidget = EmojisTextField.extend(DynamicPlaceholderFieldMixin, { * @override * @private */ - _onBlur: function () { - var content = this._getValue(); + async onBlur() { + var content = this.props.value || ''; if( !content.trim().length && content.length > 0) { - this.displayNotification({ title: _t("Your SMS Text Message must include at least one non-whitespace character"), type: 'danger' }); - this.$input.val(content.trim()); - this._updateSMSInfo(); + this.notification.add( + this.env._t("Your SMS Text Message must include at least one non-whitespace character"), + { type: 'danger' }, + ) + await this.props.update(content.trim()); } - }, + } /** * @override * @private */ - _onChange: function () { - this._super.apply(this, arguments); - this._updateSMSInfo(); - }, - - /** - * @override - * @private - */ - _onInput: function (ev) { - this._super.apply(this, arguments); + async onInput(ev) { + await this.props.update(this.targetEditElement.el.value); + super.onInput(...arguments); const key = ev.originalEvent ? ev.originalEvent.data : ''; - if (this.nodeOptions && - this.nodeOptions.dynamic_placeholder && - key === this.DYNAMIC_PLACEHOLDER_TRIGGER_KEY) { + if (this.props.dynamicPlaceholder && key === this.DYNAMIC_PLACEHOLDER_TRIGGER_KEY) { const baseModel = this.recordData && this.recordData.mailing_model_real ? this.recordData.mailing_model_real : undefined; if (baseModel) { this.openDynamicPlaceholder(baseModel); } } - this._updateSMSInfo(); - }, -}); - -fieldRegistry.add('sms_widget', SmsWidget); - -export default SmsWidget; + } +}; +SmsWidget.template = 'sms.SmsWidget'; +SmsWidget.additionalClasses = [...(EmojisTextField.additionalClasses || []), 'o_field_text']; +patch(SmsWidget.prototype, 'sms_widget_dynamic_placeholder_field_mixin', DynamicPlaceholderFieldMixin); +registry.category("fields").add("sms_widget", SmsWidget); diff --git a/addons/sms/static/src/components/sms_widget/fields_sms_widget.xml b/addons/sms/static/src/components/sms_widget/fields_sms_widget.xml new file mode 100644 index 00000000000..e3fd7625570 --- /dev/null +++ b/addons/sms/static/src/components/sms_widget/fields_sms_widget.xml @@ -0,0 +1,18 @@ + + + + + + onBlur + + + + + + diff --git a/addons/sms/static/tests/sms_widget_test.js b/addons/sms/static/tests/sms_widget_test.js index 9d8d2c3001a..008d76de2e0 100644 --- a/addons/sms/static/tests/sms_widget_test.js +++ b/addons/sms/static/tests/sms_widget_test.js @@ -3,8 +3,10 @@ import FormView from 'web.FormView'; import ListView from 'web.ListView'; import testUtils from 'web.test_utils'; +import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; +import { getFixture } from "@web/../tests/helpers/utils"; -var createView = testUtils.createView; +const createView = testUtils.createView; QUnit.module('fields', { beforeEach: function () { @@ -36,6 +38,8 @@ QUnit.module('fields', { }] }, }; + setupViewRegistries(); + this.target = getFixture(); } }, function () { @@ -43,84 +47,79 @@ QUnit.module('fields', { QUnit.test('Sms widgets are correctly rendered', async function (assert) { assert.expect(9); - var form = await createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: '
', + await makeView({ + type: "form", + resModel: "partner", + serverData: { models: this.data }, + arch: /* xml */ `
`, }); - assert.containsOnce(form, '.o_sms_count', "Should have a sms counter"); - assert.strictEqual(form.$('.o_sms_count').text(), '0 characters, fits in 0 SMS (GSM7) ', + assert.containsOnce(this.target, '.o_sms_count', "Should have a sms counter"); + assert.strictEqual(this.target.querySelector('.o_sms_count').textContent, '0 characters, fits in 0 SMS (GSM7) ', 'Should be "0 characters, fits in 0 SMS (GSM7) " by default'); // GSM-7 - await testUtils.fields.editAndTrigger(form.$('.o_input'), "Hello from Odoo", 'input'); - assert.strictEqual(form.$('.o_sms_count').text(), '15 characters, fits in 1 SMS (GSM7) ', + await testUtils.fields.editAndTrigger(this.target.querySelector('.o_input'), "Hello from Odoo", 'input'); + assert.strictEqual(this.target.querySelector('.o_sms_count').textContent, '15 characters, fits in 1 SMS (GSM7) ', 'Should be "15 characters, fits in 1 SMS (GSM7) " for "Hello from Odoo"'); // GSM-7 with \n => this one count as 2 characters - form.$('.o_input').val("Hello from Odoo\n").trigger('input'); - assert.strictEqual(form.$('.o_sms_count').text(), '17 characters, fits in 1 SMS (GSM7) ', + await testUtils.fields.editAndTrigger(this.target.querySelector('.o_input'), "Hello from Odoo\n", 'input'); + assert.strictEqual(this.target.querySelector('.o_sms_count').textContent, '17 characters, fits in 1 SMS (GSM7) ', 'Should be "17 characters, fits in 1 SMS (GSM7) " for "Hello from Odoo\\n"'); // Unicode => ê - form.$('.o_input').val("Hêllo from Odoo").trigger('input'); - assert.strictEqual(form.$('.o_sms_count').text(), '15 characters, fits in 1 SMS (UNICODE) ', + await testUtils.fields.editAndTrigger(this.target.querySelector('.o_input'), "Hêllo from Odoo", 'input'); + assert.strictEqual(this.target.querySelector('.o_sms_count').textContent, '15 characters, fits in 1 SMS (UNICODE) ', 'Should be "15 characters, fits in 1 SMS (UNICODE) " for "Hêllo from Odoo"'); // GSM-7 with 160c var text = Array(161).join('a'); - await testUtils.fields.editAndTrigger(form.$('.o_input'), text, 'input'); - assert.strictEqual(form.$('.o_sms_count').text(), '160 characters, fits in 1 SMS (GSM7) ', + await testUtils.fields.editAndTrigger(this.target.querySelector('.o_input'), text, 'input'); + assert.strictEqual(this.target.querySelector('.o_sms_count').textContent, '160 characters, fits in 1 SMS (GSM7) ', 'Should be "160 characters, fits in 1 SMS (GSM7) " for 160 x "a"'); // GSM-7 with 161c text = Array(162).join('a'); - await testUtils.fields.editAndTrigger(form.$('.o_input'), text, 'input'); - assert.strictEqual(form.$('.o_sms_count').text(), '161 characters, fits in 2 SMS (GSM7) ', + await testUtils.fields.editAndTrigger(this.target.querySelector('.o_input'), text, 'input'); + assert.strictEqual(this.target.querySelector('.o_sms_count').textContent, '161 characters, fits in 2 SMS (GSM7) ', 'Should be "161 characters, fits in 2 SMS (GSM7) " for 161 x "a"'); // Unicode with 70c text = Array(71).join('ê'); - await testUtils.fields.editAndTrigger(form.$('.o_input'), text, 'input'); - assert.strictEqual(form.$('.o_sms_count').text(), '70 characters, fits in 1 SMS (UNICODE) ', + await testUtils.fields.editAndTrigger(this.target.querySelector('.o_input'), text, 'input'); + assert.strictEqual(this.target.querySelector('.o_sms_count').textContent, '70 characters, fits in 1 SMS (UNICODE) ', 'Should be "70 characters, fits in 1 SMS (UNICODE) " for 70 x "ê"'); // Unicode with 71c text = Array(72).join('ê'); - await testUtils.fields.editAndTrigger(form.$('.o_input'), text, 'input'); - assert.strictEqual(form.$('.o_sms_count').text(), '71 characters, fits in 2 SMS (UNICODE) ', + await testUtils.fields.editAndTrigger(this.target.querySelector('.o_input'), text, 'input'); + assert.strictEqual(this.target.querySelector('.o_sms_count').textContent, '71 characters, fits in 2 SMS (UNICODE) ', 'Should be "71 characters, fits in 2 SMS (UNICODE) " for 71 x "ê"'); - form.destroy(); }); QUnit.test('Sms widgets with non-empty initial value', async function (assert) { assert.expect(1); - var form = await createView({ - View: FormView, - model: 'visitor', - data: this.data, - arch: `
`, - res_id: 1, - res_ids: [1], + await makeView({ + type: "form", + resModel: "visitor", + resId: 1, + serverData: { models: this.data }, + arch: /* xml */ `
`, }); - assert.strictEqual(form.$('.o_field_text').text(), '+32494444444', + assert.strictEqual(this.target.querySelector('.o_field_text span').textContent, '+32494444444', 'Should have the initial value'); - form.destroy(); }); QUnit.test('Sms widgets with empty initial value', async function (assert) { assert.expect(1); - var form = await createView({ - View: FormView, - model: 'partner', - data: this.data, - arch: `
`, - res_id: 1, - res_ids: [1], + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + serverData: { models: this.data }, + arch: /* xml */ `
`, }); - assert.strictEqual(form.$('.o_field_text').text(), '', + assert.strictEqual(this.target.querySelector('.o_field_text span').textContent, '', 'Should have the empty initial value'); - form.destroy(); }); QUnit.module('PhoneWidget');