forked from CGM_Public/pretix_original
[a11y] Improved form error messages, descriptive labels, focusable toggle-link (#2002)
This commit is contained in:
@@ -8,7 +8,7 @@ from django.utils.safestring import mark_safe
|
|||||||
from django.utils.translation import pgettext
|
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
|
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:
|
if label_title:
|
||||||
attrs['title'] = 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) == ' ':
|
if text_value(content) == ' ':
|
||||||
# Empty label, e.g. checkbox
|
# Empty label, e.g. checkbox
|
||||||
attrs.setdefault('class', '')
|
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
|
# usually checkboxes have overall empty labels and special labels per checkbox
|
||||||
# => remove for-attribute as well as "required"-text appended to label
|
# => remove for-attribute as well as "required"-text appended to label
|
||||||
del(attrs['for'])
|
del(attrs['for'])
|
||||||
opt = ""
|
|
||||||
else:
|
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}>'
|
builder = '<{tag}{attrs}>{content}{opt}</{tag}>'
|
||||||
return format_html(
|
return format_html(
|
||||||
builder,
|
builder,
|
||||||
tag='label',
|
tag='label',
|
||||||
attrs=mark_safe(flatatt(attrs)) if attrs else '',
|
attrs=mark_safe(flatatt(attrs)) if attrs else '',
|
||||||
opt=opt,
|
opt=mark_safe(opt),
|
||||||
content=text_value(content),
|
content=text_value(content),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,6 +72,26 @@ class CheckoutFieldRenderer(FieldRenderer):
|
|||||||
)
|
)
|
||||||
return form_group_class
|
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):
|
def add_label(self, html):
|
||||||
label = self.get_label()
|
label = self.get_label()
|
||||||
|
|
||||||
@@ -73,11 +101,16 @@ class CheckoutFieldRenderer(FieldRenderer):
|
|||||||
else:
|
else:
|
||||||
required = self.field.field.required
|
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(
|
html = render_label(
|
||||||
label,
|
label,
|
||||||
label_for=self.field.id_for_label,
|
label_for=self.field.id_for_label,
|
||||||
label_class=self.get_label_class(),
|
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
|
) + html
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
{% if payment_provider.identifier != "free" %}
|
{% if payment_provider.identifier != "free" %}
|
||||||
<div class="pull-right flip">
|
<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>
|
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||||
{% trans "Modify" %}
|
{% trans "Modify" %}
|
||||||
</a>
|
</a>
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<div class="panel panel-primary panel-contact">
|
<div class="panel panel-primary panel-contact">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<div class="pull-right flip">
|
<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>
|
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||||
{% trans "Modify" %}
|
{% trans "Modify" %}
|
||||||
</a>
|
</a>
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
<div class="panel panel-primary panel-contact">
|
<div class="panel panel-primary panel-contact">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<div class="pull-right flip">
|
<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>
|
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||||
{% trans "Modify" %}
|
{% trans "Modify" %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -21,9 +21,9 @@
|
|||||||
</summary>
|
</summary>
|
||||||
<div id="contact">
|
<div id="contact">
|
||||||
<div class="panel-body">
|
<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 %}
|
{% if not invoice_address_asked and event.settings.invoice_name_required %}
|
||||||
{% bootstrap_form invoice_form layout="horizontal" %}
|
{% bootstrap_form invoice_form layout="checkout" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
{{ event.settings.invoice_address_explanation_text|rich_text }}
|
{{ event.settings.invoice_address_explanation_text|rich_text }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% bootstrap_form invoice_form layout="horizontal" %}
|
{% bootstrap_form invoice_form layout="checkout" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -140,6 +140,7 @@
|
|||||||
{% elif line.addon_to %}
|
{% elif line.addon_to %}
|
||||||
<div class="count"> </div>
|
<div class="count"> </div>
|
||||||
<div class="singleprice price">
|
<div class="singleprice price">
|
||||||
|
<span class="sr-only">{% trans "price per item" %}</span>
|
||||||
{% if event.settings.display_net_prices %}
|
{% if event.settings.display_net_prices %}
|
||||||
{{ line.net_price|money:event.currency }}
|
{{ line.net_price|money:event.currency }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -163,6 +164,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<span class="sr-only">{% trans "quantity" %}</span>
|
||||||
{{ line.count }}
|
{{ line.count }}
|
||||||
{% if editable %}
|
{% if editable %}
|
||||||
<form action="{% eventurl event "presale:event.cart.add" cart_namespace=cart_namespace|default_if_none:"" %}"
|
<form action="{% eventurl event "presale:event.cart.add" cart_namespace=cart_namespace|default_if_none:"" %}"
|
||||||
@@ -189,6 +191,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="singleprice price">
|
<div class="singleprice price">
|
||||||
|
<span class="sr-only">{% trans "price per item" %}</span>
|
||||||
{% if event.settings.display_net_prices %}
|
{% if event.settings.display_net_prices %}
|
||||||
{{ line.net_price|money:event.currency }}
|
{{ line.net_price|money:event.currency }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -197,6 +200,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="totalprice price">
|
<div class="totalprice price">
|
||||||
|
<span class="sr-only">{% trans "price" %}</span>
|
||||||
{% if event.settings.display_net_prices %}
|
{% if event.settings.display_net_prices %}
|
||||||
<strong>{{ line.net_total|money:event.currency }}</strong>
|
<strong>{{ line.net_total|money:event.currency }}</strong>
|
||||||
{% if line.tax_rate and line.total %}
|
{% if line.tax_rate and line.total %}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
{% if order.can_modify_answers %}
|
{% if order.can_modify_answers %}
|
||||||
<div class="pull-right flip">
|
<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>
|
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||||
{% trans "Change details" %}
|
{% trans "Change details" %}
|
||||||
</a>
|
</a>
|
||||||
@@ -280,7 +280,7 @@
|
|||||||
{% if invoice_address_asked or request.event.settings.invoice_name_required %}
|
{% if invoice_address_asked or request.event.settings.invoice_name_required %}
|
||||||
{% if order.can_modify_answers %}
|
{% if order.can_modify_answers %}
|
||||||
<div class="pull-right flip">
|
<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>
|
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||||
{% trans "Change details" %}
|
{% trans "Change details" %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ $(function () {
|
|||||||
|
|
||||||
$("input[data-required-if], select[data-required-if], textarea[data-required-if]").each(function () {
|
$("input[data-required-if], select[data-required-if], textarea[data-required-if]").each(function () {
|
||||||
var dependent = $(this),
|
var dependent = $(this),
|
||||||
|
dependentLabel = $("label[for="+this.id+"]"),
|
||||||
dependency = $($(this).attr("data-required-if")),
|
dependency = $($(this).attr("data-required-if")),
|
||||||
update = function (ev) {
|
update = function (ev) {
|
||||||
var enabled = (dependency.attr("type") === 'checkbox' || dependency.attr("type") === 'radio') ? dependency.prop('checked') : !!dependency.val();
|
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.prop('required', enabled);
|
||||||
}
|
}
|
||||||
dependent.closest('.form-group').toggleClass('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();
|
update();
|
||||||
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("change", update);
|
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("change", update);
|
||||||
|
|||||||
Reference in New Issue
Block a user