Створення CTools попапів (модальних вікон) — справа нескладна, проте має велику кількість важливих нюансів. Тому дана стаття присвячена саме нюансам створення різноманітних попапів.
Найпростіший CTools попап
Для того щоб створити найпростіший Ctools попап, достатньо написати лише один рядок JS коду Drupal.CTools.Modal.show();. Дана JS функція створить повноцінний Ctools попап, залишиться лише додати контент. Для прикладу створимо попап, який буде з’являтися при завантаженні сторінки.
;(function($) { Drupal.behaviors.firstPopup = { attach: function (context, settings) { var content = 'my content'; var title = 'my title'; Drupal.CTools.Modal.show(); $('#modal-title').html(title); // Додаємо у попап заголовок; $('#modal-content').html(content); // Додаємо у попап контент; } } })(jQuery);
Підключаємо цей JS до потрібної сторінки — і перший, найпростіший попап готовий. Проте у нього є один недолік — як і будь-який інший JS, він може виводити лише дані, сформовані на стороні клієнта. Звісно ж, у багатьох випадках цього може бути достатньо, проте частіше Ctools попапи використовуються для виведення динамічних даних з сервера. Тому далі розглянемо більш складні, проте більш поширені випадки створення та використання попапів.
Класичний CTools попап
Перш за все, створимо новий модуль з назвою my_popup, в якому і будемо ставити наші експерименти.
1. hook_menu
Почнемо зі створення необхідних сторінок, через які буде викликатися наше модальне вікно.
/** * Implements hook_menu(). */ function my_popup_menu() { // Сторінка попапа. $items['first-popup/%ctools_js'] = array( 'title' => 'My first popup', 'page arguments' => array(1), 'access callback' => TRUE, 'page callback' => 'my_popup_callback', 'type' => MENU_CALLBACK, ); // Сторінка, на якій виводитиметься лінк на попап. $items['first-popup'] = array( 'title' => 'My first popup', 'access callback' => TRUE, 'page callback' => 'my_popup_page', 'type' => MENU_CALLBACK, ); return $items; }
Ми створили дві сторінки:
'first-popup' — це сторінка, де ми будемо виводити лінк на попап. Дана сторінка не є обов’язковою і використовується в даній статті лише для демонстрації попапів та створення лінків.
'first-popup/%ctools_js' — це сторінка виклику попапа.
'page callback' — назви функцій, які будуватимуть сторінки.
'access callback' — відповідає за надання доступу до сторінки. TRUE означає, що сторінка буде завжди доступна для будь-якого користувача.
'page arguments' — масив аргументів, які передаються у функцію page callback. 1 — це порядковий номер параметра в лінку на попап, в нашому випадку це %ctools_js, саме цей параметр визначає, чи ввімкнений в браузері користувача JavaScript.
Параметри 'title' ( заголовок сторінки) та 'type' (тип меню) не обов’язкові, проте їх також бажано вказувати.
2. Сallback функція модального вікна
Тепер створимо функцію my_popup_callback, описану в hook_menu(), яка будуватиме попап. В даній статті ми розглянемо три основні варіанти створення цих функцій.
Виведення HTML контенту в попапі
/** * Popup’s callback function. */ function my_popup_callback ($js = NULL) { // Контент, який ми розмістимо в попапі. $popup_content = t('Hello World') ; // Перевіряємо, чи увімкнено JavaScript. if (!$js) { // Якщо JavaScript вимкнено — то виводимо контент без попапа. return $popup_content; } // Якщо все нормально і JavaScript увімкнено — підключаємо необхідні бібліотеки для роботи з модальними вікнами. ctools_include('modal'); ctools_include('ajax'); // Forming a modal window. $output = array(); $output[] = ctools_modal_command_display(t('My first popup'), $popup_content); // Виводимо сформований попап в браузер print ajax_render($output); drupal_exit(); }
ctools_modal_command_display() — це функція, яка відповідає за побудову простого модального вікна. Вона приймає 2 параметри : перший — заголовок попапа, другий — його HTML наповнення.
Виведення веб-форми в попапі
Якщо у попапі потрібно виводити не конкретний контент, а веб-форму, тоді callback-функцію потрібно дещо змінити.
Для прикладу створимо просту вебформу з одним текстовим полем та кнопкою.
/** * Popup’s example form. */ function id_example_form($form, $form_state) { $form = array(); $form['some_text'] = array( '#title' => t('Some text'), '#type' => 'textfield', ); $form['submit'] = array( '#type' => 'submit', '#value' => t('OK'), ); return $form; }
І перепишемо callback функцію для виклику цієї форми у попапі.
/** * Popup’s callback function for form. */ function my_popup_callback ($js = NULL) { if (!$js) { // Якщо JavaScript вимкнено — то виводимо форму без попапа. return drupal_get_form('id_example_form'); } ctools_include('ajax'); ctools_include('modal'); // Задаємо початкові налаштування форми. $form_state = array( 'ajax' => TRUE, 'title' => t('Form title’'), ); $output = ctools_modal_form_wrapper('id_example_form', $form_state); print ajax_render($output); drupal_exit(); }
Попап формується з допомогою функції ctools_modal_form_wrapper(). Вона приймає два параметри id форми, а точніше — назву функції, що будує форму (в нашому випадку це — ‘id_example_form’) та масив з її налаштуваннями — $form_state. В цьому масиві лише один обов’язковий параметр 'ajax' => TRUE.
В такий спосіб можна вивести в модальному вікні не лише свою форму, а й будь-яку іншу форму в Drupal, потрібно лише знати функцію, з допомогою якої вона була створена.
Виведення в попап форм, створених через модуль Webform
Як завжди, серед правил знайдуться винятки. От і в Drupal існують форми, які вивести в вищеописаний спосіб не вдасться. Це форми створені через популярний модуль Webforms. Ми розглянемо два способи виведення таких форм у попап.
В першому випадку ми будемо працювати з вебформою як з окремим контент-типом (нодою).
Перш за все необхідно встановити додатковий модуль Webform AJAX https://www.drupal.org/project/webform_ajax. Після чого перейти в налаштування потрібної вебформи та позначити галочку AJAX mode. Адже сам модуль Webform не вміє працювати з AJAX.
Без цих додаткових налаштувань вебформа у попапі не зможе зберігати дані та видаватиме помилку.
Тепер напишемо дещо своєрідну callback функцію для виведення вебформи у попап. Нехай у нас є webform з id = “webform-client-form-23”. Тоді 23 - це id ноди, в якій зберігається наша форма.
/** * Callback for 'webform-client-form-23'. */ function my_popup_callback($js = NULL) { $webFormNode = node_load(23); // Завантажуємо ноду з вебформою. (з id 23). if (!$js) { // Якщо JavaScript вимкнено – то переходимо до вебформи. return drupal_get_form('webform_client_form_23', $webFormNode, FALSE); } ctools_include('ajax'); ctools_include('modal'); $webFormView = node_view($webFormNode); // Генеруємо масив параметрів форми. $webForm = drupal_render($webFormView); // Формуємо HTML структуру вебформи. $output = array(); //Виводимо вебформу у попап. $output[] = ctools_modal_command_display(t('My WebForm'), $webForm); print ajax_render($output); drupal_exit(); }
В другому випадку ми скористаємось стандартною функцією для виведення форми у попап.
/** * Callback for 'webform-client-form-23'. */ function my_popup_callback($js = NULL) { $webFormNode = node_load(23); if (!$js) { return drupal_get_form('webform_client_form_23', $webFormNode, FALSE); } $form_state = array( 'title' => $webFormNode->title, 'ajax' => TRUE, ); // Передаємо у вебформу необхідні параметри. $form_state['build_info']['args'] = array($webFormNode, FALSE); // Виводимо вебформу у попап. $output = ctools_modal_form_wrapper('webform_client_form_23', $form_state); // Закриваємо попап після натиснення кнопки сабміта. if (!empty($form_state['executed'])) { if (!isset($form_state['storage'])) { ctools_add_js('ajax-responder'); // Формуємо лінк на сторінку. $redirect_url = trim($webFormNode->webform['redirect_url']); $redirect_url = _webform_filter_values($redirect_url, $webFormNode, NULL, NULL, FALSE, TRUE); $output[] = ctools_ajax_command_redirect($redirect_url); } else { $form_state['executed'] = array(); $output = ctools_modal_form_wrapper('webform_client_form_23' , $form_state); } } print ajax_render($output); drupal_exit(); }
Як в першому, так в другому випадку вебформа виведеться у попапі та зберігатиме всі свої функціональні властивості.
3. Підключення налаштувань та створення теми для попапа
В багатьох випадках даний етап можна пропустити, проте якщо для попапа задати початкові налаштування, з ним набагато простіше працювати в подальшому.
Для початку створимо функцію, в якій будемо зберігати початкові налаштування.
Функція налаштувань
/** *Popup’s setings function. */ function my_ctools_popup_style() { static $added = FALSE; if ($added == FALSE) { $added = TRUE; // Підключаємо необхідні бібліотеки ctools_include('modal'); ctools_include('ajax'); ctools_modal_add_js(); // Задаємо налаштування для попапа $popup_style = array( 'first-popup-style' => array( 'modalSize' => array( 'type' => 'fixed', // Тип попапа. 'width' => 475, // Ширина 'height' => 'auto', // Висота 'addHeight' => 700, // Максимальна висота ), 'modalOptions' => array( 'opacity' => (float) 0.8, // Прозорість заднього фону 'background-color' => '#084b57', // Колір заднього фону ), 'closeText' => '', // Текст для кнопки «close» 'loadingText' => '', // Текст при завантаженні попапа 'animation' => 'fadeIn', // Тип анімації 'modalTheme' => 'my_custom_theme', // Назва теми, яку слід підключити 'animationSpeed' => 'fast', // Швидкість анімації попапа ), ); drupal_add_js($popup_style, 'setting'); // Підключаємо налаштування ctools_add_js('my_popup_style', 'my_popup'); // Підключаємо тему (перший параметр – це назва файла теми, другий - назва модуля, в якому цей файл знаходиться) } }
Тепер розберемо цю функцію більш детально.
Особливу увагу слід звернути на такі параметри:
'first-popup-style' — це ім’я для групи налаштувань нашого попапа (з допомогою нього ми зможемо використовувати ці налаштування в подальшому)
'modalTheme' — ім’я теми, з допомогою якої ми зможемо змінювати HTML структуру нашого модального вікна.
'‘type’ — може бути fixed або scale. Якщо fixed, то розміри попапа задаються в пікселях, якщо scale — то у відсотках за замовчуванням scale.
'width' — ширина попапа. Якщо тип fixed — задається в пікселях, якщо scale — то у відсотках (але за 100% вважається одиниця, тому для прикладу 50% — це 0.5). За замовчуванням — 0.8.
‘height’ — висота попапа. Задається аналогічно як і ширина. За замовчуванням - 0.8.
'addHeight' — максимальна висота в пікселях. Корисно тільки коли type = scale, або height = auto.
'animation' — задає спосіб анімації при завантаженні попапа. Може приймати значення: show, fadeIn, slideDown. За замовчуванням — show..
'animationSpeed' — швидкість анімації. Може приймати значення slow, medium, fast. За замовчуванням — fast.
Створення теми
Тема для попапа — це «js» файл, в якому описана його структура. Стандартна тема має наступний вигляд.
Drupal.theme.prototype.CToolsModalDialog = function () { var html = '' html += ' <div id="ctools-modal">' html += ' <div class="ctools-modal-content">' // panels-modal-content html += ' <div class="modal-header">'; html += ' <a class="close" href="#">'; html += Drupal.CTools.Modal.currentSettings.closeText + Drupal.CTools.Modal.currentSettings.closeImage; html += ' </a>'; html += ' <span id="modal-title" class="modal-title"> </span>'; html += ' </div>'; html += ' <div id="modal-content" class="modal-content">'; html += ' </div>'; html += ' </div>'; html += ' </div>'; return html; }
Через те, що всі попапи мають абсолютно однакову структуру, виникають проблеми при верстці, тому змінимо її і створимо власний файл для теми — 'my_popup_style.js' (назва файлу повинна обов’язково збігатися з назвою, яку ми задали у функції ctools_add_js(), під час опису налаштувань). Даний файл треба розмістити або в папці нашого модуля, або створити папку «js» і зберегти його там.
Опишемо нову структуру для модального вікна.
Drupal.theme.prototype.my_custom_theme= function () { var html = ''; html += '<div id="ctools-modal" class="popups-box my-first-popup">'; html += ' <div class="ctools-modal-content my-popup ">'; html += ' <span class="popups-close"><a class="close" href="#"></a></span>'; html += ' <div class="modal-msg"></div>'; html += ' <div class="modal-scroll"><div id="modal-content" class="modal-content popups-body"></div></div>'; html += ' </div>'; html += '</div>'; return html; }
Таким чином, ми дещо спростили наш попап і позбулися зайвих елементів, а також додали йому власний клас ‘my-first-popup’, що допоможе відрізнити цей попап від інших під час верстки.
Зверніть увагу на перший рядок коду — Drupal.theme.prototype.my_custom_theme. Де «my_custom_theme» — це ім’я теми, яке ми задали під час опису налаштувань, в параметрі 'modalTheme'. Також під час створення своєї теми для попапа не варто змінювати стандартні класи: «popups-close», «ctools-modal-content» і т.д. Якщо потрібно, можна додавати свої класи.
4. Лінк на попап
Що ж, наш попап майже готовий. Залишається лише створити “правильний” лінк для його виклику. Існує багато способів створити такий лінк. Проте спільними для всіх є такі правила.
- Функція з налаштуваннями завжди підключається перед лінком.
- Якщо ви пропустили етап з заданням налаштувань для попапа, то перед лінком ОБОВ’ЯЗКОВО мають бути підключені наступні бібліотеки: ctools_include('modal'); ctools_include('ajax'); ctools_modal_add_js();
- Лінк має містити клас типу ctools-modal-[ім’я групи налаштувань попапа]. В нашому випадку це ctools-modal-first-popup-style. Оскільки саме через лінк підключаються налаштування для попапа.
- Якщо налаштування не задавалися, то потрібно додати лише клас ctools-use-modal.
Розглянемо декілька способів створення лінка на попап. Його ми будемо виводити на сторінці first-popup, яку ми раніше створили через hook_menu.
1. (рекомендований) Створити лінк на попап з допомогою спеціальної функції ctools_modal_text_button();
function my_popup_page() { // Підключаємо функцію з налаштуваннями. my_ctools_popup_style(); // Виводимо лінк. return ctools_modal_text_button(t('Popup link'), 'first-popup/nojs', t('test popup'), 'ctools-modal-first-popup-style'); }
2. Використати стандартну Drupal функцію l();
function my_popup_page() { // Підключаємо функцію з налаштуваннями. my_ctools_popup_style(); // Виводимо лінк. return l(t('Popup link'), 'first-popup/nojs', array('attributes'=>array('class'=>array('ctools-use-modal', 'ctools-modal-first-popup-style')))); }
3. Створити лінк, використовуючи Drupal render API.
function my_popup_page() { // Підключаємо функцію з налаштуваннями. my_ctools_popup_style(); // Задаємо налаштування для лінка. $link = array( '#type' => 'link', '#title' => t('Popup link'), '#href' => 'first-popup/nojs', '#attributes' => array('class' => array('ctools-use-modal', 'ctools-modal-first-popup-style')), // Виводимо лінк return drupal_render($link); }
Взагалі кажучи, спосіб створення лінка не важливий. Головне — вказати правильний шлях до сторінки з попапом та не забути додати класи 'ctools-use-modal' та 'ctools-modal-first-popup-style' у випадку, якщо потрібно задати налаштування та підключити власну тему.
Тепер можна перейти на сторінку ‘first-popup’ і подивитися на результати нашої роботи.
Виклик CTools попапа через сабміт вебформи
Тепер розглянемо дещо складніший випадок. Нехай у нас є певна веб-форма, наприклад, інформація про користувача. І нехай завдання полягає у тому, що після натискання кнопки сабміта дані мають певним чином обробитися (наприклад, проходити валідацію), а потім виводитися у попапі через іншу форму, яка має перепитувати, чи ми дійсно хочемо зберегти цю інформацію, та мати дві кнопки “Yes”, “No”. Звісно ж, все це має відбуватися без перезавантаження сторінки.
Як бачимо, класичний підхід до створення попапів тут не зовсім підходить, хоч і можна це зробити з допомогою JS. Проте ми розглянемо інший варіант реалізації такого функціоналу.
Для початку створимо форму, з допомогою якої буде викликатися попап.
/** * First example form. */ function my_ctools_popup_first_form($form, $form_state) { $form['user_name'] = array( '#title' => t('User name'), '#type' => 'textfield', ); my_ctools_popup_style(); $form['submit'] = array( '#type' => 'submit', '#value' => t('OK'), '#ajax' => array( 'callback' => 'my_ctools_popup_submit_callback', ), '#attributes' => array('class' => array('ctools-modal-first-popup-style')), ); return $form; }
Це звичайна вебформа, але кнопка 'submit' має деякі особливості:
По-перше, ми підключили початкові налаштування через функцію my_ctools_popup_style(), яку описували вище.
По-друге, додали атрибут '#ajax', що дозволить відправляти дані вебформи без перезавантаження сторінки в функцію 'my_ctools_popup_submit_callback'.
По-третє, додали клас 'ctools-modal-first-popup-style' — в такий спосіб ми підключаємо початкові налаштування з функції my_ctools_popup_style для майбутнього попапа.
Тепер створимо callback функцію для сабміта першої форми, в якій буде викликатися попап з другою формою.
/** * Сallback function for first form submit. */ function my_ctools_popup_submit_callback($form, &$form_state) { // Отримуємо дані з першої форми $values = $form_state['values']; $form_state = array( 'title' => t('Example form'), 'ajax' => TRUE, 'values' => array( 'first_fоrm_values' => $values, // Зберігаємо дані з першої форми для того, аби мати до них доступ через другу форму. ), ); // Підключаємо необхідні бібліотеки ctools_include('modal'); ctools_include('ajax'); // Виводим другу форму у попапі. $commands = ctools_modal_form_wrapper('my_ctools_popup_second_form', $form_state); return array('#type' => 'ajax', '#commands' => $commands); }
'my_ctools_popup_second_form' — це назва функції, з допомогою якої буде створено другу форму, яка і виводитиметься у попап.
/** * Second example form. */ function my_ctools_popup_second_form($form, $form_state) { $values = $form_state['values']['first_fоrm_values']; // Отримуємо дані з першої форми. $form['user_name'] = array( '#type' => 'html_tag', '#tag' => 'h2', '#value' => $values['user_name'], // Виводимо дані з першої форми. ); $form['submit_yes'] = array( '#type' => 'submit', '#value' => t('Yes'), '#ajax' => array( 'callback' => 'my_ctools_popup_confirm_submit', ), ); $form['submit_no'] = array( '#type' => 'submit', '#value' => t('No'), '#ajax' => array( 'callback' => 'my_ctools_popup_confirm_submit', ), ); return $form; }
В даному прикладі ми просто вивели дані з першої форми в другій через змінну $values['first_fоrm_values'].
Тепер залишилося створити callback функцію для сабміта другої форми та вивести все це на нашій тестовій сторінці.
/** * Сallback function for second form submit. */ function my_ctools_popup_confirm_submit($form, &$form_state) { $value = $form_state['value']; // тут робимо щось важливе з даними… // Підключаємо необхідні бібліотеки ctools_include('modal'); ctools_include('ajax'); //closing the popup. $commands[] = ctools_modal_command_dismiss(); print ajax_render($commands); exit; }
Виведемо першу форму на тестовій сторінці і перевіримо результати нашої роботи.
/** * View firs form on page. */ function my_popup_page() { $form = drupal_get_form('my_ctools_popup_first_form'); return drupal_render($form); }
Отже, ми зробили одну просту, але дуже важливу річ. Ми передали дані з першої форми в другу і вивели другу форму у попапі. В такий спосіб можна легко працювати з даними на кожному етапі, додавати для форм валідацію, обробляти дані, робити запити в БД тощо.
Позиціонування попапа
Ctools попапи позиціонуються на сторінці автоматично в центрі вікна. Проте часто автоматичне позиціонування є не зручним, оскільки не реагує на динамічні зміни сторінки (скрол, зміну розмірів вікна, зміну розмірів самого попапа).
Дану проблему можна вирішити з допомогою простого JS, який необхідно підключити на сторінку з попапом.
(function($) { Drupal.behaviors.AlingnPopup = { attach: function() { popup = $('#modalContent'); // Function align element in the middle of the screen. function alignCenter () { // Popup size. var wpopup = popup.width(); var hpopup = popup.height(); // Window size. var hwindow = $(window).height(); var wwindow = $(window).width(); var Left = Math.max(40, parseInt($(window).width()/2 - wpopup/2)); var Top = Math.max(40, parseInt($(window).height()/2 - hpopup/2)); if(hwindow < hpopup) { popup.css({'position':'absolute'}); } else { popup.css({'position':'fixed'}); } popup.css({'left':Left, 'top':Top}); }; alignCenter(); $(window).resize(function() { alignCenter(); }); popup.resize(function(){ alignCenter(); }); } } }(jQuery))
Даний JS правильно спозиціонує попап в залежності від розміру монітора та самого попапа, а також реагуватиме на динамічні зміни середовища.
Помилки
1) Найчастіша помилка при створенні попапів виглядає приблизно так:
An AJAX HTTP error occurred. HTTP Result Code: 200 Debugging information follows. ... Status test: OK …
Швидше за все це означає, що помилка в callback функції. Це може бути будь-що — від не підключених бібліотек чи неправильно використаних функцій до забутої крапки з комою.
Також проблема може бути в неправильному посиланні, або в hook_menu().
2) При кліку на посилання, яке повинно викликати попап, відбувається перехід на сторінку, хоча JS включені.
Швидше за все, це означає, що неправильно підключений JS з темою для попапа, або в ньому є помилки.
3) Також часто попапи не спрацьовують через помилки в зовнішніх JS, потрібно перевіряти консоль браузера, щоб переконатись, що усі JS працюють без помилок.