Как создать кастомный pane для панелайзера
Опубликовано пн, 21/03/2016 - 23:58
На стандартных пейнах из коробки далеко не уедешь, писать кастомный придется в 90% случаев. Как написать кастомный пейн, какие функции использовать и какая должна быть структура плагина - сегодня в этой статье.
Необходимо определиться за что будет отвечать плагин, какие в нем будут настройки и что он будет выводить. Чтобы пример не был совсем тривиальным, пусть плагин выводит кастомные табы (по сути простые ссылки, сверстанные в виде табов). В настройках пейна можно будет добавлять/удалять таб по аяксу, определять его заголовок, путь, видимость и порядок (вес).
Содержание
Структура плагина
Для объявления плагина можно использовать уже готовый тип content_types или же объявить собственный. Воспользуемся первым вариантом. Пейн для панелайзера (как и для панелей) будет создан на основе ctools plugin API. Структура как и большинства плагинов, схожая - в модуле располагаем папку plugins, в ней тип плагина (в данном случае content_types), далее непосредственно сам файл, содержащий практически все необходимое для работы кастомного pane.
Объявление плагина
Объявление плагина в файле MYMODULE_tabs.inc
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * Объявление плагина. */ $plugin = array( 'title' => t('MYMODULE tabs'), 'description' => t('Output custom tabs'), 'render callback' => 'MYMODULE_tabs_render', 'admin title' => 'MYMODULE_tabs_admin_title', 'admin info' => 'MYMODULE_tabs_admin_info', // категория расположения пейна. 'category' => t('MYMODULE'), 'edit form' => 'MYMODULE_tabs_edit_form', ); |
В объявлении плагина приведены колбеки для формы настроек, рендера, для формирования заголовка и описания на странице редактирования. Далее распишем каждый из них.
Начнем с рендер функции MYMODULE_tabs_render(). Размещаем в этом же файле. В данной функции получаем все добавленные табы, пробегаем по каждому из них для формирования массива ссылок и отдаем теме theme_item_list() для построения обычного HTML списка.
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 |
/** * Render callback for MYMODULE tabs. */ function MYMODULE_tabs_render($subtype, $conf, $args, $context) { $block = new stdClass(); $tabs = $conf['tabs']; $items = array(); foreach ($tabs as $tab) { if ($tab['enabled']) { $items[] = l($tab['name'], $tab['path']); } } $content['#markup'] = theme('item_list', array( 'items' => $items, 'title' => '', 'type' => 'ul', 'attributes' => array('class' => array('clearfix')), )); $path = drupal_get_path('module', 'MYMODULE'); $content['#attached']['css'][] = $path . '/css/MYMODULE_tabs.css'; $block->content['content'] = $content; return $block; } |
MYMODULE_tabs.css содержит немного кода для придания ссылкам стиля табов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
.pane-mymodule-tabs ul.clearfix { margin: 0; padding: 0; } .pane-mymodule-tabs ul.clearfix li { list-style: none; float: left; border: 1px solid #73909e; padding: 0.1em 0.5em; margin: 0.2em; border-radius: 5px; background: #A0C4D2; } .pane-mymodule-tabs ul.clearfix li:hover { cursor: pointer; } .pane-mymodule-tabs ul.clearfix li a { text-decoration: none; color: #fff; font-family: Arial, Helvetica, sans-serif; } |
Следующие два - MYMODULE_tabs_admin_title() и MYMODULE_tabs_admin_info() отвечают за отображение административного заголовка и краткой информации о добавленных табах соответственно. Функция MYMODULE_tabs_get_names() является вспомогательной, которая агрегирует и возвращает массив заголовков табов.
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 |
/** * Returns the administrative title for a module. */ function MYMODULE_tabs_admin_title($subtype, $conf) { $title = 'MYMODULE tabs'; $items = MYMODULE_tabs_get_names($conf); $title .= !empty($items) ? ': ' . implode(', ', $items) : ''; return $title; } /** * Returns admin info about Module pane. */ function MYMODULE_tabs_admin_info($subtype, $conf, $context) { $block = new stdClass(); $items = MYMODULE_tabs_get_names($conf); $block->title = 'Expand for view the added tabs'; $block->content = !empty($items) ? implode(', ', $items) : t('No available info.'); return $block; } /** * Returns the array of tab names. */ function MYMODULE_tabs_get_names($conf) { $tabs = $conf['tabs']; $items = array(); foreach ($tabs as $tab) { if ($tab['enabled']) { $items[] = $tab['name']; } } return $items; } |
И, наконец, остается функция формы настроек, с помощью которой и будет осуществляться добавление табов и их конфигурация. Т.к. данная функция будет иметь аякс операции (добавление/удаление), то необходимо чтобы она всегда была в области видимости. Если расположить ее в тот же файл, где находится описание плагина - форма не будет видна после аякс событий. Поэтому расположим ее, вместе с аякс колбеком в файле MYMODULE.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 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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
/** * MYMODULE tabs form. */ function MYMODULE_tabs_edit_form($form, &$form_state) { $conf = $form_state['conf']; $default = array( 'enabled' => 0, 'name' => '', 'path' => '', 'weight' => 0, ); $tabs = isset($conf['tabs']) ? $conf['tabs'] : array($default); if (isset($form_state['values']['tabs'])) { $tabs = $form_state['values']['tabs']; } // Если пользователь нажимает на кнопку "Добавить" - добавляем массив с дефолтными значениями. $is_trigger = isset($form_state['triggering_element']['#value']); if ($is_trigger && isset($form_state['triggering_element']['#add_more_id'])) { $tabs[] = $default; } // Если пользователь нажимает на кнопку "Удалить" - убираем соответствующий таб из массива. if ($is_trigger && isset($form_state['triggering_element']['#remove_id'])) { list(, $id) = explode('|', $form_state['triggering_element']['#remove_id']); unset($tabs[$id]); } $form['tabs'] = array( '#type' => 'container', // Будем рендерить через свою темирующую функцию. '#theme' => 'MYMODULE_tabs', '#tree' => TRUE, ); // Кнопка "добавить". $form['add_more'] = array( '#type' => 'button', '#add_more_id' => 'mymodule-button-add-more', // Важное свойство, не позволяющее аякс кнопке сабмитать форму. '#executes_submit_callback' => FALSE, '#value' => t('Add more'), '#ajax' => array( 'callback' => 'MYMODULE_tabs_ajax_callback', 'wrapper' => 'mymodule-tabs-id', 'method' => 'replace', 'effect' => 'fade', ), ); foreach ($tabs as $key => $tab) { $form['tabs'][$key]['enabled'] = array( '#type' => 'checkbox', '#default_value' => $tab['enabled'], ); // Заголовок таба. $form['tabs'][$key]['name'] = array( '#type' => 'textfield', '#default_value' => $tab['name'], ); // Урл таба. $form['tabs'][$key]['path'] = array( '#type' => 'textfield', '#default_value' => $tab['path'], ); // Кнопка "удалить". $form['tabs'][$key]['del'] = array( '#type' => 'button', '#executes_submit_callback' => FALSE, '#value' => t('remove'), '#remove_id' => 'mymodule-remove-button-id|' . $key, '#name' => 'op-' . $key, '#disabled' => count($tabs) < 2, '#ajax' => array( 'callback' => 'MYMODULE_tabs_ajax_callback', 'wrapper' => 'mymodule-tabs-id', ), ); // Вес. $form['tabs'][$key]['weight'] = array( '#type' => 'weight', '#delta' => 10, '#title' => t('Weight'), '#title_display' => 'invisible', '#default_value' => $tab['weight'], '#attributes' => array('name' => 'weight[' . $key . ']'), ); } return $form; } |
В сабмит колбеке добавляем созданные/измененные табы в массив conf.
1 2 3 4 5 6 |
/** * Submit callback for MYMODULE_tabs_edit_form(). */ function MYMODULE_tabs_edit_form_submit($form, &$form_state) { $form_state['conf']['tabs'] = $form_state['input']['tabs']; } |
Ajax колбек, как и вся ajax логика взята из статьи Разные способы создания Ajax запросов
1 2 3 4 5 6 |
/** * Ajax callback for adding/removing tab. */ function MYMODULE_tabs_ajax_callback($form, &$form_state) { return $form['tabs']; } |
Так как список добавленных табов рендерится в виде таблицы с помощью темы MYMODULE_tabs, необходимо объявить ее в хук тем.
1 2 3 4 5 6 7 8 9 10 |
/** * Implements hook_theme(). */ function MYMODULE_theme() { return array( 'MYMODULE_tabs' => array( 'render element' => 'form', ), ); } |
И непосредственно сам код темы возвращает таблицу draggable, описанную по аналогии с третьим примером из статьи Создаем разные виды Drupal HTML таблиц (часть 1).
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 |
/** * Возвращает HTML для таблицы настроек табов. */ function theme_MYMODULE_tabs($variables) { $tabs = $variables['form']; $rows = array(); foreach (element_children($tabs) as $key) { $tabs[$key]['weight']['#attributes']['class'] = array('tabs-order-weight'); $rows[] = array( 'data' => array( drupal_render($tabs[$key]['enabled']), drupal_render($tabs[$key]['name']), drupal_render($tabs[$key]['path']), drupal_render($tabs[$key]['del']), drupal_render($tabs[$key]['weight']), ), 'class' => array('draggable'), ); } // Заголовок таблицы. $header = array( t('Enabled'), t('Tab name'), t('Path'), t('Action'), t('Weight'), ); $output = theme('table', array( 'header' => $header, 'rows' => $rows, 'empty' => t('Table is empty'), 'attributes' => array('id' => 'tabs-order'), 'sticky' => FALSE, )); $output = '<div id="mymodule-tabs-id">' . $output . '</div>'; $output .= drupal_render_children($form); drupal_add_tabledrag('tabs-order', 'order', 'sibling', 'tabs-order-weight'); return $output; } |
Также не забываем объявить хук hook_ctools_plugin_directory()[1], чтобы кастомный плагин был виден панелям и сбросить кеш.
1 2 3 4 5 6 7 8 |
/** * Implements hook_ctools_plugin_directory(). */ function MYMODULE_ctools_plugin_directory($module, $plugin) { if (in_array($module, array('panelizer', 'ctools', 'page_manager', 'panels'))) { return 'plugins/' . $plugin; } } |
Демонстрация работы
Для демо перейдем в настройки контента любой сущности.
Добавляем кастомный пейн.
Добавляем несколько табов (заполняем имя, путь, ставим галочку что включен и меняем вес по желанию).
Сохраняем изменения. Предварительно сохраненный пейн имеет теперь свой административный заголовок и описание.
Жмем кнопку Save и переходим на превью обновленной сущности.
Создание кастомных пейнов для контрибного модуля panels аналогично.
Дополнительная информация по статье
- http://www.drupalcontrib.org/api/drupal/contributions!ctools!ctools.api.php/function/hook_ctools_plugin_directory/7 - описание хука hook_ctools_plugin_directory.