Drupal 8's Modal API

Creating a simple dialog by example...

drupal, drupal 8, ctools, modals, dialogs

Comments

Using Drupal 7, the contributed module CTools provided the ability to implement dialogs, even with a fallback when JavaScript was not enabled, allowing a graceful degradation.

The very popular Views module relied heavily on CTools for dialogs, but even though Views is in Drupal 8 core now, CTools is not. At least, a lot of the ideas and concepts of CTools did made it into core though.

The Modal API is a good example of this. You can try out some example code directly or read further how to create a basic implementation of a dialog.

Of course you must start creating a new module (let’s say modal_example) by creating a module folder. Contrary to Drupal 7, a .module file is not longer required, so proceed with creating the modal_example.info.yml file.

1
2
3
4
name: 'Modal Example'
type: module
description: 'Module which demonstrates an example of a Modal implementation.'
core: 8.x

Drupal needs to know the paths (or better routes) involved for the callbacks of our module, in particular the page and the dialog callback. Therefore we need to implement the modal_example.routing.yml file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
modal_example.page:
  path: 'modal-example'
  defaults:
    _title: 'Modal Example'
    _controller: '\Drupal\modal_example\Controller\ModalExampleController::page'
  requirements:
    _permission: 'access content'
modal_example.modal:
  path: 'modal-example/modal/{js}'
  defaults:
    _title: 'Modal'
    _controller: '\Drupal\modal_example\Controller\ModalExampleController::modal'
  requirements:
    _permission: 'access content'
    js: 'nojs|ajax'

Optionally you can also implement a modal_example.links.menu.yml file in order to have a navigation link in the menu.

1
2
3
4
modal_example.page:
  title: 'Modal Example'
  route_name: modal_example.page
  expanded: TRUE

And last but not least, we need a Controller class which returns the content for either our page and our dialog callback we defined the routing YAML file earlier. It is of course possible to split up the page and dialog callback in their own Controller class, but we keep it simple for this example.

So lastly implement a ModalExampleController class in a file located at ./src/Controller/ModalExampleController.php relative to the root of the module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php

/**
 * @file
 * ModalExampleController class.
 */

namespace Drupal\modal_example\Controller;

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\Component\Serialization\Json;

class ModalExampleController extends ControllerBase {

  public function page() {
    $link_url = Url::fromRoute('modal_example.modal', ['js' => 'nojs']);

    $link_url->setOptions([
      'attributes' => [
        'class' => ['use-ajax', 'button', 'button--small'],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => Json::encode(['width' => 400]),
      ]
    ]);

    return array(
      '#type' => 'markup',
      '#markup' => Link::fromTextAndUrl(t('Open the modal'), $link_url)->toString(),
      '#attached' => ['library' => ['core/drupal.dialog.ajax']]
    );
  }

  public function modal($js = 'nojs') {
    if ($js == 'ajax') {
      $options = [
        'dialogClass' => 'popup-dialog-class',
        //'width' => '75%', // apply or override the width of the dialog
      ];
      $response = new AjaxResponse();
      $response->addCommand(new OpenModalDialogCommand(t('Modal title'), t('This is an example of a modal with Javascript.'), $options));
      return $response;
    } else {
      return t('This is an example of a fallback for a modal without Javascript.');
    }
  }
}