Универсальный селект с тегами и динамическим отображением в одну строку

Показывать справа: 0

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

Шаг 1: HTML-структура

Создадим базовую HTML-разметку для нашего селекта. Мы будем использовать контейнеры, скрытые поля для хранения данных и элементы для отображения выбранных тегов.

        
        <div class="webalan__products">
<div class="universal-tag-selector">
    <label>Выберите опции:</label>
    <div class="tag-selector-container">
        <!-- Скрытое поле для хранения выбранных значений -->
        <input type="hidden" name="selected_options" id="selected_options_hidden">

        <!-- Контейнер для отображения выбранных тегов -->
        <span class="selected-tags" id="selected-tags-display">
            <!-- Здесь будут отображаться выбранные теги -->
        </span>

        <!-- Кнопка для открытия выпадающего списка -->
        <button type="button" class="dropdown-toggle">
            <span id="selected-count">+0</span>
            <span uk-icon="triangle-down"></span> <!-- Иконка из UIkit -->
        </button>

        <!-- Выпадающий список с опциями -->
        <div uk-dropdown="mode: click; boundary: ! .tag-selector-container; shift: false; flip: false">
            <ul class="uk-nav uk-dropdown-nav" id="options-list">
                <!-- Опции будут добавлены динамически или статически -->
                <li><a href="#" data-value="option1">Опция 1</a></li>
                <li><a href="#" data-value="option2">Опция 2</a></li>
                <li><a href="#" data-value="option3">Опция 3</a></li>
                <li><a href="#" data-value="option4">Опция 4</a></li>
                <li><a href="#" data-value="option5">Опция 5</a></li>
            </ul>
        </div>
    </div>
</div>

</div>
        
        

Пояснения к HTML:

  • .universal-tag-selector: Общий контейнер для компонента.
  • .tag-selector-container: Основной контейнер, который управляет отображением тегов и позиционированием кнопки.
  • input[type="hidden"]#selected_options_hidden: Скрытое поле, куда будут записываться выбранные значения через запятую.
  • #selected-tags-display: Элемент, который будет содержать видимые выбранные теги.
  • .dropdown-toggle: Кнопка, открывающая выпадающий список.
  • #selected-count: Отображает количество выбранных элементов.
  • [uk-dropdown]: Директив UIkit для создания выпадающего меню.
  • #options-list: Список элементов в выпадающем меню. Атрибут data-value используется для идентификации опции.

Шаг 2: JavaScript для интерактивности

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

        
    // Вспомогательная функция для проверки наличия элемента в массиве
    function in_array(needle, haystack, strict) {
        var found = false, key, strict = !!strict;
        for (key in haystack) {
            if ((strict && haystack[key] === needle) || (!strict && haystack[key] == needle)) {
                found = true;
                break;
            }
        }
        return found;
    }

    jQuery(document).ready(function(){
        let selectedValues = []; // Массив для хранения выбранных значений

        const tagContainer = $('.tag-selector-container');
        const selectedTagsDisplay = $('#selected-tags-display');
        const optionsList = $('#options-list');
        const hiddenInput = $('#selected_options_hidden');
        const selectedCountDisplay = $('#selected-count');

        // Функция для обновления отображения выбранных тегов
        function updateDisplay() {
            selectedTagsDisplay.empty(); // Очищаем текущие отображаемые теги
            const currentValues = selectedValues.slice(); // Создаем копию массива

            // Если ничего не выбрано, показываем "Все опции" (или аналогичный текст)
            if (currentValues.length === 0) {
                selectedTagsDisplay.append($('Все опции'));
                selectedCountDisplay.text("+0");
                hiddenInput.val('');
                return;
            }

            let totalWidth = 0;
            let visibleTagsCount = 0;
            let allVisible = true;

            // Итерируем по выбранным значениям, чтобы определить, какие поместятся
            for (let i = 0; i < currentValues.length; i++) {
                const value = currentValues[i];
                // Находим текст опции по значению
                const optionText = optionsList.find(`a[data-value="${value}"]`).text();
                const tagElement = $(`${optionText}`);

                selectedTagsDisplay.append(tagElement);

                // Проверяем, помещается ли тег в контейнер (учитывая ширину кнопки)
                const tagWidth = tagElement.outerWidth(true);
                totalWidth += tagWidth;

                // 110 - это примерная ширина кнопки и отступов. Требуется тонкая настройка.
                if (totalWidth > tagContainer.width() - 110) {
                    allVisible = false;
                    tagElement.remove(); // Удаляем тег, который не поместился
                    break;
                }
                visibleTagsCount++;
            }

            // Если не все теги поместились, добавляем индикатор "еще X"
            if (!allVisible) {
                const remainingCount = currentValues.length - visibleTagsCount;
                const remainingElement = $(`+${remainingCount}`);
                selectedTagsDisplay.append(remainingElement);
            }

            // Обновляем счетчик выбранных элементов
            selectedCountDisplay.text(`+${selectedValues.length}`);
            // Обновляем скрытое поле
            hiddenInput.val(selectedValues.join(','));
        }

        // Инициализация: обновляем отображение при загрузке страницы
        updateDisplay();

        // Обработчик клика по элементам в выпадающем списке
        optionsList.on('click', 'a', function(event) {
            event.preventDefault();
            const value = $(this).data('value');
            const $this = $(this);

            if (selectedValues.includes(value)) {
                // Удаляем значение, если оно уже выбрано
                selectedValues = selectedValues.filter(v => v !== value);
                $this.removeClass('selected');
            } else {
                // Добавляем значение, если оно не выбрано
                selectedValues.push(value);
                $this.addClass('selected');
            }
            updateDisplay(); // Обновляем отображение
        });

        // Обработчик клика по кнопке удаления тега
        selectedTagsDisplay.on('click', '.remove-tag', function(e) {
            e.preventDefault();
            const $tagItem = $(this).closest('.tag-item');
            const tagText = $tagItem.contents().filter(function() { return this.nodeType === 3; })[0].nodeValue.trim(); // Получаем текст тега

            // Находим значение опции по тексту
            const valueToRemove = optionsList.find('a').filter(function() {
                return $(this).text().trim() === tagText;
            }).data('value');

            if (valueToRemove) {
                selectedValues = selectedValues.filter(v => v !== valueToRemove);
                // Снимаем класс 'selected' с элемента в выпадающем списке
                optionsList.find(`a[data-value="${valueToRemove}"]`).removeClass('selected');
                updateDisplay();
            }
        });

        // Обработчик для открытия/закрытия дропдауна при клике на контейнер (опционально, если UIkit не справляется)
        tagContainer.on('click', function(e) {
            // Если клик был не по кнопке дропдауна и не по элементам внутри него
            if (!$(e.target).closest('.uk-dropdown').length && !$(e.target).closest('.dropdown-toggle').length) {
                $('.dropdown-toggle').trigger('click'); // Имитируем клик по кнопке для открытия/закрытия
            }
        });
    });
    
  

Пояснения к JavaScript:

  • selectedValues: Массив хранит выбранные значения (например, `['option1', 'option3']`).
  • updateDisplay():
    • Очищает #selected-tags-display.
    • Создает элементы тегов для каждого выбранного значения.
    • Динамически проверяет, помещаются ли созданные теги в .tag-selector-container.
    • Если теги не помещаются, добавляет элемент .remaining-tags с количеством скрытых тегов.
    • Обновляет счетчик #selected-count и скрытое поле #selected_options_hidden.
  • Обработчик клика по #options-list a:
    • Добавляет или удаляет значение из selectedValues.
    • Переключает класс .selected для визуального выделения опции в списке.
    • Вызывает updateDisplay().
  • Обработчик клика по .remove-tag:
    • Находит соответствующее значение в selectedValues.
    • Удаляет его.
    • Снимает класс .selected с опции в выпадающем списке.
    • Вызывает updateDisplay().
  • Обработчик клика на .tag-selector-container: Позволяет открыть/закрыть дропдаун, кликнув вне самой кнопки.

Шаг 3: Подключение стилей и UIkit

Стили для нашего скрипта:


<style>
  /* Множественный выбор  */
#tag-dropdown-list a.selected-tag {
  background-color: #f0506e;
  color: white;
  border-radius: 5px;
}

#tag-dropdown-list a {
    display: block;
    /* padding: 3px 9px; */
}
#tag-dropdown-list li
 
{
  margin-bottom: 5px;   
}   
#tag-dropdown-list input[type="checkbox"] {
    display: none;
}

.webalan__products .tag-selector-container {
    display: flex;
    flex-wrap: wrap;
    /* border: 1px solid #e5e5e5; */
    padding: 5px;
    border-radius: 0;
    min-height: 37px;
    border-bottom: 1px solid #333;
}
 .webalan__products .tag-selector-container button {
     flex-shrink: 0; padding: 0 10px;
     max-height: 39px;
 }

 .webalan__products button .uk-icon{
    position: relative;
    top: 0px;
 }


#tag-count {
display: none;
}

.webalan__products .uk-label.all__noselect {
border-radius: 5px;
    padding: 5px 10px;
    border: 0px solid #ccc;
    background: #3c7be0;
    color: rgb(255 255 255);
    margin: 0 auto;
    font-size: 14px;
    font-weight: 500;

}
.webalan__products .selected-tags{
display: flex; flex-wrap: wrap; align-items: center; margin-right: 5px;
width: 100%;
}

.webalan__products .select_tags{
    cursor: pointer;
}
.webalan__products .select_tags button{
    margin-left: auto;
    position: absolute;
    right: 5px;
    margin: 0px !important;
    border-radius: 8px;
}

.webalan__products .select_tags
.uk-drop.uk-dropdown.uk-open
{
    left: 0!important;
    width: 100%;
}


.webalan__products .uk-margin-small-right.select_tags_carcas {
       margin-right: 3px !important;
}

.webalan__products .select_tags_carcas {
    display: inline-block; white-space: nowrap; background-color: #f0506e; color: white; border-radius: 5px; padding: 5px 10px;
     

}
.webalan__products .stop__right_not {
    border-radius: 5px; padding: 5px 10px; border: 1px solid #ccc;
}

/*==============*/
</style>

Для работы данного кода необходимо подключить библиотеку UIkit. Используйте CDN:

        
<!-- Подключение CSS UIkit -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript..0/dist/css/uikit.min.css" />

<!-- Подключение JavaScript UIkit (иконок) -->
<script src="https://cdn.jsdelivr.net/npm/Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript..0/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript..0/dist/js/uikit-icons.min.js"></script>

<!-- Подключение jQuery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
        
        

Поместите эти строки перед закрывающим тегом </body>.

Заключение

Мы создали универсальный компонент селекта с тегами, который динамически управляет отображением выбранных элементов. Он адаптируется к доступному пространству, показывая лишь часть тегов и индикатор скрытых. Этот паттерн очень полезен для улучшения UX в различных сценариях веб-разработки.

Покупка готового скрипта joomla 3

или просто напишите в телеграмм https://t.me/webalan