Modal dialogs are great and provide a great experience for the end user - they allow for quick display of content in an overlay without navigating to another page. Getting forms to load and render properly in a modal can sometimes be a little tricky, but fortunately, it’s relatively straightforward to implement in Drupal 8.

We will be setting up a custom form containing a button that opens up another form in a modal using Drupal’s FormBuilder and AJAX API. So, let’s get started!

Setting Up

In this example, we will be created a custom module to contain all the files we need. The custom module directory structure should look like this:

modules
└── custom
    └── modal_form_example
        ├── modal_form_example.info.yml
        ├── modal_form_example.module
        ├── modal_form_example.routing.yml
        └── src
            ├── Controller
            │   └── ModalFormExampleController.php
            └── Form
                ├── ExampleForm.php
                └── ModalForm.php

 

ModalFormExampleController.php
This controller contains a callback method openModalForm() which uses the form_builder service to build the form and uses the OpenModalDialogCommand to show it in the modal.

<?php

namespace Drupal\modal_form_example\Controller;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Form\FormBuilder;

/**
 * ModalFormExampleController class.
 */
class ModalFormExampleController extends ControllerBase {

  /**
   * The form builder.
   *
   * @var \Drupal\Core\Form\FormBuilder
   */
  protected $formBuilder;

  /**
   * The ModalFormExampleController constructor.
   *
   * @param \Drupal\Core\Form\FormBuilder $formBuilder
   *   The form builder.
   */
  public function __construct(FormBuilder $formBuilder) {
    $this->formBuilder = $formBuilder;
  }

  /**
   * {@inheritdoc}
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   The Drupal service container.
   *
   * @return static
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('form_builder')
    );
  }

  /**
   * Callback for opening the modal form.
   */
  public function openModalForm() {
    $response = new AjaxResponse();

    // Get the modal form using the form builder.
    $modal_form = $this->formBuilder->getForm('Drupal\modal_form_example\Form\ModalForm');

    // Add an AJAX command to open a modal dialog with the form as the content.
    $response->addCommand(new OpenModalDialogCommand('My Modal Form', $modal_form, ['width' => '800']));

    return $response;
  }

}

 

Notice that OpenModalDialogCommand takes in 3 arguments - the modal title, the content to display (our modal form), and any dialog options. (In this example, we specified a width of 800 for the modal dialog).

modal_form_example.routing.yml
We set up 2 routes -

  • modal_form_example.form - the route to the base form containing our button
  • modal_form_example.open_modal_form - route that triggers our controller callback (ModalFormExampleController::openModalForm) which invokes the AJAX command to open up the modal.

 

modal_form_example.form:
  path: '/admin/config/example_form'
  defaults:
    _form: 'Drupal\modal_form_example\Form\ExampleForm'
    _title: 'Example Form'
  requirements:
    _permission: 'administer site configuration'

modal_form_example.open_modal_form:
  path: '/admin/config/modal_form'
  defaults:
    _title: 'Modal Form'
    _controller: '\Drupal\modal_form_example\Controller\ModalFormExampleController::openModalForm'
  requirements:
    _permission: 'administer site configuration'
  options:
    _admin_route: TRUE

 

Now, it’s time to create our forms!

Building the forms

ExampleForm.php

<?php

namespace Drupal\modal_form_example\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;

/**
 * ExampleForm class.
 */
class ExampleForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, $options = NULL) {
    $form['open_modal'] = [
      '#type' => 'link',
      '#title' => $this->t('Open Modal'),
      '#url' => Url::fromRoute('modal_form_example.open_modal_form'),
      '#attributes' => [
        'class' => [
          'use-ajax',
          'button',
        ],
      ],
    ];

    // Attach the library for pop-up dialogs/modals.
    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {}

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'modal_form_example_form';
  }

  /**
   * Gets the configuration names that will be editable.
   *
   * @return array
   *   An array of configuration object names that are editable if called in
   *   conjunction with the trait's config() method.
   */
  protected function getEditableConfigNames() {
    return ['config.modal_form_example_form'];
  }

}

 

A form element called open_modal is created which will be the button to trigger our modal form. This element is created as a link type so we can specify a #url value which will be the \Drupal\Url object pointing to the custom route created earlier to trigger open the modal form (modal_form_example.open_modal_form).

Modal Forms in Drupal 8

One important line to include is:
$form['#attached']['library'][] = 'core/drupal.dialog.ajax';

This line “attaches” the core/drupal.dialog.ajax library to our form and is necessary to render the modal dialogs. Alternatively, you can include this as a dependency in your module’s *.info.yml file.

ModalForm.php
And, finally, the star of this example - the modal form itself!

For simplicity, the modal form will contain only a “required” checkbox and an AJAX “submit” button. On successful submission, a success message will display.

<?php

namespace Drupal\modal_form_example\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\ReplaceCommand;

/**
 * ModalForm class.
 */
class ModalForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'modal_form_example_modal_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, $options = NULL) {
    $form['#prefix'] = '<div id="modal_example_form">';
    $form['#suffix'] = '</div>';

    // The status messages that will contain any form errors.
    $form['status_messages'] = [
      '#type' => 'status_messages',
      '#weight' => -10,
    ];

    // A required checkbox field.
    $form['our_checkbox'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('I Agree: modal forms are awesome!'),
      '#required' => TRUE,
    ];

    $form['actions'] = array('#type' => 'actions');
    $form['actions']['send'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit modal form'),
      '#attributes' => [
        'class' => [
          'use-ajax',
        ],
      ],
      '#ajax' => [
        'callback' => [$this, 'submitModalFormAjax'],
        'event' => 'click',
      ],
    ];

    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';

    return $form;
  }

  /**
   * AJAX callback handler that displays any errors or a success message.
   */
  public function submitModalFormAjax(array $form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    // If there are any form errors, re-display the form.
    if ($form_state->hasAnyErrors()) {
      $response->addCommand(new ReplaceCommand('#modal_example_form', $form));
    }
    else {
      $response->addCommand(new OpenModalDialogCommand("Success!", 'The modal form has been submitted.', ['width' => 800]));
    }

    return $response;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {}

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {}

  /**
   * Gets the configuration names that will be editable.
   *
   * @return array
   *   An array of configuration object names that are editable if called in
   *   conjunction with the trait's config() method.
   */
  protected function getEditableConfigNames() {
    return ['config.modal_form_example_modal_form'];
  }

}

 

We added an element of type status_messages to display any form validation errors in the modal dialog. Without this, the error messages set by drupal_set_messages() will display on the page itself on the next page load.

The submitModalFormAjax() AJAX submit callback checks for any errors before displaying the success message.

Voilà!

And that’s really it! Let’s trigger open that modal window:

If there are any errors (i.e., if you submit the form without checking the required checkbox), the form will be updated to show the errors:

Modal Forms in Drupal 8

Otherwise, a success message will show:

Modal Forms in Drupal 8

Additional Resources
Flag Migrations in Drupal 8 | Blog
5 Command Line Tips and Tricks | Video
Migrating Content References in Drupal 8 | Blog