mirror of
https://github.com/pretix/pretix.git
synced 2026-01-01 18:32:27 +00:00
[a11y] Improved form error messages, descriptive labels, focusable toggle-link (#2002)
This commit is contained in:
committed by
GitHub
parent
954fece6cf
commit
11f23c3fd2
@@ -8,7 +8,7 @@ from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import pgettext
|
||||
|
||||
|
||||
def render_label(content, label_for=None, label_class=None, label_title='', optional=False):
|
||||
def render_label(content, label_for=None, label_class=None, label_title='', optional=False, is_valid=None):
|
||||
"""
|
||||
Render a label with content
|
||||
"""
|
||||
@@ -20,6 +20,15 @@ def render_label(content, label_for=None, label_class=None, label_title='', opti
|
||||
if label_title:
|
||||
attrs['title'] = label_title
|
||||
|
||||
opt = ""
|
||||
|
||||
if is_valid is not None:
|
||||
if is_valid:
|
||||
validation_text = pgettext('form', 'is valid')
|
||||
else:
|
||||
validation_text = pgettext('form', 'has errors')
|
||||
opt += '<strong class="sr-only"> {}</strong>'.format(validation_text)
|
||||
|
||||
if text_value(content) == ' ':
|
||||
# Empty label, e.g. checkbox
|
||||
attrs.setdefault('class', '')
|
||||
@@ -27,16 +36,15 @@ def render_label(content, label_for=None, label_class=None, label_title='', opti
|
||||
# usually checkboxes have overall empty labels and special labels per checkbox
|
||||
# => remove for-attribute as well as "required"-text appended to label
|
||||
del(attrs['for'])
|
||||
opt = ""
|
||||
else:
|
||||
opt = mark_safe('<i class="sr-only"> {}</i>'.format(pgettext('form', 'required'))) if not optional else ''
|
||||
opt += '<i class="sr-only label-required">, {}</i>'.format(pgettext('form', 'required')) if not optional else ''
|
||||
|
||||
builder = '<{tag}{attrs}>{content}{opt}</{tag}>'
|
||||
return format_html(
|
||||
builder,
|
||||
tag='label',
|
||||
attrs=mark_safe(flatatt(attrs)) if attrs else '',
|
||||
opt=opt,
|
||||
opt=mark_safe(opt),
|
||||
content=text_value(content),
|
||||
)
|
||||
|
||||
@@ -64,6 +72,26 @@ class CheckoutFieldRenderer(FieldRenderer):
|
||||
)
|
||||
return form_group_class
|
||||
|
||||
def append_to_field(self, html):
|
||||
help_text_and_errors = []
|
||||
help_text_and_errors += self.field_errors
|
||||
if self.field_help:
|
||||
help_text_and_errors.append(self.field_help)
|
||||
for idx, text in enumerate(help_text_and_errors):
|
||||
html += '<div class="help-block" id="help-for-{id}-{idx}">{text}</div>'.format(id=self.field.id_for_label, text=text, idx=idx)
|
||||
return html
|
||||
|
||||
def add_help_attrs(self, widget=None):
|
||||
super().add_help_attrs(widget)
|
||||
if widget is None:
|
||||
widget = self.widget
|
||||
help_cnt = len(self.field_errors)
|
||||
if self.field_help:
|
||||
help_cnt += 1
|
||||
if help_cnt > 0:
|
||||
help_ids = ["help-for-{id}-{idx}".format(id=self.field.id_for_label, idx=idx) for idx in range(help_cnt)]
|
||||
widget.attrs["aria-describedby"] = " ".join(help_ids)
|
||||
|
||||
def add_label(self, html):
|
||||
label = self.get_label()
|
||||
|
||||
@@ -73,11 +101,16 @@ class CheckoutFieldRenderer(FieldRenderer):
|
||||
else:
|
||||
required = self.field.field.required
|
||||
|
||||
if self.field.form.is_bound:
|
||||
is_valid = len(self.field.errors) == 0
|
||||
else:
|
||||
is_valid = None
|
||||
html = render_label(
|
||||
label,
|
||||
label_for=self.field.id_for_label,
|
||||
label_class=self.get_label_class(),
|
||||
optional=not required and not isinstance(self.widget, CheckboxInput)
|
||||
optional=not required and not isinstance(self.widget, CheckboxInput),
|
||||
is_valid=is_valid
|
||||
) + html
|
||||
return html
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<div class="panel-heading">
|
||||
{% if payment_provider.identifier != "free" %}
|
||||
<div class="pull-right flip">
|
||||
<a href="{% eventurl request.event "presale:event.checkout" step="payment" cart_namespace=cart_namespace|default_if_none:"" %}">
|
||||
<a href="{% eventurl request.event "presale:event.checkout" step="payment" cart_namespace=cart_namespace|default_if_none:"" %}" aria-label="{% trans "Modify payment" %}">
|
||||
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||
{% trans "Modify" %}
|
||||
</a>
|
||||
@@ -79,7 +79,7 @@
|
||||
<div class="panel panel-primary panel-contact">
|
||||
<div class="panel-heading">
|
||||
<div class="pull-right flip">
|
||||
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}?invoice=1">
|
||||
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}?invoice=1" aria-label="{% trans "Modify invoice information" %}">
|
||||
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||
{% trans "Modify" %}
|
||||
</a>
|
||||
@@ -129,7 +129,7 @@
|
||||
<div class="panel panel-primary panel-contact">
|
||||
<div class="panel-heading">
|
||||
<div class="pull-right flip">
|
||||
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}">
|
||||
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}" aria-label="{% trans "Modify contact information" %}">
|
||||
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||
{% trans "Modify" %}
|
||||
</a>
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
</summary>
|
||||
<div id="contact">
|
||||
<div class="panel-body">
|
||||
{% bootstrap_form contact_form layout="horizontal" %}
|
||||
{% bootstrap_form contact_form layout="checkout" %}
|
||||
{% if not invoice_address_asked and event.settings.invoice_name_required %}
|
||||
{% bootstrap_form invoice_form layout="horizontal" %}
|
||||
{% bootstrap_form invoice_form layout="checkout" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,7 +46,7 @@
|
||||
{{ event.settings.invoice_address_explanation_text|rich_text }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_form invoice_form layout="horizontal" %}
|
||||
{% bootstrap_form invoice_form layout="checkout" %}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
{% elif line.addon_to %}
|
||||
<div class="count"> </div>
|
||||
<div class="singleprice price">
|
||||
<span class="sr-only">{% trans "price per item" %}</span>
|
||||
{% if event.settings.display_net_prices %}
|
||||
{{ line.net_price|money:event.currency }}
|
||||
{% else %}
|
||||
@@ -163,6 +164,7 @@
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<span class="sr-only">{% trans "quantity" %}</span>
|
||||
{{ line.count }}
|
||||
{% if editable %}
|
||||
<form action="{% eventurl event "presale:event.cart.add" cart_namespace=cart_namespace|default_if_none:"" %}"
|
||||
@@ -189,6 +191,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="singleprice price">
|
||||
<span class="sr-only">{% trans "price per item" %}</span>
|
||||
{% if event.settings.display_net_prices %}
|
||||
{{ line.net_price|money:event.currency }}
|
||||
{% else %}
|
||||
@@ -197,6 +200,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="totalprice price">
|
||||
<span class="sr-only">{% trans "price" %}</span>
|
||||
{% if event.settings.display_net_prices %}
|
||||
<strong>{{ line.net_total|money:event.currency }}</strong>
|
||||
{% if line.tax_rate and line.total %}
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
<div class="panel-heading">
|
||||
{% if order.can_modify_answers %}
|
||||
<div class="pull-right flip">
|
||||
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}">
|
||||
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}" aria-label="{% trans "Change ordered items" %}">
|
||||
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||
{% trans "Change details" %}
|
||||
</a>
|
||||
@@ -280,7 +280,7 @@
|
||||
{% if invoice_address_asked or request.event.settings.invoice_name_required %}
|
||||
{% if order.can_modify_answers %}
|
||||
<div class="pull-right flip">
|
||||
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}">
|
||||
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}" aria-label="{% trans "Change your information" %}">
|
||||
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||
{% trans "Change details" %}
|
||||
</a>
|
||||
|
||||
@@ -292,6 +292,7 @@ $(function () {
|
||||
|
||||
$("input[data-required-if], select[data-required-if], textarea[data-required-if]").each(function () {
|
||||
var dependent = $(this),
|
||||
dependentLabel = $("label[for="+this.id+"]"),
|
||||
dependency = $($(this).attr("data-required-if")),
|
||||
update = function (ev) {
|
||||
var enabled = (dependency.attr("type") === 'checkbox' || dependency.attr("type") === 'radio') ? dependency.prop('checked') : !!dependency.val();
|
||||
@@ -299,6 +300,12 @@ $(function () {
|
||||
dependent.prop('required', enabled);
|
||||
}
|
||||
dependent.closest('.form-group').toggleClass('required', enabled);
|
||||
if (enabled) {
|
||||
dependentLabel.append('<i class="sr-only label-required">, ' + gettext('required') + '</i>');
|
||||
}
|
||||
else {
|
||||
dependentLabel.find(".label-required").remove();
|
||||
}
|
||||
};
|
||||
update();
|
||||
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("change", update);
|
||||
|
||||
Reference in New Issue
Block a user