diff --git a/benefits/core/templates/core/includes/form.html b/benefits/core/templates/core/includes/form.html index ed4ec3b08..cdf69abac 100644 --- a/benefits/core/templates/core/includes/form.html +++ b/benefits/core/templates/core/includes/form.html @@ -59,7 +59,29 @@ $("#{{ form.id }}").on("submit", function(e) { $(this).trigger("submitting"); }); - }); + + {% if form.use_custom_validity %} + const validate = function(input_element) { + input_element.setCustomValidity(""); // clearing message sets input_element.validity.customError back to false + + const valid = input_element.checkValidity(); + if (!valid) { + input_element.setCustomValidity(input_element.dataset.customValidity); + } + } + + $("button[type=submit]", "#{{ form.id }}").on("click", function(e) { + // revalidate all fields + $("[data-custom-validity]").each(function() { + let input_element = $(this)[0]; + if (input_element) { + validate(input_element); + } + }); + }); + + {% endif %} + }); {% if request.recaptcha %} diff --git a/benefits/eligibility/forms.py b/benefits/eligibility/forms.py index a7ff58dbe..d66ee506b 100644 --- a/benefits/eligibility/forms.py +++ b/benefits/eligibility/forms.py @@ -30,8 +30,11 @@ def __init__(self, agency: models.TransitAgency, *args, **kwargs): self.classes = "col-lg-8" # second element is not used since we render the whole label using selection_label_template, # therefore set to None - self.fields["verifier"].choices = [(v.id, None) for v in verifiers] - self.fields["verifier"].widget.selection_label_templates = {v.id: v.selection_label_template for v in verifiers} + verifier_field = self.fields["verifier"] + verifier_field.choices = [(v.id, None) for v in verifiers] + verifier_field.widget.selection_label_templates = {v.id: v.selection_label_template for v in verifiers} + verifier_field.widget.attrs.update({"data-custom-validity": _("Please choose a transit benefit.")}) + self.use_custom_validity = True def clean(self): if not recaptcha.verify(self.data): @@ -48,11 +51,6 @@ class EligibilityVerificationForm(forms.Form): submit_value = _("Find my record") submitting_value = _("Checking") - _error_messages = { - "invalid": _("Check your input. The format looks wrong."), - "missing": _("This field is required."), - } - def __init__( self, title, @@ -68,6 +66,8 @@ def __init__( sub_input_mode=None, sub_max_length=None, sub_pattern=None, + sub_custom_validity=None, + name_custom_validity=None, *args, **kwargs, ): @@ -117,6 +117,9 @@ def __init__( sub_widget.attrs.update({"inputmode": sub_input_mode}) if sub_max_length: sub_widget.attrs.update({"maxlength": sub_max_length}) + if sub_custom_validity: + sub_widget.attrs.update({"data-custom-validity": sub_custom_validity}) + self.use_custom_validity = True self.fields["sub"] = forms.CharField( label=sub_label, @@ -127,6 +130,9 @@ def __init__( name_widget = widgets.FormControlTextInput(placeholder=name_placeholder) if name_max_length: name_widget.attrs.update({"maxlength": name_max_length}) + if name_custom_validity: + name_widget.attrs.update({"data-custom-validity": name_custom_validity}) + self.use_custom_validity = True self.fields["name"] = forms.CharField(label=name_label, widget=name_widget, help_text=name_help_text) @@ -157,6 +163,8 @@ def __init__(self, *args, **kwargs): sub_input_mode="numeric", sub_max_length=5, sub_pattern=r"\d{5}", + sub_custom_validity=_("Please enter a 5-digit number."), + name_custom_validity=_("Please enter your last name."), *args, **kwargs, ) @@ -185,6 +193,8 @@ def __init__(self, *args, **kwargs): sub_input_mode="numeric", sub_max_length=4, sub_pattern=r"\d{4}", + sub_custom_validity=_("Please enter a 4-digit number."), + name_custom_validity=_("Please enter your last name."), *args, **kwargs, ) diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po index d2b96190b..ac7831352 100644 --- a/benefits/locale/en/LC_MESSAGES/django.po +++ b/benefits/locale/en/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n" -"POT-Creation-Date: 2024-04-22 12:32-0700\n" +"POT-Creation-Date: 2024-04-25 12:29-0700\n" "Language: English\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -303,16 +303,13 @@ msgstr "" msgid "Choose this benefit" msgstr "" -msgid "Find my record" -msgstr "" - -msgid "Checking" +msgid "Please choose a transit benefit." msgstr "" -msgid "Check your input. The format looks wrong." +msgid "Find my record" msgstr "" -msgid "This field is required." +msgid "Checking" msgstr "" msgid "Agency card information" @@ -340,6 +337,12 @@ msgstr "" msgid "This is a 5-digit number on the front and back of your card." msgstr "" +msgid "Please enter a 5-digit number." +msgstr "" + +msgid "Please enter your last name." +msgstr "" + msgid "" "We use the information on your SBMTD Reduced Fare Mobility ID card to find " "the record of your transit benefit in our system." @@ -351,6 +354,9 @@ msgstr "" msgid "This is a 4-digit number on the back of your card." msgstr "" +msgid "Please enter a 4-digit number." +msgstr "" + msgid "Your contactless card details" msgstr "" diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po index 3da76c2fd..dde67440d 100644 --- a/benefits/locale/es/LC_MESSAGES/django.po +++ b/benefits/locale/es/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n" -"POT-Creation-Date: 2024-04-22 12:32-0700\n" +"POT-Creation-Date: 2024-04-25 12:29-0700\n" "Language: Español\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -390,18 +390,15 @@ msgstr "¿A qué beneficio de tránsito le gustaría inscribirse?" msgid "Choose this benefit" msgstr "Elegir este beneficio" +msgid "Please choose a transit benefit." +msgstr "" + msgid "Find my record" msgstr "Comprobar elegibilidad" msgid "Checking" msgstr "Comprobando" -msgid "Check your input. The format looks wrong." -msgstr "Verifique su entrada. El formato parece incorrecto." - -msgid "This field is required." -msgstr "Este campo es requerido." - msgid "Agency card information" msgstr "Información de la tarjeta de agencia" @@ -427,6 +424,12 @@ msgstr "Número de tarjeta de cortesía de MST" msgid "This is a 5-digit number on the front and back of your card." msgstr "Este es un número de 5 dígitos en el anverso y reverso de su tarjeta." +msgid "Please enter a 5-digit number." +msgstr "" + +msgid "Please enter your last name." +msgstr "" + msgid "" "We use the information on your SBMTD Reduced Fare Mobility ID card to find " "the record of your transit benefit in our system." @@ -438,6 +441,9 @@ msgstr "Número de SBMTD Reduced Fare Mobility ID" msgid "This is a 4-digit number on the back of your card." msgstr "Este es un número de 4 dígitos en el reverso de su tarjeta." +msgid "Please enter a 4-digit number." +msgstr "" + msgid "Your contactless card details" msgstr "Los datos de su tarjeta sin contacto" diff --git a/tests/pytest/eligibility/test_forms.py b/tests/pytest/eligibility/test_forms.py new file mode 100644 index 000000000..42799ebac --- /dev/null +++ b/tests/pytest/eligibility/test_forms.py @@ -0,0 +1,37 @@ +from benefits.eligibility.forms import MSTCourtesyCard, SBMTDMobilityPass + + +def test_MSTCourtesyCard(): + form = MSTCourtesyCard(data={"sub": "12345", "name": "Gonzalez"}) + + assert form.is_valid() + + sub_attrs = form.fields["sub"].widget.attrs + assert sub_attrs["pattern"] == r"\d{5}" + assert sub_attrs["inputmode"] == "numeric" + assert sub_attrs["maxlength"] == 5 + assert sub_attrs["data-custom-validity"] == "Please enter a 5-digit number." + + name_attrs = form.fields["name"].widget.attrs + assert name_attrs["maxlength"] == 255 + assert name_attrs["data-custom-validity"] == "Please enter your last name." + + assert form.use_custom_validity + + +def test_SBMTDMobilityPass(): + form = SBMTDMobilityPass(data={"sub": "1234", "name": "Barbara"}) + + assert form.is_valid() + + sub_attrs = form.fields["sub"].widget.attrs + assert sub_attrs["pattern"] == r"\d{4}" + assert sub_attrs["maxlength"] == 4 + assert sub_attrs["inputmode"] == "numeric" + assert sub_attrs["data-custom-validity"] == "Please enter a 4-digit number." + + name_attrs = form.fields["name"].widget.attrs + assert name_attrs["maxlength"] == 255 + assert name_attrs["data-custom-validity"] == "Please enter your last name." + + assert form.use_custom_validity