mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 10:28:41 -04:00
Refactor GovukCheckboxField and all variants
Aims to make the structure of their code more like GovukTextInputField so this convention can be extended for radios. It does that by: - moving the code in their widget methods out into the govuk_checkbox_field_widget helper function - moving the code that wraps the checkboxes in HTML to make them collapsible into the helper function - remove GovukCheckboxesMixin in favour of having the extend_params method as a stand-alone function* *The code is generic enough that it can be used with other fields that share the same type of data. These changes also: - don't alter the interface of any classes involved - don't break any tests
This commit is contained in:
+137
-128
@@ -688,33 +688,139 @@ class RegisterUserFromOrgInviteForm(StripWhitespaceForm):
|
|||||||
auth_type = HiddenField('auth_type', validators=[DataRequired()])
|
auth_type = HiddenField('auth_type', validators=[DataRequired()])
|
||||||
|
|
||||||
|
|
||||||
class GovukCheckboxesMixin:
|
def extend_params(params, extensions):
|
||||||
|
items = None
|
||||||
|
param_items = len(params['items']) if 'items' in params else 0
|
||||||
|
|
||||||
def extend_params(self, params, extensions):
|
# split items off from params to make it a pure dict
|
||||||
items = None
|
if 'items' in extensions:
|
||||||
param_items = len(params['items']) if 'items' in params else 0
|
items = extensions['items']
|
||||||
|
del extensions['items']
|
||||||
|
|
||||||
# split items off from params to make it a pure dict
|
# merge dicts
|
||||||
if 'items' in extensions:
|
merge_jsonlike(params, extensions)
|
||||||
items = extensions['items']
|
|
||||||
del extensions['items']
|
|
||||||
|
|
||||||
# merge dicts
|
# merge items
|
||||||
merge_jsonlike(params, extensions)
|
if items:
|
||||||
|
if 'items' not in params:
|
||||||
# merge items
|
params['items'] = items
|
||||||
if items:
|
else:
|
||||||
if 'items' not in params:
|
for idx, _item in enumerate(items):
|
||||||
params['items'] = items
|
if idx >= param_items:
|
||||||
else:
|
params['items'].append(items[idx])
|
||||||
for idx, _item in enumerate(items):
|
else:
|
||||||
if idx >= param_items:
|
params['items'][idx].update(items[idx])
|
||||||
params['items'].append(items[idx])
|
|
||||||
else:
|
|
||||||
params['items'][idx].update(items[idx])
|
|
||||||
|
|
||||||
|
|
||||||
class GovukCheckboxField(GovukCheckboxesMixin, BooleanField):
|
def govuk_checkbox_field_widget(self, field, param_extensions=None, **kwargs):
|
||||||
|
|
||||||
|
# error messages
|
||||||
|
error_message = None
|
||||||
|
if field.errors:
|
||||||
|
error_message = {
|
||||||
|
"attributes": {
|
||||||
|
"data-module": "track-error",
|
||||||
|
"data-error-type": field.errors[0],
|
||||||
|
"data-error-label": field.name
|
||||||
|
},
|
||||||
|
"text": " ".join(field.errors).strip()
|
||||||
|
}
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'name': field.name,
|
||||||
|
'errorMessage': error_message,
|
||||||
|
'items': [
|
||||||
|
{
|
||||||
|
"name": field.name,
|
||||||
|
"id": field.id,
|
||||||
|
"text": field.label.text,
|
||||||
|
"value": 'y',
|
||||||
|
"checked": field.data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# extend default params with any sent in during instantiation
|
||||||
|
if self.param_extensions:
|
||||||
|
extend_params(params, self.param_extensions)
|
||||||
|
|
||||||
|
# add any sent in though use in templates
|
||||||
|
if param_extensions:
|
||||||
|
extend_params(params, param_extensions)
|
||||||
|
|
||||||
|
return Markup(
|
||||||
|
render_template('forms/fields/checkboxes/macro.njk', params=params))
|
||||||
|
|
||||||
|
|
||||||
|
def govuk_checkboxes_field_widget(self, field, wrap_in_collapsible=False, param_extensions=None, **kwargs):
|
||||||
|
|
||||||
|
def _wrap_in_collapsible(field_label, checkboxes_string):
|
||||||
|
# wrap the checkboxes HTML in the HTML needed by the collapisble JS
|
||||||
|
result = Markup(
|
||||||
|
f'<div class="selection-wrapper"'
|
||||||
|
f' data-module="collapsible-checkboxes"'
|
||||||
|
f' data-field-label="{field_label}">'
|
||||||
|
f' {checkboxes_string}'
|
||||||
|
f'</div>'
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
# error messages
|
||||||
|
error_message = None
|
||||||
|
if field.errors:
|
||||||
|
error_message = {
|
||||||
|
"attributes": {
|
||||||
|
"data-module": "track-error",
|
||||||
|
"data-error-type": field.errors[0],
|
||||||
|
"data-error-label": field.name
|
||||||
|
},
|
||||||
|
"text": " ".join(field.errors).strip()
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'name': field.name,
|
||||||
|
"fieldset": {
|
||||||
|
"attributes": {"id": field.name},
|
||||||
|
"legend": {
|
||||||
|
"text": field.label.text,
|
||||||
|
"classes": "govuk-fieldset__legend--s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"asList": self.render_as_list,
|
||||||
|
'errorMessage': error_message,
|
||||||
|
'items': items
|
||||||
|
}
|
||||||
|
|
||||||
|
# extend default params with any sent in during instantiation
|
||||||
|
if self.param_extensions:
|
||||||
|
extend_params(params, self.param_extensions)
|
||||||
|
|
||||||
|
# add any sent in though use in templates
|
||||||
|
if param_extensions:
|
||||||
|
extend_params(params, param_extensions)
|
||||||
|
|
||||||
|
if wrap_in_collapsible:
|
||||||
|
|
||||||
|
# add a blank hint to act as an ARIA live-region
|
||||||
|
params.update(
|
||||||
|
{"hint": {"html": "<div class=\"selection-summary\" role=\"region\" aria-live=\"polite\"></div>"}})
|
||||||
|
|
||||||
|
return _wrap_in_collapsible(
|
||||||
|
self.field_label,
|
||||||
|
Markup(render_template('forms/fields/checkboxes/macro.njk', params=params))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return Markup(
|
||||||
|
render_template('forms/fields/checkboxes/macro.njk', params=params))
|
||||||
|
|
||||||
|
|
||||||
|
class GovukCheckboxField(BooleanField):
|
||||||
|
|
||||||
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
|
||||||
super(GovukCheckboxField, self).__init__(label, validators, false_values=None, **kwargs)
|
super(GovukCheckboxField, self).__init__(label, validators, false_values=None, **kwargs)
|
||||||
@@ -725,48 +831,11 @@ class GovukCheckboxField(GovukCheckboxesMixin, BooleanField):
|
|||||||
# 2. calls field.widget
|
# 2. calls field.widget
|
||||||
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
||||||
def widget(self, field, param_extensions=None, **kwargs):
|
def widget(self, field, param_extensions=None, **kwargs):
|
||||||
|
return govuk_checkbox_field_widget(self, field, param_extensions=param_extensions, **kwargs)
|
||||||
# error messages
|
|
||||||
error_message = None
|
|
||||||
if field.errors:
|
|
||||||
error_message = {
|
|
||||||
"attributes": {
|
|
||||||
"data-module": "track-error",
|
|
||||||
"data-error-type": field.errors[0],
|
|
||||||
"data-error-label": field.name
|
|
||||||
},
|
|
||||||
"text": " ".join(field.errors).strip()
|
|
||||||
}
|
|
||||||
|
|
||||||
params = {
|
|
||||||
'name': field.name,
|
|
||||||
'errorMessage': error_message,
|
|
||||||
'items': [
|
|
||||||
{
|
|
||||||
"name": field.name,
|
|
||||||
"id": field.id,
|
|
||||||
"text": field.label.text,
|
|
||||||
"value": 'y',
|
|
||||||
"checked": field.data
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# extend default params with any sent in during instantiation
|
|
||||||
if self.param_extensions:
|
|
||||||
self.extend_params(params, self.param_extensions)
|
|
||||||
|
|
||||||
# add any sent in though use in templates
|
|
||||||
if param_extensions:
|
|
||||||
self.extend_params(params, param_extensions)
|
|
||||||
|
|
||||||
return Markup(
|
|
||||||
render_template('forms/fields/checkboxes/macro.njk', params=params))
|
|
||||||
|
|
||||||
|
|
||||||
# based on work done by @richardjpope: https://github.com/richardjpope/recourse/blob/master/recourse/forms.py#L6
|
# based on work done by @richardjpope: https://github.com/richardjpope/recourse/blob/master/recourse/forms.py#L6
|
||||||
class GovukCheckboxesField(GovukCheckboxesMixin, SelectMultipleField):
|
class GovukCheckboxesField(SelectMultipleField):
|
||||||
|
|
||||||
render_as_list = False
|
render_as_list = False
|
||||||
|
|
||||||
@@ -791,84 +860,24 @@ class GovukCheckboxesField(GovukCheckboxesMixin, SelectMultipleField):
|
|||||||
# 2. calls field.widget
|
# 2. calls field.widget
|
||||||
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
# this bypasses that by making self.widget a method with the same interface as widget.__call__
|
||||||
def widget(self, field, param_extensions=None, **kwargs):
|
def widget(self, field, param_extensions=None, **kwargs):
|
||||||
|
return govuk_checkboxes_field_widget(self, field, param_extensions=param_extensions, **kwargs)
|
||||||
# error messages
|
|
||||||
error_message = None
|
|
||||||
if field.errors:
|
|
||||||
error_message = {
|
|
||||||
"attributes": {
|
|
||||||
"data-module": "track-error",
|
|
||||||
"data-error-type": field.errors[0],
|
|
||||||
"data-error-label": field.name
|
|
||||||
},
|
|
||||||
"text": " ".join(field.errors).strip()
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
params = {
|
|
||||||
'name': field.name,
|
|
||||||
"fieldset": {
|
|
||||||
"attributes": {"id": field.name},
|
|
||||||
"legend": {
|
|
||||||
"text": field.label.text,
|
|
||||||
"classes": "govuk-fieldset__legend--s"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"asList": self.render_as_list,
|
|
||||||
'errorMessage': error_message,
|
|
||||||
'items': items
|
|
||||||
}
|
|
||||||
|
|
||||||
# extend default params with any sent in during instantiation
|
|
||||||
if self.param_extensions:
|
|
||||||
self.extend_params(params, self.param_extensions)
|
|
||||||
|
|
||||||
# add any sent in though use in templates
|
|
||||||
if param_extensions:
|
|
||||||
self.extend_params(params, param_extensions)
|
|
||||||
|
|
||||||
return Markup(
|
|
||||||
render_template('forms/fields/checkboxes/macro.njk', params=params))
|
|
||||||
|
|
||||||
|
|
||||||
# Extends fields using the GovukCheckboxesField interface to wrap their render in HTML needed by the collapsible JS
|
# Wraps checkboxes rendering in HTML needed by the collapsible JS
|
||||||
class GovukCollapsibleCheckboxesMixin:
|
class GovukCollapsibleCheckboxesField(GovukCheckboxesField):
|
||||||
def __init__(self, label='', validators=None, field_label='', param_extensions=None, **kwargs):
|
def __init__(self, label='', validators=None, field_label='', param_extensions=None, **kwargs):
|
||||||
|
|
||||||
super(GovukCollapsibleCheckboxesMixin, self).__init__(label, validators, param_extensions, **kwargs)
|
super(GovukCollapsibleCheckboxesField, self).__init__(label, validators, param_extensions, **kwargs)
|
||||||
self.field_label = field_label
|
self.field_label = field_label
|
||||||
|
|
||||||
def widget(self, field, **kwargs):
|
def widget(self, field, **kwargs):
|
||||||
|
return govuk_checkboxes_field_widget(self, field, wrap_in_collapsible=True, param_extensions=None, **kwargs)
|
||||||
# add a blank hint to act as an ARIA live-region
|
|
||||||
if self.param_extensions is not None:
|
|
||||||
self.param_extensions.update(
|
|
||||||
{"hint": {"html": "<div class=\"selection-summary\" role=\"region\" aria-live=\"polite\"></div>"}})
|
|
||||||
else:
|
|
||||||
self.param_extensions = \
|
|
||||||
{"hint": {"html": "<div class=\"selection-summary\" role=\"region\" aria-live=\"polite\"></div>"}}
|
|
||||||
|
|
||||||
# wrap the checkboxes HTML in the HTML needed by the collapisble JS
|
|
||||||
return Markup(
|
|
||||||
f'<div class="selection-wrapper"'
|
|
||||||
f' data-module="collapsible-checkboxes"'
|
|
||||||
f' data-field-label="{self.field_label}">'
|
|
||||||
f' {super(GovukCollapsibleCheckboxesMixin, self).widget(field, **kwargs)}'
|
|
||||||
f'</div>'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GovukCollapsibleCheckboxesField(GovukCollapsibleCheckboxesMixin, GovukCheckboxesField):
|
# GovukCollapsibleCheckboxesField adds an ARIA live-region to the hint and wraps the render in HTML needed by the
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# GovukCollapsibleCheckboxesMixin adds an ARIA live-region to the hint and wraps the render in HTML needed by the
|
|
||||||
# collapsible JS
|
# collapsible JS
|
||||||
# NestedFieldMixin puts the items into a tree hierarchy, pre-rendering the sub-trees of the top-level items
|
# NestedFieldMixin puts the items into a tree hierarchy, pre-rendering the sub-trees of the top-level items
|
||||||
class GovukCollapsibleNestedCheckboxesField(GovukCollapsibleCheckboxesMixin, NestedFieldMixin, GovukCheckboxesField):
|
class GovukCollapsibleNestedCheckboxesField(NestedFieldMixin, GovukCollapsibleCheckboxesField):
|
||||||
NONE_OPTION_VALUE = None
|
NONE_OPTION_VALUE = None
|
||||||
render_as_list = True
|
render_as_list = True
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user