Сетки (LayoutApi)
Подсистема сеток позволяет управлять расположением виджетов на странице или в любом другом контейнере.
В качестве фреймворка для реализации данной плагинной системы использован PluginApi. Поэтому технически LayoutApi реализована в виде PluginApi-плагинов типа layout и работает в соответствии с особенностями PluginApi.
Layout-плагин - это именованная сетка, которая имеет набор именованных регионов, которые могут быть использованы для группировки виджетов, добавленных в сетку.
Сетки могут располагаться как в модулях, так в темах.
На уровне файловой системы каждая сетка представляет собой папку вида
{module_or_theme}/src/Plugins/layout/{layout-name}
Регионы
Регион - это именованная и неделимая область (переменная), имеющая своё расположение в шаблоне сетки. Регион используется для группировки блоков переданных в сетку извне.
Каждая сетка имеет строго заданный набор регионов. Каждая сетка имеет по крайней мере один регион main.
Регионы могут иметь доп. настройки, с помощью которых можно указать стиль группировки блоков, кэширование, дефолтное содержание.
Для включения определённого стиля региона необходимо передать соответствующие настройки региона.
Примеры:
layout_info.regions.main.style.type='xtabs'
layout_info.regions.main.style.type='xslider'
Стили отображения регионов
В настоящий момент доступны следующие стили отображения регионов:
defaultxtabsxslider
Рассмотрим подробнее каждый из этих стилей.
Стиль 'default'
Простое вертикальное склеивание блоков региона в порядке их добавления. Каждый блок оборачивается в следующий html-код:
<div class='b-xl__block $class'>
<div class='b-xl__block-title h2'><span>$title</span></div>
<div class='b-xl__block-content'>$block_html</div>
</div>
Стиль 'xtabs'
Отображение блоков региона в виде табов с помощью модуля xtabs.
Отображаются только блоки, у которых есть title.
Возможна передача параметров в widget('xtabs') с помощью настроек стиля региона.
Например:
layout_info.regions.main.style=['type'=>'xtabs','params'=>['id'=>'xxx','format'=>'accordion','js.history'=>1]]
Стиль 'xslider'
Отображение блоков региона в виде слайдов с помощью клиентского плагина xslider.
Задание настроек для региона производится следующим образом:
layout_info.regions.<region_name>.<option_name>=<value>;
или
$data['layout_info']['regions']['<region_name>']['<option_name>']=<value>;
В настоящий момент поддерживаются следующие опции:
style- Настройки стиля отображения регионаdefault_block- Здесь можно указать дефолтный блок, который будет добавляться в том случае, если содержимое региона по какой-то причине оказалось пустым
Блоки
Блок - это HTML-код, который, в зависимости от настроек при передаче в layout, встраивается в один из регионов layout.
Существует несколько способов передачи блоков в layout:
- В виде готового html-кода в формате
['src'=>"<html_code>"]или даже проще<html_code> - В виде виджета в формате
['src'=>'widget:<widget_name>','args'=>[...]] - В виде "ленивого" виджета в формате
['src'=>'widget_lazzy:<widget_name>','args'=>[...]] - В виде формы в формате
['src'=>'form:<form_id>','args'=>[...]] - В виде массива с настройками для вложенного layout в формате
['src'=>['layout'=>'<some_layout>','blocks'=>[[...]]] - В виде командных блоков в формате
['src'=>'cmd:<cmd_id>', ...]
В общем случае в настройки блока можно передавать следующие параметры:
src- Источник контента для блокаargs- (опционально и только для источника в виде виджета или формы) Набор аргументов в виде индексированного массива для передачи в theme-функциюregion- (по умолчанию используется регион main) Название региона, в который следует встроить блокtitle- (опционально) Заголовок, который будет ассоциирован с блоком. Стиль отображения заголовка может зависеть от текущего стиля региона. Например, если для региона выбран стильtabsилиaccordion, то заголовок блок будет использован в качастве названия вкладки. Чтобы запретить отображение title для такого блока, достаточно установить title в<none>.class- (опционально) CSS-классы, которые добавятся в обёртку блокаattrs- (опционально, пока не поддерживается) HTML-аттрибуты, которые добавятся в обёртку блокаid- (опционально) id блока
Примеры добавления блоков в сетку
// Блоки на странице хаба.
$blocks = [];
//Добавим форму фильтрации в правую колонку
$blocks[] = [
'src' => 'form:form_hashhub_filters',
'region' => 'sidebar2',
'args' => [$hub, $hashes],
'weight' => 0,
];
//Добавим карточку хаба в правую колонку
$blocks[] = [
'src' => 'widget:block-hashhub-card',
'region' => 'sidebar2',
'args' => [$hub],
'weight' => 100,
];
$advSidebarKey = Config::getValue('xtagsHashHubPageAdvSidebar', '');
if (!empty($advSidebarKey)){
//Добавим ленивый виджет рекламного баннера в правую колонку в самый низ
$blocks[] = [
'src' => 'widget_lazzy:block-xad',
'args' => [$advSidebarKey],
'region' => 'sidebar2',
'title' => '<none>',
'weight' => 900,
];
}
//В главную колонку в самый верх добавим список хэшей хаба
$blocks[] = [
'src' => 'widget:block-hashhub-hashes',
'region' => 'sidebar2',
'weight' => -1,
];
//Выключим регион content_header, поскольку не требуется отображение заголовка страницы
$blocks[] = [
'src' => 'cmd:disable_region',
'region' => 'content_header',
];
//Добавим блоки в сетку страницы
$layoutApi->addPageBlocks($blocks);
Добавление коммандных блоков в регион
Рассмотрим отдельно вариант передачи командных блоков. Коммандный блок заставляет LayoutApi выполнить определённую команду над набором блоков и регионов текущей сетки.
На данный момент существуют следующие команды:
disable_region- Выключает регион. Выключенный регион полностью игнорируетсяLayoutApiпри формировании сетки
Пример:$blocks[] = [
'src' => 'cmd:disable_region',
'region' => 'content_header',
];
$layoutApi->addPageBlocks($blocks);enable_region- Включает регион, если он был выключен ранее.
Пример:$blocks[] = [
'src' => 'cmd:enable_region',
'region' => 'content_header',
];
$layoutApi->addPageBlocks($blocks);clear_region- Очищает регион (т.е. удаляет все блоки, содержащиеся в нём), указанный в параметреregion.
Пример:$blocks[] = [
'src' => 'cmd:clear_region',
'region' => 'content_header',
];
$layoutApi->addPageBlocks($blocks);
Описание файлов сетки
В папке виджета могут быть следующие файлы:
{layout-name}.info.incфайл с настройками{layout-name}.tpl.phpфайл, представляющий собой php-шаблон сетки{layout-name}.incфайл, представляющий собой контроллер сетки{layout-name}.helpers.inc{layout-name}.css{layout-name}.js{layout-name}.icon.pngили{layout-name}.icon.svg- изображение-схема сеткипапки с ресурсами(images, fonts, ...)
Что даёт LayoutApi
- Чёткое разделение сетки и блоков, что даёт возможность сосредоточиться на разработке блоков как самостоятельных строительных элементов интерфейса, не беспокоясь о настройке отступов, а также размещении блоков внутри сетки в зависимости от типа и размера устройства (компьютер, планшет, смартфон).
- Спецификация единых подходов при построении пользовательского интерфейса страниц и отдельных элементов
- Выделение типовых сеток при построении интерфейсов. Это формирует определённость и стабильность.
- Наделение сеток именем, что добавляет семантику коду интерфейсов
- Единый стандарт к разработке интерфейсов
- Повторное использование сеток
- Выработка чёткого и лаконичного "словаря" приложения
- Дополнительные возможности по оптимизации, мониторингу и отладке кода
Создание новых сеток
Поскольку сетка это всегда PluginApi-плагин типа layout, то добавление новой сетки сводится к следующим шагам:
В модуле или теме создаём папку
src/Plugins/layout/{layout-name}(например,src/Plugins/layout/page)Создаём .info.inc файл с определением сетки.
$plugin_info['name']='Шаблон страницы';
$plugin_info['regions']=[
'content_header'=>[
'name'=>'Шапка контента',
'visibility' => [
'response_statuses'=> ['!403'], //Регион не будет отображаться в случае ошибки 403
// 'perms' => [],
// 'pathes' => ['!<front>'],
],
],
'content_toolbar'=>[
'name'=>'Тулбар контента',
'visibility' => [
'response_statuses'=> ['!403'],
],
],
'messages'=>[
'name'=>'Сообщения и нотификации системы',
],
'tabs'=>[
'name'=>'Табы',
],
'main_top'=>[
'name'=>'Верх содержимого',
'blocks' => [ //Блоки будут добавляться в регион по умолчанию
[
'src'=>'widget:content-header_default',
'default_block'=>true, //Блок будет добавлен в регион только, если в этот регион не было добавлено других блоков
]
],
],
'main'=>[
'name'=>'Содержимое'
],
'main_bottom'=>[
'name'=>'Низ содержимого',
'visibility' => [
'response_statuses'=> ['!403'], //Блок будет отображаться только, если это не страница ошибки 403
],
],
'sidebar1'=>[
'name'=>'Левая колонка',
'visibility' => [
'response_statuses'=> ['!403'],
],
'blocks' => [ [ 'src'=>'widget:xmenu--vert'] ],
],
'sidebar2'=>[
'name'=>'Правая колонка',
'visibility' => [
'response_statuses'=> ['!403'],
],
],
'page_header'=>[
'name'=>'Шапка страницы',
'blocks' => [ ['src'=>'widget:page-header'] ],
],
'page_footer'=>[
'name'=>'Подвал страницы',
'blocks' => [ ['src'=>'widget:page-footer'] ],
],
];
$plugin_info['scope'] = 'page'; // Для сеток, используемых для отрисовки страниц, необходимо указать этот параметрСоздаём файл шаблона
src/Plugins/layout/page/page.tpl.php
В файле шаблона создаём необходимую разметку и отображаем в нужных местах регионы.В файле
layout-шаблона всегда доступна переменная$data, имеющая следующую структуру:$data=[
'blocks' => [], // Исходный массив блоков
'regions' => [], // Массив полностью обработанных и готовых для отображения регионов
];Пример шаблона
page.tpl.php:<div class='b-page'>
<?php print $data['regions']['page_header']; ?>
<div class="b-page__content" >
<?php if($data['regions']['content_header']): ?>
<div class="b-page__content-top">
<?php print $data['regions']['content_header']; ?>
<?php print $data['regions']['content_header__toolbar']; ?>
</div>
<?php endif; ?>
<div class="<?php print $main_class; ?> b-page__content-main">
<?php print $data['regions']['main']; ?>
</div>
<?php if($data['regions']['content_footer']): ?>
<div class="b-page__content-bottom">
<?php print $data['regions']['content_footer']; ?>
</div>
<?php endif; ?>
</div>
<?php print $data['regions']['page_footer']; ?>
</div>
Отрисовка сетки
Для того чтобы отрисовать любую сетку, достаточно сделать вызов:
$html = print widget('xlayout',$layoutData)`; //, где `$layoutData` - это массив с настройками сетки и массив блоков.
Отрисовка страницы
Несмотря на то, что LayoutApi можно использовать для отрисовки любых сеток внутри любых виджетов, основное назначение LayoutApi - это управление отрисовкой запрашиваемой страницы.
Это удобно и позволяет получить полный контроль на структурой любой страницы сайта.
Вам становятся доступны следующие методы манипулирования структурой страницы:
- Использование возможности автоматического выбора подходящяей сетки исходя из адреса текущей страницы
- Использования хука
hook_page_layout_variants_alter()для управления выбором подходящей сетки для текущей страницы - Использования хука
hook_page_layout_alter()для управления содержимым сетки текущей страницы - Использования
LayoutApi-методов:- Для добавления блоков в сетку текущей страницы
- Для удаления блоков из сетки текущей страницы
- Для отключения регионов из сетки текущей страницы
Далее будут подробно рассмотрены все эти возможности.
Пример добавления блоков на страницу
Чаще всего использование LayoutApi сводится к простому добавлению блоков в сетку страницы.
Пример:
$blocks = [
['src' => 'widget:block-xad', 'args' => ['main_banner'], 'region' => 'main_top'],
['src' => 'widget:block-homepage-banner','region'=>'main_top'],
['src' => 'widget:block-xad', 'args' => ['main_banner2'], 'region' => 'main_top'],
['src' => 'widget:block-events','region'=>'main_top'],
['src' => 'widget:block-forum-updates','region'=>'main_top'],
['src' => 'widget:block-wish-and-poll','region'=>'main_top'],
['src' => 'widget:block-reading','region'=>'main_top'],
['src' => 'widget:block-clubs','region'=>'main_top'],
['src' => 'widget:block-users-online','region'=>'main_top'],
['src' => 'widget:block-photo-video','region'=>'main_top'],
['src' => 'widget:block-xad', 'args' => ['main_banner5'], 'region' => 'main_top'],
];
$layoutApi->addPageBlocks($blocks);
Использование хука hook_page_layout_alter()
С помощью данного хука любой модуль сможет полностью переопределить настройки для передачи в widget('xlayout', $data) текущей страницы, т.е. выбрать подходящий для тек. страницы layout, а также сформировать настройки отображения блоков, добавить доп. блоки.
Данный хук будет срабатывать только для Server::isFrontContext().
Для Server::isAdminContext() используйте хук hook_admin_page_layout_alter() можно изменить массив вариантов сеток для текущего пути и таким образом повлиять на выбор сетки.
Пример, как может выглядеть реализация hook_page_layout_alter():
//Implements hook_page_layout_alter().
function app_page_layout_alter(array &$data): void
{
$context_api = xlib(ContextPluginRegistry::class);
$ctx_main=$context_api->get_main_context();
$ctx_node=$context_api->get_context('node');
$ctx_user=$context_api->get_context('user');
switch($ctx_main['type']){
case 'user':
$data['layout'] = 'user_page',
if($ctx_main['data']['type']=='root'){
$data['blocks'][]=['src'=>'widget:content-header_user-root','region'=>'content_header'];
}
break;
case 'blogs':
case 'adverts':
if($ctx_node){
if($ctx_node['data']['mode']=='view'){
$data['blocks'][]=['src'=>'widget:content-header_author-node','region'=>'content_header'];
}
}
else if($ctx_user){
$data['blocks'][]=['src'=>'widget:content-header_user-section','region'=>'content_header'];
}
break;
case 'marks':
if($ctx_node && $ctx_node['data']['mode']=='view'){
$data['blocks'][]=['src'=>'widget:content-header_marks-node','region'=>'content_header'];
$node = $ctx_node['data']['node'];
$data['blocks'][]=['src'=>'widget:node_cars','args'=>[$node],'region'=>'content_footer'];
}
break;
case 'reviews':
if($ctx_node && $ctx_node['data']['mode']=='view'){
$data['blocks'][]=['src'=>'widget:content-header_reviews-node','region'=>'content_header'];
}
else if($ctx_user){
$data['blocks'][]=['src'=>'widget:content-header_user-section','region'=>'content_header'];
}
break;
}
$data['#regions']['page_header']['default_block']=['src'=>'widget:page__header'];
$data['#regions']['page_footer']['default_block']=['src'=>'widget:page__footer'];
if(!Url::isPath('<front> user/%d/garage')){
$data['#regions']['content_header']['default_block']=['src'=>'widget:content-header_default'];
}
}
Использование хука hook_page_layout_variants_alter()
При отрисовке страницы LayoutApi выбирает наиболее подходящую сетку. По умолчанию выбор сетки для текущей страницы делается автоматически на основе текущего пути: рассчитывается наиболее подходящая по названию под текущий url path сетка.
LayoutApi формирует массив вариантов сеток для текущего пути. Варианты в конце массива считаются более специфичным (т.е. более подходящими) и поэтому имеют больший приоритет при определении сетки для страницы.
Принцип формирования массива вариантов покажем на примерах ниже.
Пример 1 - Для пути node/%node/delete будет сформирован следующий массив:
Для пути $path = 'node/%node/delete' будет сформирован следующий массив:
$layoutVariants = [
"page",
"page-x",
"page-x-x",
"page-x-x-x",
"page-node",
"page-node-x",
"page-node-x-x",
"page-node-add",
"page-node-add-x",
"page-node-add-page",
];
Пример 2 - Для пути user/%/settings/profile будет сформирован следующий массив:
$layoutVariants = [
"page",
"page-x",
"page-x-x",
"page-x-x-x",
"page-x-x-x-x",
"page-user",
"page-user-x",
"page-user-x-x",
"page-user-x-x-x",
"page-user-x-x",
"page-user-x-x-x",
"page-user-x-settings",
"page-user-x-settings-x",
"page-user-x-settings-profile",
];
Для Server::isFrontContext() с помощью хука hook_page_layout_variants_alter() можно изменить массив вариантов сеток для текущего пути и таким образом повлиять на выбор сетки.
Для Server::isAdminContext() с помощью хука hook_admin_page_layout_variants_alter() можно изменить массив вариантов сеток для текущего пути и таким образом повлиять на выбор сетки.
/**
* Implementation of hook_page_layout_variants_alter().
*/
function app_page_layout_variants_alter(array &$pageLayoutVariants): void
{
$layoutApi = xlib(LayoutPluginRegistry::class);
$contextApi = xlib(ContextPluginRegistry::class);
$ctxMain = $contextApi->get_main_context();
$ctxNode = $contextApi->get_context('node');
if (empty($ctxNode) && in_array($ctxMain['type'], ['articles', 'contests', 'events', 'parties', 'reviews'])) {
if (!Url::isPath('calendar')) {
$layoutApi->appendPageLayoutVariant('page-nodes');
$layoutApi->appendPageLayoutVariant("page-nodes-{$ctxMain['type']}");
}
}
if ($ctxMain['type'] == 'clubs' && $ctxMain['data']['type'] == 'page') {
if (empty($ctxNode) || $ctxNode['data']['type'] == 'view') {
$layoutApi->appendPageLayoutVariant('page-node-club-x');
$clubSection = arg(2);
if ($clubSection) {
$layoutApi->appendPageLayoutVariant("page-node-club-{$clubSection}");
}
}
}
if (Url::isPath('(photo|video|audio)*')) {
$mediaType = arg(0);
$layoutApi->appendPageLayoutVariant('page-media');
$layoutApi->appendPageLayoutVariant("page-media-{$mediaType}");
}
if (xlib(NodeUtils::class)->isNodePage('forum')) {
$layoutApi->appendPageLayoutVariant('page-node-forum');
}
// Страница всех отправок формы.
if (Url::isPath('user/%d/xpay/transactions*,login,signup,pass')) {
$layoutApi->appendPageLayoutVariant('page-x');
}
}
LayoutApi
В рамках данного раздела рассмотрим наиболее важные программные средства, которые доступны прикладным программистам при работе с LayoutApi.
- API-методы класса
LayoutPluginRegistry
Класс LayoutPluginRegistry
Логика работы подсистемы сеток реализована в классе LayoutPluginRegistry, который представляет собой реализацию репозитория плагинов типа layout в рамках PluginApi.
Как и любая другая подсистема, построенная на базе PluginApi, подсистема сеток предоставляет доступ к своему API с помощью xlib(LayoutPluginRegistry::class):
$layoutApi = xlib(LayoutPluginRegistry::class);
В контроллере виджета (.inc-файлы) и в шаблоне виджета (.tpl.php-файлы) уже есть объект $layoutApi, который рекомендуется использовать вместо вызова xlib(LayoutPluginRegistry::class).
Класс LayoutPluginRegistry предоставляет следующие собственные публичные методы:
renderLayout(array $data): stringaddPageBlocks(array $blocks): voidsetPageRegionStyle(string $region, string $style): voidappendPageLayoutVariant(string $variant): voidremovePageLayoutVariant(string $variant): voiddelegate(array &$data, string $layoutId = null, bool $overrideSettings = true): void
Метод renderLayout(array $data): string
Данный метод формирует html для layout-сетки, определяемой настройками $data.
Пример вызова addPluginAssets():
xlib(LayoutPluginRegistry::class)->renderLayout($data);
Метод addPageBlocks(array $blocks): void
Позволяет добавить блоки в сетку, отвечающую за отрисовку страницы.
Пример:
$blocks=[
['src'=>'form:form_events_filter','region'=>'sidebar2','weight'=>-1],
['src'=>'widget:section-menu','region'=>'sidebar'],
];
$layoutApi->addPageBlocks($blocks);
Метод setPageRegionStyle(string $region, string $style): void
Позволяет установить стиль региона $region
Пример:
$layoutApi->setPageRegionStyle('main', 'list');
Метод appendPageLayoutVariant(string $variant): void
Добавляет вариант сетки для отрисовки страницы в конец массива вариантов.
Перед добавлением варианта сначала производится его удаление. Это гарантирует однократное присутствие сетки в массиве вариантов.
Добавленный вариант автоматически получает приоритет перед уже добавленными, поскольку варианты из конца массива имеют приоритет перед вариантами из начала.
Примеры использования:
$layoutId = $layoutApi->appendPageLayoutVariant('page-node-article');
Метод removePageLayoutVariant(string $variant): void
Удаляет вариант сетки для отрисовки страницы из массива вариантов.
Примеры использования:
$layoutId = $layoutApi->removePageLayoutVariant('page-node-article');
Метод delegate(array &$data, string $layoutId = null, bool $overrideSettings = true): void
Передаёт (делегирует) управление, а также все переменные от текущей сетки к сетке $layoutId.
Предназначена для вызова в inc-файлах сеток.
Пример:
// Допустим есть сетка page-node, а такжа сетка page-node-blog.
// При попадании в обработчик сетки page-node-blog мы хотим использовать в качестве основы (шаблон, настройки, стили)сетку page-node, но при этом добавить некоторые доп. блоки. Эту задачу можнорешить так:
$data['blocks'][] = ['src' => 'widget:block-specific-for-blog','region' => 'sidebar'];
$res = $layoutApi->delegate($data, 'page-node');
if($res) { //делегирование успешно настроено, сетка заменена
//Здесь мы также можем поменять настройки $data['layout_info']
$data['layout_info']['params']['regions']['main_top'] = [
'name'=>'Верх содержимого',
'blocks' => [ ['src'=>'widget:content-header_default','default_block'=>true] ] ,
];
}