Select в стиле bitrix24

Форма ввода — это очень значимый элемент пользовательского интерфейса. При создании собственного интерфейса самым «долгим» моментом является кастомизация списка выбора под стандартный дизайн 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, достаточно подключить плагин в нужном, указать элементы, которые требуется привести к должному виду и наслаждаться результатами своей работы, или потратить время на что-то другое.

Добавить комментарий