fixed validation for radio and text input fields

This commit is contained in:
Beverly Nguyen
2025-03-21 18:53:49 -07:00
parent 78ee70b0c1
commit 3a38b8bf31
13 changed files with 50 additions and 24 deletions

View File

@@ -7,13 +7,17 @@ function showError(input, errorElement, message) {
errorElement.textContent = message; errorElement.textContent = message;
}, 10); }, 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); input.setAttribute("aria-describedby", errorElement.id);
} }
function hideError(input, errorElement) { function hideError(input, errorElement) {
errorElement.style.display = "none"; 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"); input.removeAttribute("aria-describedby");
} }
@@ -24,13 +28,14 @@ function getFieldLabel(input) {
// Attach validation logic to forms // Attach validation logic to forms
function attachValidation() { function attachValidation() {
const forms = document.querySelectorAll("form.send-one-off-form"); const forms = document.querySelectorAll('form[data-force-focus="True"]');
forms.forEach((form) => { forms.forEach((form) => {
const inputs = form.querySelectorAll("input, textarea, select"); const inputs = form.querySelectorAll("input, textarea, select");
form.addEventListener("submit", function (event) { form.addEventListener("submit", function (event) {
let isValid = true; let isValid = true;
let firstInvalidInput = null; let firstInvalidInput = null;
const validatedRadioNames = new Set();
inputs.forEach((input) => { inputs.forEach((input) => {
const errorId = input.id ? `${input.id}-error` : `${input.name}-error`; const errorId = input.id ? `${input.id}-error` : `${input.name}-error`;
@@ -41,16 +46,25 @@ function attachValidation() {
errorElement.id = errorId; errorElement.id = errorId;
errorElement.classList.add("usa-error-message"); errorElement.classList.add("usa-error-message");
errorElement.setAttribute("aria-live", "polite"); 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 (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 // Find all radio buttons with the same name
const radioGroup = document.querySelectorAll(`input[name="${input.name}"]`); const radioGroup = document.querySelectorAll(`input[name="${input.name}"]`);
const isChecked = Array.from(radioGroup).some(radio => radio.checked); const isChecked = Array.from(radioGroup).some(radio => radio.checked);
if (!isChecked) { if (!isChecked) {
showError(input, errorElement, `Error: ${getFieldLabel(input)} must be selected.`); showError(input, errorElement, `Error: A selection must be made.`);
isValid = false; isValid = false;
if (!firstInvalidInput) { if (!firstInvalidInput) {
firstInvalidInput = input; firstInvalidInput = input;
@@ -73,11 +87,21 @@ function attachValidation() {
inputs.forEach((input) => { inputs.forEach((input) => {
input.addEventListener("input", function () { input.addEventListener("input", function () {
const errorElement = document.getElementById(`${input.id}-error`); const errorElement = document.getElementById(input.id ? `${input.id}-error` : `${input.name}-error`);
if (input.value.trim() !== "" && errorElement) { if (errorElement && input.value.trim() !== "") {
hideError(input, errorElement); 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);
});
}
}); });
}); });
} }

View File

@@ -781,6 +781,9 @@ def govuk_radios_field_widget(self, field, param_extensions=None, **kwargs):
# returns either a list or a hierarchy of lists # returns either a list or a hierarchy of lists
# depending on how get_items_from_options is implemented # depending on how get_items_from_options is implemented
items = self.get_items_from_options(field) 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 = { params = {
"name": field.name, "name": field.name,
@@ -793,6 +796,8 @@ def govuk_radios_field_widget(self, field, param_extensions=None, **kwargs):
}, },
"errorMessage": error_message, "errorMessage": error_message,
"items": items, "items": items,
"required": is_field_required,
} }
# extend default params with any sent in during instantiation # extend default params with any sent in during instantiation

View File

@@ -56,7 +56,6 @@
{% set itemHintId = id + '-item-hint' %} {% set itemHintId = id + '-item-hint' %}
<div class="usa-radio"> <div class="usa-radio">
<input class="usa-radio__input" id="{{ id }}" name="{{ params.name }}" type="radio" value="{{ item.value }}" <input class="usa-radio__input" id="{{ id }}" name="{{ params.name }}" type="radio" value="{{ item.value }}"
{{-" checked" if item.checked }}
{{-" disabled" if item.disabled }} {{-" disabled" if item.disabled }}
{%- if item.conditional %} data-aria-controls="{{ conditionalId }}"{% endif -%} {%- if item.conditional %} data-aria-controls="{{ conditionalId }}"{% endif -%}
{%- if hasHint %} aria-describedby="{{ itemHintId }}"{% endif -%} {%- if hasHint %} aria-describedby="{{ itemHintId }}"{% endif -%}

View File

@@ -5,7 +5,8 @@
class=None, class=None,
id=None, id=None,
module=None, module=None,
data_kwargs={} data_kwargs={},
data_force_focus=False
) %} ) %}
<form <form
method="{{ method }}" method="{{ method }}"
@@ -19,6 +20,7 @@
data-{{ key }}="{{ val }}" data-{{ key }}="{{ val }}"
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data_force_focus %}data-force-focus="{{ data_force_focus }}"{% endif %}
novalidate novalidate
> >
{{ caller() }} {{ caller() }}

View File

@@ -14,7 +14,7 @@
{{ page_header('About your service') }} {{ 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"}}) }} {{ form.name(param_extensions={"hint": {"text": "You can change this later"}}) }}

View File

@@ -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.' %} {% set content_hint = 'Your service name will be added to the start of your message. You can turn this off in Settings.' %}
{% endif %} {% endif %}
{% call form_wrapper() %} {% call form_wrapper(data_force_focus=True) %}
<div class="grid-container padding-0"> <div class="grid-container padding-0">
<div class="tablet:grid-col-9 mobile-lg:grid-col-12"> <div class="tablet:grid-col-9 mobile-lg:grid-col-12">
{{ form.name(param_extensions={ {{ form.name(param_extensions={

View File

@@ -34,7 +34,8 @@
{% call form_wrapper( {% call form_wrapper(
class='js-stick-at-top-when-scrolling send-one-off-form' if template.template_type != 'sms' else 'send-one-off-form', class='js-stick-at-top-when-scrolling send-one-off-form' if template.template_type != 'sms' else 'send-one-off-form',
module="autofocus", module="autofocus",
data_kwargs={'force-focus': True} data_kwargs={'force-focus': True},
data_force_focus=True
) %} ) %}
<div class="grid-row"> <div class="grid-row">
{% set extra_class = "extra-tracking" if form.placeholder_value.label.text == "phone number" else "" %} {% set extra_class = "extra-tracking" if form.placeholder_value.label.text == "phone number" else "" %}

View File

@@ -24,7 +24,7 @@
label='Search by name', label='Search by name',
autofocus=True autofocus=True
) }} ) }}
{% call form_wrapper() %} {% call form_wrapper(data_force_focus=True) %}
{% if has_organizations %} {% if has_organizations %}
{{ form.organizations }} {{ form.organizations }}
{{ sticky_page_footer('Save') }} {{ sticky_page_footer('Save') }}

View File

@@ -24,7 +24,7 @@
See <a class="usa-link" href="{{ url_for(".pricing") }}">pricing</a> for the list See <a class="usa-link" href="{{ url_for(".pricing") }}">pricing</a> for the list
of rates. of rates.
</p> </p>
{% call form_wrapper() %} {% call form_wrapper(data_force_focus=True) %}
{{ form.enabled }} {{ form.enabled }}
{{ page_footer('Save') }} {{ page_footer('Save') }}
{% endcall %} {% endcall %}

View File

@@ -20,12 +20,7 @@
<p> <p>
You may send up to 250,000 text messages during the pilot period. You may send up to 250,000 text messages during the pilot period.
</p> </p>
<!--<p> {% call form_wrapper(data_force_focus=True) %}
You have a free allowance of
{{ '{:,}'.format(current_service.free_sms_fragment_limit) }} text messages each
financial year.
</p>-->
{% call form_wrapper() %}
{{ form.enabled }} {{ form.enabled }}
{{ page_footer('Save') }} {{ page_footer('Save') }}
{% endcall %} {% endcall %}

View File

@@ -16,7 +16,7 @@
{{ page_header('Start text messages with service name') }} {{ page_header('Start text messages with service name') }}
{% call form_wrapper() %} {% call form_wrapper(data_force_focus=True) %}
{{ form.enabled }} {{ form.enabled }}
{{ page_footer('Save') }} {{ page_footer('Save') }}
{% endcall %} {% endcall %}

View File

@@ -16,7 +16,7 @@
{{ page_header('Add text message sender') }} {{ page_header('Add text message sender') }}
{% call form_wrapper() %} {% call form_wrapper(data_force_focus=True) %}
{{ form.sms_sender(param_extensions={ {{ form.sms_sender(param_extensions={
"hint": {"text": "Up to 11 characters, letters, numbers and spaces only"} "hint": {"text": "Up to 11 characters, letters, numbers and spaces only"}
}) }} }) }}

View File

@@ -18,7 +18,7 @@
{% block maincolumn_content %} {% block maincolumn_content %}
{{ page_header('Change text message sender') }} {{ page_header('Change text message sender') }}
{% call form_wrapper() %} {% call form_wrapper(data_force_focus=True) %}
{% if inbound_number %} {% if inbound_number %}
<p> <p>
<span class="bottom-gutter-1-3"> {{ sms_sender.sms_sender }} </span> <span class="bottom-gutter-1-3"> {{ sms_sender.sms_sender }} </span>