Форма ввода — это очень значимый элемент пользовательского интерфейса. При создании собственного интерфейса самым «долгим» моментом является кастомизация списка выбора под стандартный дизайн bitrix24. В этой статье я хочу поделиться js расширением, которое позволит превратить обычный html элемент select в красивый и функциональный компонент. Про создание, подключение и использование js расширений тоже расскажу.
В bitrix24 существует прекрасное js расширение «Диалог выбора сущностей», предоставляющий интерфейс для создания интерактивных диалогов выбора элементов. Элементами выбора могут быть как стандартные данные (такие, как пользователи, элементы различных сущностей CRM и пр.) так и свои собственные. С полным функционалом предоставляемым данным расширением вы можете ознакомиться на странице официальной документации.
Взяв за основу стандартное расширение — создадим новое, задачей которого будет превратить обычный html элемент select в нечто похожее по виду и функционалу. По сути — это будет обёртка над существующей библиотекой.

Шаг 1 создание js расширения (extension)
Js расширение в bitrix24 представляет собой набор javascrit и css файлов. Подгружаемые файлы описываются в конфигурационном файле config.php. Сама директория расширения располагается в папке local/js/ Структура нашего расширения будет следующей:
- local/
-- js/
--- aclips/
---- ui-selector/
----- config.php
----- script.js
Подключение расширения происходит через метод load класса \Bitrix\Main\UI\Extension. В качестве параметра метод принимает название подключаемого расширения. Название расширения формируется из имён вложенных директорий, находящихся в папке local/js (Для справки: стандартные расширения расположены в папке bitrix/js и подключаются аналогичным образом).
Так будет выглядеть подключение нашего расширение, например, в шаблоне компонента:
<?php
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) {
die();
}
use Bitrix\Main\UI\Extension;
Extension::load('aclips.ui-selector');Заполним конфигурационный файл. В нём нужно указать какие js и css файлы будут загружены при подключении расширения, а также какие зависимости в нём присутствуют. У нас есть зависимость от стандартного ui.entity-selector, нам нужно загрузить файл script.js, файл стилей в нашем примере отсутствует.
<?php
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) {
die();
}
return [
"js" => [
"./script.js",
],
"rel" => [
"ui.entity-selector"
]
];Шаг 2 Определимся как должен работать плагин
Прежде чем приступить к написанию кода, нужно договориться с самим собой, как этот плагин должен работать. В базовом варианте, нужно указать как можно меньше параметров для упрощения работы с нашим расширением.
Предположим, что нужно передать сам элемент или его идентификатор в функцию, которая преобразит наш select и передать какой-то набор опций вторым параметром. В опциях будем указывать вкладки, сортировку и вообще всё, что нам может понадобиться для настройки наших интерактивных списков. А быть может ничего не будет передавать.
Шаг 3 Пишем задуманное
Ознакомившись с API базового диалога выбора интерфейсов и придерживаясь договорённостей из шага 2, файл script.js получился следующего вида:
BX.namespace('Plugin.UiSelector')
/**
* Плагин для создания объекта TagSelector на базе статического списка
* @type {{createTagSelector: BX.Plugin.UiSelector.createTagSelector, renderTagSelector: BX.Plugin.UiSelector.renderTagSelector}}
*/
BX.Plugin.UiSelector = {
/**
* {HTMLElement|String} container HTML element or its ID ("select" tag only) select
* {{}} params
*/
createTagSelector: function (select, params) {
let target
let tabs = [{id: 'base', title: 'Элементы', itemOrder: {title: 'asc'}}]
if (typeof params == 'object') {
if (params.tabs) {
tabs = params.tabs
}
}
if (typeof select === 'string') {
target = document.getElementById(select)
if (!target) {
throw new Error('Container parameter is not valid ID.')
}
} else {
target = select;
}
if (target.type != 'select-one' && target.type != 'select-multiple') {
throw new Error('Container must be Select')
}
let options = target.options
let items = []
for (let option of options) {
if (!option.value) {
continue
}
let tab = 'base'
let datatTab = option.getAttribute('data-tab')
if (datatTab) {
tab = datatTab
}
let label = option.text
if (tabs.length > 1) {
let optionTab = tabs.find(e => e.id == tab)
if (optionTab) {
label = optionTab.title + ': ' + label
}
}
items.push({
id: option.value,
title: label,
entityId: 'main',
tabs: tab,
selected: option.selected
})
}
let config = {
node: target,
multiple: target.type == 'select-multiple',
items: items,
tabs: tabs
}
this.renderTagSelector(config)
},
/**
* @param {HTMLElement} config.node
* @param {boolean} config.multiple
* @param {[]} config.items
*/
renderTagSelector: function (config) {
config.node.style.display = 'none'
const tagSelector = new BX.UI.EntitySelector.TagSelector({
multiple: config.multiple,
dialogOptions: {
multiple: config.multiple,
items: config.items,
selectedItems: config.items.filter(e => e.selected),
dropdownMode: true,
enableSearch: false,
compactView: false,
tabs: config.tabs,
},
events: {
onBeforeTagAdd: function (event) {
const {tag} = event.getData();
if(config.multiple){
let options = config.node.querySelectorAll('option[value="'+tag.getId()+'"]')
if(options.length > 0) {
options[0].selected = true
}
} else {
config.node.value = tag.getId()
}
config.node.dispatchEvent(new Event('change'))
},
onBeforeTagRemove: function (event) {
if(config.multiple){
const {tag} = event.getData();
let options = config.node.querySelectorAll('option[value="'+tag.getId()+'"]')
if(options.length > 0) {
options[0].selected = false
}
} else {
config.node.value = null
}
config.node.dispatchEvent(new Event('change'))
},
}
});
tagSelector.renderTo(config.node.parentNode);
}
}Шаг 4 Проверка работы
Использование плагина без опций
<?php \Bitrix\Main\UI\Extension::load('aclips.ui-selector') ?>
<select id='my_select'>
<option value='1'>Option 1</option>
<option value='2'>Option 2</option>
<option value='3'>Option 3</option>
<option selected value='4'>Option 4</option>
<option value='5'>Option 5</option>
</select>
<script type="text/javascript">
BX.ready(function () {
BX.Plugin.UiSelector.createTagSelector('my_select') // or BX.Plugin.UiSelector.createTagSelector(document.getElementById('my_select'))
})
</script>
Использование плагина с опциями
<?php \Bitrix\Main\UI\Extension::load('aclips.ui-selector') ?>
<select id='my_select'>
<option data-tab='tab_1' value='1'>Option 1</option>
<option data-tab='tab_2' value='2'>Option 2</option>
<option data-tab='tab_3' value='3'>Option 3</option>
<option data-tab='tab_4' selected value='4'>Option 4</option>
<option data-tab='tab_4' value='5'>Option 5</option>
</select>
<script type="text/javascript">
BX.ready(function () {
BX.Plugin.UiSelector.createTagSelector('my_select', {
tabs: [
{id: 'tab_1', title: 'Tab 1', itemOrder: {title: 'asc'}},
{id: 'tab_2', title: 'Tab 2', itemOrder: {title: 'asc'}},
{id: 'tab_3', title: 'Tab 3', itemOrder: {title: 'asc'}},
{id: 'tab_4', title: 'Tab 4', itemOrder: {title: 'asc'}}
]
})
})
</script>
Итог
Разработав расширение можно надолго забыть о проблеме кастомизации (в нашем случае) элементов select, достаточно подключить плагин в нужном, указать элементы, которые требуется привести к должному виду и наслаждаться результатами своей работы, или потратить время на что-то другое.