From 3d6d9802bfc93a9eadbf83df48bf73c4bb89606d Mon Sep 17 00:00:00 2001 From: EliasKeller Date: Mon, 18 Aug 2025 13:09:24 +0200 Subject: [PATCH 01/37] [T2586]: basic regex validation phone nubmer in my2_new_sponsorship_wizard.js --- .../src/js/my2_new_sponsorship_wizard.js | 25 +++++++++++++++++++ .../pages/my2_new_sponsorship_wizard.xml | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/my_compassion/static/src/js/my2_new_sponsorship_wizard.js b/my_compassion/static/src/js/my2_new_sponsorship_wizard.js index 0b04e4b2d..490ee765a 100644 --- a/my_compassion/static/src/js/my2_new_sponsorship_wizard.js +++ b/my_compassion/static/src/js/my2_new_sponsorship_wizard.js @@ -103,6 +103,31 @@ document.addEventListener("DOMContentLoaded", function (event) { } } }); + + this.$("input[phone_number]:visible").each(function () { + var $input = $(this); + if ($input.hasClass("is-invalid")) { + return; + } + + var phoneRegex = /^\+?(\d[\d\s-]{5,}\d)$/; + + if (!phoneRegex.test($input.val())) { + isValid = false; + $input.addClass("is-invalid"); + + var $errorHint = $( + '
Please enter a valid phone number.
' + ); + + var $select_container = $input.parent(".SelectComponent"); + if ($select_container.length > 0) { + $select_container.before($errorHint); + } else { + $input.before($errorHint); + } + } + }); return isValid; }, diff --git a/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml b/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml index 7dcd8c8ba..1b1db1cd4 100644 --- a/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml +++ b/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml @@ -131,7 +131,7 @@ Page: My Compassion 2 New Sponsorship Wizard Page - +
From cb65fcb56934465a09927dad83944fa0738727de Mon Sep 17 00:00:00 2001 From: EliasKeller Date: Mon, 18 Aug 2025 15:21:52 +0200 Subject: [PATCH 02/37] [T2586]: basic regex validation email in my2_new_sponsorship_wizard.js --- .../src/js/my2_new_sponsorship_wizard.js | 26 +++++++++++++++++++ .../pages/my2_new_sponsorship_wizard.xml | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/my_compassion/static/src/js/my2_new_sponsorship_wizard.js b/my_compassion/static/src/js/my2_new_sponsorship_wizard.js index 490ee765a..5c61460b9 100644 --- a/my_compassion/static/src/js/my2_new_sponsorship_wizard.js +++ b/my_compassion/static/src/js/my2_new_sponsorship_wizard.js @@ -128,6 +128,32 @@ document.addEventListener("DOMContentLoaded", function (event) { } } }); + + this.$("input[email]:visible").each(function () { + var $input = $(this); + if ($input.hasClass("is-invalid")) { + return; + } + + var phoneRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + + if (!phoneRegex.test($input.val())) { + isValid = false; + $input.addClass("is-invalid"); + + var $errorHint = $( + '
Please enter a valid email address.
' + ); + + var $select_container = $input.parent(".SelectComponent"); + if ($select_container.length > 0) { + $select_container.before($errorHint); + } else { + $input.before($errorHint); + } + } + }); + return isValid; }, diff --git a/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml b/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml index 1b1db1cd4..0fbfda5ab 100644 --- a/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml +++ b/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml @@ -124,7 +124,7 @@ Page: My Compassion 2 New Sponsorship Wizard Page - + From 615847388f978d4fc4cd1688a7519b2c46dcc7fc Mon Sep 17 00:00:00 2001 From: EliasKeller Date: Mon, 18 Aug 2025 16:07:48 +0200 Subject: [PATCH 03/37] [T2586] FEAT: add event (onBlur) binding for the email and phone number in my2_new_sponsorship_wizard.js --- .../src/js/my2_new_sponsorship_wizard.js | 124 ++++++++++++------ 1 file changed, 84 insertions(+), 40 deletions(-) diff --git a/my_compassion/static/src/js/my2_new_sponsorship_wizard.js b/my_compassion/static/src/js/my2_new_sponsorship_wizard.js index 5c61460b9..92d741f45 100644 --- a/my_compassion/static/src/js/my2_new_sponsorship_wizard.js +++ b/my_compassion/static/src/js/my2_new_sponsorship_wizard.js @@ -17,8 +17,14 @@ document.addEventListener("DOMContentLoaded", function (event) { selector: ".new-sponsorship-wizard-form", events: { "click .btn-next, .btn-previous, .btn-finish": "_onStepClick", + "blur input[email]:visible": "_onEmailBlur", + "blur input[phone_number]:visible": "_onPhoneNumberBlur", }, + // ==================================================================== + // Event Handlers + // ==================================================================== + /** * Handles the click on "Next" or "Previous" buttons. * @param {Event} ev @@ -70,6 +76,24 @@ document.addEventListener("DOMContentLoaded", function (event) { ); }, + /** + * Event handler for when the email input loses focus. + */ + _onEmailBlur: function (ev) { + this._validateEmail($(ev.currentTarget)); + }, + + /** + * Event handler for when the phone input loses focus. + */ + _onPhoneNumberBlur: function (ev) { + this._validatePhoneNumber($(ev.currentTarget)); + }, + + // ==================================================================== + // Validation Logic + // ==================================================================== + /** * Validates required fields in the current step. * @returns {boolean} - True if valid, false otherwise. @@ -104,59 +128,79 @@ document.addEventListener("DOMContentLoaded", function (event) { } }); - this.$("input[phone_number]:visible").each(function () { - var $input = $(this); - if ($input.hasClass("is-invalid")) { - return; + // validate email fields + this.$("input[email]:visible").each(function (i, el) { + if (!this._validateEmail($(el))) { + isValid = false; } + }.bind(this)); - var phoneRegex = /^\+?(\d[\d\s-]{5,}\d)$/; - - if (!phoneRegex.test($input.val())) { + // validate phone number fields + this.$("input[phone_number]:visible").each(function (i, el) { + if (!this._validatePhoneNumber($(el))) { isValid = false; - $input.addClass("is-invalid"); + } + }.bind(this)); - var $errorHint = $( - '
Please enter a valid phone number.
' - ); + return isValid; + }, - var $select_container = $input.parent(".SelectComponent"); - if ($select_container.length > 0) { - $select_container.before($errorHint); - } else { - $input.before($errorHint); - } - } - }); + /** + * Validates a single email field for format. + * @param {jQuery} $input - The jQuery object for the input field. + * @returns {boolean} + */ + _validateEmail: function($input) { + if (!$input.val()) { + return true; + } - this.$("input[email]:visible").each(function () { - var $input = $(this); - if ($input.hasClass("is-invalid")) { - return; - } + var emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; - var phoneRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + $input.siblings('.input-invalid-hint').remove(); + $input.removeClass('is-invalid'); - if (!phoneRegex.test($input.val())) { - isValid = false; - $input.addClass("is-invalid"); + if (!emailRegex.test($input.val())) { + $input.addClass("is-invalid"); + var $errorHint = $( + '
Please enter a valid email address.
' + ); + $input.before($errorHint); + return false; + } + return true; + }, - var $errorHint = $( - '
Please enter a valid email address.
' - ); + /** + * Validates a single phone number field for format. + * @param {jQuery} $input - The jQuery object for the input field. + * @returns {boolean} + */ + _validatePhoneNumber: function($input) { + if (!$input.val()) { + return true; + } - var $select_container = $input.parent(".SelectComponent"); - if ($select_container.length > 0) { - $select_container.before($errorHint); - } else { - $input.before($errorHint); - } - } - }); + var phoneRegex = /^\+?(\d[\d\s-]{5,}\d)$/; - return isValid; + $input.siblings('.input-invalid-hint').remove(); + $input.removeClass('is-invalid'); + + if (!phoneRegex.test($input.val())) { + $input.addClass("is-invalid"); + var $errorHint = $( + '
Please enter a valid phone number.
' + ); + $input.before($errorHint); + return false; + } + return true; }, + // ==================================================================== + // Helper Functions + // ==================================================================== + /** * Helper to convert form data array to a key-value object. * @param {Array} formData From 5da7fec880d91770cd7471984d65db26c0086402 Mon Sep 17 00:00:00 2001 From: EliasKeller Date: Tue, 19 Aug 2025 10:25:21 +0200 Subject: [PATCH 04/37] [T2586] FEAT: started to build a centralized field validation in utils/form_field_validator.js --- .../src/js/my2_new_sponsorship_wizard.js | 43 ++++++--- .../pages/my2_new_sponsorship_wizard.xml | 6 +- .../static/src/utils/form_field_validator.js | 94 +++++++++++++++++++ .../templates/components/FormField.xml | 2 +- theme_compassion_2025/views/assets.xml | 1 + 5 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 theme_compassion_2025/static/src/utils/form_field_validator.js diff --git a/my_compassion/static/src/js/my2_new_sponsorship_wizard.js b/my_compassion/static/src/js/my2_new_sponsorship_wizard.js index 92d741f45..8ff15b3a9 100644 --- a/my_compassion/static/src/js/my2_new_sponsorship_wizard.js +++ b/my_compassion/static/src/js/my2_new_sponsorship_wizard.js @@ -98,7 +98,23 @@ document.addEventListener("DOMContentLoaded", function (event) { * Validates required fields in the current step. * @returns {boolean} - True if valid, false otherwise. */ - _validateForm: function () { + _validateForm: function () { + var isValid = true; + + // Finde alle FormField-Komponenten im aktuellen Schritt + this.$(".form-field-component:visible").each(function () { + // Odoo hängt die Widget-Instanz an die DOM-Element-Daten an + var fieldWidget = $(this).data("widget"); + + // Rufe die öffentliche validate() Methode unseres neuen Widgets auf + if (fieldWidget && !fieldWidget.validate()) { + isValid = false; + } + }); + + return isValid; + } + /*_validateForm: function () { var isValid = true; // Remove previous error messages and styles this.$(".input-invalid-hint").remove(); @@ -126,21 +142,20 @@ document.addEventListener("DOMContentLoaded", function (event) { $input.before($errorHint); } } - }); + });*/ - // validate email fields - this.$("input[email]:visible").each(function (i, el) { - if (!this._validateEmail($(el))) { - isValid = false; - } - }.bind(this)); + var fieldsToFurtherValidate = [ + {selector: "input[email]:visible", validator: this._validateEmail}, + {selector: "input[phone_number]:visible", validator: this._validatePhoneNumber} + ]; - // validate phone number fields - this.$("input[phone_number]:visible").each(function (i, el) { - if (!this._validatePhoneNumber($(el))) { - isValid = false; - } - }.bind(this)); + fieldsToFurtherValidate.forEach(function (fieldType) { + this.$(fieldType.selector).each(function (i, el) { + if (!fieldType.validator.call(this, $(el))) { + isValid = false; + } + }.bind(this)); + }, this); return isValid; }, diff --git a/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml b/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml index 0fbfda5ab..268b5d551 100644 --- a/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml +++ b/my_compassion/templates/pages/my2_new_sponsorship_wizard.xml @@ -131,7 +131,11 @@ Page: My Compassion 2 New Sponsorship Wizard Page - +
diff --git a/theme_compassion_2025/static/src/utils/form_field_validator.js b/theme_compassion_2025/static/src/utils/form_field_validator.js new file mode 100644 index 000000000..3c061e16f --- /dev/null +++ b/theme_compassion_2025/static/src/utils/form_field_validator.js @@ -0,0 +1,94 @@ +odoo.define("my_compassion.form_field_validator", function (require) { + "use strict"; + + var publicWidget = require("web.public.widget"); + console.log("RangeInput component loaded"); + + publicWidget.registry.FormFieldValidator = publicWidget.Widget.extend({ + selector: ".form-field-component", + events: { + "blur input, input select": "_onBlur", + }, + + init: function () { + this._super.apply(this, arguments); + }, + + /** + * Die start-Methode wird aufgerufen, nachdem das DOM-Element des Widgets + * gerendert und verfügbar ist. Dies ist der richtige Ort für DOM-Manipulationen. + */ + start: function () { + this._super.apply(this, arguments); + this.$input = this.$("input, select"); + this.validationType = this.$input.data("validate-type"); + + this.errorMessages = { + required: this.$input.data("error-required") || "This field is required.", + format: this.$input.data("error-format") || "Invalid format.", + }; + + this.regex = { + email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, + phone: /^\+?(\d[\d\s-]{5,}\d)$/, + }; + }, + /** + * Validiert das Feld, wenn der Fokus verlassen wird. + */ + _onBlur: function () { + this.validate(); + }, + + /** + * Die zentrale Validierungsfunktion. Kann auch von aussen aufgerufen werden. + * @returns {boolean} + */ + validate: function () { + this._clearError(); + var value = this.$input.val(); + + // 1. Required-Prüfung + if (this.$input.prop("required") && !value) { + this._showError(this.errorMessages.required); + return false; + } + + // 2. Format-Prüfung (nur wenn ein Wert vorhanden ist) + if (this.validationType && value && this.regex[this.validationType]) { + if (!this.regex[this.validationType].test(value)) { + this._showError(this.errorMessages.format); + return false; + } + } + + this.$input.removeClass("is-invalid"); + return true; + }, + + /** + * Zeigt eine Fehlermeldung an. + */ + _showError: function (message) { + this.$input.addClass("is-invalid"); + var $errorHint = $('
').text(message); + + var $selectContainer = this.$input.closest(".SelectComponent"); + if ($selectContainer.length > 0) { + $selectContainer.before($errorHint); + } else { + this.$input.before($errorHint); + } + }, + + /** + * Entfernt bestehende Fehlermeldungen. + */ + _clearError: function () { + this.$el.find(".input-invalid-hint").remove(); + this.$input.removeClass("is-invalid"); + }, + }); + + return publicWidget.registry.FormFieldValidator; +}); \ No newline at end of file diff --git a/theme_compassion_2025/templates/components/FormField.xml b/theme_compassion_2025/templates/components/FormField.xml index 7087bd632..4d2341fde 100644 --- a/theme_compassion_2025/templates/components/FormField.xml +++ b/theme_compassion_2025/templates/components/FormField.xml @@ -12,7 +12,7 @@ -->