Skip to main content

Виджеты (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 последовательности поиска и добавления плагинов в реестр. Версия плагина, добавленная в реестр позднее, получает больший приоритет перед другими версиями данного плагина и, таким образом, переопределяет его полностью или отдельные его части.

Последовательность поиска и включения виджетов в реестр​

  1. core-модули
  2. x- и contrib-модули
  3. app-модули
  4. родительские темы
  5. активная тема

На каждом следующем уровне могут быть переопределены/дополнены ресурсы предыдущего.

Важные принципы при переопределении виджета или его части​

  • Файл {widget-name}.tpl.php может быть только переопределён.
  • Файл {widget-name}.info.inc по умолчанию не переопределяет родительские версии, а расширяет (дополняет)
  • Файл {widget-name}.inc по умолчанию не переопределяет родительские версии, а расширяет (дополняет)
  • Файлы {widget-name}.css и {widget-name}.js по умолчанию не переопределяют родительские версии, а дополняют
  • Если требуется переопределить какой-либо файл, то в начале имени файла нужно добавить '-'
  • Если требуется переопределить виджет циликом, то перед названием его папки нужно добавить '-'. В этом случае все ранее добавленные в реестр виджетов файлы данного виджета не будут учитываться.
  • Если в виджете есть какие-либо переопределния или модификации, то перед названием его папки нужно добавить '~'. Это ни на что не повлияет, но даст понимание, что в данной папке не полноценный новый виджет, а переопределение частей уже существующего виджета.

Вложенные виджеты​

Виджеты могут быть вложены в папки, название которых входит в название виджета. По этому принципу можно группировать виджеты, а также вкадывать одни виджеты в другие. Если в начале названия виджета (а значит и его папки) есть символы '_' или '-', то они игнорируются. Добавление данных символов имеет следующий смысл:

  1. Если в начале названия виджета есть '_', то такой виджет считается внутренним (приватным) в рамках родительского виджета.
    Как правило, такие виджеты используется в качестве делегатов родительским виджетом.
  2. Если в начале названия виджета есть '-', то такой виджет полностью замещает все иные реализации данного виджета и запрещает какое-либо наследование ресурсов других имплиментаций данного виджета.

Соглашение по именованию вложенных виджетов:

  1. {widget-name}__element-name - вложенные виджеты, которые являются элементом (частью) родительского и не предназначены для использования вне родительского. По сути цель таких виджетов - лишь упростить структуру родительского виджета, разбив его на более мелкие составляющие.
  2. {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-запросом).
info

Под 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 - объект, представляющий данные для отрисовки строки datagrid
  • options - массив опций, которые можно передать для изменения отрисовки ячейки

Пример:

$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 '&mdash;';
}

$value = $row->{$options['fieldname']};

if (empty($value))
{
return '&mdash;';
}

$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]); //ТАК НЕЛЬЗЯ ИСПОЛЬЗОВАТЬ!
Примечания
  1. Можно использовать только с виджетами, которые имеют свойство $plugin_info['access']
  2. Нельзя передавать в 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