23. Dialog Forms

Dialog forms are a way to create a form that is displayed in a dialog box. In django-formset this is possible by using a formset.collection.FormCollection and as one of its members, an instance of type formset.dialog.DialogForm. This is very similar to a setup as described in Nested Collection. The difference is that such a dialog form is not displayed by default, and hence shall be used for additional, but optional fields. Otherwise, there is no difference in the data structure, regardless of using a normal form or a dialog form as a member of a collection.

This example shows how to use an Activator field to open and close a dialog form:

form.py
from django.forms.fields import CharField, ChoiceField
from django.forms.forms import Form
from django.forms.widgets import RadioSelect
from formset.collection import FormCollection
from formset.dialog import ApplyButton, CancelButton, DialogForm
from formset.formfields.activator import Activator
from formset.renderers import ButtonVariant
from formset.widgets import Button

class CoffeeForm(Form):
    flavors = Activator(
        label="Add flavors",
        help_text="Open the dialog to edit flavors",
    )
    nickname = CharField()

class FlavorForm(DialogForm):
    title = "Choose a Flavor"
    induce_open = 'coffee.nickname == "Cappuccino" || coffee.flavors:active'
    induce_close = '.cancel:active || .apply:active'

    flavors = ChoiceField(
        choices=(
            ('caramel', "Caramel Macchiato"),
            ('cinnamon', "Cinnamon Dolce Latte"),
            ('hazelnut', "Turkish Hazelnut"),
            ('vanilla', "Vanilla Latte"),
            ('chocolate', "Chocolate Fudge"),
            ('almonds', "Roasted Almonds"),
            ('cream', "Irish Cream"),
        ),
        widget=RadioSelect,
        required=False,
    )
    cancel = Activator(
        label="Close",
        widget=CancelButton,
    )
    apply = Activator(
        label="Apply",
        widget=ApplyButton,
    )

class CoffeeOrderCollection(FormCollection):
    legend = "Order your coffee"
    coffee = CoffeeForm()
    flavor = FlavorForm()

This Form Dialog class has a few special attributes:

title

The title of the dialog form, shown in the header.

induce_open

A JavaScript expression that determines when the dialog form is opened. It opens when this expression evaluates to true. The expression is evaluated in the context of the collection form, so you can refer to other fields accessing them through their path.

Here we check if the field “Nickname” contains the word “Cappuccino”, and if so opens the dialog. Another way of opening the dialog is to activate the button labeled “Add flavors”.

induce_close

A JavaScript expression that determines when the dialog form is closed. It closes when this expression evaluates to true. Here we allow two fields to close the dialog: the “Cancel” button and the “Apply” button. They provide different parameters to the underlying dialog functionality: CancelButton closes the dialog without applying any changes, while ApplyButton closes the dialog and applies the changes to the form.

prologue and epilogue

These are optional attributes that can be used to add additional text to the dialog form. They are rendered before and after the form fields, respectively. If this text contains HTML, remember to mark the strings as safe using the Django mark_safe function.

ApplyButton and CancelButton

These special buttons shall only be used in classes inheriting from DialogForm. They are syntactic sugar for:

ApplyButton = Button(action='activate("apply")', button_variant=ButtonVariant.PRIMARY)

CancelButton = Button(action='activate("close")', button_variant=ButtonVariant.SECONDARY)

The CoffeeOrderCollection then puts everything together and is rendered by a Django view:

Order your coffee
Open the dialog to edit flavors

Choose a Flavor

The dialog form is rendered as a <dialog> element, which recently has been added to the HTML standard. Its main child element is a <form method="dialog"> which is submitted via the dialog method. The states of the form controls are saved but not submitted, and the returnValue property gets set to the value of the button that was activated. This is why we have to pass different arguments (“apply”, “close”) to those buttons.

If a collection implements more than one Dialog Form, some or all of them can be opened simultaneously. To prevent them from overlapping, these dialogs can be dragged to any position on the screen, simply by clicking on their header and moving them around.