3

I try to improve my wagtail-admin and now I stack because there is no way to open a modal window. Yes, of course, I could create a div with a close button, but this would be not the right way. As I've got, there is a special function (or object) which is responsible for opening such a window.

There is no reference for such a structure of js objects of wagtail-admin. May be somebody knows, how to do it? Or maybe I should forget about it and make my modal window just by vanilla javascript?

Valeed Anjum
  • 447
  • 1
  • 5
  • 17
Savel Mtrx
  • 83
  • 5

1 Answers1

7

The short answer is that there is no documented way to use the existing Wagtail admin modals.

However, with a bit of looking at the source code it is possible to leverage the modal workflow to implement your own modals. The approach in Wagtail is to have a server side template response supplied by render_modal_workflow.

On the client, a function is available ModalWorkflow. That will call a URL async and render the html content inside the modal on response, it expects a response formed by the above render_modal_workflow helper.

From these basics it is possible to add open behaviour by a button trigger, error handling, render callbacks and callbacks based on value from inside the modal.

Below is a bare minimum example of a way to render a modal in the admin using this approach.

Example

1. Render some html content that has a button as a trigger

  • For the sake of example, we will render a modal on the Wagtail home (dashboard) page.
  • Using the construct_homepage_panels we can add some html to a panel part way down the page.
wagtail_hooks.py
from django.utils.safestring import mark_safe
from wagtail.core import hooks

class WelcomePanel:
    order = 110

    def render(self):
        return mark_safe("""
        <section class="panel summary nice-padding">
          <h3>Dashboard Panel Section Title</h3>
          <button data-modal-trigger="some-param">Open Modal</button>
        </section>
        """)

@hooks.register('construct_homepage_panels')
def add_another_welcome_panel(request, panels):
    panels.append(WelcomePanel())

2. Ensure the modal-workflow JS script is loaded

  • By default, only pages that handle editing have the modal-workflow script loaded
  • To add it to this specific page we need to override the wagtailadmin/home.html template template.
  • We will also add a bit of jquery to find any elements that have the data-modal-trigger attribute and add an onClick listener which will call our ModalWorkflow function. This data can be passed back to the modal view, along with any other specific data.
templates/wagtailadmin/home.html
{% extends "wagtailadmin/home.html" %}
{% load wagtailadmin_tags %}

{% comment %}
    Javascript declaration added to bring in the modal loader, by default it is only available on edit pages
    example of usage - wagtail/search/templates/wagtailsearch/queries/chooser_field.js
{% endcomment %}

{% block extra_js %}
  {{ block.super }}
  <script src="{% versioned_static 'wagtailadmin/js/modal-workflow.js' %}"></script>
  <script type="text/javascript">
    $(function() {
      $('[data-modal-trigger]').on('click', function(element) {
        /* options passed in 'opts':
          'url' (required): initial
          'responses' (optional): dict of callbacks to be called when the modal content
              calls modal.respond(callbackName, params)
          'onload' (optional): dict of callbacks to be called when loading a step of the workflow.
              The 'step' field in the response identifies the callback to call, passing it the
              modal object and response data as arguments
        */
        ModalWorkflow({
          onError: function(error) { console.log('error', error); },
          url: '/admin/modal/?trigger=' + element.target.dataset.modalTrigger
        });
      });
    });
  </script>
{% endblock %}

3. Create a view and url to handle the modal requests

  • Ensure there is an admin/... url that we can request the modal content from
  • This url must go to a view that returns a response based on render_modal_workflow
  • It is possible to initiate data on the client side along with using a normal Django template response for the server side rendered modal content
views.py
from django.template.response import TemplateResponse

from wagtail.admin.modal_workflow import render_modal_workflow


def modal_view(request):

    return render_modal_workflow(
        request,
        'base/modal.html', # html template
        None, # js template
        {'trigger': request.GET.get('trigger')}, # html template vars
        json_data={'some': 'data'} # js template data
    )

urls.py
from django.conf.urls import url
from .views import modal_view

urlpatterns = [
    url(r'^admin/modal/', modal_view, name='modal'),
    url(r'^admin/', include(wagtailadmin_urls)),
    # ...
]

4. Set up your template to render the modal content

  • Modals all use the same shared header template, which gives a nice way to make it feel consistent.
templates/base/modal.html
{% include "wagtailadmin/shared/header.html" with title="Modal Title" icon="no-view" %}

<div class="nice-padding">
    <p>Modal Triggered by {{ trigger }}</p>
</div>

LB Ben Johnston
  • 4,751
  • 13
  • 29