Перейти к основному содержимому

Клиентские плагины (ClientApi)

Система для создания именованных наборов ресурсов (js, css, fonts), необходимых на стороне клиента.

В качестве фреймворка для реализации данной плагинной системы использован PluginApi. Поэтому технически ClientApi реализована в виде PluginApi-плагинов типа client и работает в соответствии с особенностями PluginApi.

Клиентские плагины могут располагаться как в модулях, так в темах.

На уровне файловой системы каждый плагин представляет собой папку вида

  {module_or_theme}/src/Plugins/client/{plugin-name}
tip

Подсистема клиентских плагинов тесно связана с классом AssetBundler, который отвечает за группировку, оптимизацию и минификацию клиентских ресурсов, запрошенных в рамках для текущего запроса. Поэтому для полного понимания и контроля над процессом подключения клиентских ресурсов рекомендуется ознакомиться и с работой AssetBundler.

Описание файлов плагина

В папке плагина могут быть следующие файлы:

  • {plugin-name}.info.inc файл с определением и настройками плагина
  • {plugin-name}.js автоматически подключаемый js-файл плагина
  • {plugin-name}.css автоматически подключаемый css-файл плагина
  • дополнительные js-файлы
  • дополнительные css-файлы
  • папки с необходимыми ресурсами, библиотеками, плагинами (vendor, images, fonts, ...)

Переопределение ресурсов плагина

Плагины могут быть переопределены. Это достигается с учётом принятой в PluginApi последовательности поиска и добавления плагинов в реестр. Версия плагина, добавленная в реестр позднее, получает больший приоритет перед другими версиями данного плагина и, таким образом, переопределяет его полностью или отдельные его части.

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

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

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

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

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

Файл {plugin-name}.info.inc

Файл содержит параметры и настройки плагина по умолчанию.
Все параметры и настройки определяются в виде элементов массива $plugin_info.

Как и все .info.inc файлы в рамках PluginApi файл {plugin-name}.info.inc подключается и обрабатывается только в момент формирования реестра плагинов типа client. После этого все параметры и настройки, добавленные в $plugins_info, сохраняются в реестр и в дальнейшем загружаются из реестра.

Параметры $plugins_info

Будут рассмотрены следующие важные параметры:

  • $plugin_info['defaults']
  • $plugin_info['resources']
  • $plugin_info['start']
  • $plugin_info['autostart']
  • $plugin_info['default_bundle']
'defaults' - дефолтные настройки плагина

Массив настроек плагина, которые будут использоваться по умолчанию при подключении плагина.
Список настроек полностью зависит от конкретного плагина.

Любые настройки, переданные в defaults имеют меньший приоритет перед опциями, передаваемыми в аргументе $options метода require($plugin, $options) при подключении плагина.

Для плагинов, имеющих функцию автозапуска, важным параметром является $plugin_info['defaults']['selector']. С его помощью можно определить css-селекторы элементов страницы, к которым будет применено действие плагина.

С помощью параметра $plugin_info['defaults']['allow'] можно определить условия, при которых будет разрешено использование плагина.

Возможные варианты условий:

  • <IF:MOBILE> - true, если запрос с мобильного устройства
  • <IF:NOT_MOBILE> - true, если запрос с немобильного устройства
  • <IF:PROD> - true, если активирован режим 'production'
  • <IF:DEV> - true, если активирован режим 'development'

Условия можно объединять (по правилу "логическое И"):

  $plugin_info['defaults']['allow'] = "<IF:MOBILE><IF:PROD>";

С помощью параметра $plugin_info['defaults']['#plugin_func'] можно указать имя функции, которая будет использоваться при запуске плагина (по умолчанию функция должна называться как плагин).

Пример:

  $plugin_info['defaults']['#func_name'] = "tipsy";

С помощью параметра $plugin_info['defaults']['js'] можно передать массив опций, которые будут переданы в старт-функцию плагина.

Можно использовать точечную нотацию для настройки вложенных опций. Т.е. следующие определения будут эквивалентны:

$plugin_info['defaults']=[
'js' = [
'html' => true,
'gravity' => 'raw:$.fn.tipsy.autoNS',//Префикс raw: позволяет передать параметр как есть, не оборачивая его в кавычки как строку
'delayIn' => 250,
'offset' => 3,
],
];
$plugin_info['defaults']=[
'js.html' => true,
'js.gravity' => 'raw:$.fn.tipsy.autoNS',
'js.delayIn' => 250,
'js.offset' => 3,
];

Пример 1 - определение $plugin_info['defaults']:

$plugin_info['defaults'] = [
'selector' => 'a[href^="http[s]?:"],.b-xlink',
];

Пример 2 - определение $plugin_info['defaults']:

$plugin_info['defaults']=[
'#plugin_func' => 'tipsy',
'selector' => '.xtip,.b-xtip',
'js.html' => true,
'js.gravity' => 'raw:$.fn.tipsy.autoNS',
'js.delayIn' => 250,
'js.offset' => 3,
'allow' => '<IF:NOT_MOBILE>',
];
'resources' - настройка ресурсов плагина

В этом массиве перечисляются файлы (css,js), которые требуется подключить для работы плагина.
Для перечисления js-файлов используется $plugin_info['resources']['js'].
Для перечисления css-файлов используется $plugin_info['resources']['css'].
Для перечисления других плагинов, которые необходимы для работы настраиваемого плагина, используется $plugin_info['resources']['plugin'].

В общем случае формат массива с информацией о ресурсах следующий:

[
'js'=>[
'path/to/js/file1.js',
'path/to/js/file2.js',
],
'css'=>[
'path/to/css/file1.css',
'path/to/css/file2.css',
],
'plugin'=>[
'plugin_name_1',
'plugin_name_2|action1,action2,action3',
array('name'=>'plugin_name_3',array({plugin_options})),
array('name'=>'plugin_name_4|action1,action2,action3',array({plugin_options})),
],
]

При указании пути к файлу можно (и нужно) использовать плейсхолдеры:

  • {plugin_path} - путь к модулю, в которому определён плагин
  • {plugin-name} - имя плагина
  • {module_path} - путь к папке ресурсов плагина {module_path}/client/{plugin-name}

Также доступны следующие переменные:

  • ключи массива $options['resvars'], обрамлённые в '<' и '>', заменяются на их значения
  • <IF:DEV> - если выбран режим DEV, то будет заменено на '', иначе будет заменено на <IF:DEV=false>
  • <IF:PROD> - если выбран режим PROD, то будет заменено на '', иначе будет заменено на <IF:PROD=false>
  • <IF:MOBILE> - если для просмотра используется мобильное устройство, то заменено на '', иначе будет заменено на <IF:MOBILE=false>
  • <IF:NOT_MOBILE> - если для просмотра используется немобильное устройство, то заменено на '', иначе будет заменено на <IF:NOT_MOBILE=false>

В определении имени плагина могут использоваться следующие переменные:

  • <IF:DEV>, <IF:PROD>, <IF:MOBILE>, <IF:NOT_MOBILE>

Если путь или имя плагина включает в себя переменные, то эти переменные будут обработаны и заменены на соответствующее значение. Если хотя бы одна переменная не была заменена, то такой ресурс не будет подключаться. Именно поэтому переменные <IF:DEV>, <IF:PROD>, <IF:MOBILE>, <IF:NOT_MOBILE> могут использоваться как предикаты, влияющие на подключение ресурса.

В определение пути к файлу можно включать переменные из массива $plugin_info['resvars'].
Например, если $plugin_info['resvars']=['skin'=>'red','version'=>'2'], то в определении пути можно использовать плейсхолдеры {skin} и {version}:

Пример определния пути к js-файлу: <IF:MOBILE><IF:PROD>{plugin_path}/{plugin-name}-{skin}.v{version}.min.js

Можно использовать точечную нотацию для настройки вложенных опций.

Пример:

$plugin_info['resources'] = [
'js.*' => '{plugin_path}/jquery.{plugin-name}.js',
'css.*' => '{plugin_path}/{plugin-name}.css',
'css.*' => '{plugin_path}/{plugin-name}-skins.css',
];
tip

РЕКОМЕНДУЕТСЯ добавлять в папку плагина файл README.md (предполагается использование синтаксиса Mark Down) с описанием назначения, функционала и всех опций плагина.

Хороший пример можно посмотреть здесь: плагин xoverlay/client/xpopup.

'start' - настройка стартовой функции плагина

Если плагин после инициализации dom-элементов на странице должен выполнить какие-либо стартовые действия, то для этого необходимо указать старт-функцию в $plugin_info['start'].

Для большинства стандартных плагинов можно использовать спец. плейсхолдер <default-start> для указания дефолтной старт-функции. Это приведёт к том, что будет использована функция default_start(), определённая в ClientPluginRegistry.php.

Пример:

  $plugin_info['start'] = '<default_start>';

Принцип работы дефолтной старт-функции зависит от $plugin_info['defaults']['selector']:

1) Если в $plugins_info['defaults']['selector'] указан селектор, то будет сгенерирован и добавлен js-код:

  `XSyst.onReady('{plugin_func}',{_plugin_id: "{plugin_id}", _selector: "{selector}" [, {js_options} ] });`

где:

  • {selector} - это $plugins_info['defaults']['selector'],
  • {plugin_id} - это $plugins_info['#plugin_id'], //генерируется автоматически ({plugin-name}_{md5(serialize($options))}, обеспечивает возможность вызова одного и того же плагина с разными настройками
  • {plugin_func} - это $plugins_info['#plugin_func'], //по умолчанию устанавливается в значение plugin_id. Но можно изменить.
  • {js_options} - это $plugins_info['defaults']['js'], //массив js-опций

Реальный пример сегенерированного кода:

  XSyst.onReady('xbacktotop',{_plugin_id: "xbacktotop_32cbf64e09", _selector: "body", speed: 200, effect: 'ease'});

1) Если $plugins_info['defaults']['selector'] не указан, то будет сгенерирован и добавлен js-код:

  XSyst.onReady('{plugin_func}',{_plugin_id: "{plugin_id}" [, {js_options} ] });

Реальный пример сегенерированного кода:

  XSyst.onReady('ctools_ajax_extra',{_plugin_id: "ctools_ajax_extra_40cd750bba"});
'autostart' - автоматический запуск плагина

Если опция $plugin_info['autostart'] установлена в true, то такой плагин будет подключаться автоматически при каждом запросе страницы.

Опцию автозапуска можно включить/выключить в settings.php для любого плагина с помощью конфигурационного параметра имеющего вид:

  $conf["xplugins.types.client.{$plugin}.autostart"]=true;

Пример - на всех страницах по умолчанию будет активирован плагин xextlinks:

  $conf["xplugins.types.client.xextlinks.autostart"]=true;
'default_bundle' - id ресурсного бандла

Клиентские плагины очень тесно связаны с AssetBundler, отвечающим за группировку, оптимизацию и минимифацию js-файлов и css-файлов. Параметр $plugin_info['default_bundle'] даёт возможность указать AssetBundler, каким образом группировать данный плагин с другими плагинами, запрошенными в рамках текущего запроса.

По умолчанию все клиентские плагины используют id ресурсного бандла равный 100.
При необходимости можно установить другой id (например, 10 или 1 или app). Это позволяет управлять тем, как будут склеиваться ресурсы разных плагинов.

Пример:

  $plugin_info['default_bundle'] = 'xpopup';

Файл {plugin-name}.inc

Файл, представляющий собой контроллер плагина.
Код этого файла первым получает управление при вызове плагина с помощью xlib(ClientPluginRegistry::class)->require($plugins,$options).

Данный файл можно использовать для следующих задач:

  • Изменение аргументов, переданных в require()
  • Добавление переменных в массив $vars для передачи в шаблон
  • Подключение других клиентских плагинов (с помощью $clientApi)
  • Запрет подключения плагина (например, если недостаточно прав)

Файл содержит следующие важные переменные:

  • $options
    Содержит опции, переданные на вход require().
    При запуске плагина в режиме автостарта, эта переменная пустая.

  • $plugin_info
    Содержит данные о плагине, полученные из реестра плагинов.

Большой пример определения плагина:

$plugin_info['defaults'] = [    
'selector' => '.b-megamenu-menu',
'resvars.skin' => 'vert',
'resvars.color' => 'red',
]
$plugin_info['resources'] = [
'js'=>[
'<IF:PROD>{plugin_path}/js/jquery.{plugin-name}.js', //Рекомендуется всегда использовать {plugin_path}
'{plugin_path}/js/some_other_script.js',
'<IF:DEV>{module_path}/client/{plugin-name}/js/some_other_script2.js',
'<IF:PROD><IF:MOBILE><{plugin_path}/js/some_other_script3.js',
'<IF:PROD><IF:NOT_MOBILE><{plugin_path}/js/some_other_script4.js',
],
'css'=> [
'<IF:DEV>{plugin_path}/css/style.dev.css',
'<IF:PROD>{plugin_path}/css/style.css',
'<IF:MOBILE>{plugin_path}/css/style.mobile.css',
'<IF:NOT_MOBILE>{plugin_path}/css/style.notmobile.css',
'<IF:NOT_MOBILE>{plugin_path}/css/{skin}.css', //skin - это один из ключей массива $options['resvars']
'<IF:NOT_MOBILE>{plugin_path}/css/fonts.{font}.css', //font - это один из ключей массива $options['resvars']
],
'plugin'=>[
'plugin_name1',
'plugin_name2|defaults,resources',
'<IF:MOBILE>plugin_name3|resources',
['name'=>'<IF:NOT_MOBILE>plugin_name4|resources','options'=>['resvars.skin'=>'vk','resvars.font'=>'pt_sans'] ],
],
];
$plugin_info['start'] = '<default_start>';

Подключение плагинов

Подключение плагина осуществляется вызовом метода require() объекта xlib(ClientPluginRegistry::class).
Рекомендуется подключать те или иные плагины только при необходимости.

Подключение плагина удобно осуществлять путём вызова xlib(ClientPluginRegistry::class)->require($plugin_name) в контроллере виджета, которому требуется подключение плагина. При вызове в контроллере виджета вместо xlib(ClientPluginRegistry::class) можно использовать объект $clientApi. Пример: $clientApi->require($plugin_name).

Если требуется подключение плагина, который необходим в разных частях/виджетах страницы (например, xpopup, xtip), то можно использовать hook_before_page_render().

Использование хука hook_init() для подключения плагинов возможно, но не рекомендуется, т.к. имеет ряд побочных эффектов по сравнению с использованием hook_before_pager_render().

Примеры подключения плагинов

  1. Подключим плагин xtooltip и передадим опции (параметры для start-функции начинаются с js.):
xlib(ClientPluginRegistry::class)->require('xtooltip',[
'selector'=>'.xt-user',
'js.style.classes.tooltip' => 'xtooltip xtooltip-user',
'js.xtype' => 'ajax',
'js.dataType' => Config::getValue('xtools_xtooltip_data_type', 'html'),
'js.style.background' => 'none',
'js.style.tip.corner' => true,
'js.style.tip.color' => '#f4f4f4',
'js.style.tip.size.width' => 30,
'js.style.tip.size.height' => 20,
'resvars.skin' => 'light',
]);

  1. Подключим плагин 'ximgselectbox'. Для данного плагина не требуется передача каких-либо настроек.
xlib(ClientPluginRegistry::class)->require('ximgselectbox');

Режимы работы плагинов

Система подключения плагинов может находится в одном из 2-х состояний:

  • DEV - Режим разработки
    В этом режиме предполагается подключение не сжатых версий ресурсных файлов для максимального удобства отладки. Для включения этого режима необходимо установить Config::setValue('xtools_client_plugins_mode','dev');

  • PROD - Рабочий режим (по умолчанию) В этом режиме предполагается подключение сжатых версий ресурсных файлов. Для включения этого режима можно установить Config::setValue('xtools_client_plugins_mode','prod'); либо ничего не устанавливать, т.к. этот режим активируются по умолчанию.

Условное подключение плагина

С помощью опций allow и deny возможно задать условия подключения плагина.
Данные опции можно передавать как в defaults, так и в $options метода require($plugin,$options).

Опция allow представляет собой строку, которая может содержать один или несколько предикатов (<IF:DEV>,<IF:PROD>,<IF:MOBILE>,<IF:NOT_MOBILE>).

Если предикатов несколько, то для того чтобы плагин подключился, необходимо, чтобы все они были true.

Опция deny по синтаксису такая же как и allow. Если хотя бы один из предикатов, перечисленных в ней, true, то плагин не будет подключен.

Примеры условного подключения плагина

$plugin_info['defaults'] = [
'selector' => '.xscrolltext',
'js.speedPerChar'=>100,
'js.scrollDelay'=>300,
'allow'=>'<IF:NOT_MOBILE><IF:PROD>', //Плагин будет подключен только если не моб. устройство и prod-режим
'deny'=>'<IF:MOBILE><IF:DEV>', //Плагин НЕ будет подключен, если моб. устройство или dev-режим
];
$plugin_info['resources'] = [
'js.0' => '{plugin_path}/xscrolltext.js',
'css.0' => '{plugin_path}/xscrolltext.css',
]
$plugin_info['resources'] = <default_start>';

Примеры реальных плагинов

Приведенные ниже примеры можно использовать для изучения или в качестве основы при создании новых плагинов.

  • Модуль xtools, плагин xextlinks
    Пример стандартного плагина с селектором

  • Модуль system, плагин emoji
    Пример плагина без селектора

  • Модуль xtools, плагин xconfirm
    Пример плагина с автостартом