Наші розробники уже ділилися знаннями про Twig у Drupal 8, темізацію в Drupal 8, розробку на Drupal 8 загалом, конфігурацію в Drupal 8. Сьогодні поговоримо про створення модальних вікон (попапів) у Drupal 8.
Зручність модальних вікон (або, як їх ще називають - попапів) важко переоцінити. Можливість переглядати/отримувати нові та/або змінені дані без перезавантаження всієї сторінки дуже покращує юзабіліті сайту та його привабливість для кінцевих користувачів. Звичайно, ми не говоримо про ті випадки, коли попапи заважають нам переглядати наш улюблений веб-сайт, настирливо пропонуючи подивитись рекламу або підписатись на якусь спільноту в соцмережах. Тож сьогодні ми будемо говорити про те, як створювати і працювати з модальними вікнами у Drupal 8.
Для того, щоб пригадати, як створюються попапи у Drupal 7, можна прочитати статтю "Великий мануал зі створення CTools попапів в Drupal 7", а ми свою увагу зосередимо на вісімці :)
Отже, почнемо із найпростішого. Варто нагадати, що стандартні модальні вікна у Drupal 8 генеруються за допомогою бібліотеки JQuery UI.
Для того, щоб відкрити якусь ноду/сторінку в модальному вікні, можна прикрутити до посилання на цей матеріал таку конструкцію:
class="use-ajax” data-dialog-type="modal"
І посилання в кінцевому результаті має такий вигляд:
Клацаєм по цьому лінку і… у більшості випадків ризикуємо отримати не те, що хотіли…
Чому так сталось? Тому що заголовок нашої ноди містить у собі теги, призначені для мікророзмітки, які у свою чергу генеруються модулем RDF, вшитим у ядро Drupal 8. Відключати цей модуль — не вихід. Тож як вирішити цю ситуацію? Доведеться писати код, який буде обрізати непотрібні нам html теги лише в заголовку модального вікна, оскільки в решті випадках (наприклад, при прямому перегляді ноди) вони нам не заважають.
Я вирішив писати код на стороні клієнта, і після кількох чашок кави та суперечок із собі подібними народилося рішення, представлене нижче :)
(function ($, Drupal, settings) { "use strict"; Drupal.behaviors.Crutch = { //назва нашого behavior’a attach: function (context, settings) { function strip_tags(input, allowed) { //функція strip_tags, яка обрізає непотрібні нам теги по регулярному виразу і повертає чистий текст. Важливо! Параметр input коректно працює тільки з рядковим типом даних. 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() { //запускаємо основний код лише після того, як Ajax-ом буде успішно завантажена наша нода var value = $(".ui-dialog-title"); //чіпляємось до класу заголовка попапа if (value.length && !value.hasClass('do-once')) { //якщо класу do-once нема, var text = strip_tags($(value).text()); //тоді запускаємо функцію strip_tags() $(value).text(text); value.addClass('do-once'); } $(this).unbind('ajaxSuccess.Crutch'); }); } }; })(jQuery, Drupal, drupalSettings);
Це рішення можна застосувати двома способами. Перший спосіб — помістити його у *.js-файл Вашої теми, де ви пишете свої behaviors. Другий спосіб — створити свій модуль, і свої маніпуляції проводити з нього. Так як в подальшому нам все-таки знадобиться наш власний модуль, то давайте його створимо і назвемо Modal. Для його (і не тільки його) створення настійно рекомендую установити і користуватись Drupal-консоллю, реально економить багато часу.
Коли наш модуль створено і підключено, приступаємо до роботи. Для початку у файлі modal.libraries.yml (його потрібно створити вручну) створюємо кастомну бібліотеку, яка буде завантажувати наш JS-файл. Ось її код з коментарями:
modal: //назва бібліотеки, зазвичай починається з назви модуля version: 1.x // версія js: // прописуємо, який тип файлів будемо підключати (напр., css/js/etc) js/crutch.js: {} // безпосередньо сам файл dependencies: // прописуємо залежності нашої бібліотеки від основних - core/jquery - core/ajax
Далі у файлі modal.module створюємо функцію, яка буде аттачити нашу бібліотеку зі скріптом і стандартну бібліотеку Ajax:
function modal_preprocess_html(&$variables) { $variables['page']['#attached']['library'][] = 'modal/modal'; $variables['page']['#attached']['library'][] = 'core/drupal.ajax'; }
чистимо кеш і насолоджуємось результатом)).
До речі, шановний читач, напевно, зауважив, що я препроцес-функцію помістив у файл модуля, а не теми. І це нормально, тому що, згідно з документацією, таке робити можливо.
Якщо говорити про стилізацію стандартних модальних вікон Drupal 8, то ось список класів, до яких можна чіплятись у верстці:
- ui-dialog: Основний контейнер діалогового вікна
- ui-dialog-titlebar: Зона для розміщення заголовку
- ui-dialog-title: Контейнер, в якому безпосередньо знаходиться текст заголовку
- ui-dialog-titlebar-close: Кнопка для закриття вікна
- ui-dialog-content: Зона для контенту
- ui-dialog-buttonpane: Якщо заданий набір кнопок, тоді в силу вступає контейнер для кнопкового набору))
- ui-dialog-buttonset: Контейнер, де безпосередньо розміщуються самі кнопки.
Отже, нами був розглянутий найпростіший варіант виклику модального вікна. Але бувають ситуації, коли нам недостатньо стандартних можливостей. Тому приступаємо до складнішого рівня роботи.
Давайте у нашому модулі створимо форму із текстовим полем і кнопкою сабміта. І нехай ця форма повертає нам у модальному вікні ID ноди, заголовок котрої ми введемо у текстове поле.
Починаємо. Для початку створимо файл, який називається modal.routing.yml, в якому ми пропишемо шлях і параметри, необхідні нам для доступу до нашої форми. Ось код цього файлу:
modal.test_modal_form: path: '/modal/test-form' defaults: _form: '\Drupal\modal\Form\TestModalForm' requirements: _access: 'TRUE'
А сам файл, в якому буде код з формою, ми розміщуємо за таким шляхом: modal\src\Form\TestModalForm.php. Ось код цього файлу:
<?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( // тут ми вішаємо ajax-callback, в якому будемо обробляти 'callback' => '::open_modal', // дані, які прийшли з форми, і який поверне нам результат у модальному вікні ), ); $form['#title'] = 'Load node ID'; return $form; } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { } // безпосередньо сам callback 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; } }
І ось вигляд самої форми:
Повертаючись до коду, давайте подивимось, що ж ми тут зробили? До кнопки сабміта ми повісили ajax-callback, в якому ми працюємо з даними, що прийшли з форми. Давайте детально розберемо по рядку код нашого callback’a.
public function open_modal(&$form, FormStateInterface $form_state)
- тут ми оголошуємо цю функцію, яка одночасно є методом нашого класу TestModalForm і в яку прилітають дані з нашої форми.
$node_title = $form_state->getValue('node_title');
- тут за допомогою методу getValue() ми витягуємо значення текстового поля node_title із $form_state
$title = 'Node ID';
- заголовок нашого майбутнього модального вікна.
$query = \Drupal::entityQuery('node') ->condition('title', $node_title); $entity = $query->execute();
- в цих рядках ми, використовуючи метод entityQuery з єдиним параметром 'node' (тип сутності) і умову із заголовком ноди, витягнемо нашу id.
$key = array_keys($entity); $id = !empty($key[0]) ? $key[0] : NULL;
- так як результат в нас є масивом, в якому його ключ - id ноди, змінній $id ми присвоюємо непорожнє значення змінної $key (якщо нода з таким заголовком існує), інакше зміннa $key вважається NULL.
$response = new AjaxResponse();
- а тут вже цікаво. Цим рядком ми створюємо об’єкт AjaxResponse(), який дасть нам можливість відправити ajax-відповідь у форматі JSON. Як саме його використовувати, ми поговоримо пізніше.
if ($id !== NULL)
- тут і так все ясно ;)
$content = '<div class="test-popup-content"> Node ID is: ' . $id . '</div>';
- тут ми генеруємо змінну $content, в яку помістимо нашу змінну $id. Тобто, результат наших маніпуляцій/запитів/etc ми поміщаємо у змінну $content.
$options = array( 'dialogClass' => 'popup-dialog-class', 'width' => '300', 'height' => '300', );
- у цьому масиві ми задаємо список параметрів, таких як ширина, висота, кастомні класи і т.д.
$response->addCommand(new OpenModalDialogCommand($title, $content, $options));
- тут ми додаємо ajax-команду OpenModalDialogCommand, в яку додаємо наші параметри $title(заголовок вікна), $content(начинка вікна), $options(додаткові параметри)
else { $content = 'Not found record with this title <strong>' . $node_title .'</strong>'; $response->addCommand(new OpenModalDialogCommand($title, $content)); }
- в цій секції ми прописуємо поведінку скрипта, коли він не знайде ноди з таким заголовком.
return $response
- повертаємо Ajax-відповідь. Давайте введемо заголовок якоїсь створеної нами ноди. І в результаті, якщо все зроблено правильно, ми ми можемо спостерігати таку картину…
У якості висновку. Отже, відкрити якусь сторінку/ноду можна за допомогою лінка, який в собі має клас “use-ajax” i параметр data-dialog-type="modal". Якщо справа доходить до форм, і відкриття результату сабміта необхідно реалізувати у модальному вікні, тоді до сабміту необхідно повісити Ajax-обробник. В нього будуть прилітати дані з форми, і з нього буде йти відповідь у модальне вікно. При цьому необхідно переконатись, що у своєму коді ви підключаєте ajax-бібліотеку core/drupal.ajax за допомогою параметра #attached. Гарного вам коду!
P.S Ось архів з модулем - modal.zip