Виджеты (WidgetApi)
Система компонентов UI (виджетов), используемыех для формирования пользовательского интерфейса.
Система виджетов позволяет формировать страницу в виде набора компонентов (виджетов).
Система реализована на основе PluginApi и работает в соответствии с особенностями PluginApi, предоставляя плагины типа widget.
Виджеты могут располагаться как в модулях, так в темах.
На уровне файловой системы каждый виджет представляет собой папку вида:
{module_or_theme}/src/Plugins/widget/{widget-name}
Простые виджеты также могут быть сгруппированы и реализованы в рамках плагинного класса.
Файлы виджета
В папке виджета могут быть следующие файлы:
{widget-name}.info.incфайл с настройками{widget-name}.incфайл, представляющий собой контроллер виджета{widget-name}.tpl.phpфайл, представляющий собой php-шаблон{widget-name}.helpers.inc{widget-name}.css{widget-name}.element.css{widget-name}.js{widget-name}.elements.js{widget-name}.icon.pngили{widget-name}.icon.svg- иконка виджета{widget-name}__a-local-template.tpl.phpили@__a-local-template.tpl.php- локальные шаблоны, используемые виджетом (см. методrenderLocalTemplate()){widget-name}__a-local-file.some-extили@__a-local-file.some-ext- локальные файлы (например,inline.js), используемые виджетом (см. методreadLocalFile())папки с ресурсами(images, fonts, ...)папки вложенных виджетов- папки вложенных виджетов должны начинаться с{widget-name}, например:{widget-name}__element-name
Переопределение виджета
Виджеты могут быть переопределены.
Это достигается с учётом принятой в PluginApi последовательности поиска и добавления плагинов в реестр. Версия плагина, добавленная в реестр позднее, получает больший приоритет перед другими версиями данного плагина и, таким образом, переопределяет его полностью или отдельные его части.
Последовательность поиска и включения виджетов в реестр
- core-модули
- x- и contrib-модули
- app-модули
- родительские темы
- активная тема
На каждом следующем уровне могут быть переопределены/дополнены ресурсы предыдущего.
Важные принципы при переопределении виджета или его части
- Файл
{widget-name}.tpl.phpможет быть только переопределён. - Файл
{widget-name}.info.incпо умолчанию не переопределяет родительские версии, а расширяет (дополняет) - Файл
{widget-name}.incпо умолчанию не переопределяет родительские версии, а расширяет (дополняет) - Файлы
{widget-name}.cssи{widget-name}.jsпо умолчанию не переопределяют родительские версии, а дополняют - Если требуется переопределить какой-либо файл, то в начале имени файла нужно добавить '-'
- Если требуется переопределить виджет циликом, то перед названием его папки нужно добавить '-'. В этом случае все ранее добавленные в реестр виджетов файлы данного виджета не будут учитываться.
- Если в виджете есть какие-либо переопределния или модификации, то перед названием его папки нужно добавить '~'. Это ни на что не повлияет, но даст понимание, что в данной папке не полноценный новый виджет, а переопределение частей уже существующего виджета.
Вложенные виджеты
Виджеты могут быть вложены в папки, название которых входит в название виджета. По этому принципу можно группировать виджеты, а также вкадывать одни виджеты в другие. Если в начале названия виджета (а значит и его папки) есть символы '_' или '-', то они игнорируются. Добавление данных символов имеет следующий смысл:
- Если в начале названия виджета есть '_', то такой виджет считается внутренним (приватным) в рамках родительского виджета.
Как правило, такие виджеты используется в качестве делегатов родительским виджетом. - Если в начале названия виджета есть '-', то такой виджет полностью замещает все иные реализации данного виджета и запрещает какое-либо наследование ресурсов других имплиментаций данного виджета.
Соглашение по именованию вложенных виджетов:
{widget-name}__element-name- вложенные виджеты, которые являются элементом (частью) родительского и не предназначены для использования вне родительского. По сути цель таких виджетов - лишь упростить структуру родительского виджета, разбив его на более мелкие составляющие.{widget-name}--element-name- вложенные виджеты, которые могут использоваться и как элемент (часть) родительского и независимо от родительского.
Идентификация виджетов
Для вызова и отображения виджета с помощью функции widget($widget_impl_id,...) очень важно понимать,
как система производит поиск нужного виджета в реестре и что такое $widget_impl_id.
widget_impl_id уникально идентифицирует конкретный виджет на странице. Поскольку на одной и той же странице
могут быть отображены несколько виджетов одного типа, различающихся форматом отображения и содержанием,
должна быть возможность учитывать это различие с помощью widget_impl_id. Поэтому widget_impl_id далеко не всегда
соответствует реальной папке виджета.
Полный формат widget_impl_id слудующий: widget_id[--mod][~delta]
Здесь:
- widget_id - это название виджета в реестре виджетов. Это обязательная часть widget_impl_id. Ей всегда соответствует папка на виджета.
- [--mod] - это необязательный параметр модифакации виджета. Данный параметр никак не отражается в файловой структуре.
- [~delta] - это необязательный параметр индекса конкретного виджета на странице. Данный параметр никак не отражается в файловой структуре.
Примеры вызова widget():
widget('xmenu'); //будет вызван виджет xmenu
widget('xmenu--list'); //будет вызыван виджет xmenu, в $vars['id_data'] будет $vars['id_data']['mod']=='list'
widget('xmenu--list~1'); //будет вызыван виджет xmenu, в $vars['id_data'] будет $vars['id_data']['mod']=='list' и $vars['id_data']['delta']=='1'
widget('xmenu--list~2');
Файл {widget-name}.info.inc
Файл может содержать параметры и настройки виджета по умолчанию.
Все параметры и настройки определяются в виде элементов массива $plugin_info.
Как и все .info.inc файлы в рамках PluginAPI {widget-name}.info.inc подключается и обрабатывается
только в момент формирования реестра виджетов. После этого все параметры и настройки, добавленные в
$plugin_info, сохраняются в реестр и в дальнейшем загружаются из реестра.
Параметры $plugin_info:
Будут рассмотрены следующие важные параметры:
$plugin_info['hook_widget']$plugin_info['hook']$plugin_info['name']$plugin_info['description']$plugin_info['settings']$plugin_info['hook_element']$plugin_info['access']$plugin_info['tags']
$plugins_info['hook_widget'] - настройки вызова виджета с помощью функции widget().
Если значение этого параметра "пусто", то такой виджет нельзя вызвать при помощи функций widget().
$plugin_info['hook_widget'] может содержать следующие параметры:
arguments- ассоциативный массив с названиями и дефолтными значениями аргументов, которые могут быть переданы при вызовеwidget().defaults- ассоциативный массив с названиями и дефолтными значениями всех переменных, которые могут быть переданы в .tpl.php данного виджета. ВАЖНО: все возможные параметры, передаваемые в .tpl.php виджета, ДОЛЖНЫ БЫТЬ перечислены вdefaults!hook- widget_id виджета в реестре. По умолчанию widget_id виджета устанавливается в{widget-name}. Но с помощью этого параметра widget_id можно изменить. Именно widget_id передаётся первым параметром вwidget().hook_mod_name- если такой параметр добавлен, то значение модификатора, добавленного в widget_impl_id, будет добавлено в$vars['{hook_mod_name}'']. Например, еслиhook_mod_name=>'type', то значение модификатора будет добавлено в$vars['type'].hook_delta_name- всё тоже самое дельты как и для модификатора.
Пример hook_widget виджета 'page'
$plugin_info['hook_widget'] = [
//В массиве 'arguments' обязательно следует перечислить все аргументы, которые могут быть переданы в виджет.
//ВАЖНО: порядок перечисления аргументов в данном случае важен!
//Желательно добавить описания к аргументам
'arguments' => [
'content' => null, //html-контент страницы
'show_block' => false, //
'show_messages' => true, //добавлять ли разметку для отображения Flash-сообщений на странице
],
//В массиве 'defaults' обязательно следует перечислить все переменные, которые используются в шаблоне виджета.
//Желательно добавить описания к переменным
'defaults' => [
'head_title' => null, // Содержание тега <title></title>
'head' => false, // Содержание тега <head></head>
'styles' => true, // Отрисованные теги подключения файлов стилей
'scripts' => true,
'content' => true,
'messages' => true,
'closure' => true,
'dataTheme' => 'default',
'body_classes' => '',
'bootstrap_touch_icons' => null,
'posterUrl' => null,
'isFront' => false,
],
];
$plugin_info['hook'] - уникальный ID виджета
Если добавлен этот параметр, то именно он будет использоваться в качестве идинтификатора виджета.
В этом случае именно это значение следует использовать в функции widget().
Данный параметр не обязателен. Его не следует использовать без крайней необходимости.
Если параметр hook не указан, то в качестве идентификатора виджета будет использоваться название его папки.
Данный параметр в основном используется в случаях, когда нет возможности использовать разделитель '-' в названиии виджета. В настоящее время это необходимо для виджетов форм и элементов форм.
$plugin_info['name'] - название виджета
Если добавлен этот параметр, то именно он будет использоваться в любых интерфейсах, где требуется упоминание виджета. Например, во всплывающем уведомлении после сохраненения настроек виджета. Т.е. это "человекочитаемое" название виджета.
Это не обязательный параметр, но рекомендуется всегда его добавлять.
$plugin_info['description'] - описание виджета
Если добавлен этот параметр, то он будет использоваться в панели управления виджетами, а также других административных интерфейсах.
Это не обязательный параметр, но рекомендуется всегда его добавлять для виджетов, у которых есть форма настроек.
$plugin_info['settings'] - массив конфигурационных параметров виджета
С помощью $plugin_info['settings'] можно добавить настройки виджета. Если виджет имеет это свойство, то при активации режима
настроек виджетов в правом верхнем углу виджета появиться иконка 'ред', при клике по которой откроется окно настроек параметров,
определённых в $plugin_info['settings'].
Структура $plugin_info['settings'] должна быть следующей:
$plugin_info['settings']['<param_name1>']=[
'value' => '<значение по умолчанию>',
'form_element' => [ //Определение элемнта формы, который будет использоваться для изменения этого свойства в форме настроек виджета
'#type' => 'select',
'#title' => 'Параметр 1',
'#options' => [...]
],
];
Пример:
//// Файл {widget-name}.info.inc
$plugin_info['hook_widget'] = true;
$plugin_info['name'] = 'Главный баннер';
//Форма настроек данного виджета
$plugin_info['settings']["text1"] = [
'value' => '',
'form_element' => [
'#title' => 'Основной текст баннера',
'#type' => 'textarea',
'#rows' => 4,
'#description' => 'В тексте вы можете использовать тег [br] для переноса на новую строку.',
],
];
$plugin_info['settings']["text2"] = [
'value' => '',
'form_element' => [
'#title' => 'Дополнительный текст баннера',
'#type' => 'textarea',
'#rows' => 4,
'#description' => 'В тексте вы можете использовать тег [br] для переноса на новую строку.',
],
];
$plugin_info['settings']["btnLabel"] = [
'value' => '',
'form_element' => [
'#title' => 'Текст кнопки',
'#type' => 'textfield',
'#attributes' => [
'placeholder' => 'Например: Попробовать бесплатно…',
],
],
];
$plugin_info['settings']["photo"] = [
'value' => '',
'form_element' => [
'#title' => "Изображение",
'#type' => 'image_upload',
'#destination' => 'landing/mainbanner/',
'#description' => 'Рекомендуемое разрешение 749×734px. Формат — PNG или WEBP.',
],
];
//Можно использовать 'markup' элемент для нестандартного оформления формы настроек
//(хотя в данном случае лучше использовать элемент 'fieldset')
$plugin_info['settings']["itemOpen2"] = [
'form_element' => [
'#type' => 'markup',
'#value' => "
<div class=\"b-form-xwidget-editor__section\">
<div class=\"b-form-xwidget-editor__section-label\">Настройка панели счётчиков</div>
<div class=\"b-form-xwidget-editor__section-content\">",
],
];
$plugin_info['settings']["countClassesAndProgramms"] = [
'value' => '',
'form_element' => [
'#title' => 'Число классов и программ',
'#type' => 'input_integer',
'#attributes' => [
'placeholder' => 'Например: 200',
],
],
];
$plugin_info['settings']["countDanceStyles"] = [
'value' => '',
'form_element' => [
'#title' => 'Число танцевальных стилей',
'#type' => 'input_integer',
'#attributes' => [
'placeholder' => 'Например: 20',
],
],
];
$plugin_info['settings']["countTeachers"] = [
'value' => '',
'form_element' => [
'#title' => 'Число Хореографов',
'#type' => 'input_integer',
'#attributes' => [
'placeholder' => 'Например: 100',
],
],
];
$plugin_info['settings']["counterupDuration"] = [
'value' => '1500',
'form_element' => [
'#title' => 'Время анимации счётчиков',
'#description' => 'Чем больше время анимации, тем дольше крутяся счётчики. Чтобы отключить анимацию счётчиков установите значени 0',
'#type' => 'input_integer',
'#field_suffix' => 'мсек',
'#attributes' => [
'placeholder' => 'Например: 2000',
],
],
];
$plugin_info['settings']["itemClose2"] = [
'form_element' => [
'#type' => 'markup',
'#value' => "
<div class=\"b-form-xwidget-editor__clear\"></div>
</div>
</div>",
],
];
//Мы можем при необходимости также определить свои (нестандартные обработчики для формы настроек данного виджета)
//Эти обработчики должны быть помещены в файл {widget-name}.helpers.inc
$plugin_info['settings_access']['callback'] = '_masterLandingMainbannerAccessSettings';
$plugin_info['settings_after_submit callback'] = '_masterLandingMainbannerSettingsAfterSubmit';
$plugin_info['settings_form_alter'] = '_masterLandingMainbannerSettingsFormAlter';
//// Файл {widget-name}.helpers.inc
/**
* Ф-ция разрешение на доступ к форме настроек виджета.
*/
function _masterLandingMainbannerAccessSettings($widgetImplInfo)
{
return User::checkPerm('administer nodes') ? true : false;
}
/**
* Ф-ция submit обработчика формы настроек виджета.
*/
function _masterLandingMainbannerSettingsFormAlter(&$form, $widgetImplInfo)
{
...
}
/**
* Ф-ция submit обработчика формы настроек виджета.
*/
function _masterLandingMainbannerSettingsAfterSubmit($form, &$formState)
{
$values = $formState['values'];
// Обновляем статус загруженных файлов.
$fid = $values["photo"][0]['fid'];
if (!empty($fid)) {
$file = EM::getRepository(FileEntity::class)->find($fid);
$file->status = 1;
EM::save($file);
}
}
$plugin_info['hook_element'] - доп. настройки для виджетов типа "Элемент формы"
Если добавлено, то такой виджет будет обрабатываться как элемент форм. Он может использоваться при построении форм
как и другие элементы форм. Значение $plugin_info['hook_element'] представляет собой ассоциативный массив с
настройками согласно описанию хука hook_element.
Для таких виджетов требуется установка значения параметра $plugininfo['hook'] в значение, в котором все '-' заменены на ''.
Также такие виджеты должны иметь $plugin_info['hook_widget']['arguments']=['form'=>null,...];
Пример виджета, реализующего элемент формы (и имеющего 'hook_element')
$plugin_info['hook_widget'] = [
'hook' => 'input_integer', // это название элемента, по которому к нему можно обратиться
'arguments' => ['element' => null], // при определении элементов форм, первый аргумент такого виджета всегда 'element'
];
$plugin_info['hook_element'] = [
'#input' => true,
'#size' => 60,
'#maxlength' => 24,
'#mask' => null, // можно задать маску в виде шаблона '99 999'
'#min' => null, // можно задать минимальное значение, например: 0
'#max' => null, // можно задать минимальное значение, например: 1000
'#element_validate' => 'element_input_integer_validate', // можно передать название функции-валидатора
'#process' => [ // здесь определяем обработчики, которые будут срабатывать при построении элемента
'form_element_input_process_display_mode',
'element_input_integer_process',
],
];
$plugin_info['access'] - настройки доступа к виджету
С помощью параметра access можно определить настройки доступа к данному виджету: Если access добавлен в $plugin_info, то перед тем, как сформировать и отобразить содержимое виджета, будет проверятся, имеет ли текущий пользователь достаточно прав на него. Проверка проводиться с помощью функции system_check_widget_access() из модуля system.
Добавление $plugin_info['access'] в настройки виджета бывает необходима в следующих ситуациях:
- для виджетов, отображаемых в админке, доступ к которым зависит от прав пользователя
- для виджетов, которые могут загружаться в
lazzy-режиме* (т.е. отдельным ajax-запросом).
Под lazzy-режимом отображения виджета понимается режим, когда контент виджета подгружается отдельным ajax-запросом. Это может быть полезно, например, при загрузке рекламных блоков или тяжёлых мультимедийных блоков. Чтобы отобразить виджет в lazzy-режиме, необходимо воспользоваться функцией widget_lazzy(). Подробное описание lazzy-режима будет приведено ниже.
Пример определения $plugin_info['access']
$plugin_info['access'] представляет собой ассоциативный массив, который может включать в себя следующие ключи:
callback-callable, который будет вызываться для проверки прав пользователя. В качестве аргументов этотcallbableполучит на вход$plugin_info['access']['arguments], если оно было передано. Если$plugin_info['access']['callback']не указан явно, то будет использован вариант по умолчанию -User::checkPerms($perms).arguments- содержит индексированный массив пермишенов, который будут переданы в качестве первого аргумента$plugin_info['access']['callback']default_value- значение (trueилиfalse), которое будет использоваться, когда не указано ни$plugin_info['access']['callback'], ни$plugin_info['access']['arguments']. Значениеtrueиспользуется в сочетании с флагомdynamic_check.dynamic_check- флаг (true), который означает, что проверка доступа к виджету будет проводится в его .inc файле. Это необходимо, когда логика проверки слишком сложна и требует дополнительных данных, доступных только на этапе выполнения .inc-файла виджета.
Пример определения $plugin_info['access']
// Для выполнения виджета, требуется, чтобы у пользователя были права 'administer payments' или 'adminter users'
$plugin_info['access'] = [
'arguments' => [
'administer payments',
'administer users',
],
];
Файл {widget-name}.inc
Файл, представляющий собой препроцессор виджета.
Код этого файла первым получает управление при каждом вызове виджета с помощью widget().
Данный файл можно использовать для следующих задач:
- Изменение аргументов, переданных в
widget() - Добавление переменных в массив
$varsдля передачи в шаблон - Вызов других виджетов
- Подключение доп. рессурсов (с помощью
$clientApi) - Прекращение работы виджета (метод
$widgetApi->stop()) - Делегирование выполнения другим виджетам (метод
$widgetApi->delegate()) - Добавление блоков в регионы страницы (с помощью
$layoutApi)
В результате работы фронт-контроллера виджета $widgetApi->buildWidgetVars($widget_impl_id,$args) создаётся и заполняется массив $vars.
Все переменные из этого массива затем преобразуются в реальные переменные с помощью extract(). Если в описании виджета был добавлен массив дефолтных значений переменных, передаваемых в шаблон виджета ($plugin_info['defaults']), то перед включением файла шаблона эти переменные будут также преобразованы в реальные перменные с помощью extract(). При этом уже существующие переменные не будут перезаписываться, поскольку extract() вызвается для этих переменных в режиме EXTR_SKIP.
Крайне рекомендуется для виджетов, у которых есть шаблон, обязательно добавлять $plugin_info['defaults'] и перечислять в нём все переменные, которые могут быть в шаблоне.
$vars
Содержит аргументы, переданные на входwidget(),
переменные, переданные в других контроллерах данного виджета,
переменные, переданные в препроцессорах данного виджета
системные данные, переданные подсистемой widgetApi$widget_info
Содержит данные виджета, полученные из реестра виджетов.
В частности$widget_info['settings']содержит настройки виджета (если они были заданы в .info.inc файле).$rootclass
Содержит имя css-класса виджета. Не должно изменяться. По умолчанию класс имеет видb-{widget-name}.$widget_classes
Содержит набор классов виджата. Включает в себя класс$rootclass.
Если при отрисовке виджета требуется добавить css-классы, то необходимо добавлять их в$widget_classes.
Пример:$widget_classes. = " b-xpopup-data-container {$rootclass}_some_prop1 {$rootclass}_some_prop2 ";$widget_attrs
Содержит набор аттрибутов виджата.
Если при отрисовке виджета требуется добавить аттрибуты, то необходимо добавлять их в$widget_attrs.
Пример:$widget_attrs. = " data-xpopup-caption='Привет' ";$vars['widget_id']
Название виджета в реестре виджетов$vars['widget_impl_id']
Уникальный идентификатор виджета на странице (именно он передаётся в функциюwidget())$vars['widget_id_data']
Массив данных об id виджета, его модификаторе и дельте$vars['widget_info']
Информация о виджете из реестра виджетов$vars['widget_args']
Индексированный массив аргументов (кроме первого), поступивших на входwidget()илиwidget_lazzy()$vars['widget_output']
Результат работы виджета$widgetApi
Ссылка на API подсистемы виджетов, которая возвращается путём вызоваxlib(WidgetPluginRegistry::class).
Содержит ряд важных методов для управления работой виджета. Подробное описание наиболее важных методов
будет рассмотрено ниже в разделе Описание API-методов.$clientApi
Ссылка на API подсистемы клиентских плагинов. Позволяет подключать клиентские плагины.$layoutApi
Ссылка на API подсистемы сеток, управляющих распределением виджетов внутри страницы или родительских виджетов.
Файл {widget-name}.tpl.php
Файл шаблона виджета, который используется при отрисовке html-представления виджета.
Стандартная структура шаблона должна быть следующей:
<div <?php print $widgetApi->renderAttrs($vars); ?> >
...
</div>
Если виджет имеет элементы, которые требуется стилизовать, то необходимо добавить им классы.
При этом классы должны начинаться с <?php print $rootclass; ?>__.
Пример:
<div <?php print $widgetApi->renderAttrs($vars); ?> >
<div class="<?php print $rootclass; ?>__some-element1">
...
</div>
<div class="<?php print $rootclass; ?>__some-element2">
...
</div>
</div>
Файл {widget-name}.css
Файл CSS-стилей виджета.
Эти стили подключатся только один раз при первом использовании данного виджета.
Если в реестре есть несколько имплиментаций виджета, которые содержат {widget-name}.css,
то все версии {widget-name}.css будут подключены в порядке от самой базовой до активной.
Если требуется полностью запретить подключение других версий {widget-name}.css,
то необходимо в начало названия файла необходимо добавить '-', т.е. полное имя должно получиться -{widget-name}.css.
Вы также можете добавить в свой виджет файл {widget-name}.elements.css. Принцип работы данного файла такой же с одним важным отличием: все .elements.css файлы всех виджетов страницы никогда не подключаются по одиничке, а предварительно объединяются в один файл elements-{hash}.css. Это позволяет не переживать, что большое кол-во мелких виджетов на странице приведёт к большому числу запросов к серверу для загрузки всех этих файлов.
Файл {widget-name}.js
Файл js-скриптов виджета.
Эти скрипты подключатся только один раз при первом использовании данного виджета.
Если в реестре есть несколько имплиментаций виджета, которые содержат {widget-name}.js,
то все версии {widget-name}.js будут подключены в порядке от самой базовой до активной.
Если требуется полностью запретить подключение других версий {widget-name}.js,
то необходимо в начало названия файла необходимо добавить '-', т.е. полное имя должно получиться -{widget-name}.js.
Вы также можете добавить в свой виджет файл {widget-name}.elements.js. Принцип работы данного файла такой же с одним важным отличием: все .elements.css файлы всех виджетов страницы никогда не подключаются по одиничке, а предварительно объединяются в один файл elements-{hash}.js. Это позволяет не переживать, что большое кол-во мелких виджетов на странице приведёт к большому числу запросов к серверу для загрузки всех этих файлов.
Файл {widget-name}.helpers.inc
Если виджет имеет файл {widget-name}.helpers.inc,
то такой файл будет подключаться один раз перед началом первого использования виджета.
Это позволяет использовать данный файл для хранения функций и классов, необходимых для логики работы виджета.
Если в реестре есть несколько имплиментаций виджета, которые содержат {widget-name}.helpers.inc,
то все версии {widget-name}.helpers.inc будут подключены.
Если требуется полностью запретить подключение других версий {widget-name}.helpers.inc,
то необходимо в начало названия файла необходимо добавить '-', т.е. полное имя должно получиться -{widget-name}.helpers.inc.
Поскольку функции/классы попадают в глобальное пространство имён необходимо соблюдать правила именования, исключающие появление нескольких функций/классов с одинаковым именем.
Но более удобным и надёжным подходом можно считать использование namespace.
Пример .helpers.inc-файла для виджета 'liteforum-node':
namespace modules\liteforum\Plugins\widget\LiteforumNode;
use XSyst\Format\FormatApi;
function renderVotingWidget(array $vars, NodeEntity $node): string
{
$vud = widget('xvoting-rating', 'liteforum_node', $node->nid, [
'uid' => $node->uid,
]);
return $vud;
}
Основные типы виджетов
Для удобства терминологии выделим несколько основных типов виджетов:
- Обработчики запросов (master-виджеты)
- Блоки (block)
- Формы (form)
- Элементы форм (form_element)
- Элементы (element)
- Ячейки datagrid (datagrid-cell)
- Прочие виджеты
Для каждого типа виджета определено своё правило именование. Описание этих правил с примерами приведено ниже. Рекомендуется строго придерживаться этих правил именования.
Обработчики запросов (master)
Это такие виджеты, которые используются для обработки запроса и формирования конечного ответа.
Правило именования:
master-{path} (для app)
или
master-{module}-{path}
Пример:
master-home
master-users
master-catalog-products
Блоки (block)
Это такие виджеты, которые используются напрямую для вставки в регионы сетки.
Как правило, блоки не имеют arguments.
Также именно для блоков чаще всего требуется добавление settings, что даёт возможность настройки блока через веб-интерфейс.
Правило именования:
block-{name} (для app)
или
block-{module}-{name}
Пример:
block-users-online
block-banner
block-banner_home
block-catalog-slide-banner
block-catalog-hits
Формы (form):
Это такие виджеты, которые содержат полное определение формы.
Для таких виджетов требуется установка значения параметра $plugin_info['hook'] в значение, в котором все '-' заменены на '_'.
Также такие виджеты должны иметь $plugin_info['hook_widget']['arguments']=['form'=>null,...];.
Такие виджеты должны иметь .helpers.inc файл, в котором необходимо добавить стандартные FormAPI ф-ции
для определения структуры формы, валидации и обработки ответа.
Правило именования:
form-{name} (для app)
или
form-{module}-{name}
Пример:
form-user-profile
form-catalog-filters
Элементы форм
Это такие виджеты, которые представляют собой новый тип элемента форм.
Такие элементы затем могут использоваться при создании форм согласно общим правилам FormAPI.
Для таких виджетов требуется установка значения параметра $plugin_info['hook'] в значение, в котором все '-' заменены на '_'.
Также такие виджеты должны иметь $plugin_info['hook_widget']['arguments']=['element'=>null,...];
Такие виджеты могут иметь .helpers.inc файл, если требуется добавить validate или process-функции для данного элемента.
Кроме этого необходимо добавить в .info.inc файл определение свойств элемента с помощью $plugin_info['hook_element'].
$plugin_info['hook_element'] представляет собой ассоциативный массив свойств элемента, удовлетворяющий требованиям hook_element.
Пример 1 - простой input-элемент:
$plugin_info['hook_widget'] = [
'hook' => 'simple_textarea',
'arguments' => [
'element' => null,
],
];
$plugin_info['hook_element'] = [
'#input' => true,
'#cols' => 60,
'#rows' => 3,
'#process' => [
'form_element_input_process_display_mode',
'form_expand_ahah',
],
];
Реализация логики работы и отрисовки для такого элемента помещается в .inc файл и может выглядеть примерно так:
use XSyst\Form\FormApi;
$class = ['form-simple-textarea form-control'];
// Add resizable behavior
if ($element['#resizable'] !== false) {
$class[] = 'resizable';
}
xlib(FormApi::class)->formSetClass($element, $class);
return widget('form_element', $element, '<textarea cols="'.$element['#cols'].'" rows="'.$element['#rows'].'" name="'.$element['#name'].'" id="'.$element['#id'].'" '.htmlTagAttrs($element['#attributes']).'>'
.escapeHtml($element['#value']).'</textarea>');
Правило именования:
form-element-{name} (для app)
или
form-element-{module}-{name}
Пример:
form-element-property-grid
form-element-xtable-xtable
Элементы (element):
Это такие "маленькие" виджеты, которые никогда не могут быть использованы, как самостоятельные блоки.
Они всегда используются для отрисовки какой-то определенной и небольшой части других виджетов.
Правило именования:
element-{name} (для app)
или
element-{module}-{name}
Пример:
element-node-title
element-node-body
Ячейки datagrid (datagrid-cell):
Это такие виджеты, которые предназначены для отрисовки ячеек datagrid.
В аргументах таких виджетов должно быть 2 параметра:
row- объект, представляющий данные для отрисовки строки datagridoptions- массив опций, которые можно передать для изменения отрисовки ячейки
Пример:
$plugin_info['hook_widget'] = [
'arguments' => ['row' => null, 'options' => null],
];
Шаблон, как правило не используется в таких виджетах, поскольку с одной стороны не требуется сложная вёрстка, а с другой стороны требуется максимально высокая производительность. В связи с этим рекомендуется использовать анонимную функцию-отрисовщик, возвращаемую из .inc-файла.
Пример:
return function (array &$vars, $row, array $options = null): string
{
//с помощью опций можно, например, передать название поля в объекте $row,
//которое нужно использовать для отрисовки ячейки
if (!isset($row->{$options['fieldname']}))
{
return '—';
}
$value = $row->{$options['fieldname']};
if (empty($value))
{
return '—';
}
$format = !empty($options['format']) ? $options['format'] : '';
if(empty($options['format'])) {
$value = $this->formatApi->formatDate($value, 'medium');
} else {
$value = $this->formatApi->formatDate($value, 'custom', $format);
}
return $value;
};
Правило именования:
datagrid-cell-{cell-type-name}
Пример:
datagrid-cell-datetime
Прочие виджеты:
Это такие виджеты, которые по совокупности признаков не удалось отнести ни к одной из выше перечисленных категорий.
Правило именования:
{name} (для app)
или
{module}-{name}
Пример:
user-list
user-list-item
xcomments-comments
xcomments-comments-item
Именование делегатов
Делегаты - это такие виджеты, которые не могут быть вызваны напрямую.
Они могут получить задачу только от другого управляющего виджета и отработать за него.
Такие виджеты должны лежать внутри управляющего виджета и их название должно начинаться с подчёркивания.
Примеры:
xcomments-list
_xcomments-list-tree-view
Виджеты для админки
В системе может быть множество виджетов, предназначенныл только для админки.
Правила такие же как описано выше за исключением того, что название всегда должно начинаться с admin-.
Примеры:
admin-master-users
admin-form-module-edit
admin-block-users-registrations
admin-catalog-master-products
admin-catalog-block-products-summary
admin-catalog-form-product-edit
Дополнительные рекомендации по именованию и группировке виджетов
При формировании названия виджетов необходимо стараться придерживаться системы.
Также стараться отражать иерархию, типизацию.
Это позволит эффективно использовать возможности вложения виджетов друг в друга.
Примеры:
node-list //виджет для отрисоки список материалов
node-list--item //виджет, который необходим для node-list, но может использоваться и независимо от него
node-list--item_blog //модификация node-list--item для типа blog
node-list--item_article //модификация node-list--item для типа article
node-list--item__title //часть (неотъемлимый элемент) виджета node-list--item для отрисовки названия
node-list_article //виджет для отрисоки список материалов типа 'article'
Использование анонимных функций в .inc-файлах для реализации высокопроизводительных виджетов
Согласно рекомендации по структуре виджета необходимо разделять логику работы виджета (препроцессор) от его представления (шаблон). Препроцессор реализуется с помощью .inc файла. Шаблон - с помощью .tpl.php файла.
Для виджетов, которые могут вызываться многократно на странице, это будет приводить к большому числу одинаковых I/O операций, связанных с чтением/парсингом .inc и .tpl.php файлов при каждом вызове widget(). Современные версии PHP умеют неплохо кэшировать многократное подключение одних и тех же php-файлов. Тем не менее, всё равно гораздо быстрее работает вызов уже готовой функции.
С учётом этого WidgetApi поддерживает следующий трюк - в .inc файле добавляется:
return function(array &$vars, inc $param1, string $param2, $param3=null): string
{
// ...
// Вся логика работы и отрисовки виджета здесь
// ...
};
Дополнительным преимуществом использования анонимных функций является возможность использовать типизированные аргументы.
В результате после первого вызова виджета WidgetApi закэширует анонимную функцию в static хэше, выполнит функцию и вернёт результат.
При следующем вызове этого же виджета WidgetApi не будет подключать .inc файл, а сразу выполнит данную функцию и вернёт результат.
Пример - виджет element-node-title:
return function(array &$vars, NodeEntity $node): string
{
$title = escapeHtml($node->title);
return $title;
};
Примеры виджетов, при реализации которых имеет смысл использовать данную технику:
- username
- user-avatar
- submitted
- element-node-title
WidgetApi
В рамках данного раздела рассмотрим наиболее важные программные средства, которые доступны прикладным программистам при работе с WidgetApi.
- API-функции
WidgetApi - API-методы класса
WidgetPluginRegistry
API-функции WidgetApi
Для высокоуровневой работы с виджетами предусмотрены следующие важные функции:
- Функция
widget(...) - Функция
widget_lazzy(...)
Функция widget(...)
Данная функция предназначена для выполнения виджета.
Это основная функция для работы с виджетами. Она принимает на вход набор аргументов, необходимых для выполнения виджета. При этом первый аргумент всегда - название виджета.
Примеры использования:
print widget('node-tags', $node, APP_NODE_TAGS_VID);
print widget('node-comments', $node->nid);
Функция widget_lazzy(...)
Данная функция предназначена для выполнения виджета в lazzy-режиме (или ленивого-режима) отрисовки.
По сути данная функция производит генерацию контейнера для ленивого виджета.
Ленивый виджет - это такой виджет, содержимое которого подгружается в отдельном ajax-запросе
и вставляется в нужное место страницы (контейнер ленивого виджета).
Принцип действия:
1) widget_lazzy() генерирует контейнер "ленивого" виджета, а также подключает клиентский плагин xwidget_lazzy_loader, который обеспечивает обработку и заполнение таких контейнеров после загрузки страницы.
Контейнер "ленивого" виджета должен содержать 2 data-аттрибута, с помощью которых виджет будет однозначно идентифицироваться:
data-xwidget-type- это название виджетаdata-xwidget-id- это id экземпляра виджета. Может быть пустым. По умолчанию используется второй аргумент, переданный вwidget_lazzy()1) После загрузки страницы начинает работать javascript-плагинxwidget_lazzy_loader, который извлекает из контейнера 2 дата-аттрибута: data-xwidget-type и data-xwidget-id, формирует и отправляет ajax-запрос//<base_url>/xwidget/load/%widgetType/%widgetIdна сервер для получения контента виджета. 2) Выполняет запрос и обрабатывает ответ сервера: сгенерированный контент заменяет содержимое контейнера данного виджета.
Примеры использования:
$banner = widget_lazzy('block-xad','main-banner');
$comments = widget_lazzy('block-xcomments');
$user_list = widget_lazzy('user-list', ['count'=>10]); //ТАК НЕЛЬЗЯ ИСПОЛЬЗОВАТЬ!
- Можно использовать только с виджетами, которые имеют свойство
$plugin_info['access'] - Нельзя передавать в
widget_lazzy()более 2-х аргументов или сложный (array, object) 2-ой аргумент. Такие виджеты пока не могут быть однозначано идентифицированы.
Примеры виджетов, которые отображаются в lazzy-режиме:
block-xadв модулеxad
Класс WidgetPluginRegistry
Логика работы подсистемы виджетов реализована в классе WidgetPluginRegistry, который представляет собой реализацию репозитория плагинов типа 'widget' в рамках PluginApi.
Как и любая другая подсистема, построенная на базе PluginApi, подсистема виджетов предоставляет доступ к своему API с помощью xlib(WidgetPluginRegistry::class):
$widgetApi = xlib(WidgetPluginRegistry::class);
В контроллере виджета (.inc-файлы) и в шаблоне виджета (.tpl.php-файлы) уже есть объект $widgetApi,
который рекомендуется использовать вместо вызова xlib(WidgetPluginRegistry::class).
Класс WidgetPluginRegistry предоставляет следующие собственные публичные методы:
- addPluginAssets()
- addAttr()
- addAttrs()
- setAttrs()
- setRootClass()
- setClasses()
- appendClass()
- appendClasses()
- prependClass()
- prependClasses()
- addLocalCssFile()
- addLocalJsFile()
- readLocalFile()
- renderLocalTemplate()
- renderStandardWrapper()
- runWidgetImplPreprocessor()
- stop()
- delegate()
- call_widget_preprocess()
- buildWidgetVars()
- loadWidgetSettings()
Метод addPluginAssets()
Данный метод подключает все клиентские ресурсы виджета (css,js), если они есть. При этом никакой работы по выполнению контроллера или шаблона виджета не происходит.
Пример вызова addPluginAssets():
xlib(WidgetPluginRegistry::class)->addPluginAssets('xcomments-list'); //подключим xcomments-list.css и xcomments-list.js
Метод addAttr(array &$vars, string $attrName, string $attrValue)
Добавляет в конец списка аттрибутов виджета $vars['widget_attrs'] аттрибут $attrName = '$attrValue'.
Данный метод предназначен для вызова в .inc файле виджета.
Пример использоования:
$widgetApi->addAttr($vars, 'id', 'comment-'.$attrValue);
$widgetApi->addAttr($vars, 'data-xpopup-caption', 'Просмотр сообщения от '.$authorName);
Метод addAttrs(array &$vars, array $attrs)
Добавляет в конец списка аттрибутов виджета $vars['widget_attrs'] аттрибуты из ассоциативного массива $attrs.
Данный метод предназначен для вызова в .inc файле виджета.
Пример использоования:
$widgetApi->addAttrs($vars, [
'id' => 'comment-'.$attrValue,
'data-xpopup-caption' => 'Просмотр сообщения от '.$authorName,
]);
Метод setAttrs(array &$vars, array $attrs)
Перезаписывает список аттрибутов виджета $vars['widget_attrs'] аттрибутами из ассоциативного массива $attrs.
Данный метод предназначен для вызова в .inc файле виджета.
Пример использоования:
$widgetApi->setAttrs($vars, [
'id' => 'comment-'.$attrValue,
'data-xpopup-caption', 'Просмотр сообщения от '.$authorName,
]);
//Очистить все аттрибуты виджета
$widgetApi->setAttrs($vars, []);
Метод setRootclass(array &$vars, string $newClass)
Перезаписывает rootclass виджета, если $vars['rootclass'] != $newClass.
В результате изменяются следующие переменные:
$vars['rootclass']$vars['widget_classes']
Пример использования:
//Где-то в liteforum-node-comment-item.inc:
$widgetApi->setRootclass($vars, 'b-comment');
Метод setClasses(array &$vars, array $classes)
Перезаписывает список классов виджета $vars['widget_classes'] классами $classes.
Данный метод предназначен для вызова в .inc файле виджета.
Пример использоования:
$widgetApi->setClasses($vars, [$rootclass,'b-xfixto-container',$rootclass.'_status_active']);
Метод appendClasses(array &$vars, array $classes)
Добавляет в конец списка классов виджета $vars['widget_classes'] классы $classes.
Данный метод предназначен для вызова в .inc файле виджета.
Пример использоования:
$widgetApi->appendClasses($vars, ['b-xfixto-container',$rootclass.'_status_active']);
Метод appendClass(array &$vars, string $className)
Добавляет в конец списка классов виджета $vars['widget_classes'] класс $className.
Данный метод предназначен для вызова в .inc файле виджета.
Пример использоования:
$widgetApi->appendClass($vars, 'b-xfixto-container');
$widgetApi->appendClass($vars, $rootclass.'_status_active');
Метод prependClasses(array &$vars, array $classes)
Добавляет в начало списка классов виджета $vars['widget_classes'] классы $classes.
Данный метод предназначен для вызова в .inc файле виджета.
Пример использоования:
$widgetApi->prependClasses($vars, ['b-xfixto-container',$rootclass.'_status_active']);
Метод prependClass(array &$vars, string $className)
Добавляет в начало списка классов виджета $vars['widget_classes'] класс $className.
Данный метод предназначен для вызова в .inc файле виджета.
Пример использоования:
$widgetApi->prependClass($vars, 'b-xfixto-container');
$widgetApi->prependClass($vars, $rootclass.'_status_active');
Метод addLocalCssFile(array $widgetImpl, string $localFilePath): bool
Подключает css-файл $localFilePath, локальный по отношению к виджету определяемого через $vars.
Полезно, когда требуется програмно (в .inc-файле) подключить доп. стили виджета (например, скины).
Примеры использования:
$widgetApi->addLocalCssFile($widget_info,"user-list-skin-ios.css");
$widgetApi->addLocalCssFile($widget_info,"[widget_id]-skin-ios.css");
$widgetApi->addLocalCssFile($widget_info,"@-skin-ios.css");
$widgetApi->addLocalCssFile($widget_info,"@-skin-ios"); //Рекомендуемый способ именования файлов
Метод addLocalJsFile(array $widgetImpl, string $localFilePath): bool
Подключает js-файл $localFilePath, локальный по отношению к виджету $widgetImpl.
Полезно, когда требуется програмно (в .inc-файле) подключить доп. скрипты виджета.
Примеры использования:
$widgetApi->addLocalJsFile($widgetImpl,"user-list-skin-ios.js");
$widgetApi->addLocalJsFile($widgetImpl,"[widget_id]-skin-ios.js");
$widgetApi->addLocalJsFile($widgetImpl,"@-skin-ios.js");
$widgetApi->addLocalJsFile($widgetImpl,"@-skin-ios"); //Рекомендуемый способ именования файлов
Метод readLocalFile(array $widgetImplInfo, string $localFilePath): string
Загружает контент локального по отношению к виджету $widget_impl_info файла $localFilePath.
Полезно при использовании Vuejs-компонентов: позволяет вынести js-логику в отдельные .inline.js файлы, а в шаблоне загружаить и вставлять их содержимое в нужное место. В настоящий момент это единственный надёжный способ выделения логики vuejs в отдельный файл.
Примеры использования:
print $widgetApi->readLocalFile($widget_info,"user-list.inline.js");
print $widgetApi->readLocalFile($widget_info,"[widget_id].inline.js");
print $widgetApi->readLocalFile($widget_info,"@.inline.js");
Метод renderLocalTemplate(array $vars, string $localFilePath): string
Подключает контент локального по отношению к виджету определяемого через $vars файла $localFilePath.
Полезно, когда требуется разбить отрисовку большого виджета на более мелкие части (шаблоны), но при этом не хочется создавать подвиджеты или делегаты.
Примеры использования:
print $widgetApi->renderLocalTemplate($vars,"user-list__items.tpl.php");
print $widgetApi->renderLocalTemplate($vars,"[widget_id]__items.tpl.php");
print $widgetApi->renderLocalTemplate($vars,"@__items.tpl.php");
print $widgetApi->renderLocalTemplate($vars,"@__items");
Метод renderStandardWrapper(array $vars, string $html): string
Оборачивает $html в \<div> с добавленными в него widget_classes и widget_attrs.
В некоторых случаях позволяет избавиться от .tpl.php виджета.
Примеры использования:
//.inc файл виджета, отрисовывающего список элементов, поддерживающий ajax-подгрузку.
$vars['items'] = $items;
$itemsRendered = $widgetApi->renderLocalTemplate($vars, '@__items');
if($isAjax) {
return $itemsRendered;
}
$clientApi->require('xextlinks');
return $widgetApi->renderStandardWrapper($vars, $itemsRendered);
Метод runWidgetImplPreprocessor(array &$vars, string $moduleType, string $moduleName): bool
Выполняет препроцессор виджета $vars['widget_id'] из имплиментации $moduleType:$moduleName.
Пример использования:
$res1 = $widgetapi->runWidgetImplPreprocessor($vars, 'module', 'system');
$res2 = $widgetapi->runWidgetImplPreprocessor($vars, 'theme', 'main');
Метод stop(&$vars,$content = null)
Данный метод позволяет остановить дальнейшее выполнение виджета.
1) Если второй параметр $content = null, то шаблон виджета не будет вызван, а ресурсы виджета не будут подключены.
2) Если второй параметр $content не пустая строка, то шаблон виджета не будет вызван, а в качестве результата работы widget() вернёт $content.
Ресурсы виджета будут подключены.
Примеры использования:
//Отменить применение виджета
return $widgetApi->stop($vars);
//Отменить применение виджета без вызова stop()
return false;
//Отменить отрисовку шаблона (если он есть), в качестве результата использовать текст второго параметра
return $widgetApi->stop($vars,'Шаблон виджета не используем! Отображаем этот текст!');
//То же самое без использования stop()
return 'Шаблон виджета не используем! Отображаем этот текст!';
Метод delegate(&$vars, $widget_id)
Передаёт (делегирует) управление, а также все переменные текущего виджета виджету-модификатору $widget_id.
Под виджетом-модификатором понимается виджет, находящийся в папке текущего виджета и зависящий от данных,
подготовленных в основном виджете.
Подразумевается, что виджеты-модификаторы не будут вызываться напрямую с помощью widget(). Рекоммендуется название виджета-модификатора начинать с '_'.
Предназначен для вызова в *.inc файлах виджетов. Если виджет-модификатор имеет шаблон, то он выполняется
и передаётся в $vars, как результат работы виджета.
Примеры использования:
Допустим есть виджет xpager, а также его виджеты-модификаторы _xpager_normal, _xpager_more.
В xpager.inc готовим данные ($vars), после чего передаём управление подходящему виджету-модификатору.
Например, так:
if($options['type']=='more')
$widgetApi->delegate($vars,'_xpager_more');
else
$widgetApi->delegate($vars,'_xpager_normal');
Метод call_widget_preprocess(&$vars,$widget_id)
Выполняет .inc файлы на всех имплиментациях плагина $widget_id, в которых они есть.
Вызывается автоматически при обработке виджета. Также может быть вызван в ручном ручном режиме в .inc файлах виджетов, если требуется выполнить контроллеры другого виджета на $vars текущего виджета.
Примеры ручного вызова call_widget_preprocess():
Пусть есть виджет block-homepage-banner, в контроллере которого выполняется вся работа по получению данных баннеров для отображения. Предположим, что мы создали виджет block-super-banner, в контроллере которого нам необходимо получить те же данные, что и в контроллере block-homepage-banner. В этом случае досаточно в block-super-banner.inc сделать следующий вызов:
$widgetApi->call_widget_preprocess($vars,'block-homepage-banner');
//здесь можно что-то ещё сделать с $vars, если нужно..
Метод buildWidgetVars($hook, $args = [])
Подготавливает переменные для передачи отрисовщику
Предназначена для вызова внутри метода WidgetPluginRegistry::renderWidget().
Но может быть вызвана и отдельно, когда требуется получить массив подготовленных переменных виджета.
Пример работы:
$widgetImplId = 'xmenu--vert~1';
//Получим $vars для виджета.
//Это необходимо для получения $vars['widget_info'], который потребуется для
//загрузки настроек виджета
$wigetApi = xlib(WidgetPluginRegistry::class);
$vars = [];
$wigetApi->buildWidgetVars($widgetImplId, $vars);
$widgetInfo = $vars['widget_info'];
//теперь, например, можем получить настройки виджета
$widgetApi->loadWidgetSettings($widgetInfo, $vars);
$settings = $widgetInfo['settings'];
Метод loadWidgetSettings(&$widget_info,$vars,$widget_theme=null)
Если виджет имеет $plugin_info['settings'],
то эта фукнция должна использоваться в .inc-файле виджета для загрузки его настроек из базы данных.
$widgetApi->loadWidgetSettings($widget_info,$vars);
$settings = $widget_info['settings'];
Примеры реальных виджетов
Приведенные ниже примеры можно использовать для изучения или в качестве основы при создании новых виджетов.
Модуль xmsgp, плагин xmsgp-view-page
Использование делегатовМодуль xmsg2, плагин xmsg-filter-form
Использование .helpers.inc
Реализация формы в виде виджетаМодуль xmsgp, плагин xmsgp-form
Использование .helpers.inc
Реализация формы в виде виджетаМодуль xcomments, плагин xcomments-list
Группировка множества вспомогательных виджетов
Использование делегатовМодуль xcomments, плагин _xcomments-list-tree-view
Использование .helpers.inc
Использование namespaceМодуль xnodes, плагин element-node-title
Создание елементарных виджетовМодуль xtools, плагин handler-error-403
Создание виджетов обработчиков запросовМодуль xtools, плагин xmenu
Использование$plugin_info['settings']
Использование$plugin_info['widget_impl_id']Модуль xmsg_tmpl, плагин xmsg_template_select_elem
Создание простых элементов для FormApiМодуль xtable, плагин xtable
Создание сложных элементов для FormApi