diff --git a/app/assets/javascripts/validation.js b/app/assets/javascripts/validation.js index bd556f0c4..f7bb4e6a9 100644 --- a/app/assets/javascripts/validation.js +++ b/app/assets/javascripts/validation.js @@ -7,13 +7,17 @@ function showError(input, errorElement, message) { errorElement.textContent = message; }, 10); - input.classList.add("usa-input--error"); + if (input.type !== "radio" && input.type !== "checkbox") { + input.classList.add("usa-input--error"); + } input.setAttribute("aria-describedby", errorElement.id); } function hideError(input, errorElement) { errorElement.style.display = "none"; - input.classList.remove("usa-input--error"); + if (input.type !== "radio" && input.type !== "checkbox") { + input.classList.remove("usa-input--error"); + } input.removeAttribute("aria-describedby"); } @@ -24,13 +28,14 @@ function getFieldLabel(input) { // Attach validation logic to forms function attachValidation() { - const forms = document.querySelectorAll("form.send-one-off-form"); + const forms = document.querySelectorAll('form[data-force-focus="True"]'); forms.forEach((form) => { const inputs = form.querySelectorAll("input, textarea, select"); form.addEventListener("submit", function (event) { let isValid = true; let firstInvalidInput = null; + const validatedRadioNames = new Set(); inputs.forEach((input) => { const errorId = input.id ? `${input.id}-error` : `${input.name}-error`; @@ -41,16 +46,25 @@ function attachValidation() { errorElement.id = errorId; errorElement.classList.add("usa-error-message"); errorElement.setAttribute("aria-live", "polite"); - input.insertAdjacentElement("afterend", errorElement); - } + if (input.type === "radio") { + const group = form.querySelectorAll(`input[name="${input.name}"]`); + const lastRadio = group[group.length - 1]; + lastRadio.parentElement.insertAdjacentElement("afterend", errorElement); + } else { + input.insertAdjacentElement("afterend", errorElement); + } } if (input.type === "radio") { + if (validatedRadioNames.has(input.name)) { + return; // already validated this group + } + validatedRadioNames.add(input.name); // Find all radio buttons with the same name const radioGroup = document.querySelectorAll(`input[name="${input.name}"]`); const isChecked = Array.from(radioGroup).some(radio => radio.checked); if (!isChecked) { - showError(input, errorElement, `Error: ${getFieldLabel(input)} must be selected.`); + showError(input, errorElement, `Error: A selection must be made.`); isValid = false; if (!firstInvalidInput) { firstInvalidInput = input; @@ -73,11 +87,21 @@ function attachValidation() { inputs.forEach((input) => { input.addEventListener("input", function () { - const errorElement = document.getElementById(`${input.id}-error`); - if (input.value.trim() !== "" && errorElement) { + const errorElement = document.getElementById(input.id ? `${input.id}-error` : `${input.name}-error`); + if (errorElement && input.value.trim() !== "") { hideError(input, errorElement); } }); + + if (input.type === "radio") { + input.addEventListener("change", function () { + console.log(`Radio ${input.id} changed`); + + const groupError = document.getElementById(`${input.name}-error`) + || document.getElementById(`${input.id}-error`); + if (groupError) hideError(input, groupError); + }); + } }); }); } diff --git a/app/main/forms.py b/app/main/forms.py index 90a4409fc..c39e9afb9 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -781,6 +781,9 @@ def govuk_radios_field_widget(self, field, param_extensions=None, **kwargs): # returns either a list or a hierarchy of lists # depending on how get_items_from_options is implemented items = self.get_items_from_options(field) + is_field_required = False + if getattr(field, 'flags', None) and 'required' in field.flags: + is_field_required = True params = { "name": field.name, @@ -793,6 +796,8 @@ def govuk_radios_field_widget(self, field, param_extensions=None, **kwargs): }, "errorMessage": error_message, "items": items, + "required": is_field_required, + } # extend default params with any sent in during instantiation diff --git a/app/templates/components/components/radios/template.njk b/app/templates/components/components/radios/template.njk index ef1054c5f..234a7a4ca 100644 --- a/app/templates/components/components/radios/template.njk +++ b/app/templates/components/components/radios/template.njk @@ -56,7 +56,6 @@ {% set itemHintId = id + '-item-hint' %}
{{ caller() }} diff --git a/app/templates/views/add-service.html b/app/templates/views/add-service.html index d0f4f98d9..ca37aa520 100644 --- a/app/templates/views/add-service.html +++ b/app/templates/views/add-service.html @@ -14,7 +14,7 @@ {{ page_header('About your service') }} - {% call form_wrapper() %} + {% call form_wrapper(data_force_focus=True) %} {{ form.name(param_extensions={"hint": {"text": "You can change this later"}}) }} diff --git a/app/templates/views/edit-sms-template.html b/app/templates/views/edit-sms-template.html index fdc15af5d..87dba61e4 100644 --- a/app/templates/views/edit-sms-template.html +++ b/app/templates/views/edit-sms-template.html @@ -27,7 +27,7 @@ {% set content_hint = 'Your service name will be added to the start of your message. You can turn this off in Settings.' %} {% endif %} - {% call form_wrapper() %} + {% call form_wrapper(data_force_focus=True) %}
{{ form.name(param_extensions={ diff --git a/app/templates/views/send-test.html b/app/templates/views/send-test.html index faa71873b..3f1161f6b 100644 --- a/app/templates/views/send-test.html +++ b/app/templates/views/send-test.html @@ -34,7 +34,8 @@ {% call form_wrapper( class='js-stick-at-top-when-scrolling send-one-off-form' if template.template_type != 'sms' else 'send-one-off-form', module="autofocus", - data_kwargs={'force-focus': True} + data_kwargs={'force-focus': True}, + data_force_focus=True ) %}
{% set extra_class = "extra-tracking" if form.placeholder_value.label.text == "phone number" else "" %} diff --git a/app/templates/views/service-settings/link-service-to-organization.html b/app/templates/views/service-settings/link-service-to-organization.html index 89ff79f5a..222c3951f 100644 --- a/app/templates/views/service-settings/link-service-to-organization.html +++ b/app/templates/views/service-settings/link-service-to-organization.html @@ -24,7 +24,7 @@ label='Search by name', autofocus=True ) }} - {% call form_wrapper() %} + {% call form_wrapper(data_force_focus=True) %} {% if has_organizations %} {{ form.organizations }} {{ sticky_page_footer('Save') }} diff --git a/app/templates/views/service-settings/set-international-sms.html b/app/templates/views/service-settings/set-international-sms.html index 7475315ef..69ef92efa 100644 --- a/app/templates/views/service-settings/set-international-sms.html +++ b/app/templates/views/service-settings/set-international-sms.html @@ -24,7 +24,7 @@ See pricing for the list of rates.

- {% call form_wrapper() %} + {% call form_wrapper(data_force_focus=True) %} {{ form.enabled }} {{ page_footer('Save') }} {% endcall %} diff --git a/app/templates/views/service-settings/set-sms.html b/app/templates/views/service-settings/set-sms.html index a48fb68b6..7f27786ce 100644 --- a/app/templates/views/service-settings/set-sms.html +++ b/app/templates/views/service-settings/set-sms.html @@ -20,12 +20,7 @@

You may send up to 250,000 text messages during the pilot period.

- - {% call form_wrapper() %} + {% call form_wrapper(data_force_focus=True) %} {{ form.enabled }} {{ page_footer('Save') }} {% endcall %} diff --git a/app/templates/views/service-settings/sms-prefix.html b/app/templates/views/service-settings/sms-prefix.html index 266278d2d..4a31ab384 100644 --- a/app/templates/views/service-settings/sms-prefix.html +++ b/app/templates/views/service-settings/sms-prefix.html @@ -16,7 +16,7 @@ {{ page_header('Start text messages with service name') }} - {% call form_wrapper() %} + {% call form_wrapper(data_force_focus=True) %} {{ form.enabled }} {{ page_footer('Save') }} {% endcall %} diff --git a/app/templates/views/service-settings/sms-sender/add.html b/app/templates/views/service-settings/sms-sender/add.html index c310ee9cf..c97e42e2d 100644 --- a/app/templates/views/service-settings/sms-sender/add.html +++ b/app/templates/views/service-settings/sms-sender/add.html @@ -16,7 +16,7 @@ {{ page_header('Add text message sender') }} - {% call form_wrapper() %} + {% call form_wrapper(data_force_focus=True) %} {{ form.sms_sender(param_extensions={ "hint": {"text": "Up to 11 characters, letters, numbers and spaces only"} }) }} diff --git a/app/templates/views/service-settings/sms-sender/edit.html b/app/templates/views/service-settings/sms-sender/edit.html index 85bc352c9..9b7556cb4 100644 --- a/app/templates/views/service-settings/sms-sender/edit.html +++ b/app/templates/views/service-settings/sms-sender/edit.html @@ -18,7 +18,7 @@ {% block maincolumn_content %} {{ page_header('Change text message sender') }} - {% call form_wrapper() %} + {% call form_wrapper(data_force_focus=True) %} {% if inbound_number %}

{{ sms_sender.sms_sender }}