DEV Community

Cover image for How to extend Wagtail page listing header to have visible buttons
Adam Mateusz Brożyński
Adam Mateusz Brożyński

Posted on

How to extend Wagtail page listing header to have visible buttons

1. We need to have custom page_header_buttons tag to get all the action buttons in different form than as dropdown and not to mess up with the rest of templates:

In /app/templatetags/custom_tags.py:

from django import template
from wagtail import hooks
from wagtail.admin.templatetags.wagtailadmin_tags import page_header_buttons

register = template.Library()

@register.inclusion_tag(
    "wagtailadmin/pages/listing/custom_page_header_buttons.html", takes_context=True
)
def custom_page_header_buttons(context, page, page_perms):
    return page_header_buttons(context, page, page_perms)
Enter fullscreen mode Exit fullscreen mode

2. Now let's make custom buttons template in app/wagtailadmin/pages/listing/custom_page_header_buttons.html (we can use core.css actionbutton class to style buttons):

{% load wagtailadmin_tags i18n %}
<nav aria-label="{{ title }}">
        {% block content %}
            {% for button in buttons %}
            <div class="actionbutton mx-2 inline-block">
                <a href="{{ button.url }}" aria-label="{{ button.attrs.title }}" class="button bicolor button--icon">
                    {% if button.icon_name %}
                        <span class="icon-wrapper">
                            {% icon name=button.icon_name %}
                        </span>
                    {% endif %}
                    {{ button.label }}
                </a>
            </div>
            {% endfor %}
        {% endblock %}
</nav>
Enter fullscreen mode Exit fullscreen mode

3. Let's make breadcrumbs always extended by creating app/templates/wagtailadmin/shared/breadcrumbs.html and removing button, is_expanded conditions and hidden classes:

{% load i18n wagtailadmin_tags %}
{% comment "text/markdown" %}
    The breadcrumb component is reused across all of Wagtail’s headers when the page tree context is needed.
    Variables this template accepts:

    `pages` - A list of wagtail page objects
    `trailing_breadcrumb_title` (string?) - use this for a non linkable last breadcrumb
    `classname` - Modifier classes
{% endcomment %}
{% with breadcrumb_link_classes='w-flex w-items-center w-h-full w-text-text-label w-pr-0.5 w-text-14 w-no-underline w-outline-offset-inside hover:w-underline hover:w-text-text-label w-h-full' breadcrumb_item_classes='w-h-full w-flex w-items-center w-overflow-hidden w-transition w-duration-300 w-whitespace-nowrap w-flex-shrink-0 w-font-bold' icon_classes='w-w-4 w-h-4 w-ml-3' %}
    {# Breadcrumbs are visible on mobile by default but hidden on desktop #}
    <div class="w-breadcrumb w-flex w-flex-row w-items-center w-overflow-x-auto w-overflow-y-hidden w-scrollbar-thin {{ classname }} w-pl-3" data-breadcrumb-next{% if not pages %} hidden{% endif %}>
        <div class="w-relative w-h-slim-header w-mr-4 w-top-0 w-z-20 w-flex w-items-center w-flex-row w-flex-1 sm:w-flex-none w-transition w-duration-300">
            <nav class="w-flex w-items-center w-flex-row w-h-full"
                aria-label="{% trans 'Breadcrumb' %}">
                <ol class="w-flex w-flex-row w-justify-start w-items-center w-h-full w-pl-0 w-my-0 w-gap-2 sm:w-gap-0 sm:w-space-x-2">
                    {% for page in pages %}
                        {% if page.is_root and url_root_name %}
                            {% url url_root_name as breadcrumb_url %}
                        {% else %}
                            {% url url_name page.id as breadcrumb_url %}
                        {% endif %}
                        {% if page.is_root %}
                            <li
                                class="{{ breadcrumb_item_classes }}"
                                data-breadcrumb-item
                            >
                                <a
                                    class="{{ breadcrumb_link_classes }}"
                                    href="{{ breadcrumb_url }}{{ querystring_value }}"
                                >
                                    {% trans "Root" %}
                                </a>
                                {% icon name="arrow-right" classname=icon_classes %}
                            </li>
                        {% elif forloop.first %}
                            {# For limited-permission users whose breadcrumb starts further down from the root #}
                            <li
                                class="{{ breadcrumb_item_classes }}"
                                data-breadcrumb-item
                            >
                                <a class="{{ breadcrumb_link_classes }}" href="{{ breadcrumb_url }}{{ querystring_value }}">
                                    {% trans "Root" %}
                                </a>
                                {% icon name="arrow-right" classname=icon_classes %}
                            </li>
                        {% elif forloop.last %}
                            <li
                                class="{{ breadcrumb_item_classes }}"
                                    data-breadcrumb-item
                           >
                                <a class="{{ breadcrumb_link_classes }}"
                                    href="{{ breadcrumb_url }}{{ querystring_value }}">
                                    {{ page.get_admin_display_title }}
                                </a>
                                {% if trailing_breadcrumb_title %}
                                    {% icon name="arrow-right" classname=icon_classes %}
                                {% endif %}
                            </li>
                        {% else %}
                            <li
                                class="{{ breadcrumb_item_classes }}"
                                data-breadcrumb-item
                            >
                                <a class="{{ breadcrumb_link_classes }}" href="{{ breadcrumb_url }}{{ querystring_value }}">
                                    {{ page.get_admin_display_title }}
                                </a>
                                {% icon name="arrow-right" classname=icon_classes %}
                            </li>
                        {% endif %}
                    {% endfor %}
                    {% if trailing_breadcrumb_title %}
                        <li class="{{ breadcrumb_item_classes }}">
                            <div class="w-flex w-justify-start w-items-center">
                                {{ trailing_breadcrumb_title }}
                            </div>
                        </li>
                    {% endif %}
                </ol>
            </nav>
        </div>
    </div>
{% endwith %}
Enter fullscreen mode Exit fullscreen mode

4. We need to make a new block in slim_header.html because default ones cannot be used to have full width div with content.

We need to copy & paste wagtailadmin/templates/shared/headers/slim_header.html file and add a new block at the end. Do not modify anything else, since it's a general template used for many purposes:

app/templates/wagtailadmin/shared/headers/slim_header.html:

{% load wagtailadmin_tags i18n %}
{% fragment as nav_icon_classes %}w-w-4 w-h-4 group-hover:w-transform group-hover:w-scale-110{% endfragment %}
{% fragment as nav_icon_button_classes %}w-w-slim-header w-h-slim-header w-bg-transparent w-border-transparent w-box-border w-py-3 w-px-3 w-flex w-justify-center w-items-center w-outline-offset-inside w-text-text-meta w-transition w-group hover:w-text-text-label focus:w-text-text-label expanded:w-text-text-label expanded:w-border-y-2 expanded:w-border-b-current w-shrink-0{% endfragment %}
{% fragment as nav_icon_counter_classes %}-w-mr-3 w-py-0.5 w-px-[0.325rem] w-translate-y-[-8px] rtl:w-translate-x-[4px] w-translate-x-[-4px] w-text-[0.5625rem] w-font-bold w-text-text-button w-border w-border-surface-page w-rounded-[1rem]{% endfragment %}
{# Z index 99 to ensure header is always above  #}
<style>
    // for smaller header bar
    .w-slim-header {
        height: 50px;
    }

    // smaller buttons
    @media screen { .button {
        height: 2rem !important;
        line-height: 1.8rem;
    }}

</style>
<header class="w-slim-header w-flex w-flex-col sm:w-flex-row w-items-center w-justify-between w-bg-surface-header w-border-b w-border-border-furniture w-px-0 w-py-0 w-mb-0 w-relative w-top-0 w-z-header sm:w-sticky w-min-h-slim-header">

    {# Padding left on mobile to give space for navigation toggle, #}
    <div class="w-pl-slim-header sm:w-pl-5 w-min-h-slim-header sm:w-pr-2 w-w-full w-flex-1 w-overflow-x-auto w-box-border">
        <div class="w-flex w-flex-1 w-items-center w-overflow-hidden">
            {% block header_content %}
            {% endblock %}
        </div>
    </div>

    <div class="w-w-full sm:w-w-min w-flex sm:w-flex-nowrap sm:w-flex-row w-items-center w-p-0 sm:w-py-0 sm:w-pr-4 sm:w-justify-end">
        {% block actions %}
        {% endblock %}
    </div>
</header>
{% block after_header %}
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

5. Finally we can create copy & paste wagtailadmin/pages/page_listing_header.html to app/templates/wagtailadmin/pages/page_listing_header.html and make necessary modifications:

{% extends 'wagtailadmin/shared/headers/slim_header.html' %}
{% load wagtailadmin_tags i18n %}
{# CUSTOM TAG! #}
{% load custom_tags %}

{% block header_content %}
    {# Accessible page title #}
    <h1 class="w-sr-only">
        {{ title }}
    </h1>
    {# breadcrumbs #}
    {% breadcrumbs parent_page 'wagtailadmin_explore' url_root_name='wagtailadmin_explore_root' is_expanded=parent_page.is_root classname='sm:w-py-3 lg:w-py-7' %}
    {# Actions divider #}
    <div class="w-w-px w-h-[30px] w-ml-auto sm:w-ml-0 w-bg-border-furniture"></div>
    {# NOTHING MORE HERE! #}
{% endblock %}
{% block actions %}
    {% if not parent_page.is_root %}
        {% include "wagtailadmin/shared/side_panel_toggles.html" %}
        {# Page history #}
        {% if parent_page.get_latest_revision %}
            <a href="{% url 'wagtailadmin_pages:history' parent_page.id %}"
                class="{{ nav_icon_button_classes }}"
                data-tippy-content="{% trans 'History' %}"
                data-tippy-offset="[0, 0]"
                data-tippy-placement="bottom"
                aria-label="{% trans 'History' %}"
            >
                {% icon name="history" classname=nav_icon_classes %}
            </a>
        {% endif %}

        {% include "wagtailadmin/shared/page_status_tag_new.html" with page=parent_page %}
    {% endif %}
{% endblock %}
{% block after_header %}
{# OUR DIV WITH TITLE AND BUTTONS! #}
<div class="under-header w-mx-2 w-my-2">
    <h1 class="sr-hidden">{{ title }}</h1>
    {% custom_page_header_buttons parent_page page_perms=page_perms  %}
</div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

This still needs a lot of styling but works. I'll publish full source of ready-to-go when I'll finish it. ;)

Top comments (0)