Submitted by admin on Tue, 09/10/2019 - 16:27

Modal windows (AKA pop-ups) are incredibly convenient. They let you view/receive new and/or modified data with no need to reload the entire page. This greatly improves the website’s usability and its attractiveness for end users. Of course, we are not referring to those cases where pop-ups hamper us from viewing our favorite website, persistently trying to show an ad or offering to subscribe to a new community in social networks.

How to create and work with modal windows (pop-ups) in Drupal 8

Let's start with the simplest things. It’s worth reminding that standard modal windows in Drupal 8 are generated with the JQuery UI library.

In order to open some node/page in a modal window, you can add the following structure to the link to this material:

class="use-ajax” data-dialog-type="modal"

And ultimately the link will look like this:

Image removed.

Click on this link and...in most cases you will not get what you wanted...

Image removed.

Why? Because our node title includes the tags designed for micro-markup, which, in their turn, are generated by the RDF module, contained in the Drupal 8 core. Disabling the module is not a good option. So how can you solve this situation? We'll have to write the code that will cut off the unnecessary html tags only in the modal window title, because the rest of the time (e.g. during the direct viewing of the node) they are not a problem for us.

I decided to write my code on the client’s side, and after a few cups of coffee and disputes with my colleagues I got a solution presented below :)

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
(function ($, Drupal, settings) {
 
  "use strict";
 
  Drupal.behaviors.Crutch = { //the name of our behavior
    attach: function (context, settings) {
      function strip_tags(input, allowed) { //the strip_tags function that cuts unnecessary tags on regular expression and returns clean text. Important! The input parameter works correctly only string data type.
        allowed = (((allowed || '') + '')
          .toLowerCase()
          .match(/<[a-z][a-z0-9]*>/g) || [])
          .join('');
        var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
          commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
        return input.replace(commentsAndPhpTags, '')
          .replace(tags, function($0, $1) {
            return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
          });
      }
      $(document).bind('ajaxSuccess.Crutch', function() { //run the main code only after Ajax has successfully downloaded your node
        var value = $(".ui-dialog-title"); /matching the popup title class
 
        if (value.length && !value.hasClass('do-once')) { //if there is no do-once class,
          var text = strip_tags($(value).text()); //then run the strip_tags() function
          $(value).text(text);
          value.addClass('do-once');
        }
        $(this).unbind('ajaxSuccess.Crutch');
      });
    }
  };
})(jQuery, Drupal, drupalSettings);

This solution can be applied in two ways. The first way is to put it to the *.js file of your theme where you write your behaviors. The second way is to create your module and perform all these actions from it. Since later we will need our own module anyway, let's create it and call it Modal. For this and many other cases we highly recommend you to install and use Drupal console - it really saves a lot of time.

When your module is created and enabled, you can proceed to work. To start, in the modal.libraries.yml file (which you must create manually) make a custom library that will download your JS file. Here is the code with comments:

1
2
3
4
5
6
7
modal: //the library name, usually starts with the module name
 version: 1.x // version
 js: // set what type of files you will be adding (e.g. css/js/etc)
   js/crutch.js: {} // the file itself
 dependencies: // setting the dependencies of your library from the main
   - core/jquery
   - core/ajax

Next, in the modal.module file, create a function that will attach your library with the script and the standard Ajax library:

1
2
3
4
function modal_preprocess_html(&$variables) {
  $variables['page']['#attached']['library'][] = 'modal/modal';
  $variables['page']['#attached']['library'][] = 'core/drupal.ajax';
}

clean the cache and enjoy the result)).

Image removed.

By the way, you have probably noticed that I put the preprocess function module in the module file, not the theme file. And, according to the documentation, that's fine.

Speaking about styling of the standard modal windows in Drupal 8, here is a list of classes that you can use in selectors:

  • ui-dialog: The main container of the dialog window.
  • ui-dialog-titlebar: The place for the title
  • ui-dialog-title: The container where the text title itself is placed.
  • ui-dialog-titlebar-close: The button to close the window.
  • ui-dialog-content: The content zone.
  • ui-dialog-buttonpane: If you have a set of buttons, then the container for button entry is enabled)).
  • ui-dialog-buttonset: The container where all buttons themselves are placed.

We have considered the easiest option for modal window callback. But there are times when standard features are not enough. So let’s proceed to a more complex level of work.

Creating more complicated modal windows (pop-ups)

Let's create a form with a text box and a submit button in our module. In the modal window, this form should render the ID of the node whose title we enter in the text box.

Let’s begin. First, we'll create a file called modal.routing.yml, in which we set the path and parameters necessary for us to access our form. Here's the code of this file:

1
2
3
4
5
6
modal.test_modal_form:
 path: '/modal/test-form'
 defaults:
   _form: '\Drupal\modal\Form\TestModalForm'
 requirements:
     _access: 'TRUE'

As for the file that contains the code with the form, we place it on this path: modal\src\Form\TestModalForm.php. Here's the code of this file:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php
 
/**
 * @file
 * Contains \Drupal\modal\Form\TestModalForm.
 */
 
namespace Drupal\modal\Form;
 
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax;
use Drupal\Core\Ajax\OpenModalDialogCommand;
 
/**
 * Class TestModalForm.
 *
 * @package Drupal\modal\Form
 */
class TestModalForm extends FormBase {
 
  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'test_modal_form';
  }
 
  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
    $form['node_title'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Node`s title'),
    );
    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Load'),
      '#ajax' => array( // here we add Ajax callback where we will process 
        'callback' => '::open_modal'// the data that came from the form and that we will receive as a result in the modal window
      ),
    );
 
    $form['#title'] = 'Load node ID';
    return $form;
  }
 
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
 
  }
// the callback itself
  public function open_modal(&$form, FormStateInterface $form_state) {
    $node_title = $form_state->getValue('node_title');
    $query = \Drupal::entityQuery('node')
      ->condition('title', $node_title);
    $entity = $query->execute();
    $key = array_keys($entity);
    $id = !empty($key[0]) ? $key[0] : NULL;
    $response = new AjaxResponse();
    $title = 'Node ID';
    if ($id !== NULL) {
      $content = '<div class="test-popup-content"> Node ID is: ' . $id . '</div>';
      $options = array(
        'dialogClass' => 'popup-dialog-class',
        'width' => '300',
        'height' => '300',
      );
      $response->addCommand(new OpenModalDialogCommand($title, $content, $options));
    }
    else {
      $content = 'Not found record with this title <strong>' . $node_title .'</strong>';
      $response->addCommand(new OpenModalDialogCommand($title, $content));
    }
    return $response;
  }
}

Here's what the form looks like:

Image removed.

Returning to the code, let's see what we have done. To the submit form, we added Ajax callback in which we work with the data that came from the form. Let's analyze our callback code in detail.

1
public function open_modal(&$form, FormStateInterface $form_state)

- here we declare the function which is also the method of our class TestModalForm and to which the data from our form comes.

1
$node_title = $form_state->getValue('node_title');

- here, with the help of the getValue() method, we extract the value of the node_title text field from $form_state

1
$title = 'Node ID';

- the title of our would-be modal window.

1
2
3
$query = \Drupal::entityQuery('node')
   ->condition('title', $node_title);
$entity = $query->execute();

- here we, using the entityQuery method with the only 'node' parameter (the entity type) and the condition with the node title, extract our id.

1
2
$key = array_keys($entity);
$id = !empty($key[0]) ? $key[0] : NULL;

- as the result is an array whose key is the node id, we assign the non-empty value of the $key variable to the $id variable (if the node with this title already exists), otherwise the $key variable is considered NULL.

1
$response = new AjaxResponse();

- here’s an interesting thing. With this line, we create a AjaxResponse() object which will enable us to send an Ajax response in the JSON format. I’ll tell you later here in more detail how to use it.

1
if ($id !== NULL)

- everything is clear here ;)

1
$content = '<div class="test-popup-content"> Node ID is: ' . $id . '</div>';

- here we generate a $content variable in which we will place our $id variable. This, we place the result of all our actions/queries to the $content variable.

1
2
3
4
5
$options = array(
     'dialogClass' => 'popup-dialog-class',
     'width' => '300',
     'height' => '300',
);

- in this array, we set a list of parameters such as width, height, custom classes, etc.

1
$response->addCommand(new OpenModalDialogCommand($title, $content, $options));

- here we add an OpenModalDialogCommand Ajax command where we add our parameters $title(the window title), $content(the window content), $options(additional parameters)

1
2
3
4
else {
    $content = 'Not found record with this title <strong>' . $node_title .'</strong>';
    $response->addCommand(new OpenModalDialogCommand($title, $content));
  }

- in this section we set the behavior of the script when it does not find the node with this title.

1
return $response

- return the Ajax response. Enter the title of some node we have created. As a result, if everything is done correctly, we will see the following picture ...

Image removed.

Instead of a conclusion

Thus, you can open some page/node using a link which has a “use-ajax” class and a data-dialog-type="modal" parameter. When it comes to forms, and when the submit result opening must be implemented in a modal window, then Ajax handler should be added to the submit. It will receive the data from the form and send the answer to the modal window. Make sure you add the core/drupal.ajax Ajax library by using the #attached parameter. Have great coding!

Solutions Img