What is the correct method for launching a modal window in wagtail-admin?

I am currently working on enhancing my wagtail-admin interface, but I have hit a roadblock when trying to open a modal window. While I could create a div with a close button, I believe there must be a more appropriate method for achieving this. It seems that there is a specific function or object within wagtail-admin that handles the opening of modal windows.

Unfortunately, I have not been able to locate any documentation outlining the structure of these javascript objects within wagtail-admin. Does anyone have insights on how to accomplish this task? Or should I abandon the idea and build my modal window using plain vanilla javascript instead?

Answer №1

It's important to note that there is no documented method for utilizing Wagtail admin modals as they are.

However, by delving into the source code, you can harness the modal workflow to create your own custom modals. In Wagtail, this involves providing a server-side template response with render_modal_workflow.

On the client side, there is a function available called ModalWorkflow. This function calls a URL asynchronously and renders the HTML content within the modal upon response, expecting a response formatted by the aforementioned render_modal_workflow helper.

With these fundamentals in place, you can incorporate open behavior via a button trigger, handle errors, execute render callbacks, and define callbacks based on values retrieved from within the modal.

Below is a basic example demonstrating how to render a modal in the admin using this approach.

Example

1. Render some HTML content with a button trigger

  • For demonstration purposes, let's render a modal on the Wagtail home (dashboard) page.
  • Using the construct_homepage_panels, we can add HTML to a panel partway 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 handling editing have the modal-workflow script loaded.
  • To include it on this specific page, override the wagtailadmin/home.html template.
  • Add jQuery to identify elements with the data-modal-trigger attribute and attach an onClick listener to call our ModalWorkflow function. Data passed back to the modal view can include any specific information.
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 for when the modal content
              calls modal.respond(callbackName, params)
          'onload' (optional): dict of callbacks for loading steps of the workflow.
              The 'step' field in the response identifies the callback to call, passing 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 manage the modal requests

  • Ensure there is an admin/... URL for requesting the modal content.
  • This URL should lead to a view returning a response based on render_modal_workflow.
  • You can initialize data on the client side alongside using a typical 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 display the modal content

  • All modals utilize the same shared header template to ensure consistency.
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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using PHP functions in an AJAX request

I am currently attempting to execute a php loop upon clicking a radio button. I understand that ajax is necessary for this task, but as a beginner in ajax, I am struggling to achieve the desired result. Presently, I am unable to click the radio button at a ...

Choosing between creating a class with a shared instance or not

I'm curious if my class is shared across instances: for example, I have a route that looks like this: /student/:id When this route triggers the controller (simplified version): module.exports = RecalculateStudents; const recalculateActiveStudent ...

Should we integrate a MongoDB database calculation app with a POST Controller in sails.js?

The primary function of the application interface is to provide variables that have been initially posted by the client, as well as any subsequent database calculations carried out in real time by a specialized engine. Is it possible to integrate this eng ...

Accessing an array of objects within nested objects results in an undefined value

I am facing an issue with my JavaScript object that is retrieved from MySQL. The object has a property which contains an array of other objects, as demonstrated below: parentObject = { ID: "1", Desc: "A description", chi ...

Implementing the jquery mobile data-native-menu feature in a select element dynamically generated from jeditable

Greetings! I have encountered an issue where the data-native-menu="false" instruction works correctly when directly placed in a select element, but doesn't work when added to a select generated by JavaScript (using the Jeditable plugin). You can view ...

Attempting to adjust the width of a text animation loaded with jQuery using Textillate, but encountering difficulties

I found a captivating animation on this website: http://codepen.io/jschr/pen/GaJCi Currently, I am integrating it into my project. #content { position: relative; margin-left: auto; margin-right: auto; width: 1000px; height: 700px; } ...

I am looking to host several iterations of jQuery on a content delivery network within my Nuxt application

Currently, we are loading jQuery 3.1.4 externally from a CDN on the top page. index.vue head: { bodyAttrs: { id: 'overview' }, script: [ { src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min ...

ESLint's feature experimentalObjectRestSpread not being applied with expected behavior

ESLint is showing an unexpected token error, specifically error Parsing error: Unexpected token .., and I'm struggling to identify the root cause. In my .eslintrc.js file, I have: module.exports = { extends: "devmountain/react-config" , rul ...

Discovering the method for accessing a variable within jQuery from a regular JavaScript function

As someone new to jQuery, I am currently facing a challenge with accessing a variable defined inside a jQuery block from a regular function. Despite my attempts, I have been unsuccessful in accessing it. Can anyone guide me on how to do this? <script l ...

Insert elements to an XML document in Node.js using libxmljs

I've been working on updating an XML file by adding a new child node using the code below: var libxml = require('libxmljs'); var xml = '<?xml version="1.0" encoding="UTF-8"?>' + '<root>' + ...

When the URL is modified, triggering an event to load

Is there a way to make a page load directly to a specific section and automatically open an accordion? For instance, I have a page with multiple accordions displayed vertically. I already know that using id's in the URL like page#accordion1 will scro ...

Steps to obtain the precise source code of a webpage

Is there a way to download the exact source code of a webpage? I have tried using the URL method and Jsoup method, but I am not getting the precise data as seen in the actual source code. For example: <input type="image" name="ctl00$dtlAlbums$ct ...

Dynamic reloading of a div with form data using jQuery's AJAX functionality

I am currently developing an online visitor chat software using PHP and MySQL. My goal is to load the page when the submit button is clicked. Submit Button ID: send Visitor ID: vid Chat ID: cid Below is the snippet of code for an Ajax request that I hav ...

Instructions on creating a countdown timer that reveals a hidden div once it reaches zero, remains visible for a set period, and then resets

I created a countdown timer that displays a div when the timer reaches zero. However, I am struggling with getting the timer to reset and start counting down again after displaying the div. For instance, if I set the timer for 7 days and it reaches the de ...

The react-datepicker component is unable to set the state to the format dd/MM/yy

The date is currently shown in the correct format (31/08/21), but when onChange gets triggered, startDate changes to something like this: Tue Aug 31 2021 21:29:17 GMT+0200 (Central European Summer Time) Is there a way to maintain the display format I wa ...

Exploring uncharted territory with the Navigator component in React Native

I am experiencing an issue with undefined navigator when using React Native MessageTabs: _onPressItem = (item) => { const { navigate } = this.props.navigation; //console.log(JSON.stringify(item)); navigate('SingleConversation', {id ...

Issue with undefined arrays in the Angular merge sort visualization tool

I am currently working on developing a visualizer for sorting algorithms using Angular. However, I have encountered some difficulties while implementing merge sort. As a Java programmer, I suspect that there may be an issue with my TypeScript code and the ...

Using Vue.js to conditionally render content based on changes in a variable

I am facing a challenge in rendering a new element once the boolean variable waiting changes to true. The issue arises when transitioning from one v-if statement to another, as the boolean does not update until the first statement is completed. How can I s ...

Executing JavaScript in Rails after dynamically adding content through AJAX

Looking for advice on integrating JavaScript functions into a Rails app that are triggered on elements added to the page post-load via AJAX. I've encountered issues where if I include the code in create.js.erb, the event fires twice. However, removing ...

Guidance on invoking the navigate function from a component displayed at the highest level of rendering

Within the react-navigation documentation, it is explained that you can initiate navigation from the top-level component using the following method: import { NavigationActions } from 'react-navigation'; const AppNavigator = StackNavigator(SomeA ...