{% from 'govuk_frontend_jinja/components/accordion/macro.html' import govukAccordion %} {% from 'govuk_frontend_jinja/components/radios/macro.html' import govukRadios %} {% from 'govuk_frontend_jinja/components/input/macro.html' import govukInput %} {% from 'govuk_frontend_jinja/components/select/macro.html' import govukSelect %} {% from 'govuk_frontend_jinja/components/date-input/macro.html' import govukDateInput %} {% from 'govuk_frontend_jinja/components/button/macro.html' import govukButton %} {% from 'govuk_frontend_jinja/components/details/macro.html' import govukDetails %} {% from 'govuk_frontend_jinja/components/pagination/macro.html' import govukPagination %} {# Helper macro to get active filter value #} {% macro get_active_filter_value(active_filters, filter_index) -%} {%- if active_filters -%} {%- for filter_data in active_filters -%} {# Handle both 3-tuple (old format) and 4-tuple (new format with operation) #} {%- if filter_data|length == 4 -%} {%- set idx, flt_name, operation, value = filter_data -%} {%- else -%} {%- set idx, flt_name, value = filter_data -%} {%- endif -%} {%- if idx == filter_index -%}{{ value }}{%- endif -%} {%- endfor -%} {%- endif -%} {%- endmacro %} {# Render individual filter input based on filter type #} {% macro render_filter_input(filter, filter_key, current_value, filters) %} {% if filter.type == 'datepicker' %} {# GOV.UK Date Input - split into day/month/year fields #} {% set date_parts = current_value.split('-') if current_value and current_value.strip() else [] %} {% set day_value = date_parts[2] if date_parts|length == 3 else '' %} {% set month_value = date_parts[1] if date_parts|length == 3 else '' %} {% set year_value = date_parts[0] if date_parts|length == 3 else '' %} {{ govukDateInput({ "id": filter_key, "namePrefix": filter_key, "fieldset": { "legend": { "text": filter.operation, "classes": "govuk-fieldset__legend--s" } }, "hint": { "text": "For example, 27 3 2024" }, "items": [ { "name": "day", "classes": "govuk-input--width-2", "value": day_value }, { "name": "month", "classes": "govuk-input--width-2", "value": month_value }, { "name": "year", "classes": "govuk-input--width-4", "value": year_value } ] }) }} {% elif filter.type == 'datetimepicker' %} {# For datetime, provide text input with format hint #} {{ govukInput({ "id": filter_key, "name": filter_key, "label": {"text": filter.operation, "classes": "govuk-label--s"}, "hint": {"text": "Format: YYYY-MM-DD HH:MM:SS, for example 2024-03-27 14:30:00"}, "value": current_value if current_value else "", "classes": "govuk-input--width-20" }) }} {% elif filter.type == 'select-with-search' %} {% set actual_filter = filters[filter.index] if filters and filter.index < filters|length else none %} {% set is_multiple = actual_filter and actual_filter.multiple %} {% set selected_values = current_value.split(',') if (current_value and is_multiple) else ([current_value] if current_value else []) %} {% set select_items = [] %} {% if not is_multiple %} {% set _ = select_items.append({"value": "", "text": "Select..."}) %} {% endif %} {% for opt_value, opt_label in (filter.options or []) %} {% set is_selected = (opt_value|string) in selected_values %} {% set _ = select_items.append({"value": opt_value, "text": opt_label, "selected": is_selected}) %} {% endfor %} {% set params = { "id": filter_key, "name": filter_key, "label": {"text": filter.operation, "classes": "govuk-label--s"}, "multiple": is_multiple, "select_items": select_items, "attributes": {"data-remove-duplicates": "true"} if is_multiple else {} } %} {% include 'select-with-search.html' %} {% elif filter.options %} {# Select dropdown for filters with predefined options #} {# Get the actual filter object from filters list using filter.index #} {% set actual_filter = filters[filter.index] if filters and filter.index < filters|length else none %} {# Check if this is an ArrayOverlapFilter (multi-select) #} {% set is_array_overlap = actual_filter and actual_filter.__class__.__name__ == 'ArrayOverlapFilter' %} {% if is_array_overlap %} {# Render multi-select for array overlap filters #} {% set select_items = [] %} {% set selected_values = current_value.split(',') if current_value else [] %} {% for opt_value, opt_label in filter.options %} {% set is_selected = opt_value in selected_values %} {% set _ = select_items.append({"value": opt_value, "text": opt_label, "selected": is_selected}) %} {% endfor %} {% set params = { "id": filter_key, "name": filter_key, "label": {"text": filter.operation, "classes": "govuk-label--s"}, "multiple": true, "select_items": select_items, "attributes": {"data-remove-duplicates": "true"} } %} {% include 'select-with-search.html' %} {% else %} {# Standard single-select dropdown #} {% set select_items = [{"value": "", "text": "Select..."}] %} {% for opt_value, opt_label in filter.options %} {# For enum filters, Flask-Admin provides enum values in options but expects enum names in form submission #} {# Check if the actual filter object has enum_class attribute (for EnumEqualFilter, etc.) #} {% if actual_filter and actual_filter.enum_class is defined and actual_filter.enum_class is not none %} {# opt_value contains the enum value (e.g., "red") from column.type.enums #} {# We need to find the enum name (e.g., "RED") for form submission #} {# And use the enum value (e.g., "red") for display #} {% set ns = namespace(enum_name=opt_value, display_label=opt_value) %} {% for member in actual_filter.enum_class %} {% if member.value == opt_value or member.name == opt_value %} {% set ns.enum_name = opt_value %} {% set ns.display_label = member.value %} {% endif %} {% endfor %} {% set is_selected = (current_value and current_value.strip() == ns.enum_name|string) %} {% set _ = select_items.append({"value": ns.enum_name, "text": ns.display_label, "selected": is_selected}) %} {% else %} {% set is_selected = (current_value and current_value.strip() == opt_value|string) %} {% set _ = select_items.append({"value": opt_value, "text": opt_label, "selected": is_selected}) %} {% endif %} {% endfor %} {{ govukSelect({ "id": filter_key, "name": filter_key, "label": {"text": filter.operation, "classes": "govuk-label--s"}, "items": select_items }) }} {% endif %} {% else %} {# Standard text input for text/number filters #} {% set input_params = { "id": filter_key, "name": filter_key, "label": {"text": filter.operation, "classes": "govuk-label--s"}, "value": current_value if current_value else "", "classes": "govuk-input--width-20" } %} {# Add inputmode for numeric filters #} {% if filter.type in ['int', 'float'] %} {% set _ = input_params.update({"inputmode": "numeric", "pattern": "[0-9]*"}) %} {% endif %} {{ govukInput(input_params) }} {% endif %} {% endmacro %} {# Render all filters for a given filter group #} {% macro render_filter_group_content(filter_name, filter_list, active_filters, filters) %}
Filter by {{ filter_name }} {% for filter in filter_list %} {% set filter_key = "flt" ~ filter.index ~ "_" ~ filter.arg %} {% set current_value = get_active_filter_value(active_filters, filter.index)|trim %}
{{ render_filter_input(filter, filter_key, current_value, filters) }}
{% endfor %}
{% endmacro %} {# Main filter form - works without JavaScript #} {% macro filter_form() %}
{# Submit button #} {{ govukButton({ "text": "Apply filters", "preventDoubleClick": true, }) }} {# Preserve existing query state #} {% for arg_name, arg_value in extra_args.items() %} {% endfor %} {% if sort_column is not none %} {% endif %} {% if sort_desc %} {% endif %} {% if page_size != default_page_size %} {% endif %} {# Search box at top of filter form #} {% if search_supported %} {{ govukInput({ "id": "search", "name": "search", "type": "search", "label": { "text": "Search", "classes": "govuk-label--m govuk-!-margin-bottom-0" }, "hint": { "text": ("In " ~ search_placeholder) if search_placeholder else "Search records" }, "value": search if search else "", }) }} {% endif %} {# Render filter groups #} {% if filter_groups %} {% if filter_groups|length > 2 %} {# Use Accordion for multiple filter groups #} {% set accordion_items = [] %} {% for filter_name, filter_list in filter_groups.items() %} {% set filter_content %} {{ render_filter_group_content(filter_name, filter_list, active_filters, filters) }} {% endset %} {% set _ = accordion_items.append({ "heading": {"text": filter_name}, "content": {"html": filter_content} }) %} {% endfor %} {{ govukAccordion({ "id": "filter-accordion", "items": accordion_items }) }} {% else %} {# Simple rendering for 1-2 filter groups #} {% for filter_name, filter_list in filter_groups.items() %} {{ render_filter_group_content(filter_name, filter_list, active_filters, filters) }} {% endfor %} {% endif %} {% endif %}
{% endmacro %} {# Search form #} {% macro search_form() %}
{# Hidden fields to preserve state #} {% for flt_name, flt_value in filter_args.items() %} {% endfor %} {% if page_size != default_page_size %} {% endif %} {% for arg_name, arg_value in extra_args.items() %} {% endfor %} {% if sort_column is not none %} {% endif %} {% if sort_desc %} {% endif %}
{{ govukInput({ "id": "search", "name": "search", "type": "search", "label": { "text": "Search", "classes": "govuk-label--s" }, "hint": { "text": search_placeholder if search_placeholder else "Search records" }, "value": search if search else "" }) }}
{{ govukButton({ "text": "Search", "preventDoubleClick": true }) }} {% if search %} Clear search {% endif %}
{% endmacro %} {# Page size selector #} {% macro page_size_form(generator, page_size_options) %}
{# Preserve filters and search #} {% for flt_name, flt_value in filter_args.items() %} {% endfor %} {% if search %} {% endif %} {% if sort_column is not none %} {% endif %} {% if sort_desc %} {% endif %} {% for arg_name, arg_value in extra_args.items() %} {% endfor %} {% set select_items = [] %} {% for option in page_size_options %} {% set is_selected = (page_size == option) %} {% set _ = select_items.append({ "value": option, "text": option ~ " per page", "selected": is_selected }) %} {% endfor %} {{ govukSelect({ "id": "page-size", "name": "page_size", "label": {"text": "Items per page", "classes": "govuk-label--s govuk-visually-hidden"}, "classes": "xgovuk-fa-page-size-select", "items": select_items, "attributes": {"data-module": "auto-submit"} }) }}
{% endmacro %} {# Export options #} {% macro export_options() %}

Export

{% endmacro %} {# MOJ Filter Layout - wraps filter sidebar and content area with actions and pagination #} {% macro mojFilterLayout() %}
{% if search_supported or filters %}

Filter

{# Selected filters section #} {% if active_filters or search %}

Selected filters

{# Search filter tag - remove search while preserving all filters #} {% if search %}

Search

{% endif %} {# Active filter tags grouped by filter name #} {% if active_filters %} {% set ns = namespace(current_filter_name='') %} {% for filter_data in active_filters %} {# Handle both 3-tuple (old format) and 4-tuple (new format with operation) #} {% if filter_data|length == 4 %} {% set idx, flt_name, operation, value = filter_data %} {% else %} {% set idx, flt_name, value = filter_data %} {% set operation = None %} {% endif %} {% if ns.current_filter_name != flt_name %} {% if ns.current_filter_name != '' %} {% endif %} {% set ns.current_filter_name = flt_name %}

{{ flt_name }}

    {% endif %} {# Resolve raw filter values to their option labels when the underlying filter has an options list (e.g. SelectWithSearchFilter, ArrayOverlapFilter). #} {% set option_map = {} %} {% if filters and idx < filters|length %} {% set opts = filters[idx].get_options(admin_view) %} {% if opts %} {% for opt_value, opt_label in opts %} {% set _ = option_map.update({opt_value|string: opt_label}) %} {% endfor %} {% endif %} {% endif %} {% set raw_parts = value.split(',') if ',' in value|string else [value|string] %} {% set display_parts = [] %} {% for part in raw_parts %} {% set _ = display_parts.append(option_map.get(part|trim, part)) %} {% endfor %}
  • Remove this filter {% if operation %}{{ operation }}: {% endif %} {{ display_parts|join(', ') }}
  • {% if loop.last %}
{% endif %} {% endfor %} {% endif %}
{% endif %} {# Filter options #}
{{ filter_form() }}
{% endif %}
{# Action bar wrapper - fixed, doesn't scroll with table #}
{% if search_supported or filters %}
{# Filter toggle button created by FilterToggleButton JS #}
{% endif %} {# Combined actions menu - only show if there are any actions available #} {% if actions or admin_view.can_create or admin_view.can_export %}
{# Create button (if enabled) #} {% if admin_view.can_create %} Create new {{ admin_view.name|lower }} {% endif %} {# Bulk actions (if available) #} {% if actions %} {% for value, text in actions %} {{ govukButton({ "name": "action", "text": text, "type": "submit", "classes": "moj-button-menu__item govuk-button--secondary", "attributes": {"form": "bulk-action-form", "value": value} }) }} {% endfor %} {% endif %} {# Export buttons (if enabled) #} {% if admin_view.can_export %} {% for export_type in admin_view.export_types %} Download {{ count }} results as {{ export_type|upper }} {% endfor %} {% endif %}
{% endif %}
{# Scrollable content wrapper - only wraps the table #}
{# Bulk actions form - wrap table if actions are enabled #} {% if actions %}
{% if action_form.csrf_token is defined and action_form.csrf_token %} {{ action_form.csrf_token }} {% elif csrf_token is defined and csrf_token %} {% endif %} {% endif %} {# Content area - caller() allows list.html to inject the table #} {{ caller() }} {% if actions %}
{% endif %}
{# Footer - pagination, result count, page size selector #} {% set hasPagination = num_pages and num_pages > 1 %}
{# end moj-filter-layout__content #}
{# end moj-filter-layout #} {% endmacro %}