Клиентские плагины (ClientApi)
Система для создания именованных наборов ресурсов (js, css, fonts), необходимых на стороне клиента.
В качестве фреймворка для реализации данной плагинной системы использован PluginApi. Поэтому технически ClientApi реализована в виде PluginApi-плагинов типа client и работает в соответствии с особенностями PluginApi.
Клиентские плагины могут располагаться как в модулях, так в темах.
На уровне файловой системы каждый плагин представляет собой папку вида
{module_or_theme}/src/Plugins/client/{plugin-name}
Подсистема клиентских плагинов тесно связана с классом AssetBundler, который отвечает за группировку, оптимизацию и минификацию клиентских ресурсов, запрошенных в рамках для текущего запроса. Поэтому для полного понимания и контроля над процессом подключения клиентских ресурсов рекомендуется ознакомиться и с работой AssetBundler.
Описание файлов плагина
В папке плагина могут быть следующие файлы:
{plugin-name}.info.incфайл с определением и настройками плагина{plugin-name}.jsавтоматически подключаемый js-файл плагина{plugin-name}.cssавтоматически подключаемый css-файл плагина- дополнительные js-файлы
- дополнительные css-файлы
- папки с необходимыми ресурсами, библиотеками, плагинами (vendor, images, fonts, ...)
Переопределение ресурсов плагина
Плагины могут быть переопределены.
Это достигается с учётом принятой в PluginApi последовательности поиска и добавления плагинов в реестр. Версия плагина, добавленная в реестр позднее, получает больший приоритет перед другими версиями данного плагина и, таким образом, переопределяет его полностью или отдельные его части.
Последовательность поиска и включения клиентских плагинов в реестр
- core-модули
- x- и contrib-модули
- app-модули
- родительские темы
- активная тема
На каждом следующем уровне могут быть переопределены/дополнены ресурсы предыдущего.
Важные принципы при переопределении виджета или его части
- Файл
{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',
];
РЕКОМЕНДУЕТСЯ добавлять в папку плагина файл 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().
Примеры подключения плагинов
- Подключим плагин
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',
]);
- Подключим плагин '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
Пример плагина с автостартом