23 Apr.2017

Drupal 8 & 9: Creating modal windows (pop-ups) with a form and to send a mail programmatically

Drupal 8: Creating modal windows (pop-ups) with a form

Modal windows/dialog (AKA pop-ups) are great and provide a great experience for the end user - they allow for a 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. 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 favourite website, persistently trying to show an ad or offering to subscribe to a new community in social networks.

Assume we already created such link in HTML <a href="/contact-teacher/15513" class="use-ajax">Send message to teacher</a>. The class class="use-ajax" is very important.

So, let’s get started with the example of part of a module I made before and called "tl_session" (you can rename to your own name).

File
custom/tl_session/tl_session.permissions.yml
contact teachers:
  title: 'Contact teachers'
File
custom/tl_session/tl_session.routing.yml
tl_session.open_teacher_form:
  path: '/contact-teacher/{teacher}'
  defaults:
    _title: 'Contact form'
    _controller: '\Drupal\tl_session\Controller\TeacherContactController::openModalForm'
  requirements:
    _permission: 'contact teachers'

The route that triggers our controller callback (TeacherContactController::openModalForm) which invokes the AJAX command to open up the modal.

File
tl_session/src/Controller/TeacherContactController.php
<?php
 
namespace Drupal\tl_session\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;
 
/**
 * Class TeacherContactController
 *
 * @package Drupal\tl_session\Controller
 */
class TeacherContactController extends ControllerBase {
 
  /**
   * The form builder.
   *
   * @var \Drupal\Core\Form\FormBuilder
   */
  protected $formBuilder;
 
  /**
   * The TeacherContactController 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\tl_session\Form\TeacherForm');
    // Add an AJAX command to open a modal dialog with the form as the content.
    $response->addCommand(new OpenModalDialogCommand('Contact form', $modal_form, ['width' => '555']));
 
    return $response;
  }
}

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.

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 555 for the modal dialog).

File
tl_session/src/Form/TeacherForm.php
<?php
 
namespace Drupal\tl_session\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;
 
/**
 * TeacherForm class.
 */
class TeacherForm extends FormBase {
 
  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'tl_teacher_modal_form';
  }
 
  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, $options = NULL) {
    $teacher_id = \Drupal::request()->get('teacher');
    $teacher_details = \Drupal\user\Entity\User::load($teacher_id);
    $teacher_name = alex_tweaks_get_real_name($teacher_details);
    $teacher_details = alex_tweaks_get_extra_info($teacher_details);
    $teacher_mail = $teacher_details['mail'];
 
    $account_details = tl_session_get_user_details();
 
    $form['#prefix'] = '<div id="tl_teacher_form">';
    $form['#suffix'] = '</div>';
 
    // The status messages that will contain any form errors.
    $form['status_messages'] = [
      '#type' => 'status_messages',
      '#weight' => -10,
    ];
 
    $form['contact_name'] = [
      '#type' => 'hidden',
      '#value' => $teacher_name,
    ];
    $form['contact_mail'] = [
      '#type' => 'hidden',
      '#value' => $teacher_mail,
    ];
    $form['email_type'] = [
      '#type' => 'hidden',
      '#value' => 'send_message',
    ];
    $form['name'] = [
      '#type' => 'textfield',
      '#title' => t('Your name'),
      '#default_value' => $account_details['name'],
      '#required' => TRUE,
    ];
    $form['email'] = [
      '#type' => 'email',
      '#title' => t('Your email'),
      '#default_value' => $account_details['mail'],
      '#required' => TRUE,
    ];
    $form['phone'] = [
      '#type' => 'tel',
      '#title' => t('Your telephone'),
      '#default_value' => $account_details['phone'],
      '#required' => FALSE,
    ];
    $form['message'] = [
      '#type' => 'textarea',
      '#default_value' => 'Dear ' . $teacher_name . ',',
      '#rows' => 8,
      '#resizable' => FALSE,
      '#title' => t('Message'),
      '#required' => TRUE,
    ];
 
    $form['actions'] = ['#type' => 'actions'];
    $form['actions']['send'] = [
      '#type' => 'submit',
      '#value' => $this->t('Send message'),
      '#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('#tl_teacher_form', $form));
    }
    else {
      $message = $this->t('The email has been sent.');
      drupal_set_message($message);
      \Drupal::logger('tl_session')->notice($message);
      $response->addCommand(new OpenModalDialogCommand("Success!", $message, ['width' => 555]));
    }
    return $response;
  }
 
  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    // TODO: Check if it looks like we are going to exceed the flood limit.
    // Not ported to Drupal 8.0.3 yet.
  }
 
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $data['contact_name'] = trim($form_state->getValue('contact_name'));
    $data['mail_to'] = trim($form_state->getValue('contact_mail'));
    $data['mail_type'] = trim($form_state->getValue('email_type'));
    $data['sender_name'] = trim($form_state->getValue('name'));
    $data['sender_email'] = trim($form_state->getValue('email'));
    $data['sender_phone'] = trim($form_state->getValue('phone'));
    $data['sender_message'] = trim($form_state->getValue('message'));
    tl_session_send_mail($data);
  }
 
  /**
   * 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.tl_session_modal_form'];
  }
}

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

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.

File
tl_session/tl_session.module
<?php
 
use Drupal\Component\Utility\Html;
 
/**
 * Helper function to get basic user details.
 *
 * @return array
 */
function tl_session_get_user_details() {
  $details = [
    'name' => '',
    'mail' => '',
    'phone' => '',
  ];
 
  $account = \Drupal::currentUser();
  if ($account->id() > 0) {
    $details['name'] = alex_tweaks_get_real_name($account);
    $extra_info = alex_tweaks_get_extra_info($account);
    $details['mail'] = $extra_info['mail'];
    $details['phone'] = $extra_info['phone'];
  }
 
 
  return $details;
}
 
/**
 * Implements hook_mail().
 */
function tl_session_mail($key, &$message, $params) {
  switch ($key) {
    case 'send_enquiry':
    case 'send_message':
      $message['from'] = 'no-reply@stat.org.uk';
      $message['subject'] = Html::escape($params['subject']);
      $message['body'][] = Html::escape($params['message']);
      break;
  }
}
 
/**
 * Custom function for mail.
 *
 * @param $data
 */
function tl_session_send_mail($data) {
  // Load the site name out of configuration.
  $config = \Drupal::config('system.site');
  $site_name = $config->get('name');
 
  $mailManager = \Drupal::service('plugin.manager.mail');
  $module = 'tl_session';
  $key = $data['mail_type'];
  $to = $data['mail_to'];
  // Uncomment for testing.
  // $to = \Drupal::currentUser()->getEmail();
  $params['reply-to'] = $data['sender_email'];
 
  $params['subject'] = $site_name . ' ' . $data['sender_name'] . ' sent you a message';
  $params['message'] = 'Dear ' . $data['contact_name'] . ',
' . $data['sender_name'] . ' has sent you a message via your contact form at ' . $site_name . '.
 
Message:
' . $data['sender_message'] . '
 
---
 
Contact
Name: ' . $data['sender_name'] . '
Email: ' . $data['sender_email'] . '
Phone: ' . $data['sender_phone'];
 
  $langcode = \Drupal::currentUser()->getPreferredLangcode();
  $send = TRUE;
 
  $result = $mailManager->mail($module, $key, $to, $langcode, $params, NULL, $send);
  if ($result['result'] != TRUE) {
    $message = t('There was a problem sending your email notification to @email.', ['@email' => $to]);
    drupal_set_message($message, 'error');
    \Drupal::logger('tl_session')->error($message);
    return;
  }
}
Conclusion

Hope this helps, also checkout "How to programmatically create a title for a new node" it can be interesting, too.

Ruslan Piskarov

Ukraine
PHP/WEB Developer / Drupal Expert. More than 11 years of experience delivering Drupal based General Purpose solutions for different sectors such as Job Boards, Product Portfolios, Geo Coding, Real Estate solutions, E-Commerce, Classifieds, Corporate and online Magazines/Newspapers.

Versions